mybatis源码解析之Configuration加载

Posted 厨房小码农

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis源码解析之Configuration加载相关的知识,希望对你有一定的参考价值。

概述

上一篇我们主要分析了下<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 { // 以上是关于mybatis源码解析之Configuration加载的主要内容,如果未能解决你的问题,请参考以下文章

mybatis源码-解析配置文件之配置文件Mapper解析

Mybatis之Configuration初始化(配置文件.xml的解析)

五Mybatis 核心对象之 Configuration 对象初始化详解

精通Mybatis之Configuration配置体系

精通Mybatis之Configuration配置体系

mybatis之Configuration解析