Skip to content

Commit 1e485fe

Browse files
authored
Merge pull request #16 from ZorTik/development
Development
2 parents 348e20c + 1287a9c commit 1e485fe

12 files changed

Lines changed: 293 additions & 88 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: 24 additions & 19 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,50 +44,49 @@ 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-
Optional.ofNullable(endpoint)
69-
.ifPresent(endpoint -> {
70-
jdbc += (jdbc.contains("?") ? "&" : "?");
71-
jdbc += key + "=" + value;
72-
});
68+
public @NotNull SQLConnectionBuilder withParam(String key, String value) {
69+
if (endpoint != null) {
70+
jdbc += (jdbc.contains("?") ? "&" : "?");
71+
jdbc += key + "=" + value;
72+
}
7373
return this;
7474
}
7575

76-
public SQLConnectionBuilder withDriver(String driver) {
76+
public @NotNull SQLConnectionBuilder withDriver(String driver) {
7777
this.driver = driver;
7878
return this;
7979
}
8080

81-
public SQLDatabaseConnection build() {
81+
public @NotNull SQLDatabaseConnection build() {
8282
return build(null);
8383
}
8484

85-
public SQLDatabaseConnection build(@Nullable SQLDatabaseOptions options) {
85+
public @NotNull SQLDatabaseConnection build(@Nullable SQLDatabaseOptions options) {
8686
return build(driver, options);
8787
}
8888

89-
public SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabaseOptions options) {
89+
public @NotNull SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabaseOptions options) {
9090
Objects.requireNonNull(endpoint, "Endpoint must be set!");
9191
Objects.requireNonNull(jdbc);
9292
if(driver == null) {
@@ -100,6 +100,11 @@ public SQLDatabaseConnection build(@Nullable String driver, @Nullable SQLDatabas
100100
}
101101
}
102102

103+
@Override
104+
protected SQLConnectionBuilder clone() throws CloneNotSupportedException {
105+
return (SQLConnectionBuilder) super.clone();
106+
}
107+
103108
@RequiredArgsConstructor
104109
public static class BuilderSQLConnectionFactory implements SQLConnectionFactory {
105110

Lines changed: 144 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,159 @@
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+
/**
15+
* A connection pool.
16+
* <p></p>
17+
* This class is used to create a pool of connections to a database.
18+
* It is recommended to use this class instead of creating a new connection
19+
* every time you need to execute a query.
20+
*
21+
* @author ZorTik
22+
*/
23+
@RequiredArgsConstructor
824
public final class SQLConnectionPool {
925

10-
private SQLConnectionPool() {
26+
@Data
27+
public static final class Options {
28+
// Max number of connections in the pool
29+
private int maxConnections = 10;
30+
// Max wait time for getResource in milliseconds when the pool is exhausted
31+
private long borrowObjectTimeout = 5000L;
32+
// Block or throw an exception when the pool is exhausted
33+
private boolean blockWhenExhausted = true;
34+
}
35+
36+
private final SQLConnectionBuilder builder;
37+
private final int maxConnections;
38+
private final long borrowObjectTimeout;
39+
private final boolean blockWhenExhausted;
40+
41+
// --***-- Pooled connection caches --***--
42+
private final Queue<SQLPooledConnection> freeConnections = new ConcurrentLinkedQueue<>();
43+
private final List<SQLPooledConnection> usedConnections = new CopyOnWriteArrayList<>();
44+
45+
public SQLConnectionPool(@NotNull SQLConnectionBuilder from) {
46+
this(from, new Options());
47+
}
48+
49+
/**
50+
* Creates a new connection pool.
51+
* A builder is used as a factory for creating new connections.
52+
*
53+
* @param from The builder used to create new connections.
54+
* @param poolOptions The pool options.
55+
*/
56+
public SQLConnectionPool(@NotNull SQLConnectionBuilder from, @NotNull Options poolOptions) {
57+
this.builder = from;
58+
this.maxConnections = poolOptions.maxConnections;
59+
this.borrowObjectTimeout = poolOptions.borrowObjectTimeout;
60+
this.blockWhenExhausted = poolOptions.blockWhenExhausted;
1161
}
1262

13-
private static final List<SQLDatabaseConnection> CONNECTIONS = new CopyOnWriteArrayList<>();
63+
/**
64+
* Gets a resource from the pool, or returns an existing one from
65+
* the pool if there is any available.
66+
*
67+
* @return The resource.
68+
* @throws SQLException Connection error.
69+
*/
70+
@NotNull
71+
public Resource getResource() throws SQLException {
72+
freeConnections.removeIf(SQLPooledConnection::expired);
73+
SQLPooledConnection polled = freeConnections.poll();
74+
if (polled == null && size() < maxConnections) {
75+
polled = establishObject();
76+
} else if(polled == null) {
77+
78+
if (!blockWhenExhausted) {
79+
throw new SQLException("No connections available.");
80+
}
1481

15-
static void register(SQLDatabaseConnection connection) {
16-
CONNECTIONS.add(connection);
82+
long start = System.currentTimeMillis();
83+
while ((polled = freeConnections.poll()) == null) {
84+
if (System.currentTimeMillis() - start > borrowObjectTimeout) {
85+
throw new SQLException("Timeout while waiting for a connection.");
86+
} else if(size() < maxConnections) {
87+
polled = establishObject();
88+
break;
89+
}
90+
}
91+
}
92+
usedConnections.add(polled);
93+
return new Resource(this, polled);
1794
}
1895

19-
static Optional<SQLDatabaseConnection> find(Connection connection) {
20-
return CONNECTIONS.stream().filter(c -> c.getConnection() == connection).findFirst();
96+
private SQLPooledConnection establishObject() throws SQLException {
97+
SQLPooledConnection polled = new SQLPooledConnection(builder.build());
98+
polled.connection.connect();
99+
100+
SQLException error = polled.connection.getLastError();
101+
if (error != null) throw error;
102+
103+
if (polled.connection instanceof SQLDatabaseConnectionImpl) {
104+
((SQLDatabaseConnectionImpl) polled.connection).addErrorHandler(code -> {
105+
// Remove the connection from the pool and disconnect
106+
// on fatal errors.
107+
freeConnections.remove(polled);
108+
usedConnections.remove(polled);
109+
polled.connection.disconnect();
110+
});
111+
}
112+
113+
return polled;
114+
}
115+
116+
public int size() {
117+
return usedConnections.size() + freeConnections.size();
118+
}
119+
120+
/**
121+
* Closes all connections in the pool and
122+
* clears the caches.
123+
*/
124+
public void close() {
125+
usedConnections.forEach(c -> c.connection.disconnect());
126+
freeConnections.forEach(c -> c.connection.disconnect());
127+
usedConnections.clear();
128+
freeConnections.clear();
129+
}
130+
131+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
132+
private static final class SQLPooledConnection {
133+
private final SQLDatabaseConnection connection;
134+
private long lastUsed = System.currentTimeMillis();
135+
136+
public boolean expired() {
137+
return System.currentTimeMillis() - lastUsed > 1000 * 60 * 5;
138+
}
139+
}
140+
141+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
142+
public static final class Resource implements AutoCloseable {
143+
144+
private final SQLConnectionPool pool;
145+
private final SQLPooledConnection connection;
146+
147+
public SQLDatabaseConnection getConnection() {
148+
return connection.connection;
149+
}
150+
151+
@Override
152+
public void close() {
153+
connection.lastUsed = System.currentTimeMillis();
154+
pool.freeConnections.add(connection);
155+
pool.usedConnections.remove(connection);
156+
}
21157
}
22158

23159
}
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: 8 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,15 @@ 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();
163+
lastError = null;
163164
} catch (SQLException e) {
164165
logSqlError(e);
165166
connection = null;
167+
lastError = e;
166168
}
167169
return isConnected();
168170
}
@@ -172,8 +174,10 @@ public void disconnect() {
172174
if(isConnected()) {
173175
try {
174176
connection.close();
177+
lastError = null;
175178
} catch (SQLException e) {
176179
logSqlError(e);
180+
lastError = e;
177181
}
178182
}
179183
}

0 commit comments

Comments
 (0)