Skip to content

Commit bf4d217

Browse files
committed
Connection pool, fixes
1 parent 71edc1f commit bf4d217

10 files changed

Lines changed: 150 additions & 71 deletions

File tree

core/src/main/java/me/zort/sqllib/LocalLogger.java

Lines changed: 0 additions & 21 deletions
This file was deleted.

core/src/main/java/me/zort/sqllib/SQLClient.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

core/src/main/java/me/zort/sqllib/SQLConnectionBuilder.java

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
99
import me.zort.sqllib.internal.impl.DefaultSQLEndpoint;
1010
import me.zort.sqllib.internal.impl.SQLEndpointImpl;
11+
import org.jetbrains.annotations.NotNull;
1112
import org.jetbrains.annotations.Nullable;
1213

1314
import java.sql.Connection;
@@ -16,17 +17,17 @@
1617
import java.util.Objects;
1718
import java.util.Optional;
1819

19-
public class SQLConnectionBuilder {
20+
public final class SQLConnectionBuilder implements Cloneable {
2021

21-
public static SQLConnectionBuilder of(String address, int port, String database, String username, String password) {
22+
public static @NotNull SQLConnectionBuilder of(String address, int port, String database, String username, String password) {
2223
return of(new DefaultSQLEndpoint(address + ":" + port, database, username, password));
2324
}
2425

25-
public static SQLConnectionBuilder of(String jdbc, String username, String password) {
26+
public static @NotNull SQLConnectionBuilder of(String jdbc, String username, String password) {
2627
return of(new SQLEndpointImpl(jdbc, username, password));
2728
}
2829

29-
public static SQLConnectionBuilder ofSQLite(String path) {
30+
public static @NotNull SQLConnectionBuilder ofSQLite(String path) {
3031
SQLConnectionBuilder builder = of(new SQLEndpointImpl("jdbc:sqlite:" + path, null, null));
3132
builder.withDriver("org.sqlite.JDBC");
3233
return builder;
@@ -43,28 +44,28 @@ public static SQLConnectionBuilder of(SQLEndpoint endpoint) {
4344
private String jdbc;
4445
private String driver = null;
4546

46-
public SQLConnectionBuilder(String address, int port, String database, String username, String password) {
47-
this(new DefaultSQLEndpoint(address + ":" + port, database, username, password));
48-
}
49-
5047
public SQLConnectionBuilder() {
5148
this(null);
5249
}
5350

51+
public SQLConnectionBuilder(@NotNull String address, int port, @NotNull String database, @Nullable String username, @Nullable String password) {
52+
this(new DefaultSQLEndpoint(address + ":" + port, database, username, password));
53+
}
54+
5455
public SQLConnectionBuilder(@Nullable SQLEndpoint endpoint) {
5556
this.endpoint = endpoint;
5657
this.jdbc = endpoint != null
5758
? endpoint.buildJdbc()
5859
: null;
5960
}
6061

61-
public SQLConnectionBuilder withEndpoint(SQLEndpoint endpoint) {
62+
public @NotNull SQLConnectionBuilder withEndpoint(SQLEndpoint endpoint) {
6263
this.endpoint = endpoint;
6364
this.jdbc = endpoint.buildJdbc();
6465
return this;
6566
}
6667

67-
public SQLConnectionBuilder withParam(String key, String value) {
68+
public @NotNull SQLConnectionBuilder withParam(String key, String value) {
6869
Optional.ofNullable(endpoint)
6970
.ifPresent(endpoint -> {
7071
jdbc += (jdbc.contains("?") ? "&" : "?");
@@ -73,20 +74,20 @@ public SQLConnectionBuilder withParam(String key, String value) {
7374
return this;
7475
}
7576

76-
public SQLConnectionBuilder withDriver(String driver) {
77+
public @NotNull SQLConnectionBuilder withDriver(String driver) {
7778
this.driver = driver;
7879
return this;
7980
}
8081

81-
public SQLDatabaseConnection build() {
82+
public @NotNull SQLDatabaseConnection build() {
8283
return build(null);
8384
}
8485

85-
public SQLDatabaseConnection build(@Nullable SQLDatabaseOptions options) {
86+
public @NotNull SQLDatabaseConnection build(@Nullable SQLDatabaseOptions options) {
8687
return build(driver, options);
8788
}
8889

89-
public SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabaseOptions options) {
90+
public @NotNull SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabaseOptions options) {
9091
Objects.requireNonNull(endpoint, "Endpoint must be set!");
9192
Objects.requireNonNull(jdbc);
9293
if(driver == null) {
@@ -100,6 +101,11 @@ public SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabas
100101
}
101102
}
102103

104+
@Override
105+
protected SQLConnectionBuilder clone() throws CloneNotSupportedException {
106+
return (SQLConnectionBuilder) super.clone();
107+
}
108+
103109
@RequiredArgsConstructor
104110
public static class BuilderSQLConnectionFactory implements SQLConnectionFactory {
105111

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,88 @@
11
package me.zort.sqllib;
22

3-
import java.sql.Connection;
3+
import lombok.AccessLevel;
4+
import lombok.Data;
5+
import lombok.RequiredArgsConstructor;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
import java.sql.SQLException;
49
import java.util.List;
5-
import java.util.Optional;
10+
import java.util.Queue;
11+
import java.util.concurrent.ConcurrentLinkedQueue;
612
import java.util.concurrent.CopyOnWriteArrayList;
713

14+
@RequiredArgsConstructor
815
public final class SQLConnectionPool {
916

10-
private SQLConnectionPool() {
17+
@Data
18+
public static final class Options {
19+
private int maxConnections = 10;
20+
}
21+
22+
private final SQLConnectionBuilder from;
23+
private final int maxConnections;
24+
private final Queue<SQLPooledConnection> freeConnections = new ConcurrentLinkedQueue<>();
25+
private final List<SQLPooledConnection> usedConnections = new CopyOnWriteArrayList<>();
26+
27+
public SQLConnectionPool(@NotNull SQLConnectionBuilder from) {
28+
this(from, new Options());
29+
}
30+
31+
public SQLConnectionPool(@NotNull SQLConnectionBuilder from, @NotNull Options poolOptions) {
32+
this.from = from;
33+
this.maxConnections = poolOptions.maxConnections;
34+
}
35+
36+
@NotNull
37+
public Resource getResource() throws SQLException {
38+
freeConnections.removeIf(SQLPooledConnection::expired);
39+
SQLPooledConnection polled = freeConnections.poll();
40+
if (polled == null && usedConnections.size() < maxConnections) {
41+
polled = new SQLPooledConnection(from.build());
42+
polled.connection.connect();
43+
44+
SQLException error = polled.connection.getLastError();
45+
if (error != null) throw error;
46+
} else if(polled == null) {
47+
throw new IllegalStateException("Connection limit reached!");
48+
}
49+
usedConnections.add(polled);
50+
return new Resource(this, polled);
51+
}
52+
53+
public void close() {
54+
usedConnections.forEach(c -> c.connection.disconnect());
55+
freeConnections.forEach(c -> c.connection.disconnect());
56+
usedConnections.clear();
57+
freeConnections.clear();
1158
}
1259

13-
private static final List<SQLDatabaseConnection> CONNECTIONS = new CopyOnWriteArrayList<>();
60+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
61+
private static class SQLPooledConnection {
62+
private final SQLDatabaseConnection connection;
63+
private long lastUsed = System.currentTimeMillis();
1464

15-
static void register(SQLDatabaseConnection connection) {
16-
CONNECTIONS.add(connection);
65+
public boolean expired() {
66+
return System.currentTimeMillis() - lastUsed > 1000 * 60 * 5;
67+
}
1768
}
1869

19-
static Optional<SQLDatabaseConnection> find(Connection connection) {
20-
return CONNECTIONS.stream().filter(c -> c.getConnection() == connection).findFirst();
70+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
71+
public static final class Resource implements AutoCloseable {
72+
73+
private final SQLConnectionPool pool;
74+
private final SQLPooledConnection connection;
75+
76+
public SQLDatabaseConnection getConnection() {
77+
return connection.connection;
78+
}
79+
80+
@Override
81+
public void close() {
82+
connection.lastUsed = System.currentTimeMillis();
83+
pool.freeConnections.add(connection);
84+
pool.usedConnections.remove(connection);
85+
}
2186
}
2287

2388
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package me.zort.sqllib;
2+
3+
import java.sql.Connection;
4+
import java.util.List;
5+
import java.util.Optional;
6+
import java.util.concurrent.CopyOnWriteArrayList;
7+
8+
public final class SQLConnectionRegistry {
9+
10+
private SQLConnectionRegistry() {
11+
}
12+
13+
private static final List<SQLDatabaseConnection> CONNECTIONS = new CopyOnWriteArrayList<>();
14+
15+
static void register(SQLDatabaseConnection connection) {
16+
CONNECTIONS.add(connection);
17+
}
18+
19+
static Optional<SQLDatabaseConnection> find(Connection connection) {
20+
return CONNECTIONS.stream().filter(c -> c.getConnection() == connection).findFirst();
21+
}
22+
23+
public static void debug(Connection connection, String message) {
24+
find(connection)
25+
.filter(c -> c instanceof SQLDatabaseConnectionImpl)
26+
.ifPresent(c -> {
27+
if (c.isDebug()) ((SQLDatabaseConnectionImpl) c).debug(message);
28+
});
29+
}
30+
}

core/src/main/java/me/zort/sqllib/SQLDatabaseConnection.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ public abstract class SQLDatabaseConnection implements SQLConnection {
2626
private final SQLConnectionFactory connectionFactory;
2727
@Getter(onMethod_ = {@Nullable})
2828
private Connection connection;
29+
@Getter(onMethod_ = {@Nullable})
30+
private SQLException lastError = null;
2931

3032
public SQLDatabaseConnection(SQLConnectionFactory connectionFactory) {
3133
this.connectionFactory = connectionFactory;
3234
this.connection = null;
3335

34-
SQLConnectionPool.register(this);
36+
SQLConnectionRegistry.register(this);
3537
}
3638

3739
/**
@@ -154,15 +156,14 @@ public DeleteQuery delete() {
154156

155157
@Override
156158
public boolean connect() {
157-
if(isConnected()) {
158-
disconnect();
159-
}
159+
if(isConnected()) disconnect();
160160

161161
try {
162162
connection = connectionFactory.connect();
163163
} catch (SQLException e) {
164164
logSqlError(e);
165165
connection = null;
166+
lastError = e;
166167
}
167168
return isConnected();
168169
}
@@ -174,6 +175,7 @@ public void disconnect() {
174175
connection.close();
175176
} catch (SQLException e) {
176177
logSqlError(e);
178+
lastError = e;
177179
}
178180
}
179181
}

core/src/main/java/me/zort/sqllib/SQLDatabaseConnectionImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ private static class DefaultStatementFactory implements StatementFactory<Prepare
478478
public PreparedStatement prepare(Connection connection) throws SQLException {
479479
String queryString = query.getAncestor().buildQuery();
480480

481-
LocalLogger.debug(connection, "Query: " + queryString);
481+
SQLConnectionRegistry.debug(connection, "Query: " + queryString);
482482
return connection.prepareStatement(queryString);
483483
}
484484
}

core/src/main/java/me/zort/sqllib/internal/query/QueryDetails.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package me.zort.sqllib.internal.query;
22

33
import lombok.*;
4-
import me.zort.sqllib.LocalLogger;
4+
import me.zort.sqllib.SQLConnectionRegistry;
55
import me.zort.sqllib.util.Pair;
66
import me.zort.sqllib.util.Util;
77

@@ -52,8 +52,8 @@ protected PreparedStatement prepare(Connection connection) throws SQLException {
5252
Pair<String, Object[]> requirements = buildStatementDetails();
5353

5454
// Shows plain query for prepared statement.
55-
LocalLogger.debug(connection, String.format("P-Query: %s", requirements.getFirst()));
56-
LocalLogger.debug(connection, String.format("P-Values: %s", Arrays.toString(requirements.getSecond())));
55+
SQLConnectionRegistry.debug(connection, String.format("P-Query: %s", requirements.getFirst()));
56+
SQLConnectionRegistry.debug(connection, String.format("P-Values: %s", Arrays.toString(requirements.getSecond())));
5757

5858
PreparedStatement statement = connection.prepareStatement(requirements.getFirst());
5959
Object[] values = requirements.getSecond();

core/src/main/java/me/zort/sqllib/util/Regex.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ private Regex() {
88
}
99

1010
public static String skipRegexCharacters(String input) {
11+
boolean thrown = true;
12+
while(thrown) {
13+
try {
14+
input.replaceAll(input, input);
15+
thrown = false;
16+
} catch(PatternSyntaxException e) {
17+
input = _skipRegexCharacters(input);
18+
}
19+
}
20+
return input;
21+
}
22+
23+
private static String _skipRegexCharacters(String input) {
1124
String s = input;
1225
StringBuilder stringBuilder = new StringBuilder(s);
1326
try {

src/test/java/me/zort/sqllib/test/TestCase2.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,16 @@ public interface DatabaseRepository {
7979
@Save // Upsert
8080
QueryResult save(User user);
8181

82-
@Insert(cols = {"nickname", "points"}, vals = {"{nickname}", "{points}"})
83-
QueryResult insertNew(@Placeholder("nickname") String nickname, @Placeholder("points") int points);
82+
@Insert(cols = {"nickname", "points"}, vals = {"{name}", "{points}"})
83+
QueryResult insertNew(@Placeholder("name") String nickname, @Placeholder("points") int points);
8484

8585
@Select
8686
@Where(value = {
87-
@Where.Condition(column = "nickname", value = "{nickname}"),
87+
@Where.Condition(column = "nickname", value = "{name}"),
8888
@Where.Condition(column = "points", value = "500", type = Where.Condition.Type.BT)
8989
})
9090
@Limit(1)
91-
Optional<User> selectOne(@Placeholder("nickname") String nickname);
91+
Optional<User> selectOne(@Placeholder("name") String nickname);
9292

9393
@Select
9494
List<User> selectAll();

0 commit comments

Comments
 (0)