数据库连接池的一种实现方案

  数据库连接池有多个开源实现,像dbcp、druid等。这里我们再一次造轮子,思路很简单:当有SQL操作(增删查改)到来时,先到池子里看一眼,如果有可用的连接,拿来用,没有就新建一个连接。连接不在新建时入池,而是在被关闭时。本来应当被系统回收的连接被放入池中复用,当累计到最大连接数时,就不再入池,直接回收。

  为何要在关闭时判断最大连接数、入池,而不是在请求到来时做呢?我们把关闭入池叫方案1,请求入池叫方案2。采用方案1有两个好处:一、方案2的问题是,当一开始就有超过最大连接数的SQL操作的情况出现时,超过部分的请求会被拒绝或者等待。方案1避免了这种情况,就算超过仍然可以先把连接创建出来,因为它在关闭时才会校验最大连接数。二、方案2在系统启动时新建连接把池子填满,或者在每次请求到来时建立新的连接并入池,最终都会让池子满。方案1则支持动态调整池子大小,请求来多少给多少个连接,如果请求数少于最大连接数,根本不会让池子满。比如最大连接数是10,每批次并发请求来5个,用完后这个5个入池,下次再并发来5个,那么还是这5个出池,用完后再入池。

  方案1的优势是有前提的:一、当请求并发量很大时,池子就起不到保护缓冲的作用了,系统可能一开始就被请求洪峰冲毁了;反而方案2可以起到限流保护的作用。二、弹性的前提是请求连接数少于最大连接数,只要有一次池子满了,那以后也一直是满池运行,跟方案2没啥区别了。

  1、配置文件,放在main/java/resources目录下的jdbc.properties:

db.driver=oracle.jdbc.driver.OracleDriver
db.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
db.username=wlf
db.passwd=wlf
db.max=10

 

  2、3类合一

import javax.sql.DataSource;
import java.io.IOException;
import java.io.PrintWriter;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.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.logging.Logger;

/**
 * 数据库连接池
 */
public class DataSourcePool {

    private DataSource dataSource; // 数据源

    public DataSourcePool() {
        try {
            dataSource = new SimpleDataSource("/jdbc.properties");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 增删改
     *
     * @param sql
     * @param args
     * @return
     */
    public int update(String sql, Object... args) {
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (int i = 1; i <= args.length; i++) {
                ps.setObject(i, args[i - 1]);
            }
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    /**
     * 查
     *
     * @param sql
     * @return
     */
    public List<String> query(String sql, int index) {
        List<String> queryResults = new ArrayList<>();
        try (Connection conn = dataSource.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setInt(1, index);
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                queryResults.add(rs.getString("TITLE"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return queryResults;
    }

    public static void main(String[] args) throws InterruptedException {
        int threadNum = 10; // 起多个线程执行
        CountDownLatch countDownLatch = new CountDownLatch(threadNum); // 让插入先行,删除后行
        String updateSql = "insert into t_wlf_component_template values (?,?)";
        String deleteSql = "delete from t_wlf_component_template where id = ?";
        String querySql = "select TITLE from t_wlf_component_template where id = ?";

        DataSourcePool dataSourcePool = new DataSourcePool();

        for (int i = 0; i < threadNum; i++) {
            final int j = i + 1;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String currentThreadName = Thread.currentThread().getName();
                    long begin = System.currentTimeMillis();
                    System.out.println(currentThreadName + " 更新结果:" + dataSourcePool.update(updateSql, j, "heihei."));
                    System.out.println(currentThreadName + " 更新耗时: " + (System.currentTimeMillis() - begin) + " 微秒。");

                    begin = System.currentTimeMillis();
                    System.out.println(currentThreadName + " 查询结果:" + dataSourcePool.query(querySql, j));
                    System.out.println(currentThreadName + " 查询耗时: " + (System.currentTimeMillis() - begin) + " 微秒。");
                    countDownLatch.countDown(); // 每执行一次插入,减少一次
                }
            }).start();
        }

        countDownLatch.await(); // 主线程在此等待

        for (int i = 0; i < threadNum; i++) {
            final int j = i + 1;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String currentThreadName = Thread.currentThread().getName();
                    long begin = System.currentTimeMillis();
                    System.out.println(currentThreadName + " 删除结果:" + dataSourcePool.update(deleteSql, j));
                    System.out.println(currentThreadName + " 删除耗时: " + (System.currentTimeMillis() - begin) + " 微秒。");
                }
            }).start();
        }
    }

    /**
     * 数据源,从配置文件中加载驱动,获取连接
     */
    class SimpleDataSource implements DataSource {

        private List<Connection> conns; // 数据库连接列表
        private String dbDriver; // 驱动
        private String dbUrl; // 数据库连接url
        private String dbUserName; // 用户名
        private String dbPasswd; // 密码
        private int dbMax; // 最大连接数

        public SimpleDataSource(String configFilePath) throws IOException {
            // 读取数据库配置文件
            Properties properties = new Properties();
            properties.load(DataSourcePool.class.getResourceAsStream(configFilePath));
            dbDriver = properties.getProperty("db.driver");
            dbUrl = properties.getProperty("db.url");
            dbUserName = properties.getProperty("db.username");
            dbPasswd = properties.getProperty("db.passwd");
            dbMax = Integer.valueOf(properties.getProperty("db.max"));

            // 初始化数据连接池
            conns = Collections.synchronizedList(new ArrayList<>(dbMax));
        }

        @Override
        public Connection getConnection() throws SQLException {

            // 加载驱动
            try {
                Class.forName(dbDriver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            // 每次返回一个新的数据库连接
            return DriverManager.getConnection(dbUrl, dbUserName, dbPasswd);

            // 每次从数据库连接池中获取
//            return getConnection(dbUserName, dbPasswd);
        }

        /**
         * 获取指定用户名连接
         *
         * @param username
         * @param password
         * @return
         * @throws SQLException
         */
        @Override
        public synchronized Connection getConnection(String username, String password) throws SQLException {

            // 数据库连接池为空,新创建一个连接,否则从池中捞取一个
            if (conns.isEmpty()) {
                try {
                    Class.forName(dbDriver);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                Connection connection = new ConnectionWrapper(conns,
                        DriverManager.getConnection(dbUrl, username, password), dbMax);

                return connection;
            } else {
                System.out.println("当前数据库连接池还剩 " + conns.size() + " 个连接。");
                return conns.remove(conns.size() - 1);
            }
        }

        // 下面的方法都是在包装,不管它
        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException {
            return null;
        }

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

        @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;
        }
    }

    /**
     * 数据库连接包装器,在连接关闭时放入连接池
     */
    private class ConnectionWrapper implements Connection {
        private List<Connection> conns; // 连接池
        private Connection connection; // 连接
        private int max; // 最大连接数

        public ConnectionWrapper(List<Connection> conns, Connection connection, int max) {
            this.conns = conns;
            this.connection = connection;
            this.max = max;
        }

        // 下面的方法都是在包装,除了close方法
        @Override
        public Statement createStatement() throws SQLException {
            return connection.createStatement();
        }

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

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

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

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

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

        @Override
        public void commit() throws SQLException {
            connection.commit();
        }

        @Override
        public void rollback() throws SQLException {
            connection.rollback();
        }

        /**
         * 关闭方法需自己实现
         * @throws SQLException
         */
        @Override
        public void synchronized close() throws SQLException {

            // 当数据库连接数等于最大值时,回收,否则仍回池里复用
            if (conns.size() == max) {
                connection.close();
            } else {
                conns.add(this);
            }
        }

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

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

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

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

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

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

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

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

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

        @Override
        public void clearWarnings() throws SQLException {
            connection.clearWarnings();
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

  运行结果:

Thread-5 更新结果:1
Thread-5 更新耗时: 609 微秒。
Thread-4 更新结果:1
Thread-4 更新耗时: 616 微秒。
Thread-1 更新结果:1
Thread-1 更新耗时: 625 微秒。
Thread-3 更新结果:1
Thread-3 更新耗时: 626 微秒。
Thread-8 更新结果:1
Thread-8 更新耗时: 587 微秒。
Thread-7 更新结果:1
Thread-7 更新耗时: 624 微秒。
Thread-2 更新结果:1
Thread-2 更新耗时: 641 微秒。
Thread-0 更新结果:1
Thread-0 更新耗时: 649 微秒。
Thread-6 更新结果:1
Thread-6 更新耗时: 646 微秒。
Thread-9 更新结果:1
Thread-9 更新耗时: 608 微秒。
Thread-5 查询结果:[heihei.]
Thread-5 查询耗时: 88 微秒。
Thread-4 查询结果:[heihei.]
Thread-4 查询耗时: 97 微秒。
Thread-1 查询结果:[heihei.]
Thread-1 查询耗时: 115 微秒。
Thread-8 查询结果:[heihei.]
Thread-8 查询耗时: 134 微秒。
Thread-3 查询结果:[heihei.]
Thread-3 查询耗时: 152 微秒。
Thread-7 查询结果:[heihei.]
Thread-7 查询耗时: 166 微秒。
Thread-2 查询结果:[heihei.]
Thread-2 查询耗时: 186 微秒。
Thread-6 查询结果:[heihei.]
Thread-6 查询耗时: 199 微秒。
Thread-9 查询结果:[heihei.]
Thread-9 查询耗时: 219 微秒。
Thread-0 查询结果:[heihei.]
Thread-0 查询耗时: 241 微秒。
Thread-10 删除结果:1
Thread-10 删除耗时: 32 微秒。
Thread-11 删除结果:1
Thread-11 删除耗时: 52 微秒。
Thread-13 删除结果:1
Thread-13 删除耗时: 77 微秒。
Thread-12 删除结果:1
Thread-12 删除耗时: 101 微秒。
Thread-15 删除结果:1
Thread-15 删除耗时: 122 微秒。
Thread-14 删除结果:1
Thread-14 删除耗时: 144 微秒。
Thread-16 删除结果:1
Thread-16 删除耗时: 165 微秒。
Thread-17 删除结果:1
Thread-17 删除耗时: 187 微秒。
Thread-19 删除结果:1
Thread-19 删除耗时: 209 微秒。
Thread-18 删除结果:1
Thread-18 删除耗时: 234 微秒。

 

  我们把SimpleDataSource类的getConnection() 改写一下:

        @Override
        public Connection getConnection() throws SQLException {

//            // 加载驱动
//            try {
//                Class.forName(dbDriver);
//            } catch (ClassNotFoundException e) {
//                e.printStackTrace();
//            }
//
//            // 每次返回一个新的数据库连接
//            return DriverManager.getConnection(dbUrl, dbUserName, dbPasswd);

            // 每次从数据库连接池中获取
            return getConnection(dbUserName, dbPasswd);
        }

 

  再跑一遍:

Thread-6 更新结果:1
Thread-6 更新耗时: 663 微秒。
Thread-5 更新结果:1
Thread-5 更新耗时: 665 微秒。
Thread-7 更新结果:1
Thread-7 更新耗时: 665 微秒。
Thread-9 更新结果:1
Thread-9 更新耗时: 665 微秒。
Thread-1 更新结果:1
Thread-1 更新耗时: 668 微秒。
Thread-8 更新结果:1
Thread-8 更新耗时: 665 微秒。
当前数据库连接池还剩 6 个连接。
当前数据库连接池还剩 5 个连接。
当前数据库连接池还剩 4 个连接。
当前数据库连接池还剩 3 个连接。
当前数据库连接池还剩 2 个连接。
Thread-4 更新结果:1
Thread-4 更新耗时: 682 微秒。
当前数据库连接池还剩 2 个连接。
当前数据库连接池还剩 1 个连接。
Thread-3 更新结果:1
Thread-3 更新耗时: 689 微秒。
Thread-2 更新结果:1
Thread-2 更新耗时: 691 微秒。
Thread-0 更新结果:1
Thread-0 更新耗时: 698 微秒。
Thread-1 查询结果:[heihei.]
Thread-9 查询结果:[heihei.]
Thread-7 查询结果:[heihei.]
Thread-8 查询结果:[heihei.]
Thread-7 查询耗时: 35 微秒。
Thread-9 查询耗时: 34 微秒。
Thread-1 查询耗时: 34 微秒。
Thread-8 查询耗时: 34 微秒。
当前数据库连接池还剩 7 个连接。
Thread-6 查询结果:[heihei.]
Thread-6 查询耗时: 69 微秒。
当前数据库连接池还剩 7 个连接。
Thread-5 查询结果:[heihei.]
Thread-5 查询耗时: 67 微秒。
当前数据库连接池还剩 7 个连接。
当前数据库连接池还剩 6 个连接。
Thread-0 查询结果:[heihei.]
Thread-0 查询耗时: 36 微秒。
当前数据库连接池还剩 6 个连接。
Thread-2 查询结果:[heihei.]
Thread-2 查询耗时: 42 微秒。
Thread-3 查询结果:[heihei.]
Thread-3 查询耗时: 45 微秒。
Thread-4 查询结果:[heihei.]
Thread-4 查询耗时: 51 微秒。
当前数据库连接池还剩 8 个连接。
当前数据库连接池还剩 7 个连接。
当前数据库连接池还剩 6 个连接。
当前数据库连接池还剩 5 个连接。
当前数据库连接池还剩 4 个连接。
当前数据库连接池还剩 3 个连接。
当前数据库连接池还剩 2 个连接。
Thread-12 删除结果:1
Thread-12 删除耗时: 2 微秒。
Thread-11 删除结果:1
Thread-11 删除耗时: 3 微秒。
Thread-10 删除结果:1
Thread-13 删除结果:1
Thread-13 删除耗时: 9 微秒。
Thread-10 删除耗时: 9 微秒。
Thread-14 删除结果:1
Thread-14 删除耗时: 9 微秒。
Thread-16 删除结果:1
Thread-16 删除耗时: 8 微秒。
Thread-15 删除结果:1
Thread-15 删除耗时: 9 微秒。
当前数据库连接池还剩 8 个连接。
当前数据库连接池还剩 7 个连接。
当前数据库连接池还剩 6 个连接。
Thread-19 删除结果:1
Thread-19 删除耗时: 2 微秒。
Thread-17 删除结果:1
Thread-18 删除结果:1
Thread-18 删除耗时: 2 微秒。
Thread-17 删除耗时: 11 微秒。

 

posted on 2020-12-16 22:46  不想下火车的人  阅读(355)  评论(0编辑  收藏  举报

导航