I have built this very simple Connection Pool in Java Using Factory pattern. My thought here was to try on 2 connection Types e.g. SQL and NoSQL. Please review and suggest if my approach is fine. I am trying my hands on Java design patterns, so your review will help me to see where I need to improve in my design process.
Connection class
// very basic definition
public interface Connection {
UUID getMarker();
boolean commit();
void close();
}
JdbcConnection class
public class JdbcConnection implements Connection {
private Credentials credentials;
private HostInfo hostInfo;
private UUID marker;
public JdbcConnection(UUID marker, Credentials credentials, HostInfo hostInfo) {
this.credentials = credentials;
this.hostInfo = hostInfo;
this.marker = marker;
}
public UUID getMarker() {
return marker;
}
public boolean commit() {
return true;
}
public void close() {
System.out.println("["+ this.marker+ "] is renewed for next connection");
this.marker = UUID.randomUUID();
}
}
ConnectionFactory class
public final class ConnectionFactory {
private ConnectionFactory() {
}
public static Connection get(ConnectionType type, DbPoolconfig dbPoolconfig) {
if (type.equals(ConnectionType.SQL)) {
return new JdbcConnection(UUID.randomUUID(), dbPoolconfig.getCredentials(), dbPoolconfig.getHostInfo());
}
return new CassandraConnection(UUID.randomUUID(), dbPoolconfig.getCredentials(), dbPoolconfig.getHostInfo());
}
}
Pool class
public interface Pool<T> {
T get() throws InterruptedException;
void release(T object);
void shutdown();
}
DbConnectionPool class
public interface DbConnectionPool<T> extends Pool {
int getAvailableConnections();
int getBusyConnectionsCount();
}
DbConnectionPoolManager class
public final class DbConnectionPoolManager implements DbConnectionPool {
private static DbConnectionPoolManager instance;
private DbPoolconfig dbPoolconfig;
private BlockingQueue<Connection> pool;
private Set<Connection> busy;
public static DbConnectionPoolManager get(DbPoolconfig dbPoolconfig) {
if (instance == null) {
synchronized (DbConnectionPoolManager.class) {
if (instance == null) {
return new DbConnectionPoolManager(dbPoolconfig);
}
}
}
return instance;
}
private DbConnectionPoolManager(DbPoolconfig dbPoolconfig) {
this.dbPoolconfig = dbPoolconfig;
this.pool = new LinkedBlockingQueue<Connection>(dbPoolconfig.getMaxSize());
this.busy = new HashSet<Connection>();
init();
}
private void init() {
int initial = dbPoolconfig.getIntialSize();
for (int i = 0; i < initial; i++) {
pool.offer(ConnectionFactory.get(dbPoolconfig.getType(), dbPoolconfig));
}
}
public Connection createNew() {
if (busy.size() == dbPoolconfig.getMaxSize()) {
throw new RuntimeException("Pool is full.. cannot issue connection");
}
Connection connection = ConnectionFactory.get(dbPoolconfig.getType(), dbPoolconfig);
pool.offer(connection);
return connection;
}
public Connection get() throws InterruptedException {
Connection connection = null;
if (pool.peek() == null && busy.size() < dbPoolconfig.getMaxSize()) {
connection = createNew();
} else {
connection = pool.take();
}
this.busy.add(connection);
return connection;
}
public void release(Object object) {
if (object != null) {
Connection connection = (Connection) object;
connection.close();
pool.offer(connection);
this.busy.remove(connection);
}
}
public void shutdown() {
// clear connection and remove from queu
while (!pool.isEmpty()) {
Connection connection = null;
try {
connection = pool.take();
connection.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int getAvailableConnections() {
return (dbPoolconfig.getMaxSize() - busy.size());
}
public int getBusyConnectionsCount() {
return busy.size();
}
}
Test
public class DbConnectionPooMangerTest {
DbConnectionPoolManager mgr;
@Before
public void before() {
Credentials credentials = new Credentials("1", "2");
HostInfo info = new HostInfo("sss", "sss");
DbPoolconfig config = new DbPoolconfig(info, credentials, ConnectionType.SQL, 15, 2);
mgr = DbConnectionPoolManager.get(config);
}
@Test
public void testConnection() {
int m = 40;
ExecutorService service = Executors.newFixedThreadPool(m);
final List<Future<String>> conns = new ArrayList();
Callable<String> callable = new Callable<String>() {
public String call() throws Exception {
try {
Connection con = mgr.get();
TimeUnit.SECONDS.sleep((long) (Math.random() * 2));
mgr.release(con);
System.out.println("Available "+ mgr.getAvailableConnections());
System.out.println("Busy "+ mgr.getBusyConnectionsCount());
return (con.getMarker()) +"";
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
};
for (int i = 0; i < m; i++) {
Future<String> f = service.submit(callable);
conns.add(f);
}
int count = 0;
for(Future<String> f : conns) {
try {
String s = f.get();
System.out.println(s);
if(s != null) {
count++;
}
}catch (Exception e) {
e.printStackTrace();;
}
}
Assert.assertEquals(m, count);
service.shutdown();
}
}
```
-
\$\begingroup\$ Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers . \$\endgroup\$Simon Forsberg– Simon Forsberg2020年01月20日 17:24:44 +00:00Commented Jan 20, 2020 at 17:24
-
\$\begingroup\$ Thanks Simon for this suggestion, I will follow going forward. \$\endgroup\$here_to_learn– here_to_learn2020年01月20日 17:51:35 +00:00Commented Jan 20, 2020 at 17:51
1 Answer 1
You seem to have started down a singleton approach, then changed your mind / not completed it.
private static DbConnectionPoolManager instance;
public static DbConnectionPoolManager get(DbPoolconfig dbPoolconfig) {
if (instance == null) {
synchronized (DbConnectionPoolManager.class) {
if (instance == null) {
return new DbConnectionPoolManager(dbPoolconfig);
}
}
}
return instance;
}
You never actually set instance
to anything other than it's default value, so you're getting a new PoolManager every time you call get
. Is this by design?
-
\$\begingroup\$ Oops, Great catch! I missed assigned to 'instance', I will correct it. \$\endgroup\$here_to_learn– here_to_learn2020年01月20日 17:04:33 +00:00Commented Jan 20, 2020 at 17:04