数据库连接池的一种实现方案
数据库连接池有多个开源实现,像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 微秒。