码迷,mamicode.com
首页 > 其他好文 > 详细

mybatis源码解析之Configuration加载(三)

时间:2018-12-27 18:23:36      阅读:87      评论:0      收藏:0      [点我收藏+]

标签:解答   type   tao   nbsp   ada   ++   not   getting   test   

概述

上一篇我们主要分析了下<environments>标签下面,transactionManager的配置,上问最后还有个遗留问题:就是在设置事物管理器的时候有个autocommit的变量的初始值是在哪边处理的呢?今天我们就来解答一下。

<environments>的dataSource分析

 1   private void environmentsElement(XNode context) throws Exception {
 2     if (context != null) {
 3       if (environment == null) {
 4         environment = context.getStringAttribute("default");
 5       }
 6       for (XNode child : context.getChildren()) {
 7         String id = child.getStringAttribute("id");
 8         if (isSpecifiedEnvironment(id)) {
 9           TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
10           DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
11           DataSource dataSource = dsFactory.getDataSource();
12           Environment.Builder environmentBuilder = new Environment.Builder(id)
13               .transactionFactory(txFactory)
14               .dataSource(dataSource);
15           configuration.setEnvironment(environmentBuilder.build());
16         }
17       }
18     }
19   }

 上一篇我们分析了黄色部分的,今天我们来分析下红色部分的,我们照旧先来看下configuation.xml中这部分的配置:

 1     <environments default="development">
 2         <environment id="development">
 3             <transactionManager type="JDBC" />
 4             <dataSource type="POOLED">
 5                 <property name="driver" value="${driveClass}" />
 6                 <property name="url" value="${url}" />
 7                 <property name="username" value="${userName}" />
 8                 <property name="password" value="${password}" />
 9             </dataSource>
10         </environment>
11     </environments> 

这边4到9行就是配置的datasource的相关信息,我们来看下上面标红的第九行代码,跟进去之后,解析代码如下:

 1   private DataSourceFactory dataSourceElement(XNode context) throws Exception {
 2     if (context != null) {
 3       String type = context.getStringAttribute("type");
 4       Properties props = context.getChildrenAsProperties();
 5       DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
 6       factory.setProperties(props);
 7       return factory;
 8     }
 9     throw new BuilderException("Environment declaration requires a DataSourceFactory.");
10   }

第3行代码,根据上面配置文件中的type来确定数据源的类型,第4行,再获取连接数据源的一些必要属性配置,看下第5行代码:

 1   protected Class<?> resolveClass(String alias) {
 2     if (alias == null) {
 3       return null;
 4     }
 5     try {
 6       return resolveAlias(alias);
 7     } catch (Exception e) {
 8       throw new BuilderException("Error resolving class. Cause: " + e, e);
 9     }
10   }
11 
12   protected Class<?> resolveAlias(String alias) {
13     return typeAliasRegistry.resolveAlias(alias);
14   }

可以看出,跟前面实例化事物管理器一样,也是从typeAliasRegistry中根据type去获取,然后实例化,我上面配置的是pooled,这边最终实例化的应该是PooledDataSourceFactory这个数据源工厂,其实这边的配置不止这一个,还有JNDI,UNPOOLED,分别对应于JndiDataSourceFactory,UnpooledDataSourceFactory,这些也都是在Configuration.class中加载好的,

1     typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
2     typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
3     typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

我们依次来介绍下,先看下目录结构:

技术分享图片

可以看到,DataSourceFactory是数据源工厂接口,主要有如下两个方法:

1 public interface DataSourceFactory {
2 
3   void setProperties(Properties props);
4 
5   DataSource getDataSource();
6 
7 }

第3行代码,是用来设置数据源的相关属性,一般是在初始化完成之后进行,第5行代码,是获取数据源对象的方法。

这个接口有两个实现类,JndiDataSourceFactory和UnpooledDataSourceFactory,另外一个PooledDataSourceFactory其实是继承于UnpooledDataSourceFactory。

UnpooledDataSourceFactory

UnpooledDataSourceFactory主要用来创建UnpooledDataSource对象,代码如下:

 1 public class UnpooledDataSourceFactory implements DataSourceFactory {
 2 
 3   private static final String DRIVER_PROPERTY_PREFIX = "driver.";
 4   private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
 5 
 6   protected DataSource dataSource;
 7 
 8   public UnpooledDataSourceFactory() {
 9     this.dataSource = new UnpooledDataSource();
10   }
11 
12   @Override
13   public void setProperties(Properties properties) {
14     Properties driverProperties = new Properties();
15     MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
16     for (Object key : properties.keySet()) {
17       String propertyName = (String) key;
18       if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
19         String value = properties.getProperty(propertyName);
20         driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
21       } else if (metaDataSource.hasSetter(propertyName)) {
22         String value = (String) properties.get(propertyName);
23         Object convertedValue = convertValue(metaDataSource, propertyName, value);
24         metaDataSource.setValue(propertyName, convertedValue);
25       } else {
26         throw new DataSourceException("Unknown DataSource property: " + propertyName);
27       }
28     }
29     if (driverProperties.size() > 0) {
30       metaDataSource.setValue("driverProperties", driverProperties);
31     }
32   }
33 
34   @Override
35   public DataSource getDataSource() {
36     return dataSource;
37   }
38 
39   private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
40     Object convertedValue = value;
41     Class<?> targetType = metaDataSource.getSetterType(propertyName);
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
52 }

第8到10行代码,在构造方法中初始化UnpooledDataSource对象,并在getDataSource方法中返回UnpooledDataSource对象,在setProperties方法中完成对UnpooledDataSource对象的相关配置。我们看下setProperties方法大致流程如下:

1.创建datasource对应的MetaObject

2.遍历properties集合,这个集合中存放了数据源需要的信息,也是我们配置在configuration.xml的property

3.判断key值是否以“driver.”开头,以这个开头的是驱动类信息,保存至driverProperties中

4.不是以“driver.”开头属性,先判断在MetaObject(其实就是UnpooledDataSource)中是否有对应的set方法,没有,则抛出异常

5.有的话,现获取属性值,根据MetaObject中返回值进行类型进行类型转换,主要针对Integer,Long,Boolean

6.设置MetaObject中driverProperties的属性值,也就是datasource的属性值。

PooledDataSourceFactory

PooledDataSourceFactory 主要用来创建 PooledDataSource 对象,它继承了 UnpooledDataSource 类,设置 DataSource 参数的方法复用UnpooledDataSource 中的 setProperties 方法,只是数据源返回的是  PooledDataSource 对象而已。

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

初始化PooledDataSourceFactory这个类时,会在构造方法中初始化PooledDataSource对象,后续getDataSource方法中返回的也就是这个对象。

JndiDataSourceFactory

JndiDataSourceFactory 依赖 JNDI 服务器中获取用户配置的 DataSource,这里暂时不看。

下面我们就来看看具体的数据源是怎么实现的,

UnpooledDataSource

UnpooledDataSource 不使用连接池来创建数据库连接,每次获取数据库连接时都会创建一个新的连接进行返回;

 1 public class UnpooledDataSource implements DataSource {
 2   
 3   private ClassLoader driverClassLoader;  // 加载 Driver 类的类加载器
 4   private Properties driverProperties;  // 数据库连接驱动的相关配置
 5   private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>(); // 缓存所有已注册的数据库连接驱动
 6 
 7   private String driver;
 8   private String url;
 9   private String username;
10   private String password;
11 
12   private Boolean autoCommit; // 是否自动提交
13   private Integer defaultTransactionIsolationLevel; // 事物的隔离级别
14 
15   static {
16     Enumeration<Driver> drivers = DriverManager.getDrivers(); // 从DriverManager中获取已注册的驱动信息
17     while (drivers.hasMoreElements()) {
18       Driver driver = drivers.nextElement();
19       registeredDrivers.put(driver.getClass().getName(), driver); // 将已注册的驱动信息保存至registeredDrivers中
20     }
21   }

接下来看一下,实现DataSource的两个获取连接的方法:

1   @Override
2   public Connection getConnection() throws SQLException {
3     return doGetConnection(username, password); // 根据properties中设置的属性类来获取连接
4   }
5 
6   @Override
7   public Connection getConnection(String username, String password) throws SQLException {
8     return doGetConnection(username, password);
9   }

可以看出最终调用的都是doGetConnection(username, password)方法,具体如下:

 1   private Connection doGetConnection(String username, String password) throws SQLException {
 2     Properties props = new Properties();
 3     if (driverProperties != null) {
 4       props.putAll(driverProperties); // 设置数据库连接驱动的相关配置属性
 5     }
 6     if (username != null) {
 7       props.setProperty("user", username); // 设置用户名
 8     }
 9     if (password != null) {
10       props.setProperty("password", password); // 设置密码
11     }
12     return doGetConnection(props);
13   }
14 
15   private Connection doGetConnection(Properties properties) throws SQLException {
16     initializeDriver(); // 初始化数据库驱动
17     Connection connection = DriverManager.getConnection(url, properties); // 通过 DriverManager 来获取一个数据库连接
18     configureConnection(connection); // 配置数据库连接的 autoCommit 和隔离级别
19     return connection;
20   }
21 
22   private synchronized void initializeDriver() throws SQLException {
23     if (!registeredDrivers.containsKey(driver)) { // 当前的驱动还有注册过,进行注册
24       Class<?> driverType;
25       try {
26         if (driverClassLoader != null) {
27           driverType = Class.forName(driver, true, driverClassLoader);
28         } else {
29           driverType = Resources.classForName(driver);
30         }
33         Driver driverInstance = (Driver)driverType.newInstance();  // 创建驱动对象实例
34         DriverManager.registerDriver(new DriverProxy(driverInstance)); // 往DriverManager中注册驱动
35         registeredDrivers.put(driver, driverInstance); // 在registerDrivers中记录加载过的驱动
36       } catch (Exception e) {
37         throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
38       }
39     }
40   }
41   // 设置数据库连接的 autoCommit 和隔离级别
42   private void configureConnection(Connection conn) throws SQLException {
43     if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
44       conn.setAutoCommit(autoCommit); // 上一篇文章最后的问题答案就在这边
45     }
46     if (defaultTransactionIsolationLevel != null) {
47       conn.setTransactionIsolation(defaultTransactionIsolationLevel);
48     }
49   }

以上代码就是 UnpooledDataSource 类的主要实现逻辑,每次获取连接都是从数据库新创建一个连接进行返回,又因为,数据库连接的创建是一个耗时的操作,且数据库连接是非常珍贵的资源,如果每次获取连接都创建一个,则可能会造成系统的瓶颈,拖垮响应速度等,这时就需要数据库连接池了,Mybatis 也提供了自己数据库连接池的实现,就是 PooledDataSource 类。

PooledDataSource

这个类说真的,还是比较复杂的,我也是研究了有一会儿的,它内部创建数据库连接时基于我们上面介绍的UnpooledDataSource,但是呢,PooledDataSource并不会像UnpooledDataSource那样去管理数据库连接,而是通过PoolConnection来实现对于连接的管理,当然,既然是连接池,就有相关的状态,这边通过PoolState来管理连接池的状态,下面我们就来一次介绍下:

PoolConnection

代码如下,最明显的就是这个实现了InvocationHandler接口,说明它是一个代理类,那他肯定就会实现invoke接口。

 1 class PooledConnection implements InvocationHandler {
 2 
 3   private static final String CLOSE = "close";   // 判断是否是close方法
 4   private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
 5 
 6   private int hashCode = 0;
    
7 private PooledDataSource dataSource; // 当前PoolConnection是属于哪个PooldataSource的 8 private Connection realConnection; // 真正的数据库连接 9 private Connection proxyConnection; // 数据库连接的代理对象 10 private long checkoutTimestamp; // 从连接池中取出该连接的时间戳 11 private long createdTimestamp; // 该连接创建的时间戳 12 private long lastUsedTimestamp; // 该连接最后一次被使用的时间戳 13 private int connectionTypeCode; // 用于标识该连接所在的连接池,由URL+username+password 计算出来的hash值 14 private boolean valid; // 代理连接是否有效 15 22 public PooledConnection(Connection connection, PooledDataSource dataSource) { 23 this.hashCode = connection.hashCode(); 24 this.realConnection = connection; 25 this.dataSource = dataSource; 26 this.createdTimestamp = System.currentTimeMillis(); 27 this.lastUsedTimestamp = System.currentTimeMillis(); 28 this.valid = true; 29 this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); 30 } 31     // 废弃连接 35 public void invalidate() { 36 valid = false; 37 } 38 // 判断连接是否有效
      1.根据valid
2.向数据库中发送检测测试的SQL,查看真正的连接还是否有效 44 public boolean isValid() { 45 return valid && realConnection != null && dataSource.pingConnection(this); 46 }

上面这几个方法就是PoolConnection的构造方法,初始化相关类变量,判断连接是否有效及废弃连接的方法。下面我们再来看下invoke方法:

 1   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2     String methodName = method.getName();
 3     if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 判断是不是close方法,如果是,将连接放回到连接池中,下次继续获取使用
 4       dataSource.pushConnection(this);
 5       return null;
 6     } else {
 7       try {
 8         if (!Object.class.equals(method.getDeclaringClass())) { // 不是close方法,执行真正的数据库连接执行
 9           // issue #579 toString() should never fail
10           // throw an SQLException instead of a Runtime
11           checkConnection();  // 检查连接是否有效
12         }
13         return method.invoke(realConnection, args);
14       } catch (Throwable t) {
15         throw ExceptionUtil.unwrapThrowable(t);
16       }
17     }
18   }

这个方法主要就是通过代理判断是不是close方法,是的的话并不是这届关闭,而是将其放回连接池中,供下次使用。

PoolState

这个类主要用来管理连接池的一些状态,没有

 1 public class PoolState {
 2 
 3   protected PooledDataSource dataSource; // 该poolstate属于哪个pooleddatasource
 4 
 5   protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>(); // 来用存放空闲的 pooledConnection 连接
 6   protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>(); // 用来存放活跃的 PooledConnection 连接
 7   protected long requestCount = 0; // 请求数据库连接的次数
 8   protected long accumulatedRequestTime = 0; // 获取连接的累计时间
 9   protected long accumulatedCheckoutTime = 0; // 所有连接的累计从获取连接到归还连接的时长
10   protected long claimedOverdueConnectionCount = 0; // 连接超时的连接个数
11   protected long accumulatedCheckoutTimeOfOverdueConnections = 0; // 累计超时时间
12   protected long accumulatedWaitTime = 0; // 累计等待时间
13   protected long hadToWaitCount = 0; // 等待次数
14   protected long badConnectionCount = 0; // 无效连接数

PooledDataSource

PooledDataSource 它是一个简单的,同步的,线程安全的数据库连接池,它使用UnpooledDataSource 来创建数据库连接,使用PooledConnection来管理数据库中的连接,使用PoolState来管理连接池的状态。下面我们就来一起看下这个类,首先看下相关的类变量:

 1 public class PooledDataSource implements DataSource {
 2 
 3   private final PoolState state = new PoolState(this); // 当前连接池的状态
 4 
 5   private final UnpooledDataSource dataSource; // 用来创建真正的数据库连接对象
 6 
 7   // OPTIONAL CONFIGURATION FIELDS
 8   protected int poolMaximumActiveConnections = 10; // 最大活跃连接数 默认值 10
 9   protected int poolMaximumIdleConnections = 5; // 最大空闲连接数 默认值 5
10   protected int poolMaximumCheckoutTime = 20000; // 最大获取连接的时长 默认值 20000
11   protected int poolTimeToWait = 20000; // 获取连接时,最大的等待时长 默认值 20000
12   protected String poolPingQuery = "NO PING QUERY SET"; // 检测连接是否可用时的测试sql
13   protected boolean poolPingEnabled; // 是否允许发送测试sql
14   protected int poolPingConnectionsNotUsedFor; // 当连接超过 poolPingConnectionsNotUsedFor 毫秒未使用时,会发送一次测试 SQL 语句,测试连接是否正常
15 
16   private int expectedConnectionTypeCode; // 用来标识当前的连接池,是 url+username+password 的 hash 值

下面我们来看下从数据库获取连接的实现:

  1   public Connection getConnection(String username, String password) throws SQLException {
  2     return popConnection(username, password).getProxyConnection(); // 这边最终拿到的连接时代理的连接,不是真正的数据库连接
  3   }
  4   private PooledConnection popConnection(String username, String password) throws SQLException {
  5     boolean countedWait = false;
  6     PooledConnection conn = null; // pooledConnection对象
  7     long t = System.currentTimeMillis();
  8     int localBadConnectionCount = 0; // 无效的连接个数
  9 
 10     while (conn == null) { // pooledConnection代理对象为空
 11       synchronized (state) { // 加锁操作
 12         if (!state.idleConnections.isEmpty()) { // 判断当前idleConnection中是否存在空闲连接
 13           // Pool has available connection
 14           conn = state.idleConnections.remove(0); // 存在,则获取第一个连接
 15           if (log.isDebugEnabled()) {
 16             log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
 17           }
 18         } else { // 不存在空闲连接,作如下判断
 19           // Pool does not have available connection
 20           if (state.activeConnections.size() < poolMaximumActiveConnections) { // 如果连接池的活跃连接数小于最大活跃连接数
 21             // Can create new connection
 22             conn = new PooledConnection(dataSource.getConnection(), this); // 创建一个数据库连接代理对象,创建新的连接
 23             if (log.isDebugEnabled()) {
 24               log.debug("Created connection " + conn.getRealHashCode() + ".");
 25             }
 26           } else { // 如果连接池的活跃连接数大于等于最大活跃连接数,不能创建新的连接
 27             // Cannot create new connection
 28             PooledConnection oldestActiveConnection = state.activeConnections.get(0); // 获取最先创建的那个连接
 29             long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); // 获取它的连接时长,用来判断是否连接超时
 30             if (longestCheckoutTime > poolMaximumCheckoutTime) { // 如果该连接的已经超时
 31               // Can claim overdue connection   // 统计相应的数据
 32               state.claimedOverdueConnectionCount++;
 33               state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
 34               state.accumulatedCheckoutTime += longestCheckoutTime;
 35               state.activeConnections.remove(oldestActiveConnection);  // 将超时连接移出activeConnections
 36               if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { // 如果该连接没有设置自动提交
 37                 try {
 38                   oldestActiveConnection.getRealConnection().rollback(); // 回滚操作
 39                 } catch (SQLException e) {
 40                   log.debug("Bad connection. Could not roll back");
 41                 }  
 42               }
 43               conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); // 创建新的数据库代理对象,用的原来的数据库连接,并没有创建新的连接
 44               conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
 45               conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
 46               oldestActiveConnection.invalidate(); // 设置该超时的代理连接对象无效
 47               if (log.isDebugEnabled()) {
 48                 log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
 49               }
 50             } else { // 如果没有连接超时,那就必须等待
 51               // Must wait
 52               try {
 53                 if (!countedWait) {
 54                   state.hadToWaitCount++; // 获取连接等待次数增加
 55                   countedWait = true;
 56                 }
 57                 if (log.isDebugEnabled()) {
 58                   log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
 59                 }
 60                 long wt = System.currentTimeMillis();
 61                 state.wait(poolTimeToWait); // 阻塞等待,后续如果有连接归还,会唤醒这个阻塞的等待线程
 62                 state.accumulatedWaitTime += System.currentTimeMillis() - wt; // 统计时间
 63               } catch (InterruptedException e) {
 64                 break; // 中断退出
 65               }
 66             }
 67           }
 68         }
 69         if (conn != null) { // 获取到了代理连接对象
 70           if (conn.isValid()) { // 判断对象是否有效 -- 代理对象是否有效,发送测试Sql测试连接
 71             if (!conn.getRealConnection().getAutoCommit()) { // 代理对象无效,没有设置自动提交的话
 72               conn.getRealConnection().rollback(); // 执行回滚操作
 73             }
 74             conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password)); // 设置此代理连接的标识
 75             conn.setCheckoutTimestamp(System.currentTimeMillis());
 76             conn.setLastUsedTimestamp(System.currentTimeMillis());
 77             state.activeConnections.add(conn); // 将此连接放入activeConnection中
 78             state.requestCount++;
 79             state.accumulatedRequestTime += System.currentTimeMillis() - t;
 80           } else { // 代理对象无效
 81             if (log.isDebugEnabled()) {
 82               log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
 83             } // 统计数据
 84             state.badConnectionCount++;
 85             localBadConnectionCount++;
 86             conn = null;
 87             if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) { // 无效连接超过最大空闲连接3个,抛出异常
 88               if (log.isDebugEnabled()) {
 89                 log.debug("PooledDataSource: Could not get a good connection to the database.");
 90               }
 91               throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
 92             }
 93           }
 94         }
 95       }
 96 
 97     }
 98 
 99     if (conn == null) { // 没有获取到,抛出异常
100       if (log.isDebugEnabled()) {
101         log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
102       }
103       throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
104     }
105 
106     return conn;
107   }

上面的方法中有一个isValid方法,判断连接是否有效,我们来看一下:

1   public boolean isValid() {
2     return valid && realConnection != null && dataSource.pingConnection(this);
3   }

先判断代理连接对象是否有效,然后调用pingConnection方法尝试调用数据库,执行测试sql,看下pingConnection方法:

 1   protected boolean pingConnection(PooledConnection conn) {
 2     boolean result = true;
 3 
 4     try {
 5       result = !conn.getRealConnection().isClosed(); // 检测真实的数据库连接是否已关闭
 6     } catch (SQLException e) {
 7       if (log.isDebugEnabled()) { // 抛出异常的话,将连接设置为已关闭,直接返回
 8         log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
 9       }
10       result = false;
11     }
12 
13     if (result) { // 没有关闭的话
14       if (poolPingEnabled) { // 判断是否允许发送测试sql
15         if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {// 允许发送测试sql,并且该连接超过设定时间未使用
16           try {
17             if (log.isDebugEnabled()) {
18               log.debug("Testing connection " + conn.getRealHashCode() + " ...");
19             }
20             Connection realConn = conn.getRealConnection(); // 这边几行代码就是通过连接去发送测试sql
21             Statement statement = realConn.createStatement();
22             ResultSet rs = statement.executeQuery(poolPingQuery);
23             rs.close();
24             statement.close();
25             if (!realConn.getAutoCommit()) {
26               realConn.rollback();
27             }
28             result = true; // 执行测试sql成功,返回true
29             if (log.isDebugEnabled()) {
30               log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
31             }
32           } catch (Exception e) { // 捕获到异常,返回false
33             log.warn("Execution of ping query ‘" + poolPingQuery + "‘ failed: " + e.getMessage());
34             try {
35               conn.getRealConnection().close();
36             } catch (Exception e2) {
37               //ignore
38             }
39             result = false;
40             if (log.isDebugEnabled()) {
41               log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
42             }
43           }
44         }
45       }
46     }
47     return result;
48   }

上面讲了获取数据库连接的方法,当然就有归还数据库连接的方法,这个方法是在连接关闭的时候通过代理调用的,pushConnection方法如下:

 1   protected void pushConnection(PooledConnection conn) throws SQLException {
 2 
 3     synchronized (state) { // 加锁操作
      // 从活跃连接list中移除这个连接代理
4 state.activeConnections.remove(conn);
      // 连接代理是否有效
5 if (conn.isValid()) {
        // 连接代理有效,判断当前空闲连接list的数量是否小于最大值,并且 当前连接代理是属于当前的连接池
6 if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
        // 统计使用的时间
7 state.accumulatedCheckoutTime += conn.getCheckoutTime(); 8 if (!conn.getRealConnection().getAutoCommit()) {
        // 没有设置自动提交的话,执行回滚操作
9 conn.getRealConnection().rollback(); 10 }
        // 使用原来的数据库连接创建新的连接代理
11 PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
        // 将新建的连接代理放入空闲列表list
12 state.idleConnections.add(newConn);
        // 设置创建时间戳
13 newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
        // 设置上一次使用的时间戳
14 newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
        // 将原来的连接代理置为失效
15 conn.invalidate(); 16 if (log.isDebugEnabled()) { 17 log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); 18 }
        // 唤醒所有等待线程
19 state.notifyAll(); 20 } else {
        // 当前连接池的空闲连接已经达到最大值了
        // 统计使用时间
21 state.accumulatedCheckoutTime += conn.getCheckoutTime(); 22 if (!conn.getRealConnection().getAutoCommit()) {
        // 没有设置自动提交的话,执行回滚操作
23 conn.getRealConnection().rollback(); 24 }
        // 空闲列表放不下,只好将连接关闭
25 conn.getRealConnection().close(); 26 if (log.isDebugEnabled()) { 27 log.debug("Closed connection " + conn.getRealHashCode() + "."); 28 }
        // 将连接代理置为失效
29 conn.invalidate(); 30 } 31 } else { 32 if (log.isDebugEnabled()) { 33 log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); 34 }
       // 统计无效连接个数
35 state.badConnectionCount++; 36 } 37 } 38 }

可以看出归还连接的时候,如果空闲连接池里面还有位置,则把连接放进去,如果没有位置,则关闭连接。

我们再看回PooldataSource这个类,这里面有很多的set方法,都是去设置当前datasource相关属性,像驱动加载,url,用户名,密码,是否自动提交,事物隔离级别,驱动加载属性,最大活跃连接数,最大空闲连接数,连接超时时间,获取连接等待时间,测试连接的sql,是否允许发送测试sql,需要发送测试sql的未使用时间等,这些方法里面都有一个这样的方法,forceCloseAll(),我们来看一下:

 1   public void forceCloseAll() {
    // 这也是个同步方法
2 synchronized (state) {
     // 记录当前连接池的标志
3 expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
     // 这个for循环用来关闭活跃List中的连接
4 for (int i = state.activeConnections.size(); i > 0; i--) { 5 try {
        // 获取一个数据库连接代理
6 PooledConnection conn = state.activeConnections.remove(i - 1);
        // 将代理置为无效
7 conn.invalidate(); 8       // 从代理中获取真正的连接 9 Connection realConn = conn.getRealConnection();
        // 没有设置自动提交的,执行回滚操作
10 if (!realConn.getAutoCommit()) { 11 realConn.rollback(); 12 }
        // 关闭连接
13 realConn.close(); 14 } catch (Exception e) { 15 // ignore 16 } 17 }
     // 这个循环用来关闭空闲List中的连接
18 for (int i = state.idleConnections.size(); i > 0; i--) { 19 try { 20 PooledConnection conn = state.idleConnections.remove(i - 1); 21 conn.invalidate(); 22 23 Connection realConn = conn.getRealConnection(); 24 if (!realConn.getAutoCommit()) { 25 realConn.rollback(); 26 } 27 realConn.close(); 28 } catch (Exception e) { 29 // ignore 30 } 31 } 32 } 33 if (log.isDebugEnabled()) { 34 log.debug("PooledDataSource forcefully closed/removed all connections."); 35 } 36 }

 

可以看出来,这个方法的主要作用就是把两个list中的连接全部关闭掉,为什么呢?因为之前修改了数据源的属性,需要重新加载。

总结

关于datasource这块的内容是看完了,也看了好几天,大致总结下:

1.mybatis自带的数据源有三种类型, 池型,非池型,外部数据源

2.对于非池型的数据源,每次都回去回去连接,只适合于比较简单的,小型的工作场景,对于池型的数据源,是我们现在用的比较多的,当然现在也有很多的第三方数据源可以使用,具体流程就不回顾了。

 

 

 

 

 

 

 

 

 

 

 

mybatis源码解析之Configuration加载(三)

标签:解答   type   tao   nbsp   ada   ++   not   getting   test   

原文地址:https://www.cnblogs.com/xiaobaobei/p/10171884.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!