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