1
\$\begingroup\$

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();
    }
}
```
\$\endgroup\$
2
  • \$\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\$ Commented Jan 20, 2020 at 17:24
  • \$\begingroup\$ Thanks Simon for this suggestion, I will follow going forward. \$\endgroup\$ Commented Jan 20, 2020 at 17:51

1 Answer 1

1
\$\begingroup\$

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?

\$\endgroup\$
1
  • \$\begingroup\$ Oops, Great catch! I missed assigned to 'instance', I will correct it. \$\endgroup\$ Commented Jan 20, 2020 at 17:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.