JDBC数据源连接池(4)---自定义数据源连接池

[续上文《JDBC数据源连接池(3)---Tomcat集成DBCP》]

我们已经 了解了DBCP,C3P0,以及Tomcat内置的数据源连接池,那么,这些数据源连接池是如何实现的呢?为了究其原理,我在这里写一个自定义的数据源连接池。

我先在com.itszt.utils包下新建一个Utils_5_mydscp 文件夹,在该文件夹下写自定义的数据源连接池工具;同时,我在src包下建config_utils_5文件夹,该文件夹下建一个属性配置文件mydscp.properties,该属性配置文件信息如下:

DRIVER_NAME=com.mysql.jdbc.Driver
JDBC_URL=jdbc:mysql://localhost:3306/itszt2
USERNAME=root
PASSWORD=2017
INITIAL_SIZE=10
MAX_ACTIVE=100
MAX_IDLE=20
MIN_IDLE=10

接着,分析该数据源连接池,需要一个解析属性配置文件的类,需要一个数据源(拟采用单例模式)类,还需要一个Connection类;为了降低耦合度,采用工厂模式获得数据源实例;考虑到异常的个性化处理,还可以自定义异常。

有了上述分析后,第一步,写一个解析属性配置文件的工具类MyDSCPConfig,代码如下:

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public abstract class MyDSCPConfig {
    public static String DRIVER_NAME = null;
    public static String JDBC_URL = null;
    public static String USERNAME = null;
    public static String PASSWORD = null;
    public static Integer INITIAL_SIZE = null;
    public static Integer MAX_ACTIVE = null;
    public static Integer MAX_IDLE = null;
    public static Integer MIN_IDLE = null;

    /**
     * 解析属性配置文件
     *
     * @param propertiesPath
     */
    public static void parseConfig(String propertiesPath) {
        Properties properties = new Properties();
        InputStream resourceAsStream = MyDSCPConfig.class.getClassLoader().getResourceAsStream(propertiesPath);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVER_NAME = properties.getProperty("DRIVER_NAME");
        JDBC_URL = properties.getProperty("JDBC_URL");
        USERNAME = properties.getProperty("USERNAME");
        PASSWORD = properties.getProperty("PASSWORD");
        INITIAL_SIZE = Integer.parseInt(properties.getProperty("INITIAL_SIZE"));
        MAX_ACTIVE = Integer.parseInt(properties.getProperty("MAX_ACTIVE"));
        MAX_IDLE = Integer.parseInt(properties.getProperty("MAX_IDLE"));
        MIN_IDLE = Integer.parseInt(properties.getProperty("MIN_IDLE"));
    }
}  

第二步,先采用适配器模式,写一个实现了Connection 接口的MyConnectionAdapter抽象类,便于MyConnectionAdapter的子类有选择地覆写所需的方法,MyConnectionAdapter类代码如下:

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 适配器
 */
public abstract class MyConnectionAdapter implements Connection {
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

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

    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }

    @Override
    public void commit() throws SQLException {
    }

    @Override
    public void rollback() throws SQLException {
    }

    @Override
    public void close() throws SQLException {
    }

    @Override
    public boolean isClosed() throws SQLException {
        return false;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return null;
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {
    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return false;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {
    }

    @Override
    public String getCatalog() throws SQLException {
        return null;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {
    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return 0;
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
            throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return null;
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
    }

    @Override
    public void setHoldability(int holdability) throws SQLException {
    }

    @Override
    public int getHoldability() throws SQLException {
        return 0;
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return null;
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return null;
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {
    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
                                              int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
                                         int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return null;
    }

    @Override
    public Clob createClob() throws SQLException {
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return false;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {
    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {
    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {
    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }
}  

第三步,再写一个继承了MyConnectionAdapter的子类MyConnection,代码如下:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * 采用装饰者模式,基于Conneciton原有的close()功能,扩展其close()功能
 */
public class MyConnection extends MyConnectionAdapter {
    private Connection connection;
    private MyDataSource dataSource;

    public MyConnection(Connection connection, MyDataSource dataSource) {
        this.connection = connection;
        this.dataSource = dataSource;
    }

    @Override
    public Statement createStatement() throws SQLException {
        return connection.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return connection.prepareStatement(sql);
    }

    @Override
    public void close() throws SQLException {
        dataSource.recycle(this);
    }

    /*
    * 关闭连接,释放资源
    */
    public void dispose() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}  

第四步,写一个继承了SQLException的自定义异常MyConnectionExp,代码如下:

import java.sql.SQLException;
public class MyConnectionExp extends SQLException {
    public MyConnectionExp(String reason) {
        super(reason);
    }
}  

第五步,写一个实现了DataSource接口的抽象类MyDataSourceAdapter,覆写接口的所有方法,该类代码如下:

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.sql.DataSource;
/**
 * 适配器
 */
public abstract class MyDataSourceAdapter implements DataSource {
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
    }

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

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

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

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

    @Override
    public Connection getConnection() throws SQLException {
        return null;
    }

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

第六步,写一个继承了MyDataSourceAdapter 抽象类的子类MyDataSource,由于数据源连接池只能有一个,故采用单例模式,该子类代码如下:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
/**
 * 单例模式
 */
public class MyDataSource extends MyDataSourceAdapter {
    private static MyDataSource myDataSource;
    private LinkedList<MyConnection> connectionQueue = new LinkedList<>();
    private int totalActive = MyDSCPConfig.MAX_ACTIVE;

    static {
// 加载数据库驱动
        try {
            Class.forName(MyDSCPConfig.DRIVER_NAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private MyDataSource() {
    }

    //懒汉模式,同步,确保线程安全
    public static MyDataSource getInstance() {
        if (myDataSource == null) {
            synchronized (MyDataSource.class) {
                if (myDataSource == null) {
                    myDataSource = new MyDataSource();
                    myDataSource.initialize();
                }
            }
        }
        return myDataSource;
    }

    //初始化方法
    public void initialize() {
        for (int i = 0; i < MyDSCPConfig.INITIAL_SIZE; i++) {
            MyConnection connection = produceConnection();
            if (connection == null) {
                continue;
            }
            connectionQueue.add(connection);
        }
    }

    //构建一个连接的方法
    private MyConnection produceConnection() {
        try {
            Connection connection = DriverManager.getConnection(MyDSCPConfig.JDBC_URL, MyDSCPConfig.USERNAME, MyDSCPConfig.PASSWORD);
            return new MyConnection(connection, this);
        } catch (SQLException e) {
            MyConnectionExp exp = new MyConnectionExp("获取连接失败!");
            exp.printStackTrace();
        }
        return null;
    }

    //从连接池中获取连接
    @Override
    public Connection getConnection() throws SQLException {
        int num1 = connectionQueue.size();
        MyConnection myConnection = null;
        if (this.totalActive > 0) {
            if (num1 >= 1) {
                myConnection = connectionQueue.pop();
                this.totalActive--;
            } else {
                System.err.println("连接池已无空闲连接,需生产后才能使用!");
                myConnection = produceConnection();
                connectionQueue.add(myConnection);
                getConnection();
            }
            int num2 = connectionQueue.size();
            System.out.println("获取连接,原先有 " + num1 + " 个,现在有 " + num2 + " 个");
            if (num2 < MyDSCPConfig.MIN_IDLE) {
                System.out.println("不足最小空闲连接数,生产一个新的!");
                connectionQueue.add(produceConnection());
            }
        } else {
            System.err.println("已有一次超出最大连接数!");
            this.totalActive = MyDSCPConfig.MAX_ACTIVE;
            getConnection();
        }
        return myConnection;
    }

    /*
    * 回收连接
    */
    protected void recycle(MyConnection myConnection) {
        if (connectionQueue.size() >= MyDSCPConfig.MAX_IDLE) {
            System.out.println("超出空闲连接数上限,不再回收!");
            myConnection.dispose();
            return;
        } else {
            connectionQueue.add(myConnection);
            this.totalActive++;
        }
    }
}  

第七步,基于工厂模式,写一个获取数据源连接池实例的类MyDataSourceFactory,该类代码如下:

/**
 * 工厂模式
 */
public abstract class MyDataSourceFactory {
    public static MyDataSource createDataSource(String propertiesPath) {
//1.解析配置文件中的配置
        MyDSCPConfig.parseConfig(propertiesPath);
//2.依据上一步骤的设置,生产我们的数据源
        return MyDataSource.getInstance();
    }
}  

最后,在com.itszt.demo文件夹中的LoginServlet.java的Servlet文件中,将产生Connection对象的方式修改为自定义的数据源连接池方式即可。

总结:在Web项目中,我们经常需要频繁连接数据库,采用数据源连接池的方式,能够有效地解决建立数据库连接时耗费较多CPU、时间等资源的问题,从而提高应用性能,改进用户体验。

原文地址:https://www.cnblogs.com/lizhangyong/p/8117512.html