基于Mybatis-3.5.0版本
1.0 数据源模块
在数据持久层中,数据源是一个非常重要的组件,期性能直接关系到整个数据持久层的性能。在实践中比较常见的第三方数据源组件有DBCP、C3P0、阿里druid和Springboot2推荐的HikariCP等,Mybatis不仅可以集成第三方数据源组件,还提供了自己的数据源实现。
Mybatis数据源模块包结构如下:
2.0 DataSource 数据源接口
常见的数据源组件都实现了javax.sql.DataSource接口,Mybatis自身实现的数据源实现也不例外。Mybatis提供了两个javax.sql.DataSource接口实现,分别是PooledDataSource和UnpooledDataSource。
2.1 UnpooledDataSource 非池化DataSource
org.apache.ibatis.datasource.unpooled.UnpooledDataSource实现DataSource接口,非池化的DataSource对象。代码如下:
/**
* 非池化DataSource
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class UnpooledDataSource implements DataSource {
// Driver 类加载器
private ClassLoader driverClassLoader;
// 数据库驱动的相关配置
private Properties driverProperties;
// 缓存所有已注册的数据库连接驱动
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
// 数据库连接的驱动名称
private String driver;
// 数据库 URL
private String url;
// 用户名
private String username;
// 密码
private String password;
// 是否自动提交事务
private Boolean autoCommit;
// 默认事务隔离级别
private Integer defaultTransactionIsolationLevel;
/**
* 初始化 registeredDrivers
* 將已在DriverManager中注册的JDBC Driver复制一份
*/
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;
}
public UnpooledDataSource(String driver, String url, Properties driverProperties) {
this.driver = driver;
this.url = url;
this.driverProperties = driverProperties;
}
public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, String username,
String password) {
this.driverClassLoader = driverClassLoader;
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
public UnpooledDataSource(ClassLoader driverClassLoader, String driver, String url, Properties driverProperties) {
this.driverClassLoader = driverClassLoader;
this.driver = driver;
this.url = url;
this.driverProperties = driverProperties;
}
@Override
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username, password);
}
@Override
public void setLoginTimeout(int loginTimeout) {
DriverManager.setLoginTimeout(loginTimeout);
}
@Override
public int getLoginTimeout() {
return DriverManager.getLoginTimeout();
}
@Override
public void setLogWriter(PrintWriter logWriter) {
DriverManager.setLogWriter(logWriter);
}
@Override
public PrintWriter getLogWriter() {
return DriverManager.getLogWriter();
}
public ClassLoader getDriverClassLoader() {
return driverClassLoader;
}
public void setDriverClassLoader(ClassLoader driverClassLoader) {
this.driverClassLoader = driverClassLoader;
}
public Properties getDriverProperties() {
return driverProperties;
}
public void setDriverProperties(Properties driverProperties) {
this.driverProperties = driverProperties;
}
public String getDriver() {
return driver;
}
public synchronized void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean isAutoCommit() {
return autoCommit;
}
public void setAutoCommit(Boolean autoCommit) {
this.autoCommit = autoCommit;
}
public Integer getDefaultTransactionIsolationLevel() {
return defaultTransactionIsolationLevel;
}
public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
this.defaultTransactionIsolationLevel = defaultTransactionIsolationLevel;
}
private Connection doGetConnection(String username, String password) throws SQLException {
// 创建 Properties 对象
Properties props = new Properties();
if (driverProperties != null) {
// 设置 driverProperties 到 props 中
props.putAll(driverProperties);
}
// 设置 user和password
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化 Driver
initializeDriver();
// 获得 Connection 对象
Connection connection = DriverManager.getConnection(url, properties);
// 配置 Connection 对象
configureConnection(connection);
return connection;
}
/**
* 初始化 Driver
*
* @throws SQLException
*/
private synchronized void initializeDriver() throws SQLException {
// 检查驱动是否已注册
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
// 获得 driver 类
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 对象
Driver driverInstance = (Driver) driverType.newInstance();
// 创建 DriverProxy对象,并注册到 DriverManager中
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 添加到registeredDrivers中
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);
}
}
/**
* 静态代理
* @ClassName: DriverProxy
* @Description: 主要是针对getParentLogger方法,使用 MyBatis 自定义的 Logger 对象
* @date 2019年4月3日
*
*/
private static class DriverProxy implements Driver {
private Driver driver;
DriverProxy(Driver d) {
this.driver = d;
}
@Override
public boolean acceptsURL(String u) throws SQLException {
return this.driver.acceptsURL(u);
}
@Override
public Connection connect(String u, Properties p) throws SQLException {
return this.driver.connect(u, p);
}
@Override
public int getMajorVersion() {
return this.driver.getMajorVersion();
}
@Override
public int getMinorVersion() {
return this.driver.getMinorVersion();
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
return this.driver.getPropertyInfo(u, p);
}
@Override
public boolean jdbcCompliant() {
return this.driver.jdbcCompliant();
}
// @Override only valid jdk7+
public Logger getParentLogger() {
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
}
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException(getClass().getName() + " is not a wrapper.");
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
// @Override only valid jdk7+
public Logger getParentLogger() {
// requires JDK version 1.6
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
}
}
2.2 PooledDataSource 池化的DataSource
数据库连接的创建过程是非常耗时的,数据库能够建立的连接也非常有限,所以在绝大多数系统中,数据库连接都是非常珍贵的资源,使用数据库连接池就显得尤其必要。
org.apache.ibatis.datasource.pooled.PooledDataSource实现DataSource接口,池化的DataSource实现类。代码如下:
public class PooledDataSource implements DataSource {
private static final Log log = LogFactory.getLog(PooledDataSource.class);
private final PoolState state = new PoolState(this);
// UnpooledDataSource对象,用于获取数据库连接
private final UnpooledDataSource dataSource;
// OPTIONAL CONFIGURATION FIELDS
// 最大活动连接数(默认为10)
protected int poolMaximumActiveConnections = 10;
// 最大空闲连接数(默认为5)
protected int poolMaximumIdleConnections = 5;
/**
* 最大可回收时间。
* 即当达到最大活动链接数时,此时如果有程序获取连接,则检查最先使用的连接,看其是否超出了该时间,如果超出了该时间,则可以回收该连接。(默认20s)
*/
protected int poolMaximumCheckoutTime = 20000;
// 没有连接时,重尝试获取连接以及打印日志的时间间隔(默认20s)
protected int poolTimeToWait = 20000;
/**
* 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程.
* 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections与poolMaximumLocalBadConnectionTolerance之和
*/
protected int poolMaximumLocalBadConnectionTolerance = 3;
// 检查连接正确的语句,默认为"NO PING QUERY SET",即没有,使用会导致抛异常
protected String poolPingQuery = "NO PING QUERY SET";
// 是否开启ping检测,(默认:false)
protected boolean poolPingEnabled;
// 设置ping检测时间间隔,通常用于检测超时连接(默认为0,即当开启检测后每次从连接词中获取连接以及放回连接池都需要检测)
protected int poolPingConnectionsNotUsedFor;
// 期望 Connection 的类型编码
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) {
// 创建 UnpooledDataSource 对象
dataSource = new UnpooledDataSource(driver, url, username, password);
// 计算 expectedConnectionTypeCode 的值
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();
}
@Override
public void setLoginTimeout(int loginTimeout) {
DriverManager.setLoginTimeout(loginTimeout);
}
@Override
public int getLoginTimeout() {
return DriverManager.getLoginTimeout();
}
@Override
public void setLogWriter(PrintWriter logWriter) {
DriverManager.setLogWriter(logWriter);
}
@Override
public PrintWriter getLogWriter() {
return DriverManager.getLogWriter();
}
public void setDriver(String driver) {
dataSource.setDriver(driver);
forceCloseAll();
}
public void setUrl(String url) {
dataSource.setUrl(url);
forceCloseAll();
}
public void setUsername(String username) {
dataSource.setUsername(username);
forceCloseAll();
}
public void setPassword(String password) {
dataSource.setPassword(password);
forceCloseAll();
}
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
dataSource.setAutoCommit(defaultAutoCommit);
forceCloseAll();
}
public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
dataSource.setDefaultTransactionIsolationLevel(defaultTransactionIsolationLevel);
forceCloseAll();
}
public void setDriverProperties(Properties driverProps) {
dataSource.setDriverProperties(driverProps);
forceCloseAll();
}
/**
* The maximum number of active connections
*
* @param poolMaximumActiveConnections The maximum number of active connections
*/
public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
this.poolMaximumActiveConnections = poolMaximumActiveConnections;
forceCloseAll();
}
/**
* The maximum number of idle connections
*
* @param poolMaximumIdleConnections The maximum number of idle connections
*/
public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
this.poolMaximumIdleConnections = poolMaximumIdleConnections;
forceCloseAll();
}
/**
* The maximum number of tolerance for bad connection happens in one thread
* which are applying for new {@link PooledConnection}
*
* @param poolMaximumLocalBadConnectionTolerance max tolerance for bad
* connection happens in one
* thread
*
* @since 3.4.5
*/
public void setPoolMaximumLocalBadConnectionTolerance(int poolMaximumLocalBadConnectionTolerance) {
this.poolMaximumLocalBadConnectionTolerance = poolMaximumLocalBadConnectionTolerance;
}
/**
* The maximum time a connection can be used before it *may* be given away
* again.
*
* @param poolMaximumCheckoutTime The maximum time
*/
public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
forceCloseAll();
}
/**
* The time to wait before retrying to get a connection
*
* @param poolTimeToWait The time to wait
*/
public void setPoolTimeToWait(int poolTimeToWait) {
this.poolTimeToWait = poolTimeToWait;
forceCloseAll();
}
/**
* The query to be used to check a connection
*
* @param poolPingQuery The query
*/
public void setPoolPingQuery(String poolPingQuery) {
this.poolPingQuery = poolPingQuery;
forceCloseAll();
}
/**
* Determines if the ping query should be used.
*
* @param poolPingEnabled True if we need to check a connection before using it
*/
public void setPoolPingEnabled(boolean poolPingEnabled) {
this.poolPingEnabled = poolPingEnabled;
forceCloseAll();
}
/**
* If a connection has not been used in this many milliseconds, ping the
* database to make sure the connection is still good.
*
* @param milliseconds the number of milliseconds of inactivity that will
* trigger a ping
*/
public void setPoolPingConnectionsNotUsedFor(int milliseconds) {
this.poolPingConnectionsNotUsedFor = milliseconds;
forceCloseAll();
}
public String getDriver() {
return dataSource.getDriver();
}
public String getUrl() {
return dataSource.getUrl();
}
public String getUsername() {
return dataSource.getUsername();
}
public String getPassword() {
return dataSource.getPassword();
}
public boolean isAutoCommit() {
return dataSource.isAutoCommit();
}
public Integer getDefaultTransactionIsolationLevel() {
return dataSource.getDefaultTransactionIsolationLevel();
}
public Properties getDriverProperties() {
return dataSource.getDriverProperties();
}
public int getPoolMaximumActiveConnections() {
return poolMaximumActiveConnections;
}
public int getPoolMaximumIdleConnections() {
return poolMaximumIdleConnections;
}
public int getPoolMaximumLocalBadConnectionTolerance() {
return poolMaximumLocalBadConnectionTolerance;
}
public int getPoolMaximumCheckoutTime() {
return poolMaximumCheckoutTime;
}
public int getPoolTimeToWait() {
return poolTimeToWait;
}
public String getPoolPingQuery() {
return poolPingQuery;
}
public boolean isPoolPingEnabled() {
return poolPingEnabled;
}
public int getPoolPingConnectionsNotUsedFor() {
return poolPingConnectionsNotUsedFor;
}
/*
* Closes all active and idle connections in the pool
*/
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.");
}
}
public PoolState getPoolState() {
return state;
}
private int assembleConnectionTypeCode(String url, String username, String password) {
return ("" + url + username + password).hashCode();
}
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
// 从activeConnections集合中移除该PooledConnection对象
state.activeConnections.remove(conn);
if (conn.isValid()) {// 检测PooledConnection对象是否有效
// 检测空闲连接数是否已达到上限,以及PooledConnection是否为该连接池的连接
if (state.idleConnections.size() < poolMaximumIdleConnections
&& conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
// 累积checkout时长
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {// 回滚未提交的事务
conn.getRealConnection().rollback();
}
// 为返回的连接创建新的PooledConnection对象
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);// 添加到idleConnections集合
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();// 将原PooledConnection对象设置为无效
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
state.notifyAll();// 唤醒阻塞等待的线程
} else {
// 累积checkout时长
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 关闭真正的数据库连接
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();// 将原PooledConnection对象设置为无效
}
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode()
+ ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
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 {// 当前连接池没有空闲
// Pool does not have available connection
// 活跃的连接数没有到最大值,则可以创建新连接
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// Can create new connection
// 创建新数据库连接,并封装成PooledConnection对象
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {// 活跃连接数已到最大值,则不能穿件新的连接
// Cannot create new connection
// 获取最先创建的活跃连接
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {// 检测该连接是否超时
// 对超时的连接的信息进行统计
// Can claim overdue connection
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// 将超时列检移出activeConnections集合
state.activeConnections.remove(oldestActiveConnection);
// 如果超时连接未提交,则自动回滚
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
/*
* Just log a message for debug and continue to execute the following statement
* like nothing happened. Wrap the bad connection with a new PooledConnection,
* this will help to not interrupt current executing thread and give current
* thread a chance to join the next competition for another valid/good database
* connection. At the end of this loop, bad {@link @conn} will be set as null.
*/
log.debug("Bad connection. Could not roll back");
}
}
// 创建新PooledConnection对象,但是真正的数据库连接并未创建新的
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// 将超时PooledConnection设置为无效
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// 无空闲连接、无法创建新连接且无超时连接,则只能阻塞等待
// Must wait
try {
if (!countedWait) {
state.hadToWaitCount++;// 统计等待次数
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);// 阻塞等待
// 统计累积的等待时间
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
// ping to server and check the connection is valid or not
if (conn.isValid()) {// 检测PooledConnection是否有效
// 如果非自动提交的,需要进行回滚。即将原有执行中的事务,全部回滚。
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 配置PooledConnection的相关属性
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;
// 如果超过最大次数,抛出 SQLException 异常
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;
}
/**
* Method to check to see if a connection is still usable
*
* @param conn - the connection to check
* @return True if the connection is still usable
*/
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;// 记录ping操作是否成功
try {
//检测真正的数据库连接是否已关闭
result = !conn.getRealConnection().isClosed();
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
result = false;
}
if (result) {
if (poolPingEnabled) {// 检测poolPingEnabled设置,是否运行执行测试SQL语句
// 长时间未使用的连接,才需要ping操作来检测数据库连接是否正常
if (poolPingConnectionsNotUsedFor >= 0
&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
// 执行测试SQL语句
Connection realConn = conn.getRealConnection();
try (Statement statement = realConn.createStatement()) {
statement.executeQuery(poolPingQuery).close();
}
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
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;
}
/**
* Unwraps a pooled connection to get to the 'real' connection
*
* @param conn - the pooled connection to unwrap
* @return The 'real' connection
*/
public static Connection unwrapConnection(Connection conn) {
if (Proxy.isProxyClass(conn.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(conn);
if (handler instanceof PooledConnection) {
return ((PooledConnection) handler).getRealConnection();
}
}
return conn;
}
protected void finalize() throws Throwable {
forceCloseAll();
super.finalize();
}
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new SQLException(getClass().getName() + " is not a wrapper.");
}
public boolean isWrapperFor(Class<?> iface) {
return false;
}
public Logger getParentLogger() {
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); // requires JDK version 1.6
}
}
popConnection方法获取连接,流程如下:
pushConnection方法关闭连接,流程如下:
2.2.1 PooledConnection
从PooledDataSource并不会直接管理java.sql.Connection对象,而是管理PooledConnection对象。在PooledConnection中封装了真正的数据库连接对象以及其代理对象,这里的代理对象是通过JDK动态代理产生的。
org.apache.ibatis.datasource.pooled.PooledConnection实现 InvocationHandler 接口,池化的Connection对象,代码如下:
/**
* @author Clinton Begin
*/
class PooledConnection implements InvocationHandler {
// 关闭 Connection 方法名
private static final String CLOSE = "close";
// JDK Proxy 的接口
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
private final int hashCode;
/**
* 所属的 PooledDataSource对象
* 该PooledConnection是从该PooledDataSource中获取的;
* 当调用close()方法时会将该PooledConnection放回该PooledDataSource中
*/
private final PooledDataSource dataSource;
// 真正的数据库连接
private final Connection realConnection;
// 数据库连接的代理对象
private final Connection proxyConnection;
// 从连接池中取出该连接的时间戳
private long checkoutTimestamp;
// 该连接创建的时间戳
private long createdTimestamp;
// 最后一次使用的时间戳
private long lastUsedTimestamp;
// 由数据库URL、用户名和密码计算出来的hash值,用于标识该连接所在的连接池
private int connectionTypeCode;
/**
* 检测当前PooledConnection是否有效
* 防止程序通过close()方法将连接归还给连接池之后,依然通过该连接操作数据库
*/
private boolean valid;
/**
* Constructor for SimplePooledConnection that uses the Connection and
* PooledDataSource passed in
*
* @param connection - the connection that is to be presented as a pooled
* connection
* @param dataSource - the dataSource that the connection is from
*/
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;
// 创建代理的 Connection 对象
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
/**
* Invalidates the connection
*/
public void invalidate() {
valid = false;
}
/**
* Method to see if the connection is usable
*
* @return True if the connection is usable
*/
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
/**
* Getter for the *real* connection that this wraps
*
* @return The connection
*/
public Connection getRealConnection() {
return realConnection;
}
/**
* Getter for the proxy for the connection
*
* @return The proxy
*/
public Connection getProxyConnection() {
return proxyConnection;
}
/**
* Gets the hashcode of the real connection (or 0 if it is null)
*
* @return The hashcode of the real connection (or 0 if it is null)
*/
public int getRealHashCode() {
return realConnection == null ? 0 : realConnection.hashCode();
}
/**
* Getter for the connection type (based on url + user + password)
*
* @return The connection type
*/
public int getConnectionTypeCode() {
return connectionTypeCode;
}
/**
* Setter for the connection type
*
* @param connectionTypeCode - the connection type
*/
public void setConnectionTypeCode(int connectionTypeCode) {
this.connectionTypeCode = connectionTypeCode;
}
/**
* Getter for the time that the connection was created
*
* @return The creation timestamp
*/
public long getCreatedTimestamp() {
return createdTimestamp;
}
/**
* Setter for the time that the connection was created
*
* @param createdTimestamp - the timestamp
*/
public void setCreatedTimestamp(long createdTimestamp) {
this.createdTimestamp = createdTimestamp;
}
/**
* Getter for the time that the connection was last used
*
* @return - the timestamp
*/
public long getLastUsedTimestamp() {
return lastUsedTimestamp;
}
/**
* Setter for the time that the connection was last used
*
* @param lastUsedTimestamp - the timestamp
*/
public void setLastUsedTimestamp(long lastUsedTimestamp) {
this.lastUsedTimestamp = lastUsedTimestamp;
}
/**
* Getter for the time since this connection was last used
*
* @return - the time since the last use
*/
public long getTimeElapsedSinceLastUse() {
return System.currentTimeMillis() - lastUsedTimestamp;
}
/**
* Getter for the age of the connection
*
* @return the age
*/
public long getAge() {
return System.currentTimeMillis() - createdTimestamp;
}
/**
* Getter for the timestamp that this connection was checked out
*
* @return the timestamp
*/
public long getCheckoutTimestamp() {
return checkoutTimestamp;
}
/**
* Setter for the timestamp that this connection was checked out
*
* @param timestamp the timestamp
*/
public void setCheckoutTimestamp(long timestamp) {
this.checkoutTimestamp = timestamp;
}
/**
* Getter for the time that this connection has been checked out
*
* @return the time
*/
public long getCheckoutTime() {
return System.currentTimeMillis() - checkoutTimestamp;
}
@Override
public int hashCode() {
return hashCode;
}
/**
* Allows comparing this connection to another
*
* @param obj - the other connection to test for equality
* @see Object#equals(Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof PooledConnection) {
return realConnection.hashCode() == ((PooledConnection) obj).realConnection.hashCode();
} else if (obj instanceof Connection) {
return hashCode == obj.hashCode();
} else {
return false;
}
}
/**
* Required for InvocationHandler implementation.
*
* @param proxy - not used
* @param method - the method to be executed
* @param args - the parameters to be passed to the method
* @see java.lang.reflect.InvocationHandler#invoke(Object,
* java.lang.reflect.Method, Object[])
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 如果调用close()方法,则将其放入连接池,而不是真正关闭数据库连接
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
// 如果不是Object类的方法
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();// 检查当前连接是否有效
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private void checkConnection() throws SQLException {
if (!valid) {
throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
}
}
}
失控的阿甘,乐于分享,记录点滴