在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件,如:c3p0、Druid,另外,Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。
常见的数据源都会实现 javax.sql.DataSource 接口,Mybatis 中提供了两个该接口的实现类,分别是:PooledDataSource 和 UnpooledDataSource,并使用不同的工厂类分别管理这两个类的对象。
DataSourceFactory 系列类 的设计比较简单,DataSourceFactory 作为顶级接口,UnpooledDataSourceFactory 实现了该接口,PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。
publicinterfaceDataSourceFactory { // 设置 DataSource 的属性,一般紧跟在 DataSource 初始化之后voidsetProperties(Propertiesprops); // 获取 DataSource对象DataSourcegetDataSource(); } publicclassUnpooledDataSourceFactoryimplementsDataSourceFactory { privatestaticfinalStringDRIVER_PROPERTY_PREFIX = "driver."; privatestaticfinalintDRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length(); protectedDataSourcedataSource; // 在实例化该工厂时,就完成了 DataSource 的实例化publicUnpooledDataSourceFactory() { this.dataSource = newUnpooledDataSource(); } @OverridepublicvoidsetProperties(Propertiesproperties) { PropertiesdriverProperties = newProperties(); // 创建 dataSource 对应的 MetaObjectMetaObjectmetaDataSource = SystemMetaObject.forObject(dataSource); // 处理 properties 中配置的数据源信息for (Objectkey : properties.keySet()) { StringpropertyName = (String) key; if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) { // 以 "driver." 开头的配置项是对 DataSource 的配置,将其记录到 driverProperties 中Stringvalue = properties.getProperty(propertyName); driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value); } elseif (metaDataSource.hasSetter(propertyName)) { Stringvalue = (String) properties.get(propertyName); ObjectconvertedValue = convertValue(metaDataSource, propertyName, value); metaDataSource.setValue(propertyName, convertedValue); } else { thrownewDataSourceException("Unknown DataSource property: " + propertyName); } } if (driverProperties.size() > 0) { // 设置数据源 UnpooledDataSource 的 driverProperties属性,// PooledDataSource 中持有 UnpooledDataSource对象metaDataSource.setValue("driverProperties", driverProperties); } } @OverridepublicDataSourcegetDataSource() { returndataSource; } } publicclassPooledDataSourceFactoryextendsUnpooledDataSourceFactory { // 与 UnpooledDataSourceFactory 的不同之处是,其初始化的 DataSource 为 PooledDataSourcepublicPooledDataSourceFactory() { this.dataSource = newPooledDataSource(); } }
本实现类实现了 DataSource 接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
publicclassUnpooledDataSourceimplementsDataSource { // 加载 Driver驱动类 的类加载器privateClassLoaderdriverClassLoader; // 数据库连接驱动的相关配置,通过 UnpooledDataSourceFactory 的 setProperties()方法 设置进来的privatePropertiesdriverProperties; // 缓存所有已注册的 数据库连接驱动DriverprivatestaticMap<String, Driver> registeredDrivers = newConcurrentHashMap<>(); // 数据库连接驱动名称privateStringdriver; // 数据库urlprivateStringurl; // 用户名privateStringusername; // 密码privateStringpassword; // 是否自动提交事务privateBooleanautoCommit; // 默认的事务隔离级别privateIntegerdefaultTransactionIsolationLevel; // 默认的网络连接超时时间privateIntegerdefaultNetworkTimeout; /** * UnpooledDataSource 被加载时,会通过该静态代码块将已经在 DriverManager * 中注册的 JDBC Driver 注册到 registeredDrivers 中 */static { Enumeration<Driver> drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driverdriver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } // getConnection() 及其重载方法、doGetConnection(String username, String password)方法// 最终都会调用本方法privateConnectiondoGetConnection(Propertiesproperties) throwsSQLException { // 初始化数据库驱动,该方法会创建配置中指定的 Driver对象,// 并将其注册到 DriverManager 和 registeredDrivers 中initializeDriver(); Connectionconnection = DriverManager.getConnection(url, properties); // 配置数据库连接属性,如:连接超时时间、是否自动提交事务、事务隔离级别configureConnection(connection); returnconnection; } privatesynchronizedvoidinitializeDriver() throwsSQLException { // 判断驱动是否已注册if (!registeredDrivers.containsKey(driver)) { Class<?> driverType; try { if (driverClassLoader != null) { // 注册驱动driverType = Class.forName(driver, true, driverClassLoader); } else { driverType = Resources.classForName(driver); } // 通过反射获取 Driver实例对象DriverdriverInstance = (Driver)driverType.newInstance(); // 注册驱动到 DriverManager,DriverProxy 是 UnpooledDataSource 的内部类// 也是 Driver 的静态代理类DriverManager.registerDriver(newDriverProxy(driverInstance)); // 将 driver 缓存到 registeredDriversregisteredDrivers.put(driver, driverInstance); } catch (Exceptione) { thrownewSQLException("Error setting driver on UnpooledDataSource. Cause: " + e); } } } privatevoidconfigureConnection(Connectionconn) throwsSQLException { // 连接超时时间if (defaultNetworkTimeout != null) { conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout); } // 是否自动提交事务if (autoCommit != null && autoCommit != conn.getAutoCommit()) { conn.setAutoCommit(autoCommit); } // 事务隔离级别if (defaultTransactionIsolationLevel != null) { conn.setTransactionIsolation(defaultTransactionIsolationLevel); } } }
数据库建立连接是非常耗时的,且并发的连接数也非常有限。而数据库连接池可以实现数据库的重用、提高响应速度、防止数据库因连接过多而假死等。 数据库连接池的设计思路一般为:
- 连接池初始化时创建一定数量的连接,并添加到连接池中备用;
- 当程序需要使用数据库连接时,从连接池中请求,用完后会将其返还给连接池,而不是直接关闭;
- 连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会短暂阻塞后重新尝试获取连接,如此循环,直到有连接可用;
- 如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。
PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。
PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection 数据库连接对象 及其代理对象(JDK 动态代理生成的)。PooledConnection 继承了 JDK 动态代理 的 InvocationHandler 接口。
classPooledConnectionimplementsInvocationHandler { // 记录当前 PooledConnection对象 所属的 PooledDataSource对象// 当调用 close()方法 时会将 PooledConnection 放回该 PooledDataSourceprivatefinalPooledDataSourcedataSource; // 真正的数据库连接对象privatefinalConnectionrealConnection; // 代理连接对象privatefinalConnectionproxyConnection; // 从连接池中取出该连接时的时间戳privatelongcheckoutTimestamp; // 创建该连接时的时间戳privatelongcreatedTimestamp; // 最后一次使用的 时间戳privatelonglastUsedTimestamp; // 由 数据库URL、用户名、密码 计算出来的 hash值,可用于标识该连接所在的连接池privateintconnectionTypeCode; // 检测当前 PooledConnection连接池连接对象 是否有效,主要用于 防止程序通过 close()方法 将// 连接还给连接池之后,依然通过该连接操作数据库privatebooleanvalid; /** * invoke()方法 是本类的重点实现,也是 proxyConnection代理连接对象 的代理逻辑实现 * 它会对 close()方法 的调用进行处理,并在调用 realConnection对象 的方法之前进行校验 */@OverridepublicObjectinvoke(Objectproxy, Methodmethod, Object[] args) throwsThrowable { StringmethodName = method.getName(); // 如果调用的是 close()方法,则将其放进连接池,而不是真的关闭连接if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { dataSource.pushConnection(this); returnnull; } try { if (!Object.class.equals(method.getDeclaringClass())) { // 通过上面的 valid字段 校验连接是否有效checkConnection(); } // 调用 realConnection对象 的对应方法returnmethod.invoke(realConnection, args); } catch (Throwablet) { throwExceptionUtil.unwrapThrowable(t); } } privatevoidcheckConnection() throwsSQLException { if (!valid) { thrownewSQLException("Error accessing PooledConnection. Connection is invalid."); } } }
PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List<PooledConnection>集合 分别管理空闲状态的连接 和 活跃状态的连接。另外,PoolState 还定义了一系列用于统计的字段。
publicclassPoolState { // 所属的连接池对象protectedPooledDataSourcedataSource; // 空闲的连接protectedfinalList<PooledConnection> idleConnections = newArrayList<>(); // 活跃的连接protectedfinalList<PooledConnection> activeConnections = newArrayList<>(); // 请求数据库连接的次数protectedlongrequestCount = 0; // 获取连接的累计时间(accumulate累计)protectedlongaccumulatedRequestTime = 0; // CheckoutTime = 记录 应用从连接池取出连接到归还连接的时长// accumulatedCheckoutTime = 所有连接累计的CheckoutTimeprotectedlongaccumulatedCheckoutTime = 0; // 超时连接的个数(当连接长时间未归还给连接池时,会被认为连接超时)protectedlongclaimedOverdueConnectionCount = 0; // 累计超时时间protectedlongaccumulatedCheckoutTimeOfOverdueConnections = 0; // 累计等待时间protectedlongaccumulatedWaitTime = 0; // 等待次数protectedlonghadToWaitCount = 0; // 无效的连接数protectedlongbadConnectionCount = 0; publicPoolState(PooledDataSourcedataSource) { this.dataSource = dataSource; } publicsynchronizedlonggetRequestCount() { returnrequestCount; } publicsynchronizedlonggetAverageRequestTime() { returnrequestCount == 0 ? 0 : accumulatedRequestTime / requestCount; } publicsynchronizedlonggetAverageWaitTime() { returnhadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount; } publicsynchronizedlonggetHadToWaitCount() { returnhadToWaitCount; } publicsynchronizedlonggetBadConnectionCount() { returnbadConnectionCount; } publicsynchronizedlonggetClaimedOverdueConnectionCount() { returnclaimedOverdueConnectionCount; } publicsynchronizedlonggetAverageOverdueCheckoutTime() { returnclaimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount; } publicsynchronizedlonggetAverageCheckoutTime() { returnrequestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount; } publicsynchronizedintgetIdleConnectionCount() { returnidleConnections.size(); } publicsynchronizedintgetActiveConnectionCount() { returnactiveConnections.size(); } @OverridepublicsynchronizedStringtoString() { StringBuilderbuilder = newStringBuilder(); 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==============================================================="); returnbuilder.toString(); } }
PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。 PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection 对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图:
publicclassPooledDataSourceimplementsDataSource { privatestaticfinalLoglog = LogFactory.getLog(PooledDataSource.class); // 管理连接池状态 并统计连接信息privatefinalPoolStatestate = newPoolState(this); // 该对象用于生成真正的数据库连接对象,构造函数中会初始化该字段privatefinalUnpooledDataSourcedataSource; // 最大活跃连接数protectedintpoolMaximumActiveConnections = 10; // 最大空闲连接数protectedintpoolMaximumIdleConnections = 5; // 最大Checkout时长protectedintpoolMaximumCheckoutTime = 20000; // 在无法获取连接时,线程需要等待的时间protectedintpoolTimeToWait = 20000; // 本地坏连接最大数protectedintpoolMaximumLocalBadConnectionTolerance = 3; // 检测数据库连接是否可用时,给数据库发送的sql语句protectedStringpoolPingQuery = "NO PING QUERY SET"; // 是否允许发送上述语句protectedbooleanpoolPingEnabled; // 当连接超过poolPingConnectionsNotUsedFor毫秒未使用,// 就发送一次上述sql,检测连接连接是否正常protectedintpoolPingConnectionsNotUsedFor; // 根据数据库URL、用户名、密码 生成的一个hash值,// 该hash值用于标记当前的连接池,在构造函数中初始化privateintexpectedConnectionTypeCode; /** * 下面的两个 getConnection()方法 都会调用 popConnection() * 获取 PooledConnection对象,然后调用该对象的 getProxyConnection()方法 * 获取数据库连接的代理对象 */@OverridepublicConnectiongetConnection() throwsSQLException { returnpopConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection(); } @OverridepublicConnectiongetConnection(Stringusername, Stringpassword) throwsSQLException { returnpopConnection(username, password).getProxyConnection(); } /** * 本方法实现了连接池获取连接对象的具体逻辑,是 PooledDataSource 的核心逻辑之一 */privatePooledConnectionpopConnection(Stringusername, Stringpassword) throwsSQLException { booleancountedWait = false; PooledConnectionconn = null; longt = System.currentTimeMillis(); intlocalBadConnectionCount = 0; // 循环获取数据库连接对象,直到获取成功while (conn == null) { // 连接池的连接是公共资源,要对线程加锁synchronized (state) { // 如果连接池中有空闲的 数据库连接对象,就取出一个if (!state.idleConnections.isEmpty()) { conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else { // 没有空闲的连接对象,就判断一下 活跃的连接数是否已达 设定的峰值if (state.activeConnections.size() < poolMaximumActiveConnections) { // 还没达到峰值 就创建一个新的连接conn = newPooledConnection(dataSource.getConnection(), this); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else { // 如果活跃的连接已达上限,就取出最老的活跃连接对象,判断其是否超时PooledConnectionoldestActiveConnection = state.activeConnections.get(0); longlongestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) { // 如果最老的连接超时了,就在 PoolState 中记录一下相关信息,然后将该连接对象释放掉state.claimedOverdueConnectionCount++; state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime; state.accumulatedCheckoutTime += longestCheckoutTime; state.activeConnections.remove(oldestActiveConnection); // 如果最老的连接不是 自动提交事务的,就将事务回滚掉if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLExceptione) { /* 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 = newPooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); // 将最老活跃连接设为无效oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // 如果最老的连接对象也没超时,则进入阻塞等待,// 等待时间 poolTimeToWait 可自行设置try { if (!countedWait) { // 等待次数加一state.hadToWaitCount++; countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } longwt = System.currentTimeMillis(); // native方法,使执行到这里的线程阻塞等待 poolTimeToWait毫秒state.wait(poolTimeToWait); // 统计累计等待的时间state.accumulatedWaitTime += System.currentTimeMillis() - wt; } catch (InterruptedExceptione) { break; } } } } // 到了这里 基本上就获取到连接对象咯,但我们还要确认一下该连接对象是否是有效的 可用的if (conn != null) { // ping一下数据库服务器,确认该连接对象是否有效if (conn.isValid()) { // 如果事务提交配置为手动的,则先让该连接回滚一下事务,防止脏数据的出现if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } // 设置 由数据库URL、用户名、密码 计算出来的hash值,可用于标识该连接所在的连接池conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); // 设置 从连接池中取出该连接时的时间戳conn.setCheckoutTimestamp(System.currentTimeMillis()); // 设置 最后一次使用的时间戳conn.setLastUsedTimestamp(System.currentTimeMillis()); // 将该连接加入活跃的连接对象列表state.activeConnections.add(conn); // 请求数据库连接的次数加一state.requestCount++; // 计算 获取连接的累计时间(accumulate累计)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 = null; // 如果无效连接超出 阈值,则抛出异常if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) { if (log.isDebugEnabled()) { log.debug("PooledDataSource: Could not get a good connection to the database."); } thrownewSQLException("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."); } thrownewSQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection."); } // 返回数据库连接对象returnconn; } /** * 看一下之前讲过的 PooledConnection 中的 动态代理方法invoke(),可以发现 * 当调用数据库连接代理对象的 close()方法 时,并未关闭真正的数据库连接, * 而是调用了本方法,将连接对象归还给连接池,方便后续使用,本方法也是 PooledDataSource 的核心逻辑之一 */protectedvoidpushConnection(PooledConnectionconn) throwsSQLException { // 国际惯例,操作公共资源先上个锁synchronized (state) { // 先将该连接从活跃的连接对象列表中剔除state.activeConnections.remove(conn); // 如果该连接有效if (conn.isValid()) { // 如果连接池中的空闲连接数未达到阈值 且 该连接确实属于// 本连接池(通过之前获取的 expectedConnectionTypeCode 进行校验)if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) { // CheckoutTime = 应用从连接池取出连接到归还连接的时长// accumulatedCheckoutTime = 所有连接累计的CheckoutTimestate.accumulatedCheckoutTime += conn.getCheckoutTime(); // 不是自动提交事务的连接 先回滚一波if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } // 从 conn 中取出真正的 数据库连接对象,重新封装成 PooledConnectionPooledConnectionnewConn = newPooledConnection(conn.getRealConnection(), this); // 将 newConn 放进空闲连接对象列表state.idleConnections.add(newConn); // 设置 newConn 的相关属性newConn.setCreatedTimestamp(conn.getCreatedTimestamp()); newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp()); // 将原本的 conn 作废conn.invalidate(); if (log.isDebugEnabled()) { log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); } // 唤醒阻塞等待的线程state.notifyAll(); } else { // 如果空闲连接已达阈值 或 该连接对象不属于本连接池,则做好统计数据// 回滚连接的事务,关闭真正的连接,最后作废 该connstate.accumulatedCheckoutTime += conn.getCheckoutTime(); if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } 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++; } } } /** * 关闭连接池中 所有活跃的 及 空闲的连接 * 当修改连接池的配置(如:用户名、密码、URL等),都会调用本方法 */publicvoidforceCloseAll() { // 日常上锁synchronized (state) { // 更新当前连接池的标识expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword()); // 依次关闭活跃的连接对象for (inti = state.activeConnections.size(); i > 0; i--) { try { PooledConnectionconn = state.activeConnections.remove(i - 1); conn.invalidate(); ConnectionrealConn = conn.getRealConnection(); if (!realConn.getAutoCommit()) { realConn.rollback(); } realConn.close(); } catch (Exceptione) { // ignore } } // 依次关闭空闲的连接对象for (inti = state.idleConnections.size(); i > 0; i--) { try { PooledConnectionconn = state.idleConnections.remove(i - 1); conn.invalidate(); ConnectionrealConn = conn.getRealConnection(); if (!realConn.getAutoCommit()) { realConn.rollback(); } realConn.close(); } catch (Exceptione) { // ignore } } } if (log.isDebugEnabled()) { log.debug("PooledDataSource forcefully closed/removed all connections."); } } }
最后,我们来看一下 popConnection() 和 pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid 字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql 语句,检测连接是否正常。
classPooledConnectionimplementsInvocationHandler { /** * 检测 PooledConnection对象 的有效性 */publicbooleanisValid() { returnvalid && realConnection != null && dataSource.pingConnection(this); } } publicclassPooledDataSourceimplementsDataSource { /** * ping 一下数据库,检测数据库连接是否正常 */protectedbooleanpingConnection(PooledConnectionconn) { booleanresult = true; try { result = !conn.getRealConnection().isClosed(); } catch (SQLExceptione) { if (log.isDebugEnabled()) { log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage()); } result = false; } if (result) { // 是否允许发送检测语句,检测数据库连接是否正常,poolPingEnabled 可自行配置// 该检测会牺牲一定的系统资源,以提高安全性if (poolPingEnabled) { // 超过 poolPingConnectionsNotUsedFor毫秒 未使用的连接 才会检测其连接状态if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) { try { if (log.isDebugEnabled()) { log.debug("Testing connection " + conn.getRealHashCode() + " ..."); } // 获取真正的连接对象,执行 poolPingQuery = "NO PING QUERY SET" sql语句ConnectionrealConn = conn.getRealConnection(); try (Statementstatement = realConn.createStatement()) { statement.executeQuery(poolPingQuery).close(); } if (!realConn.getAutoCommit()) { realConn.rollback(); } result = true; if (log.isDebugEnabled()) { log.debug("Connection " + conn.getRealHashCode() + " is GOOD!"); } // 如果上面这段代码抛出异常,则说明数据库连接有问题,将该连接关闭,返回false } catch (Exceptione) { log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage()); try { conn.getRealConnection().close(); } catch (Exceptione2) { //ignore } result = false; if (log.isDebugEnabled()) { log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage()); } } } } } returnresult; } }
遵循 “接口-实现类” 的设计原则,Mybatis 也是先使用 Transaction 接口 对数据库事务做了抽象,而实现类则只提供了两个,即:JdbcTransaction 和 ManagedTransaction。这两种对象的获取,使用了两个对应的工厂类 JdbcTransactionFactory 和 ManagedTransactionFactory。 不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring,由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。
publicinterfaceTransaction { /** * 获取连接对象 */ConnectiongetConnection() throwsSQLException; /** * 提交事务 */voidcommit() throwsSQLException; /** * 回滚事务 */voidrollback() throwsSQLException; /** * 关闭数据库连接 */voidclose() throwsSQLException; /** * 获取配置的事务超时时间 */IntegergetTimeout() throwsSQLException; } publicclassJdbcTransactionimplementsTransaction { privatestaticfinalLoglog = LogFactory.getLog(JdbcTransaction.class); // 当前事务对应的数据库连接protectedConnectionconnection; // 当前事务对应的数据源protectedDataSourcedataSource; // 事务隔离级别protectedTransactionIsolationLevellevel; // 是否自动提交protectedbooleanautoCommit; publicJdbcTransaction(DataSourceds, TransactionIsolationLeveldesiredLevel, booleandesiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommit = desiredAutoCommit; } publicJdbcTransaction(Connectionconnection) { this.connection = connection; } @OverridepublicConnectiongetConnection() throwsSQLException { if (connection == null) { openConnection(); } returnconnection; } // 提交、回滚、关闭等操作的代码都比较简单,只对原生的 JDBC操作 做了简单封装@Overridepublicvoidcommit() throwsSQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); } } @Overridepublicvoidrollback() throwsSQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); } } @Overridepublicvoidclose() throwsSQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } } protectedvoidsetDesiredAutoCommit(booleandesiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); } } catch (SQLExceptione) { // Only a very poorly implemented driver would fail here,// and there's not much we can do about that.thrownewTransactionException("Error configuring AutoCommit. " + "Your driver may not support getAutoCommit() or setAutoCommit(). " + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); } } protectedvoidresetAutoCommit() { try { if (!connection.getAutoCommit()) { // MyBatis does not call commit/rollback on a connection if just selects were performed.// Some databases start transactions with select statements// and they mandate a commit/rollback before closing the connection.// A workaround is setting the autocommit to true before closing the connection.// Sybase throws an exception here.if (log.isDebugEnabled()) { log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(true); } } catch (SQLExceptione) { if (log.isDebugEnabled()) { log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e); } } } protectedvoidopenConnection() throwsSQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommit); } @OverridepublicIntegergetTimeout() throwsSQLException { returnnull; } } publicclassManagedTransactionimplementsTransaction { privatestaticfinalLoglog = LogFactory.getLog(ManagedTransaction.class); // 数据源privateDataSourcedataSource; // 事务隔离级别privateTransactionIsolationLevellevel; // 对应的数据库连接privateConnectionconnection; // 控制是否关闭持有的连接,在 close()方法 中用其判断是否真的关闭连接privatefinalbooleancloseConnection; // 本类的实现也很简单,commit()、rollback()方法 都是空实现publicManagedTransaction(Connectionconnection, booleancloseConnection) { this.connection = connection; this.closeConnection = closeConnection; } publicManagedTransaction(DataSourceds, TransactionIsolationLevellevel, booleancloseConnection) { this.dataSource = ds; this.level = level; this.closeConnection = closeConnection; } @OverridepublicConnectiongetConnection() throwsSQLException { if (this.connection == null) { openConnection(); } returnthis.connection; } @Overridepublicvoidcommit() throwsSQLException { // Does nothing } @Overridepublicvoidrollback() throwsSQLException { // Does nothing } @Overridepublicvoidclose() throwsSQLException { if (this.closeConnection && this.connection != null) { if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + this.connection + "]"); } this.connection.close(); } } protectedvoidopenConnection() throwsSQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } this.connection = this.dataSource.getConnection(); if (this.level != null) { this.connection.setTransactionIsolation(this.level.getLevel()); } } @OverridepublicIntegergetTimeout() throwsSQLException { returnnull; } } publicinterfaceTransactionFactory { /** * 配置 TransactionFactory对象,一般会在完成 TransactionFactory对象 * 初始化之后 就进行自定义属性配置 */defaultvoidsetProperties(Propertiesprops) { // NOP } /** * 在指定的数据库连接上创建 Transaction事务对象 */TransactionnewTransaction(Connectionconn); /** * 从指定数据源获取数据库连接,并在此连接上创建 Transaction对象 */TransactionnewTransaction(DataSourcedataSource, TransactionIsolationLevellevel, booleanautoCommit); } publicclassJdbcTransactionFactoryimplementsTransactionFactory { @OverridepublicTransactionnewTransaction(Connectionconn) { returnnewJdbcTransaction(conn); } @OverridepublicTransactionnewTransaction(DataSourceds, TransactionIsolationLevellevel, booleanautoCommit) { returnnewJdbcTransaction(ds, level, autoCommit); } } publicclassManagedTransactionFactoryimplementsTransactionFactory { privatebooleancloseConnection = true; @OverridepublicvoidsetProperties(Propertiesprops) { if (props != null) { StringcloseConnectionProperty = props.getProperty("closeConnection"); if (closeConnectionProperty != null) { closeConnection = Boolean.valueOf(closeConnectionProperty); } } } @OverridepublicTransactionnewTransaction(Connectionconn) { returnnewManagedTransaction(conn, closeConnection); } @OverridepublicTransactionnewTransaction(DataSourceds, TransactionIsolationLevellevel, booleanautoCommit) { // Silently ignores autocommit and isolation level, as managed transactions are entirely// controlled by an external manager. It's silently ignored so that// code remains portable between managed and unmanaged configurations.returnnewManagedTransaction(ds, level, closeConnection); } }