1.简介

本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑。搞懂这些数据源的实现,可使大家对数据源有更深入的认识。同时在配置这些数据源时,也会更清楚每种属性的意义和用途。因此,如果大家想知其然,也知其所以然。那么接下来就让我们一起去探索 MyBatis 内置数据源的源码吧。

MyBatis 支持三种数据源配置,分别为 UNPOOLED、POOLED 和 JNDI。并提供了两种数据源实现,分别是 UnpooledDataSource 和 PooledDataSource。在三种数据源配置中,UNPOOLED 和 POOLED 是我们最常用的两种配置。至于 JNDI,MyBatis 提供这种数据源的目的是为了让其能够运行在 EJB 或应用服务器等容器中,这一点官方文档中有所说明。由于 JNDI 数据源在日常开发中使用甚少,因此,本篇文章不打算分析 JNDI 数据源相关实现。大家若有兴趣,可自行分析。接下来,本文将重点分析 UNPOOLED 和 POOLED 两种数据源。其他的就不多说了,进入正题吧。

2.内置数据源初始化过程

在详细分析 UnpooledDataSource 和 PooledDataSource 两种数据源实现之前,我们先来了解一下数据源的配置与初始化过程。现在看数据源是如何配置的,如下:

  1. <dataSource type="UNPOOLED|POOLED">
  2. <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  3. <property name="url" value="jdbc:mysql..."/>
  4. <property name="username" value="root"/>
  5. <property name="password" value="1234"/>
  6. </dataSource>

数据源的配置是内嵌在 <environment> 节点中的,MyBatis 在解析 <environment> 节点时,会一并解析数据源的配置。MyBatis 会根据具体的配置信息,为不同的数据源创建相应工厂类,通过工厂类即可创建数据源实例。关于数据源配置的解析以及数据源工厂类的创建过程,我在 MyBatis 配置文件解析过程一文中分析过,这里就不赘述了。下面我们来看一下数据源工厂类的实现逻辑。

  1. public class UnpooledDataSourceFactory implements DataSourceFactory {
  2. private static final String DRIVER_PROPERTY_PREFIX = "driver.";
  3. private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
  4. protected DataSource dataSource;
  5. public UnpooledDataSourceFactory() {
  6. // 创建 UnpooledDataSource 对象
  7. this.dataSource = new UnpooledDataSource();
  8. }
  9. @Override
  10. public void setProperties(Properties properties) {
  11. Properties driverProperties = new Properties();
  12. // 为 dataSource 创建元信息对象
  13. MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
  14. // 遍历 properties 键列表,properties 由配置文件解析器传入
  15. for (Object key : properties.keySet()) {
  16. String propertyName = (String) key;
  17. // 检测 propertyName 是否以 "driver." 开头
  18. if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
  19. String value = properties.getProperty(propertyName);
  20. // 存储配置信息到 driverProperties 中
  21. driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
  22. } else if (metaDataSource.hasSetter(propertyName)) {
  23. String value = (String) properties.get(propertyName);
  24. // 按需转换 value 类型
  25. Object convertedValue = convertValue(metaDataSource, propertyName, value);
  26. // 设置转换后的值到 UnpooledDataSourceFactory 指定属性中
  27. metaDataSource.setValue(propertyName, convertedValue);
  28. } else {
  29. throw new DataSourceException("Unknown DataSource property: " + propertyName);
  30. }
  31. }
  32. if (driverProperties.size() > 0) {
  33. // 设置 driverProperties 到 UnpooledDataSourceFactory 的 driverProperties 属性中
  34. metaDataSource.setValue("driverProperties", driverProperties);
  35. }
  36. }
  37. private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
  38. Object convertedValue = value;
  39. // 获取属性对应的 setter 方法的参数类型
  40. Class<?> targetType = metaDataSource.getSetterType(propertyName);
  41. // 按照 setter 方法的参数类型进行类型转换
  42. if (targetType == Integer.class || targetType == int.class) {
  43. convertedValue = Integer.valueOf(value);
  44. } else if (targetType == Long.class || targetType == long.class) {
  45. convertedValue = Long.valueOf(value);
  46. } else if (targetType == Boolean.class || targetType == boolean.class) {
  47. convertedValue = Boolean.valueOf(value);
  48. }
  49. return convertedValue;
  50. }
  51. @Override
  52. public DataSource getDataSource() {
  53. return dataSource;
  54. }
  55. }

以上是 UnpooledDataSourceFactory 的源码分析,除了 setProperties 方法稍复杂一点,其他的都比较简单,就不多说了。下面看看 PooledDataSourceFactory 的源码。

  1. public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
  2. public PooledDataSourceFactory() {
  3. // 创建 PooledDataSource
  4. this.dataSource = new PooledDataSource();
  5. }
  6. }

以上就是 PooledDataSource 类的所有源码,PooledDataSourceFactory 继承自 UnpooledDataSourceFactory,复用了父类的逻辑,因此它的实现很简单。

关于两种数据源的创建过程就先分析到这,接下来,我们去探究一下两种数据源是怎样实现的。

3.UnpooledDataSource

UnpooledDataSource,从名称上即可知道,该种数据源不具有池化特性。该种数据源每次会返回一个新的数据库连接,而非复用旧的连接。由于 UnpooledDataSource 无需提供连接池功能,因此它的实现非常简单。核心的方法有三个,分别如下:

  1. initializeDriver - 初始化数据库驱动
  2. doGetConnection - 获取数据连接
  3. configureConnection - 配置数据库连接

下面我将按照顺序分节对相关方法进行分析,由于 configureConnection 方法比较简单,因此我把它和 doGetConnection 放在一节中进行分析。下面先来分析 initializeDriver 方法。

3.1 初始化数据库驱动

回顾我们一开始学习使用 JDBC 访问数据库时的情景,在执行 SQL 之前,通常都是先获取数据库连接。一般步骤都是加载数据库驱动,然后通过 DriverManager 获取数据库连接。UnpooledDataSource 也是使用 JDBC 访问数据库的,因此它获取数据库连接的过程也大致如此,只不过会稍有不同。下面我们一起来看一下。

  1. // -☆- UnpooledDataSource
  2. private synchronized void initializeDriver() throws SQLException {
  3. // 检测缓存中是否包含了与 driver 对应的驱动实例
  4. if (!registeredDrivers.containsKey(driver)) {
  5. Class<?> driverType;
  6. try {
  7. // 加载驱动类型
  8. if (driverClassLoader != null) {
  9. // 使用 driverClassLoader 加载驱动
  10. driverType = Class.forName(driver, true, driverClassLoader);
  11. } else {
  12. // 通过其他 ClassLoader 加载驱动
  13. driverType = Resources.classForName(driver);
  14. }
  15. // 通过反射创建驱动实例
  16. Driver driverInstance = (Driver) driverType.newInstance();
  17. /*
  18. * 注册驱动,注意这里是将 Driver 代理类 DriverProxy 对象注册到 DriverManager 中的,
  19. * 而非 Driver 对象本身。DriverProxy 中并没什么特别的逻辑,就不分析。
  20. */
  21. DriverManager.registerDriver(new DriverProxy(driverInstance));
  22. // 缓存驱动类名和实例
  23. registeredDrivers.put(driver, driverInstance);
  24. } catch (Exception e) {
  25. throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
  26. }
  27. }
  28. }

如上,initializeDriver 方法主要包含三步操作,分别如下:

  1. 加载驱动
  2. 通过反射创建驱动实例
  3. 注册驱动实例

这三步都是都是常规操作,比较容易理解。上面代码中出现了缓存相关的逻辑,这个是用于避免重复注册驱动。因为 initializeDriver 方法并不是在 UnpooledDataSource 初始化时被调用的,而是在获取数据库连接时被调用的。因此这里需要做个检测,避免每次获取数据库连接时都重新注册驱动。这个是一个比较小的点,大家看代码时注意一下即可。下面看一下获取数据库连接的逻辑。

3.2 获取数据库连接

在使用 JDBC 时,我们都是通过 DriverManager 的接口方法获取数据库连接。本节所要分析的源码也不例外,一起看一下吧。

  1. // -☆- UnpooledDataSource
  2. public Connection getConnection() throws SQLException {
  3. return doGetConnection(username, password);
  4. }
  5. private Connection doGetConnection(String username, String password) throws SQLException {
  6. Properties props = new Properties();
  7. if (driverProperties != null) {
  8. props.putAll(driverProperties);
  9. }
  10. if (username != null) {
  11. // 存储 user 配置
  12. props.setProperty("user", username);
  13. }
  14. if (password != null) {
  15. // 存储 password 配置
  16. props.setProperty("password", password);
  17. }
  18. // 调用重载方法
  19. return doGetConnection(props);
  20. }
  21. private Connection doGetConnection(Properties properties) throws SQLException {
  22. // 初始化驱动
  23. initializeDriver();
  24. // 获取连接
  25. Connection connection = DriverManager.getConnection(url, properties);
  26. // 配置连接,包括自动提交以及事务等级
  27. configureConnection(connection);
  28. return connection;
  29. }
  30. private void configureConnection(Connection conn) throws SQLException {
  31. if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
  32. // 设置自动提交
  33. conn.setAutoCommit(autoCommit);
  34. }
  35. if (defaultTransactionIsolationLevel != null) {
  36. // 设置事务隔离级别
  37. conn.setTransactionIsolation(defaultTransactionIsolationLevel);
  38. }
  39. }

如上,上面方法将一些配置信息放入到 Properties 对象中,然后将数据库连接和 Properties 对象传给 DriverManager 的 getConnection 方法即可获取到数据库连接。

好了,关于 UnpooledDataSource 就先说到这。下面分析一下 PooledDataSource,它的实现要复杂一些。

4.PooledDataSource

PooledDataSource 内部实现了连接池功能,用于复用数据库连接。因此,从效率上来说,PooledDataSource 要高于 UnpooledDataSource。PooledDataSource 需要借助一些辅助类帮助它完成连接池的功能,所以接下来,我们先来认识一下相关的辅助类。

4.1 辅助类介绍

PooledDataSource 需要借助两个辅助类帮其完成功能,这两个辅助类分别是 PoolState 和 PooledConnection。PoolState 用于记录连接池运行时的状态,比如连接获取次数,无效连接数量等。同时 PoolState 内部定义了两个 PooledConnection 集合,用于存储空闲连接和活跃连接。PooledConnection 内部定义了一个 Connection 类型的变量,用于指向真实的数据库连接。以及一个 Connection 的代理类,用于对部分方法调用进行拦截。至于为什么要拦截,随后将进行分析。除此之外,PooledConnection 内部也定义了一些字段,用于记录数据库连接的一些运行时状态。接下来,我们来看一下 PooledConnection 的定义。

  1. class PooledConnection implements InvocationHandler {
  2. private static final String CLOSE = "close";
  3. private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};
  4. private final int hashCode;
  5. private final PooledDataSource dataSource;
  6. // 真实的数据库连接
  7. private final Connection realConnection;
  8. // 数据库连接代理
  9. private final Connection proxyConnection;
  10. // 从连接池中取出连接时的时间戳
  11. private long checkoutTimestamp;
  12. // 数据库连接创建时间
  13. private long createdTimestamp;
  14. // 数据库连接最后使用时间
  15. private long lastUsedTimestamp;
  16. // connectionTypeCode = (url + username + password).hashCode()
  17. private int connectionTypeCode;
  18. // 表示连接是否有效
  19. private boolean valid;
  20. public PooledConnection(Connection connection, PooledDataSource dataSource) {
  21. this.hashCode = connection.hashCode();
  22. this.realConnection = connection;
  23. this.dataSource = dataSource;
  24. this.createdTimestamp = System.currentTimeMillis();
  25. this.lastUsedTimestamp = System.currentTimeMillis();
  26. this.valid = true;
  27. // 创建 Connection 的代理类对象
  28. this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
  29. }
  30. @Override
  31. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {...}
  32. // 省略部分代码
  33. }

下面再来看看 PoolState 的定义。

  1. public class PoolState {
  2. protected PooledDataSource dataSource;
  3. // 空闲连接列表
  4. protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
  5. // 活跃连接列表
  6. protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
  7. // 从连接池中获取连接的次数
  8. protected long requestCount = 0;
  9. // 请求连接总耗时(单位:毫秒)
  10. protected long accumulatedRequestTime = 0;
  11. // 连接执行时间总耗时
  12. protected long accumulatedCheckoutTime = 0;
  13. // 执行时间超时的连接数
  14. protected long claimedOverdueConnectionCount = 0;
  15. // 超时时间累加值
  16. protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
  17. // 等待时间累加值
  18. protected long accumulatedWaitTime = 0;
  19. // 等待次数
  20. protected long hadToWaitCount = 0;
  21. // 无效连接数
  22. protected long badConnectionCount = 0;
  23. }

上面对 PooledConnection 和 PoolState 的定义进行了一些注释,这两个类中有很多字段用来记录运行时状态。但在这些字段并非核心,因此大家知道每个字段的用途就行了。关于这两个辅助类的介绍就先到这

4.2 获取连接

前面已经说过,PooledDataSource 会将用过的连接进行回收,以便可以复用连接。因此从 PooledDataSource 获取连接时,如果空闲链接列表里有连接时,可直接取用。那如果没有空闲连接怎么办呢?此时有两种解决办法,要么创建新连接,要么等待其他连接完成任务。具体怎么做,需视情况而定。下面我们深入到源码中一探究竟。

  1. public Connection getConnection() throws SQLException {
  2. // 返回 Connection 的代理对象
  3. return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  4. }
  5. private PooledConnection popConnection(String username, String password) throws SQLException {
  6. boolean countedWait = false;
  7. PooledConnection conn = null;
  8. long t = System.currentTimeMillis();
  9. int localBadConnectionCount = 0;
  10. while (conn == null) {
  11. synchronized (state) {
  12. // 检测空闲连接集合(idleConnections)是否为空
  13. if (!state.idleConnections.isEmpty()) {
  14. // idleConnections 不为空,表示有空闲连接可以使用
  15. conn = state.idleConnections.remove(0);
  16. } else {
  17. /*
  18. * 暂无空闲连接可用,但如果活跃连接数还未超出限制
  19. *(poolMaximumActiveConnections),则可创建新的连接
  20. */
  21. if (state.activeConnections.size() < poolMaximumActiveConnections) {
  22. // 创建新连接
  23. conn = new PooledConnection(dataSource.getConnection(), this);
  24. } else { // 连接池已满,不能创建新连接
  25. // 取出运行时间最长的连接
  26. PooledConnection oldestActiveConnection = state.activeConnections.get(0);
  27. // 获取运行时长
  28. long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
  29. // 检测运行时长是否超出限制,即超时
  30. if (longestCheckoutTime > poolMaximumCheckoutTime) {
  31. // 累加超时相关的统计字段
  32. state.claimedOverdueConnectionCount++;
  33. state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
  34. state.accumulatedCheckoutTime += longestCheckoutTime;
  35. // 从活跃连接集合中移除超时连接
  36. state.activeConnections.remove(oldestActiveConnection);
  37. // 若连接未设置自动提交,此处进行回滚操作
  38. if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
  39. try {
  40. oldestActiveConnection.getRealConnection().rollback();
  41. } catch (SQLException e) {...}
  42. }
  43. /*
  44. * 创建一个新的 PooledConnection,注意,
  45. * 此处复用 oldestActiveConnection 的 realConnection 变量
  46. */
  47. conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
  48. /*
  49. * 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的
  50. * createdTimestamp 用于记录 Connection 的创建时间,而非 PooledConnection
  51. * 的创建时间。所以这里要复用原连接的时间信息。
  52. */
  53. conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
  54. conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
  55. // 设置连接为无效状态
  56. oldestActiveConnection.invalidate();
  57. } else { // 运行时间最长的连接并未超时
  58. try {
  59. if (!countedWait) {
  60. state.hadToWaitCount++;
  61. countedWait = true;
  62. }
  63. long wt = System.currentTimeMillis();
  64. // 当前线程进入等待状态
  65. state.wait(poolTimeToWait);
  66. state.accumulatedWaitTime += System.currentTimeMillis() - wt;
  67. } catch (InterruptedException e) {
  68. break;
  69. }
  70. }
  71. }
  72. }
  73. if (conn != null) {
  74. /*
  75. * 检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,
  76. * 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,
  77. * 检测连接是否可用。pingConnection 方法的逻辑不复杂,大家可以自行分析。
  78. * 另外,官方文档在介绍 POOLED 类型数据源时,也介绍了连接有效性检测方面的
  79. * 属性,有三个:poolPingQuery,poolPingEnabled 和
  80. * poolPingConnectionsNotUsedFor。关于这三个属性,大家可以查阅官方文档
  81. */
  82. if (conn.isValid()) {
  83. if (!conn.getRealConnection().getAutoCommit()) {
  84. // 进行回滚操作
  85. conn.getRealConnection().rollback();
  86. }
  87. conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
  88. // 设置统计字段
  89. conn.setCheckoutTimestamp(System.currentTimeMillis());
  90. conn.setLastUsedTimestamp(System.currentTimeMillis());
  91. state.activeConnections.add(conn);
  92. state.requestCount++;
  93. state.accumulatedRequestTime += System.currentTimeMillis() - t;
  94. } else {
  95. // 连接无效,此时累加无效连接相关的统计字段
  96. state.badConnectionCount++;
  97. localBadConnectionCount++;
  98. conn = null;
  99. if (localBadConnectionCount > (poolMaximumIdleConnections
  100. + poolMaximumLocalBadConnectionTolerance)) {
  101. throw new SQLException(...);
  102. }
  103. }
  104. }
  105. }
  106. }
  107. if (conn == null) {
  108. throw new SQLException(...);
  109. }
  110. return conn;
  111. }

上面代码冗长,过程比较复杂,下面把代码逻辑梳理一下。从连接池中获取连接首先会遇到两种情况:

  1. 连接池中有空闲连接
  2. 连接池中无空闲连接

对于第一种情况,处理措施就很简单了,把连接取出返回即可。对于第二种情况,则要进行细分,会有如下的情况。

  1. 活跃连接数没有超出最大活跃连接数
  2. 活跃连接数超出最大活跃连接数

对于上面两种情况,第一种情况比较好处理,直接创建新的连接即可。至于第二种情况,需要再次进行细分。

  1. 活跃连接的运行时间超出限制,即超时了
  2. 活跃连接未超时

对于第一种情况,我们直接将超时连接强行中断,并进行回滚,然后复用部分字段重新创建 PooledConnection 即可。对于第二种情况,目前没有更好的处理方式了,只能等待了。下面用一段伪代码演示各种情况及相应的处理措施,如下:

  1. if (连接池中有空闲连接) {
  2. 1. 将连接从空闲连接集合中移除
  3. } else {
  4. if (活跃连接数未超出限制) {
  5. 1. 创建新连接
  6. } else {
  7. 1. 从活跃连接集合中取出第一个元素
  8. 2. 获取连接运行时长
  9. if (连接超时) {
  10. 1. 将连接从活跃集合中移除
  11. 2. 复用原连接的成员变量,并创建新的 PooledConnection 对象
  12. } else {
  13. 1. 线程进入等待状态
  14. 2. 线程被唤醒后,重新执行以上逻辑
  15. }
  16. }
  17. }
  18. 1. 将连接添加到活跃连接集合中
  19. 2. 返回连接

最后用一个流程图大致描绘 popConnection 的逻辑,如下:

4.3 回收连接

相比于获取连接,回收连接的逻辑要简单的多。回收连接成功与否只取决于空闲连接集合的状态,所需处理情况很少,因此比较简单。下面看一下相关的逻辑。

  1. protected void pushConnection(PooledConnection conn) throws SQLException {
  2. synchronized (state) {
  3. // 从活跃连接池中移除连接
  4. state.activeConnections.remove(conn);
  5. if (conn.isValid()) {
  6. // 空闲连接集合未满
  7. if (state.idleConnections.size() < poolMaximumIdleConnections
  8. && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
  9. state.accumulatedCheckoutTime += conn.getCheckoutTime();
  10. // 回滚未提交的事务
  11. if (!conn.getRealConnection().getAutoCommit()) {
  12. conn.getRealConnection().rollback();
  13. }
  14. // 创建新的 PooledConnection
  15. PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
  16. state.idleConnections.add(newConn);
  17. // 复用时间信息
  18. newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
  19. newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
  20. // 将原连接置为无效状态
  21. conn.invalidate();
  22. // 通知等待的线程
  23. state.notifyAll();
  24. } else { // 空闲连接集合已满
  25. state.accumulatedCheckoutTime += conn.getCheckoutTime();
  26. // 回滚未提交的事务
  27. if (!conn.getRealConnection().getAutoCommit()) {
  28. conn.getRealConnection().rollback();
  29. }
  30. // 关闭数据库连接
  31. conn.getRealConnection().close();
  32. conn.invalidate();
  33. }
  34. } else {
  35. state.badConnectionCount++;
  36. }
  37. }
  38. }

上面代码首先将连接从活跃连接集合中移除,然后再根据空闲集合是否有空闲空间进行后续处理。如果空闲集合未满,此时复用原连接的字段信息创建新的连接,并将其放入空闲集合中即可。若空闲集合已满,此时无需回收连接,直接关闭即可。pushConnection 方法的逻辑并不复杂,就不多说了。

我们知道获取连接的方法 popConnection 是由 getConnection 方法调用的,那回收连接的方法 pushConnection 是由谁调用的呢?答案是 PooledConnection 中的代理逻辑。相关代码如下:

  1. // -☆- PooledConnection
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. String methodName = method.getName();
  4. // 检测 close 方法是否被调用,若被调用则拦截之
  5. if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
  6. // 将回收连接中,而不是直接将连接关闭
  7. dataSource.pushConnection(this);
  8. return null;
  9. } else {
  10. try {
  11. if (!Object.class.equals(method.getDeclaringClass())) {
  12. checkConnection();
  13. }
  14. // 调用真实连接的目标方法
  15. return method.invoke(realConnection, args);
  16. } catch (Throwable t) {
  17. throw ExceptionUtil.unwrapThrowable(t);
  18. }
  19. }
  20. }

在上一节中,getConnection 方法返回的是 Connection 代理对象,不知道大家有没有注意到。代理对象中的方法被调用时,会被上面的代理逻辑所拦截。如果代理对象的 close 方法被调用,MyBatis 并不会直接调用真实连接的 close 方法关闭连接,而是调用 pushConnection 方法回收连接。同时会唤醒处于睡眠中的线程,使其恢复运行。整个过程并不复杂,就不多说了。

4.4 小节

本章分析了 PooledDataSource 的部分源码及一些辅助类的源码,除此之外,PooledDataSource 中还有部分源码没有分析,大家若有兴趣,可自行分析。好了,关于 PooledDataSource 的分析就先到这。

5.总结

本篇文章对 MyBatis 两种内置数据源进行了较为详细的分析,总的来说,这两种数据源的源码都不是很难理解。大家在阅读源码的过程中,首先应搞懂源码的主要逻辑,然后再去分析一些边边角角的逻辑。不要一开始就陷入各种细节中,容易迷失方向。

好了,到此本文就结束了。若文章有错误不妥之处,希望大家指明。最后,感谢大家阅读我的文章。

附录:MyBatis 源码分析系列文章列表

更新时间 标题
2018-09-11 MyBatis 源码分析系列文章合集
2018-07-16 MyBatis 源码分析系列文章导读
2018-07-20 MyBatis 源码分析 - 配置文件解析过程
2018-07-30 MyBatis 源码分析 - 映射文件解析过程
2018-08-17 MyBatis 源码分析 - SQL 的执行过程
2018-08-19 MyBatis 源码分析 - 内置数据源
2018-08-25 MyBatis 源码分析 - 缓存原理
2018-08-26 MyBatis 源码分析 - 插件机制

本文在知识共享许可协议 4.0 下发布,转载需在明显位置处注明出处

作者:田小波

本文同步发布在我的个人博客:http://www.tianxiaobo.com


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

MyBatis 源码分析 - 内置数据源的更多相关文章

  1. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  2. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  3. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  4. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  5. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  6. MyBatis源码分析(5)——内置DataSource实现

    @(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...

  7. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  8. 精尽MyBatis源码分析 - MyBatis-Spring 源码分析

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

随机推荐

  1. <记录> HtmlHelper和 强类型页面

    HtmlHelper  路径生成 <!--普通写法--> <a href="/home/index">超链接</a> <!--利用Url类 ...

  2. Eurekalog

    Eurekalog ‪E:\Program Files (x86)\Neos Eureka S.r.l\EurekaLog 7\Packages\Studio25\EurekaLogComponent ...

  3. 安装Caffe纪实

    第一章 引言 在ubuntu16.04安装caffe,几乎折腾了一个月终于成功;做一文章做纪要,以便日后查阅.总体得出的要点是:首先,每操作一步,必须知道如何检验操作的正确性;笔者的多次失误是因为配置 ...

  4. pycharm的断点调试【转自https://blog.csdn.net/weixin_39198406/article/details/78873120】

    1. show execution point (F10)显示目前项目所有断点2. step over (F8)下一步但仅限于设置断点的文件3. step into (F7)执行下一行4. step ...

  5. 关于Shader的学习记录

    float4 _EmissiveColor; float4 _AmbientColor; float _MySliderValue; void surf (Input IN, inout Surfac ...

  6. electron-vue 淘宝源

    yarn-js config set registry https://registry.npm.taobao.org

  7. linux nginx 基本用法

    nginx -s reload -p <nginx环境目录> -c <指定的配置文件> 其中-p -c 为可选,不写为默认路径和配置 在执行命令之前可通过 nginx -t - ...

  8. springboot 整合 mybatis

    spirngboot 整合mybatis有两种方式 第一种 无配置文件注解版,这一种符合springboot的风格 第二种 有配置文件xml版本,这一种就是传统的模式 无论哪一种,首先都需要加入MyS ...

  9. DOM 节点node

    DOM可以将任何HTML或XML文档描绘成一个有多层节点构成的结构,即在HTML中所有内容都是节点.文档节点是每个文档的根节点,文档节点有一个子节点,称为文档元素.每个文档只能有一个文档元素.在HTM ...

  10. django 请求与响应

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...