在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件,如:c3p0、Druid,另外,Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。

1 DataSource

常见的数据源都会实现 javax.sql.DataSource 接口,Mybatis 中提供了两个该接口的实现类,分别是:PooledDataSource 和 UnpooledDataSource,并使用不同的工厂类分别管理这两个类的对象。

1.1 DataSourceFactory

DataSourceFactory 系列类 的设计比较简单,DataSourceFactory 作为顶级接口,UnpooledDataSourceFactory 实现了该接口,PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。

  1. public interface DataSourceFactory {
  2. // 设置 DataSource 的属性,一般紧跟在 DataSource 初始化之后
  3. void setProperties(Properties props);
  4. // 获取 DataSource对象
  5. DataSource getDataSource();
  6. }
  7. public class UnpooledDataSourceFactory implements DataSourceFactory {
  8. private static final String DRIVER_PROPERTY_PREFIX = "driver.";
  9. private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
  10. protected DataSource dataSource;
  11. // 在实例化该工厂时,就完成了 DataSource 的实例化
  12. public UnpooledDataSourceFactory() {
  13. this.dataSource = new UnpooledDataSource();
  14. }
  15. @Override
  16. public void setProperties(Properties properties) {
  17. Properties driverProperties = new Properties();
  18. // 创建 dataSource 对应的 MetaObject
  19. MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
  20. // 处理 properties 中配置的数据源信息
  21. for (Object key : properties.keySet()) {
  22. String propertyName = (String) key;
  23. if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
  24. // 以 "driver." 开头的配置项是对 DataSource 的配置,将其记录到 driverProperties 中
  25. String value = properties.getProperty(propertyName);
  26. driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
  27. } else if (metaDataSource.hasSetter(propertyName)) {
  28. String value = (String) properties.get(propertyName);
  29. Object convertedValue = convertValue(metaDataSource, propertyName, value);
  30. metaDataSource.setValue(propertyName, convertedValue);
  31. } else {
  32. throw new DataSourceException("Unknown DataSource property: " + propertyName);
  33. }
  34. }
  35. if (driverProperties.size() > 0) {
  36. // 设置数据源 UnpooledDataSource 的 driverProperties属性,
  37. // PooledDataSource 中持有 UnpooledDataSource对象
  38. metaDataSource.setValue("driverProperties", driverProperties);
  39. }
  40. }
  41. @Override
  42. public DataSource getDataSource() {
  43. return dataSource;
  44. }
  45. }
  46. public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  47. // 与 UnpooledDataSourceFactory 的不同之处是,其初始化的 DataSource 为 PooledDataSource
  48. public PooledDataSourceFactory() {
  49. this.dataSource = new PooledDataSource();
  50. }
  51. }

1.2 UnpooledDataSource

本实现类实现了 DataSource 接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:

  1. public class UnpooledDataSource implements DataSource {
  2. // 加载 Driver驱动类 的类加载器
  3. private ClassLoader driverClassLoader;
  4. // 数据库连接驱动的相关配置,通过 UnpooledDataSourceFactory 的 setProperties()方法 设置进来的
  5. private Properties driverProperties;
  6. // 缓存所有已注册的 数据库连接驱动Driver
  7. private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
  8. // 数据库连接驱动名称
  9. private String driver;
  10. // 数据库url
  11. private String url;
  12. // 用户名
  13. private String username;
  14. // 密码
  15. private String password;
  16. // 是否自动提交事务
  17. private Boolean autoCommit;
  18. // 默认的事务隔离级别
  19. private Integer defaultTransactionIsolationLevel;
  20. // 默认的网络连接超时时间
  21. private Integer defaultNetworkTimeout;
  22. /**
  23. * UnpooledDataSource 被加载时,会通过该静态代码块将已经在 DriverManager
  24. * 中注册的 JDBC Driver 注册到 registeredDrivers 中
  25. */
  26. static {
  27. Enumeration<Driver> drivers = DriverManager.getDrivers();
  28. while (drivers.hasMoreElements()) {
  29. Driver driver = drivers.nextElement();
  30. registeredDrivers.put(driver.getClass().getName(), driver);
  31. }
  32. }
  33. // getConnection() 及其重载方法、doGetConnection(String username, String password)方法
  34. // 最终都会调用本方法
  35. private Connection doGetConnection(Properties properties) throws SQLException {
  36. // 初始化数据库驱动,该方法会创建配置中指定的 Driver对象,
  37. // 并将其注册到 DriverManager 和 registeredDrivers 中
  38. initializeDriver();
  39. Connection connection = DriverManager.getConnection(url, properties);
  40. // 配置数据库连接属性,如:连接超时时间、是否自动提交事务、事务隔离级别
  41. configureConnection(connection);
  42. return connection;
  43. }
  44. private synchronized void initializeDriver() throws SQLException {
  45. // 判断驱动是否已注册
  46. if (!registeredDrivers.containsKey(driver)) {
  47. Class<?> driverType;
  48. try {
  49. if (driverClassLoader != null) {
  50. // 注册驱动
  51. driverType = Class.forName(driver, true, driverClassLoader);
  52. } else {
  53. driverType = Resources.classForName(driver);
  54. }
  55. // 通过反射获取 Driver实例对象
  56. Driver driverInstance = (Driver)driverType.newInstance();
  57. // 注册驱动到 DriverManager,DriverProxy 是 UnpooledDataSource 的内部类
  58. // 也是 Driver 的静态代理类
  59. DriverManager.registerDriver(new DriverProxy(driverInstance));
  60. // 将 driver 缓存到 registeredDrivers
  61. registeredDrivers.put(driver, driverInstance);
  62. } catch (Exception e) {
  63. throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
  64. }
  65. }
  66. }
  67. private void configureConnection(Connection conn) throws SQLException {
  68. // 连接超时时间
  69. if (defaultNetworkTimeout != null) {
  70. conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
  71. }
  72. // 是否自动提交事务
  73. if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
  74. conn.setAutoCommit(autoCommit);
  75. }
  76. // 事务隔离级别
  77. if (defaultTransactionIsolationLevel != null) {
  78. conn.setTransactionIsolation(defaultTransactionIsolationLevel);
  79. }
  80. }
  81. }

1.3 PooledDataSource

数据库建立连接是非常耗时的,且并发的连接数也非常有限。而数据库连接池可以实现数据库的重用、提高响应速度、防止数据库因连接过多而假死等。 数据库连接池的设计思路一般为:

  1. 连接池初始化时创建一定数量的连接,并添加到连接池中备用;
  2. 当程序需要使用数据库连接时,从连接池中请求,用完后会将其返还给连接池,而不是直接关闭;
  3. 连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会短暂阻塞后重新尝试获取连接,如此循环,直到有连接可用;
  4. 如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。

PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。

1.3.1 PooledConnection

PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection 数据库连接对象 及其代理对象(JDK 动态代理生成的)。PooledConnection 继承了 JDK 动态代理 的 InvocationHandler 接口。

  1. class PooledConnection implements InvocationHandler {
  2. // 记录当前 PooledConnection对象 所属的 PooledDataSource对象
  3. // 当调用 close()方法 时会将 PooledConnection 放回该 PooledDataSource
  4. private final PooledDataSource dataSource;
  5. // 真正的数据库连接对象
  6. private final Connection realConnection;
  7. // 代理连接对象
  8. private final Connection proxyConnection;
  9. // 从连接池中取出该连接时的时间戳
  10. private long checkoutTimestamp;
  11. // 创建该连接时的时间戳
  12. private long createdTimestamp;
  13. // 最后一次使用的 时间戳
  14. private long lastUsedTimestamp;
  15. // 由 数据库URL、用户名、密码 计算出来的 hash值,可用于标识该连接所在的连接池
  16. private int connectionTypeCode;
  17. // 检测当前 PooledConnection连接池连接对象 是否有效,主要用于 防止程序通过 close()方法 将
  18. // 连接还给连接池之后,依然通过该连接操作数据库
  19. private boolean valid;
  20. /**
  21. * invoke()方法 是本类的重点实现,也是 proxyConnection代理连接对象 的代理逻辑实现
  22. * 它会对 close()方法 的调用进行处理,并在调用 realConnection对象 的方法之前进行校验
  23. */
  24. @Override
  25. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  26. String methodName = method.getName();
  27. // 如果调用的是 close()方法,则将其放进连接池,而不是真的关闭连接
  28. if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
  29. dataSource.pushConnection(this);
  30. return null;
  31. }
  32. try {
  33. if (!Object.class.equals(method.getDeclaringClass())) {
  34. // 通过上面的 valid字段 校验连接是否有效
  35. checkConnection();
  36. }
  37. // 调用 realConnection对象 的对应方法
  38. return method.invoke(realConnection, args);
  39. } catch (Throwable t) {
  40. throw ExceptionUtil.unwrapThrowable(t);
  41. }
  42. }
  43. private void checkConnection() throws SQLException {
  44. if (!valid) {
  45. throw new SQLException("Error accessing PooledConnection. Connection is invalid.");
  46. }
  47. }
  48. }

1.3.2 PoolState

PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List<PooledConnection>集合 分别管理空闲状态的连接 和 活跃状态的连接。另外,PoolState 还定义了一系列用于统计的字段。

  1. public class PoolState {
  2. // 所属的连接池对象
  3. protected PooledDataSource dataSource;
  4. // 空闲的连接
  5. protected final List<PooledConnection> idleConnections = new ArrayList<>();
  6. // 活跃的连接
  7. protected final List<PooledConnection> activeConnections = new ArrayList<>();
  8. // 请求数据库连接的次数
  9. protected long requestCount = 0;
  10. // 获取连接的累计时间(accumulate累计)
  11. protected long accumulatedRequestTime = 0;
  12. // CheckoutTime = 记录 应用从连接池取出连接到归还连接的时长
  13. // accumulatedCheckoutTime = 所有连接累计的CheckoutTime
  14. protected long accumulatedCheckoutTime = 0;
  15. // 超时连接的个数(当连接长时间未归还给连接池时,会被认为连接超时)
  16. protected long claimedOverdueConnectionCount = 0;
  17. // 累计超时时间
  18. protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  19. // 累计等待时间
  20. protected long accumulatedWaitTime = 0;
  21. // 等待次数
  22. protected long hadToWaitCount = 0;
  23. // 无效的连接数
  24. protected long badConnectionCount = 0;
  25. public PoolState(PooledDataSource dataSource) {
  26. this.dataSource = dataSource;
  27. }
  28. public synchronized long getRequestCount() {
  29. return requestCount;
  30. }
  31. public synchronized long getAverageRequestTime() {
  32. return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
  33. }
  34. public synchronized long getAverageWaitTime() {
  35. return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
  36. }
  37. public synchronized long getHadToWaitCount() {
  38. return hadToWaitCount;
  39. }
  40. public synchronized long getBadConnectionCount() {
  41. return badConnectionCount;
  42. }
  43. public synchronized long getClaimedOverdueConnectionCount() {
  44. return claimedOverdueConnectionCount;
  45. }
  46. public synchronized long getAverageOverdueCheckoutTime() {
  47. return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
  48. }
  49. public synchronized long getAverageCheckoutTime() {
  50. return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
  51. }
  52. public synchronized int getIdleConnectionCount() {
  53. return idleConnections.size();
  54. }
  55. public synchronized int getActiveConnectionCount() {
  56. return activeConnections.size();
  57. }
  58. @Override
  59. public synchronized String toString() {
  60. StringBuilder builder = new StringBuilder();
  61. builder.append("\n===CONFINGURATION==============================================");
  62. builder.append("\n jdbcDriver ").append(dataSource.getDriver());
  63. builder.append("\n jdbcUrl ").append(dataSource.getUrl());
  64. builder.append("\n jdbcUsername ").append(dataSource.getUsername());
  65. builder.append("\n jdbcPassword ").append(dataSource.getPassword() == null ? "NULL" : "************");
  66. builder.append("\n poolMaxActiveConnections ").append(dataSource.poolMaximumActiveConnections);
  67. builder.append("\n poolMaxIdleConnections ").append(dataSource.poolMaximumIdleConnections);
  68. builder.append("\n poolMaxCheckoutTime ").append(dataSource.poolMaximumCheckoutTime);
  69. builder.append("\n poolTimeToWait ").append(dataSource.poolTimeToWait);
  70. builder.append("\n poolPingEnabled ").append(dataSource.poolPingEnabled);
  71. builder.append("\n poolPingQuery ").append(dataSource.poolPingQuery);
  72. builder.append("\n poolPingConnectionsNotUsedFor ").append(dataSource.poolPingConnectionsNotUsedFor);
  73. builder.append("\n ---STATUS-----------------------------------------------------");
  74. builder.append("\n activeConnections ").append(getActiveConnectionCount());
  75. builder.append("\n idleConnections ").append(getIdleConnectionCount());
  76. builder.append("\n requestCount ").append(getRequestCount());
  77. builder.append("\n averageRequestTime ").append(getAverageRequestTime());
  78. builder.append("\n averageCheckoutTime ").append(getAverageCheckoutTime());
  79. builder.append("\n claimedOverdue ").append(getClaimedOverdueConnectionCount());
  80. builder.append("\n averageOverdueCheckoutTime ").append(getAverageOverdueCheckoutTime());
  81. builder.append("\n hadToWait ").append(getHadToWaitCount());
  82. builder.append("\n averageWaitTime ").append(getAverageWaitTime());
  83. builder.append("\n badConnectionCount ").append(getBadConnectionCount());
  84. builder.append("\n===============================================================");
  85. return builder.toString();
  86. }
  87. }

1.3.3 PooledDataSource

PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。 PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection 对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图:

avatar

  1. public class PooledDataSource implements DataSource {
  2. private static final Log log = LogFactory.getLog(PooledDataSource.class);
  3. // 管理连接池状态 并统计连接信息
  4. private final PoolState state = new PoolState(this);
  5. // 该对象用于生成真正的数据库连接对象,构造函数中会初始化该字段
  6. private final UnpooledDataSource dataSource;
  7. // 最大活跃连接数
  8. protected int poolMaximumActiveConnections = 10;
  9. // 最大空闲连接数
  10. protected int poolMaximumIdleConnections = 5;
  11. // 最大Checkout时长
  12. protected int poolMaximumCheckoutTime = 20000;
  13. // 在无法获取连接时,线程需要等待的时间
  14. protected int poolTimeToWait = 20000;
  15. // 本地坏连接最大数
  16. protected int poolMaximumLocalBadConnectionTolerance = 3;
  17. // 检测数据库连接是否可用时,给数据库发送的sql语句
  18. protected String poolPingQuery = "NO PING QUERY SET";
  19. // 是否允许发送上述语句
  20. protected boolean poolPingEnabled;
  21. // 当连接超过poolPingConnectionsNotUsedFor毫秒未使用,
  22. // 就发送一次上述sql,检测连接连接是否正常
  23. protected int poolPingConnectionsNotUsedFor;
  24. // 根据数据库URL、用户名、密码 生成的一个hash值,
  25. // 该hash值用于标记当前的连接池,在构造函数中初始化
  26. private int expectedConnectionTypeCode;
  27. /**
  28. * 下面的两个 getConnection()方法 都会调用 popConnection()
  29. * 获取 PooledConnection对象,然后调用该对象的 getProxyConnection()方法
  30. * 获取数据库连接的代理对象
  31. */
  32. @Override
  33. public Connection getConnection() throws SQLException {
  34. return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  35. }
  36. @Override
  37. public Connection getConnection(String username, String password) throws SQLException {
  38. return popConnection(username, password).getProxyConnection();
  39. }
  40. /**
  41. * 本方法实现了连接池获取连接对象的具体逻辑,是 PooledDataSource 的核心逻辑之一
  42. */
  43. private PooledConnection popConnection(String username, String password) throws SQLException {
  44. boolean countedWait = false;
  45. PooledConnection conn = null;
  46. long t = System.currentTimeMillis();
  47. int localBadConnectionCount = 0;
  48. // 循环获取数据库连接对象,直到获取成功
  49. while (conn == null) {
  50. // 连接池的连接是公共资源,要对线程加锁
  51. synchronized (state) {
  52. // 如果连接池中有空闲的 数据库连接对象,就取出一个
  53. if (!state.idleConnections.isEmpty()) {
  54. conn = state.idleConnections.remove(0);
  55. if (log.isDebugEnabled()) {
  56. log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
  57. }
  58. } else {
  59. // 没有空闲的连接对象,就判断一下 活跃的连接数是否已达 设定的峰值
  60. if (state.activeConnections.size() < poolMaximumActiveConnections) {
  61. // 还没达到峰值 就创建一个新的连接
  62. conn = new PooledConnection(dataSource.getConnection(), this);
  63. if (log.isDebugEnabled()) {
  64. log.debug("Created connection " + conn.getRealHashCode() + ".");
  65. }
  66. } else {
  67. // 如果活跃的连接已达上限,就取出最老的活跃连接对象,判断其是否超时
  68. PooledConnection oldestActiveConnection = state.activeConnections.get(0);
  69. long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
  70. if (longestCheckoutTime > poolMaximumCheckoutTime) {
  71. // 如果最老的连接超时了,就在 PoolState 中记录一下相关信息,然后将该连接对象释放掉
  72. state.claimedOverdueConnectionCount++;
  73. state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
  74. state.accumulatedCheckoutTime += longestCheckoutTime;
  75. state.activeConnections.remove(oldestActiveConnection);
  76. // 如果最老的连接不是 自动提交事务的,就将事务回滚掉
  77. if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
  78. try {
  79. oldestActiveConnection.getRealConnection().rollback();
  80. } catch (SQLException e) {
  81. /*
  82. Just log a message for debug and continue to execute the following
  83. statement like nothing happened.
  84. Wrap the bad connection with a new PooledConnection, this will help
  85. to not interrupt current executing thread and give current thread a
  86. chance to join the next competition for another valid/good database
  87. connection. At the end of this loop, bad {@link @conn} will be set as null.
  88. */
  89. log.debug("Bad connection. Could not roll back");
  90. }
  91. }
  92. // 从最老连接中取出真正的 数据库连接对象及相关信息,用来构建新的 PooledConnection对象
  93. conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
  94. conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
  95. conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
  96. // 将最老活跃连接设为无效
  97. oldestActiveConnection.invalidate();
  98. if (log.isDebugEnabled()) {
  99. log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
  100. }
  101. } else {
  102. // 如果最老的连接对象也没超时,则进入阻塞等待,
  103. // 等待时间 poolTimeToWait 可自行设置
  104. try {
  105. if (!countedWait) {
  106. // 等待次数加一
  107. state.hadToWaitCount++;
  108. countedWait = true;
  109. }
  110. if (log.isDebugEnabled()) {
  111. log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
  112. }
  113. long wt = System.currentTimeMillis();
  114. // native方法,使执行到这里的线程阻塞等待 poolTimeToWait毫秒
  115. state.wait(poolTimeToWait);
  116. // 统计累计等待的时间
  117. state.accumulatedWaitTime += System.currentTimeMillis() - wt;
  118. } catch (InterruptedException e) {
  119. break;
  120. }
  121. }
  122. }
  123. }
  124. // 到了这里 基本上就获取到连接对象咯,但我们还要确认一下该连接对象是否是有效的 可用的
  125. if (conn != null) {
  126. // ping一下数据库服务器,确认该连接对象是否有效
  127. if (conn.isValid()) {
  128. // 如果事务提交配置为手动的,则先让该连接回滚一下事务,防止脏数据的出现
  129. if (!conn.getRealConnection().getAutoCommit()) {
  130. conn.getRealConnection().rollback();
  131. }
  132. // 设置 由数据库URL、用户名、密码 计算出来的hash值,可用于标识该连接所在的连接池
  133. conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
  134. // 设置 从连接池中取出该连接时的时间戳
  135. conn.setCheckoutTimestamp(System.currentTimeMillis());
  136. // 设置 最后一次使用的时间戳
  137. conn.setLastUsedTimestamp(System.currentTimeMillis());
  138. // 将该连接加入活跃的连接对象列表
  139. state.activeConnections.add(conn);
  140. // 请求数据库连接的次数加一
  141. state.requestCount++;
  142. // 计算 获取连接的累计时间(accumulate累计)
  143. state.accumulatedRequestTime += System.currentTimeMillis() - t;
  144. // 如果获取到的连接无效
  145. } else {
  146. if (log.isDebugEnabled()) {
  147. log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
  148. }
  149. // 对无效连接进行统计
  150. state.badConnectionCount++;
  151. localBadConnectionCount++;
  152. conn = null;
  153. // 如果无效连接超出 阈值,则抛出异常
  154. if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
  155. if (log.isDebugEnabled()) {
  156. log.debug("PooledDataSource: Could not get a good connection to the database.");
  157. }
  158. throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
  159. }
  160. }
  161. }
  162. }
  163. }
  164. // 如果到了这里 连接还为空,则抛出一个未知的服务异常
  165. if (conn == null) {
  166. if (log.isDebugEnabled()) {
  167. log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
  168. }
  169. throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
  170. }
  171. // 返回数据库连接对象
  172. return conn;
  173. }
  174. /**
  175. * 看一下之前讲过的 PooledConnection 中的 动态代理方法invoke(),可以发现
  176. * 当调用数据库连接代理对象的 close()方法 时,并未关闭真正的数据库连接,
  177. * 而是调用了本方法,将连接对象归还给连接池,方便后续使用,本方法也是 PooledDataSource 的核心逻辑之一
  178. */
  179. protected void pushConnection(PooledConnection conn) throws SQLException {
  180. // 国际惯例,操作公共资源先上个锁
  181. synchronized (state) {
  182. // 先将该连接从活跃的连接对象列表中剔除
  183. state.activeConnections.remove(conn);
  184. // 如果该连接有效
  185. if (conn.isValid()) {
  186. // 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于
  187. // 本连接池(通过之前获取的 expectedConnectionTypeCode 进行校验)
  188. if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
  189. // CheckoutTime = 应用从连接池取出连接到归还连接的时长
  190. // accumulatedCheckoutTime = 所有连接累计的CheckoutTime
  191. state.accumulatedCheckoutTime += conn.getCheckoutTime();
  192. // 不是自动提交事务的连接 先回滚一波
  193. if (!conn.getRealConnection().getAutoCommit()) {
  194. conn.getRealConnection().rollback();
  195. }
  196. // 从 conn 中取出真正的 数据库连接对象,重新封装成 PooledConnection
  197. PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
  198. // 将 newConn 放进空闲连接对象列表
  199. state.idleConnections.add(newConn);
  200. // 设置 newConn 的相关属性
  201. newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
  202. newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
  203. // 将原本的 conn 作废
  204. conn.invalidate();
  205. if (log.isDebugEnabled()) {
  206. log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
  207. }
  208. // 唤醒阻塞等待的线程
  209. state.notifyAll();
  210. } else {
  211. // 如果空闲连接已达阈值 或 该连接对象不属于本连接池,则做好统计数据
  212. // 回滚连接的事务,关闭真正的连接,最后作废 该conn
  213. state.accumulatedCheckoutTime += conn.getCheckoutTime();
  214. if (!conn.getRealConnection().getAutoCommit()) {
  215. conn.getRealConnection().rollback();
  216. }
  217. conn.getRealConnection().close();
  218. if (log.isDebugEnabled()) {
  219. log.debug("Closed connection " + conn.getRealHashCode() + ".");
  220. }
  221. conn.invalidate();
  222. }
  223. // 如果该连接是无效的,则记录一下无效的连接数
  224. } else {
  225. if (log.isDebugEnabled()) {
  226. log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
  227. }
  228. state.badConnectionCount++;
  229. }
  230. }
  231. }
  232. /**
  233. * 关闭连接池中 所有活跃的 及 空闲的连接
  234. * 当修改连接池的配置(如:用户名、密码、URL等),都会调用本方法
  235. */
  236. public void forceCloseAll() {
  237. // 日常上锁
  238. synchronized (state) {
  239. // 更新当前连接池的标识
  240. expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
  241. // 依次关闭活跃的连接对象
  242. for (int i = state.activeConnections.size(); i > 0; i--) {
  243. try {
  244. PooledConnection conn = state.activeConnections.remove(i - 1);
  245. conn.invalidate();
  246. Connection realConn = conn.getRealConnection();
  247. if (!realConn.getAutoCommit()) {
  248. realConn.rollback();
  249. }
  250. realConn.close();
  251. } catch (Exception e) {
  252. // ignore
  253. }
  254. }
  255. // 依次关闭空闲的连接对象
  256. for (int i = state.idleConnections.size(); i > 0; i--) {
  257. try {
  258. PooledConnection conn = state.idleConnections.remove(i - 1);
  259. conn.invalidate();
  260. Connection realConn = conn.getRealConnection();
  261. if (!realConn.getAutoCommit()) {
  262. realConn.rollback();
  263. }
  264. realConn.close();
  265. } catch (Exception e) {
  266. // ignore
  267. }
  268. }
  269. }
  270. if (log.isDebugEnabled()) {
  271. log.debug("PooledDataSource forcefully closed/removed all connections.");
  272. }
  273. }
  274. }

最后,我们来看一下 popConnection() 和 pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid 字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql 语句,检测连接是否正常。

  1. class PooledConnection implements InvocationHandler {
  2. /**
  3. * 检测 PooledConnection对象 的有效性
  4. */
  5. public boolean isValid() {
  6. return valid && realConnection != null && dataSource.pingConnection(this);
  7. }
  8. }
  9. public class PooledDataSource implements DataSource {
  10. /**
  11. * ping 一下数据库,检测数据库连接是否正常
  12. */
  13. protected boolean pingConnection(PooledConnection conn) {
  14. boolean result = true;
  15. try {
  16. result = !conn.getRealConnection().isClosed();
  17. } catch (SQLException e) {
  18. if (log.isDebugEnabled()) {
  19. log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
  20. }
  21. result = false;
  22. }
  23. if (result) {
  24. // 是否允许发送检测语句,检测数据库连接是否正常,poolPingEnabled 可自行配置
  25. // 该检测会牺牲一定的系统资源,以提高安全性
  26. if (poolPingEnabled) {
  27. // 超过 poolPingConnectionsNotUsedFor毫秒 未使用的连接 才会检测其连接状态
  28. if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
  29. try {
  30. if (log.isDebugEnabled()) {
  31. log.debug("Testing connection " + conn.getRealHashCode() + " ...");
  32. }
  33. // 获取真正的连接对象,执行 poolPingQuery = "NO PING QUERY SET" sql语句
  34. Connection realConn = conn.getRealConnection();
  35. try (Statement statement = realConn.createStatement()) {
  36. statement.executeQuery(poolPingQuery).close();
  37. }
  38. if (!realConn.getAutoCommit()) {
  39. realConn.rollback();
  40. }
  41. result = true;
  42. if (log.isDebugEnabled()) {
  43. log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
  44. }
  45. // 如果上面这段代码抛出异常,则说明数据库连接有问题,将该连接关闭,返回false
  46. } catch (Exception e) {
  47. log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
  48. try {
  49. conn.getRealConnection().close();
  50. } catch (Exception e2) {
  51. //ignore
  52. }
  53. result = false;
  54. if (log.isDebugEnabled()) {
  55. log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
  56. }
  57. }
  58. }
  59. }
  60. }
  61. return result;
  62. }
  63. }

2 Transaction

遵循 “接口-实现类” 的设计原则,Mybatis 也是先使用 Transaction 接口 对数据库事务做了抽象,而实现类则只提供了两个,即:JdbcTransaction 和 ManagedTransaction。这两种对象的获取,使用了两个对应的工厂类 JdbcTransactionFactory 和 ManagedTransactionFactory。 不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring,由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。

  1. public interface Transaction {
  2. /**
  3. * 获取连接对象
  4. */
  5. Connection getConnection() throws SQLException;
  6. /**
  7. * 提交事务
  8. */
  9. void commit() throws SQLException;
  10. /**
  11. * 回滚事务
  12. */
  13. void rollback() throws SQLException;
  14. /**
  15. * 关闭数据库连接
  16. */
  17. void close() throws SQLException;
  18. /**
  19. * 获取配置的事务超时时间
  20. */
  21. Integer getTimeout() throws SQLException;
  22. }
  23. public class JdbcTransaction implements Transaction {
  24. private static final Log log = LogFactory.getLog(JdbcTransaction.class);
  25. // 当前事务对应的数据库连接
  26. protected Connection connection;
  27. // 当前事务对应的数据源
  28. protected DataSource dataSource;
  29. // 事务隔离级别
  30. protected TransactionIsolationLevel level;
  31. // 是否自动提交
  32. protected boolean autoCommit;
  33. public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
  34. dataSource = ds;
  35. level = desiredLevel;
  36. autoCommit = desiredAutoCommit;
  37. }
  38. public JdbcTransaction(Connection connection) {
  39. this.connection = connection;
  40. }
  41. @Override
  42. public Connection getConnection() throws SQLException {
  43. if (connection == null) {
  44. openConnection();
  45. }
  46. return connection;
  47. }
  48. // 提交、回滚、关闭等操作的代码都比较简单,只对原生的 JDBC操作 做了简单封装
  49. @Override
  50. public void commit() throws SQLException {
  51. if (connection != null && !connection.getAutoCommit()) {
  52. if (log.isDebugEnabled()) {
  53. log.debug("Committing JDBC Connection [" + connection + "]");
  54. }
  55. connection.commit();
  56. }
  57. }
  58. @Override
  59. public void rollback() throws SQLException {
  60. if (connection != null && !connection.getAutoCommit()) {
  61. if (log.isDebugEnabled()) {
  62. log.debug("Rolling back JDBC Connection [" + connection + "]");
  63. }
  64. connection.rollback();
  65. }
  66. }
  67. @Override
  68. public void close() throws SQLException {
  69. if (connection != null) {
  70. resetAutoCommit();
  71. if (log.isDebugEnabled()) {
  72. log.debug("Closing JDBC Connection [" + connection + "]");
  73. }
  74. connection.close();
  75. }
  76. }
  77. protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
  78. try {
  79. if (connection.getAutoCommit() != desiredAutoCommit) {
  80. if (log.isDebugEnabled()) {
  81. log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
  82. }
  83. connection.setAutoCommit(desiredAutoCommit);
  84. }
  85. } catch (SQLException e) {
  86. // Only a very poorly implemented driver would fail here,
  87. // and there's not much we can do about that.
  88. throw new TransactionException("Error configuring AutoCommit. "
  89. + "Your driver may not support getAutoCommit() or setAutoCommit(). "
  90. + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
  91. }
  92. }
  93. protected void resetAutoCommit() {
  94. try {
  95. if (!connection.getAutoCommit()) {
  96. // MyBatis does not call commit/rollback on a connection if just selects were performed.
  97. // Some databases start transactions with select statements
  98. // and they mandate a commit/rollback before closing the connection.
  99. // A workaround is setting the autocommit to true before closing the connection.
  100. // Sybase throws an exception here.
  101. if (log.isDebugEnabled()) {
  102. log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
  103. }
  104. connection.setAutoCommit(true);
  105. }
  106. } catch (SQLException e) {
  107. if (log.isDebugEnabled()) {
  108. log.debug("Error resetting autocommit to true "
  109. + "before closing the connection. Cause: " + e);
  110. }
  111. }
  112. }
  113. protected void openConnection() throws SQLException {
  114. if (log.isDebugEnabled()) {
  115. log.debug("Opening JDBC Connection");
  116. }
  117. connection = dataSource.getConnection();
  118. if (level != null) {
  119. connection.setTransactionIsolation(level.getLevel());
  120. }
  121. setDesiredAutoCommit(autoCommit);
  122. }
  123. @Override
  124. public Integer getTimeout() throws SQLException {
  125. return null;
  126. }
  127. }
  128. public class ManagedTransaction implements Transaction {
  129. private static final Log log = LogFactory.getLog(ManagedTransaction.class);
  130. // 数据源
  131. private DataSource dataSource;
  132. // 事务隔离级别
  133. private TransactionIsolationLevel level;
  134. // 对应的数据库连接
  135. private Connection connection;
  136. // 控制是否关闭持有的连接,在 close()方法 中用其判断是否真的关闭连接
  137. private final boolean closeConnection;
  138. // 本类的实现也很简单,commit()、rollback()方法 都是空实现
  139. public ManagedTransaction(Connection connection, boolean closeConnection) {
  140. this.connection = connection;
  141. this.closeConnection = closeConnection;
  142. }
  143. public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
  144. this.dataSource = ds;
  145. this.level = level;
  146. this.closeConnection = closeConnection;
  147. }
  148. @Override
  149. public Connection getConnection() throws SQLException {
  150. if (this.connection == null) {
  151. openConnection();
  152. }
  153. return this.connection;
  154. }
  155. @Override
  156. public void commit() throws SQLException {
  157. // Does nothing
  158. }
  159. @Override
  160. public void rollback() throws SQLException {
  161. // Does nothing
  162. }
  163. @Override
  164. public void close() throws SQLException {
  165. if (this.closeConnection && this.connection != null) {
  166. if (log.isDebugEnabled()) {
  167. log.debug("Closing JDBC Connection [" + this.connection + "]");
  168. }
  169. this.connection.close();
  170. }
  171. }
  172. protected void openConnection() throws SQLException {
  173. if (log.isDebugEnabled()) {
  174. log.debug("Opening JDBC Connection");
  175. }
  176. this.connection = this.dataSource.getConnection();
  177. if (this.level != null) {
  178. this.connection.setTransactionIsolation(this.level.getLevel());
  179. }
  180. }
  181. @Override
  182. public Integer getTimeout() throws SQLException {
  183. return null;
  184. }
  185. }
  186. public interface TransactionFactory {
  187. /**
  188. * 配置 TransactionFactory对象,一般会在完成 TransactionFactory对象
  189. * 初始化之后 就进行自定义属性配置
  190. */
  191. default void setProperties(Properties props) {
  192. // NOP
  193. }
  194. /**
  195. * 在指定的数据库连接上创建 Transaction事务对象
  196. */
  197. Transaction newTransaction(Connection conn);
  198. /**
  199. * 从指定数据源获取数据库连接,并在此连接上创建 Transaction对象
  200. */
  201. Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
  202. }
  203. public class JdbcTransactionFactory implements TransactionFactory {
  204. @Override
  205. public Transaction newTransaction(Connection conn) {
  206. return new JdbcTransaction(conn);
  207. }
  208. @Override
  209. public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
  210. return new JdbcTransaction(ds, level, autoCommit);
  211. }
  212. }
  213. public class ManagedTransactionFactory implements TransactionFactory {
  214. private boolean closeConnection = true;
  215. @Override
  216. public void setProperties(Properties props) {
  217. if (props != null) {
  218. String closeConnectionProperty = props.getProperty("closeConnection");
  219. if (closeConnectionProperty != null) {
  220. closeConnection = Boolean.valueOf(closeConnectionProperty);
  221. }
  222. }
  223. }
  224. @Override
  225. public Transaction newTransaction(Connection conn) {
  226. return new ManagedTransaction(conn, closeConnection);
  227. }
  228. @Override
  229. public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
  230. // Silently ignores autocommit and isolation level, as managed transactions are entirely
  231. // controlled by an external manager. It's silently ignored so that
  232. // code remains portable between managed and unmanaged configurations.
  233. return new ManagedTransaction(ds, level, closeConnection);
  234. }
  235. }