Elegant Design and Handwritten Implementation of Database Connection Pool

1. What is the database connection pool?

Database connection is an expensive resource. Creating a database connection is a time-consuming operation, especially under the condition of multi-thread concurrency. The efficient management of database connection can affect the performance index of the program. The database connection pool is exactly for this problem brought up. The database connection pool is responsible for allocating, managing and releasing database connections. It allows an application to reuse an existing database connection instead of re-establishing a new connection. Using the database connection pool will significantly improve the performance of database operations;

2. What are the technical solutions for the database connection pool?

(1) C3P0

(2) DBCP

(3) Proxool

(4) Tomcat Jdbc Pool

(5) BoneCP

(6) Druid

(7) HiKariCP

Database connection pool is a kind of pooling technology;

Pooling technology: http access (httpclient), redis access (redisPool), thread (thread pool), etc.;

Interview: (Interview with on-site code writing)

1. Write a queue by hand (you cannot use the tools that come with jdk)

2. Write a stack by hand

3. Write a HashMap by hand

4. Write a producer consumer by hand

5. Write a (jvm) cache by hand, even the existing classes in jdk cannot be used

6….

Pure handwritten database connection pool implementation

——MyDataSourceInterface

package com.atqiyu.pool;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * @author 7575
 * @version 1.0
 * I don't have him either, but I'm familiar with you!
 */
public interface MyDataSourceInterface extends DataSource {
    @Override
    default Connection getConnection() throws SQLException {
        return null;
    }

    @Override
    default Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    //The following method can not be implemented


    @Override
    default <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    default boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    default PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    default void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    default void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    default int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    default Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

——MyAbstractDataSource

package com.atqiyu.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author 7575
 * @version 1.0
 * I don't have him either, but I'm familiar with you!
 */
public abstract class MyAbstractDataSource implements MyDataSourceInterface {

    private String url;

    private String driver;

    private String user;

    private String password;

    //Maximum number of connections in use
    private int poolMaxActiveConnections = 10;

    //Maximum number of idle connections
    private int poolMaxIdleConnections = 5;

    / / Get a connection from the connection pool to wait for the maximum number of milliseconds
    private int poolTimeToWait = 30000;

    public int getPoolMaxActiveConnections() {
        return poolMaxActiveConnections;
    }

    public void setPoolMaxActiveConnections(int poolMaxActiveConnections) {
        this. poolMaxActiveConnections = poolMaxActiveConnections;
    }

    public int getPoolMaxIdleConnections() {
        return poolMaxIdleConnections;
    }

    public void setPoolMaxIdleConnections(int poolMaxIdleConnections) {
        this. poolMaxIdleConnections = poolMaxIdleConnections;
    }

    public int getPoolTimeToWait() {
        return poolTimeToWait;
    }

    public void setPoolTimeToWait(int poolTimeToWait) {
        this. poolTimeToWait = poolTimeToWait;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUser() {
        return user;
    }

    public void setUser(String user) {
        this. user = user;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return getConnection(user, password);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return doGetConnection(username, password);
    }

    /**
     * Get database connection
     *
     * @param username
     * @param password
     * @return
     */
    private Connection doGetConnection(String username, String password) throws SQLException {
        Connection connection = DriverManager. getConnection(url, username, password);
        return connection;
    }
}

——MyDataSource

package com.atqiyu.pool;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 7575
 * @version 1.0
 * I don't have him either, but I'm familiar with you!
 *
 * The connection pool of the data source
 */
public class MyDataSource extends MyAbstractDataSource {

    // idle connection pool
    private final List<ConnectionProxy> idleConnections = new ArrayList<ConnectionProxy>();

    //activated connection pool
    private final List<ConnectionProxy> activeConnections = new ArrayList<ConnectionProxy>();



    //The monitor object is used for some uses related to some synchronous operations
    private final Object monitor = new Object();

    //The monitor object is used for some uses related to some synchronous operations
    private final Object watch = new Object();

    /**
     * Override the method of the parent class, returning a proxy connection
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection() throws SQLException {

        ConnectionProxy connectionProxy = getConnectionProxy(super. getUser(), super. getPassword());

        return connectionProxy. getProxyConnection();
    }

    /**
     * get connection
     * @param username
     * @param password
     * @return
     */
    public ConnectionProxy getConnectionProxy(String username,String password) throws SQLException {
        boolean wait = false;
        ConnectionProxy connectionProxy = null;

        // Initially there is no connection
        while (connectionProxy == null) {
            // make a synchronous thread
            synchronized (monitor) {

                //If the idle connection is not empty, then you can directly get the connection
                if (!idleConnections. isEmpty()) {
                    connectionProxy = idleConnections. remove(0);
                } else {
                    //There is no idle connection available, then we need to obtain a new connection (that is, we need to create a connection)
                    if (activeConnections. size() < super. getPoolMaxActiveConnections()) {
                        //If the number of currently activated connections is less than the maximum number of connections we allow, then a new connection can be created at this time, otherwise it cannot be created
                        connectionProxy = new ConnectionProxy(super. getConnection(), this);
                    }
                    //Otherwise, you cannot create a new connection, you need to wait, etc. private int poolTimeToWait = 30000; milliseconds

                }
            }

            if (!wait) {
                wait = true;
            }

            if (connectionProxy == null) {
                try {
                    //The connection object is empty, so you need to wait;
                    monitor.wait(super.getPoolTimeToWait());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //In case the wait is interrupted by the loop, exit the loop
                    break;
                }
            }
        }


        if (connectionProxy != null) {
            //The connection object is not empty, indicating that the connection has been obtained
            activeConnections. add(connectionProxy);
        }
        // return connection object
        return connectionProxy;
    }

    /**
     * Close the connection, but instead of closing the connection, return the connection pool to the connection pool
     * @param connectionProxy
     */
    public void closeConnection(ConnectionProxy connectionProxy) {
        synchronized (monitor) {
            //Close the connection, then turn the active connection into an idle connection
            activeConnections. remove(connectionProxy);

            if (idleConnections. size() < super. getPoolMaxIdleConnections()) {
                idleConnections. add(connectionProxy);
            }

            //Notify, wake up the above thread waiting to get the connection
            monitor. notifyAll();
        }
    }

}

——ConnectionProxy

package com.atqiyu.pool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

/**
 * @author 7575
 * @version 1.0
 * I don't have him either, but I'm familiar with you!
 *
 * Use dynamic proxy to realize proxy for database connection
 */
public class ConnectionProxy implements InvocationHandler {

    //real connection
    private Connection realConnection;

    // proxy connection
    private Connection proxyConnection;

    //Hold the data source object
    private MyDataSource myDataSource;

    public Connection getRealConnection() {
        return realConnection;
    }

    public void setRealConnection(Connection realConnection) {
        this. realConnection = realConnection;
    }

    public Connection getProxyConnection() {
        return proxyConnection;
    }

    public void setProxyConnection(Connection proxyConnection) {
        this.proxyConnection = proxyConnection;
    }

    public MyDataSource getMyDataSource() {
        return myDataSource;
    }

    public void setMyDataSource(MyDataSource myDataSource) {
        this.myDataSource = myDataSource;
    }

    /**
     * Construction method
     *
     * @param realConnection
     * @param myDataSource
     */
    public ConnectionProxy(Connection realConnection, MyDataSource myDataSource) {
        //Initialize the real connection
        this. realConnection = realConnection;
        //Initialize the data source
        this.myDataSource = myDataSource;

        //Initialize proxy connection
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
                       new Class<?>[] {Connection. class},
                       this);

    }

    /**
     * When calling a method in the Connection object, it will first be intercepted by the invoke method
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Get what method of the Connection object is currently called
        String methodName = method. getName();

        if (methodName. equalsIgnoreCase("close")) {
            //TODO return the connection to the connection pool
            myDataSource. closeConnection(this);
            return null;
        } else {
            return method. invoke(realConnection, args);
        }

    }
}