概述:
本文将会学习PooledDataSource、UnpooledDataSource、PooledConnection和PoolState这四个类的源码实现,mybatis通过这四个类实现了一个简单的数据库连接池,这四个类的功能分别为:
- PooledDataSource:数据库的连接池
- UnpooledDataSource: 非池的数据库链接 (创建真正的链接)
- PooledConnection:连接池中的连接对象
- PoolState:提供一些统计信息,用于监控当前连接池的状态
下面来分别学习这四个个类的代码实现
PooledConnection:
PooledConnection类实现了InvocationHandler接口,也就是说这是一个动态代理类,主要是代理了Connection#close方法
属性:
private static final String CLOSE = "close";
//用于创建动态代理类
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
private final int hashCode;
//连接池对象
private final PooledDataSource dataSource;
//实际连接对象
private final Connection realConnection;
//连接的代理对象,使用jdk的基于接口的动态代理
private final Connection proxyConnection;
//等待时间戳
private long checkoutTimestamp;
//连接创建时的时间戳
private long createdTimestamp;
//上一次使用时的时间戳
private long lastUsedTimestamp;
//相当于 hash(url + user + password)
private int connectionTypeCode;
//连接是否有效
private boolean valid;
构造函数
常规的赋值操作,基操
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
// 生成代理类
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
invoke
重点分析invoke方法,这里代理了Connection对象的close方法,是实现连接池功能的关键
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名
String methodName = method.getName();
//如果当前调用的是close方法,即需要关闭当前连接,则将当前连接对象重新push到连接池中,实现连接的复用
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
//当调用继承自Obejct类的方法时,抛出SQLException,而不是一个运行时异常
//对应issue #579
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
//调用实际方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
PoolState
连接池状态统计对象,逻辑简单,直接看代码
public class PoolState {
//数据源对象
protected PooledDataSource dataSource;
//空闲连接集合
protected final List<PooledConnection> idleConnections = new ArrayList<>();
//活动连接集合
protected final List<PooledConnection> activeConnections = new ArrayList<>();
//请求计数
protected long requestCount = 0;
//累计请求时间
protected long accumulatedRequestTime = 0;
//累计等待时间
protected long accumulatedCheckoutTime = 0;
//声明的过期连接计数
protected long claimedOverdueConnectionCount = 0;
//累计的过期连接的等待时间
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
//累计的等待时间
protected long accumulatedWaitTime = 0;
//等待计数
protected long hadToWaitCount = 0;
//错误的连接计数
protected long badConnectionCount = 0;
public PoolState(PooledDataSource dataSource) {
this.dataSource = dataSource;
}
public synchronized long getRequestCount() {
return requestCount;
}
//平均请求时间
public synchronized long getAverageRequestTime() {
return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
}
//平均等待时间
public synchronized long getAverageWaitTime() {
return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
}
public synchronized long getHadToWaitCount() {
return hadToWaitCount;
}
public synchronized long getBadConnectionCount() {
return badConnectionCount;
}
public synchronized long getClaimedOverdueConnectionCount() {
return claimedOverdueConnectionCount;
}
//平均超时等待时间
public synchronized long getAverageOverdueCheckoutTime() {
return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
}
//平均等待时间
public synchronized long getAverageCheckoutTime() {
return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
}
public synchronized int getIdleConnectionCount() {
return idleConnections.size();
}
public synchronized int getActiveConnectionCount() {
return activeConnections.size();
}
@Override
public synchronized String toString() {
StringBuilder builder = new StringBuilder();
builder.append("\n===CONFINGURATION==============================================");
builder.append("\n jdbcDriver ").append(dataSource.getDriver());
builder.append("\n jdbcUrl ").append(dataSource.getUrl());
builder.append("\n jdbcUsername ").append(dataSource.getUsername());
builder.append("\n jdbcPassword ").append(dataSource.getPassword() == null ? "NULL" : "************");
builder.append("\n poolMaxActiveConnections ").append(dataSource.poolMaximumActiveConnections);
builder.append("\n poolMaxIdleConnections ").append(dataSource.poolMaximumIdleConnections);
builder.append("\n poolMaxCheckoutTime ").append(dataSource.poolMaximumCheckoutTime);
builder.append("\n poolTimeToWait ").append(dataSource.poolTimeToWait);
builder.append("\n poolPingEnabled ").append(dataSource.poolPingEnabled);
builder.append("\n poolPingQuery ").append(dataSource.poolPingQuery);
builder.append("\n poolPingConnectionsNotUsedFor ").append(dataSource.poolPingConnectionsNotUsedFor);
builder.append("\n ---STATUS-----------------------------------------------------");
builder.append("\n activeConnections ").append(getActiveConnectionCount());
builder.append("\n idleConnections ").append(getIdleConnectionCount());
builder.append("\n requestCount ").append(getRequestCount());
builder.append("\n averageRequestTime ").append(getAverageRequestTime());
builder.append("\n averageCheckoutTime ").append(getAverageCheckoutTime());
builder.append("\n claimedOverdue ").append(getClaimedOverdueConnectionCount());
builder.append("\n averageOverdueCheckoutTime ").append(getAverageOverdueCheckoutTime());
builder.append("\n hadToWait ").append(getHadToWaitCount());
builder.append("\n averageWaitTime ").append(getAverageWaitTime());
builder.append("\n badConnectionCount ").append(getBadConnectionCount());
builder.append("\n===============================================================");
return builder.toString();
}
}
UnpooledDatasource
创建真正的数据库连接
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
//常规配置
private String driver;
private String url;
private String username;
private String password;
//是否自动commit 事务的隔离级别
private Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
//注册驱动
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
public UnpooledDataSource() {
}
public UnpooledDataSource(String driver, String url, String username, String password) {
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
//重点getconnnection 获取数据库连接,调用doGetConnection方法
@Override
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username, password);
}
//用户名 密码 设置配置
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
//真正获取连接的方法,由此可见,每次调用getConnection方法是,都会新建一个Connection对象,可想当并发量高是,数据库的连接就会被占满
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
//初始化 在调用doGetConnection方法中,会先进行一下判断,判断mybatis的configuration中是否已经有数据库驱动,没有的话会通过反射加载驱动
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Driver driverInstance = (Driver)driverType.newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
//设置是否自动提交 事务的隔离级别
private void configureConnection(Connection conn) throws SQLException {
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
}
PooledDataSource
This is a simple, synchronous, thread-safe database connection pool.
这是一个简单,同步,线程安全的数据库连接池
属性
//连接池状态对象,用于统计当前连接池的运行状态
private final PoolState state = new PoolState(this);
private final UnpooledDataSource dataSource;
//可配置的连接池属性
//连接池最大活动连接数
protected int poolMaximumActiveConnections = 10;
//连接池最大空闲连接数
protected int poolMaximumIdleConnections = 5;
//连接池最大等待时间
protected int poolMaximumCheckoutTime = 20000;
//连接重试的等待时间
protected int poolTimeToWait = 20000;
//连接池能容忍的最大不良连接数
protected int poolMaximumLocalBadConnectionTolerance = 3;
//连接保活sql
protected String poolPingQuery = "NO PING QUERY SET";
//是否允许连接保活
protected boolean poolPingEnabled;
//执行保活sql的时间间隔,单位为毫秒
protected int poolPingConnectionsNotUsedFor;
//需要连接类型代码 hash(url+username+password)
private int expectedConnectionTypeCode;
构造函数
和非池化数据源的构造函数类似,目的都是为了获取那几个参数
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}
public PooledDataSource(UnpooledDataSource dataSource) {
this.dataSource = dataSource;
}
public PooledDataSource(String driver, String url, String username, String password) {
dataSource = new UnpooledDataSource(driver, url, username, password);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
public PooledDataSource(String driver, String url, Properties driverProperties) {
dataSource = new UnpooledDataSource(driver, url, driverProperties);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username, String password) {
dataSource = new UnpooledDataSource(driverClassLoader, driver, url, username, password);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
public PooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
dataSource = new UnpooledDataSource(driverClassLoader, driver, url, driverProperties);
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
}
获取连接
@Override
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return popConnection(username, password).getProxyConnection();
}
实际干活的方法是popConnection方法,popConnection方法的代码如下:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
//只要没有获取到连接对象,就一直自旋
while (conn == null) {
//加锁,获取连接的动作是同步的
synchronized (state) {
//如果空闲连接集合不为空,则直接从空闲连接结合中拿一个连接
if (!state.idleConnections.isEmpty()) {
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
//活跃连接集合的大小小于配置的连接池最大活动连接数
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// 还能创建新的对象,直接创建连接对象就完事儿了
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {//活跃连接集合的大小大于或等于配置的连接池最大活动连接数
//进入这里就表示不能再通过创建新的连接对象来解决问题了,只能通过以下方法解决问题
//获取活动连接结合中的第一个连接,也就是最早创建的那一个
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
//获取该连接对象的等待时间,这个等待时间也是整个连接池中最长的等待时间
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
//等待时间大于连接池配置的最大等待时间,从业务的角度看这个连接其实已经是无效的了
//也就是说这个连接是可以被剔除的
if (longestCheckoutTime > poolMaximumCheckoutTime) {
//设置各种属性的值
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
//回滚当前连接中的事务
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back");
}
}
//重新创建一个新的PooledConnection对象。
//注意,这里PooledConnection对象中包装的真实连接没有变
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
//进入这里表示连接池中没有可以剔除的连接,则当前线程会被阻塞在这里,
//直到连接池中有新的连接后被唤醒
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
//调用Object.wait方法阻塞当前线程
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
if (conn.isValid()) {//当前连接有效
//回滚连接中的事务
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//更新各种属性
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {//当前连接是无效的
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
//conn置为空,继续自旋
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
popConnection方法执行的流程图如下所示:
获取连接方法的思考
1、方法中剔除最老的连接的方案只是一种思路,觉得在这里定义一个剔除策略的接口,并内置几个默认策略。
这样做的好处一是可以提供多种策略让用户选择,二是也算是在这里留了一个扩展点,方便用户自己扩展
2、方法中阻塞线程使用的是Object.wait()/Object.notify()的api,觉得在这里还是使用JUC下的ReentrantLock的api可能会好一点
归还连接
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//从活跃连接集合中删除该连接
state.activeConnections.remove(conn);
if (conn.isValid()) {
//空闲连接数小于配置的最大空闲连接数,表示需要将当前对象放入到空闲连接集合中
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//这里为什么要重新创建一个新的连接对象?查了资料,别人的解释是避免使用方还在使用conn,
// 通过将它设置为失效,万一使用方再次调用,会抛出异常
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
//唤醒阻塞在state对象上的线程
state.notifyAll();
} else {
//走到这里表示空闲连接集合已经满了,所以当前连接无家可归了,直接就可以被关闭了
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//获取PooledConnection包装的真实连接并将其关闭
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
pushConnection方法的流程图如下
连接保活
定期执行poolPingQuery中配置的sql,用于保证数据库连接不会失效
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;
//判断真实连接是否已经被关闭
try {
result = !conn.getRealConnection().isClosed();
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
result = false;
}
//允许连接保活 --->true
//保活sql执行的时间间隔 ---> 大于0
//当时时间-连接上次执行时间 --->大于保活sql执行的时间间隔
if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
//获取真实连接
Connection realConn = conn.getRealConnection();
//执行poolPingQuery中配置的sql语句
try (Statement statement = realConn.createStatement()) {
statement.executeQuery(poolPingQuery).close();
}
//不是自动提交事务的话就回滚
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
//一切ok,就返回true,表示当前连接没问题
result = true;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
}
} catch (Exception e) {
log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
try {
conn.getRealConnection().close();
} catch (Exception e2) {
// ignore
}
result = false;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
}
}
return result;
}
关闭所有连接
代码逻辑为遍历活跃连接集合和空闲连接集合,将集合中的每一个连接对象置为无效,并将该对象包装的真实连接关闭。
public void forceCloseAll() {
synchronized (state) {
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
for (int i = state.activeConnections.size(); i > 0; i--) {
try {
PooledConnection conn = state.activeConnections.remove(i - 1);
conn.invalidate();
Connection realConn = conn.getRealConnection();
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
realConn.close();
} catch (Exception e) {
// ignore
}
}
for (int i = state.idleConnections.size(); i > 0; i--) {
try {
PooledConnection conn = state.idleConnections.remove(i - 1);
conn.invalidate();
Connection realConn = conn.getRealConnection();
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
realConn.close();
} catch (Exception e) {
// ignore
}
}
}
if (log.isDebugEnabled()) {
log.debug("PooledDataSource forcefully closed/removed all connections.");
}
}