mybatis源码解析之Configuration加载(二)
概述
上一篇我们讲了configuation.xml中几个标签的解析,例如<properties>,<typeAlises>,<settings>等,今天我们来介绍剩下的两个比较重要的标签之一,<environments>,这个标签主要用于我们访问数据库的配置进行设置。
<environments>解析
我们先来看下configuation.xml中<environments>元素的配置:
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>
从上面的具体配置我们可以看出一些东西来:首先我们可以配置多个<environment>标签,它的下面主要配置两个东西,transactionManager和datasource,但是真正起作用的是<environments>标签中default元素标出的那个。下面我们看下具体的解析流程:
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 }
我们从代码中的for 循环遍历可以看出,<environments>标签下面是可以配置多个<environment>子标签的,每个子标签用id来区分,具体使用哪一个,看<environments>的default值,这也印证了我们一开始的分析。第3到5行代码,就是去获取<environments>的default属性,第7行代码,获取子标签<environment>的id属性,判断是不是上面default配置的那个,如果是,第9行代码,根据<transactionManager>标签获取事物管理器:
1 private TransactionFactory transactionManagerElement(XNode context) throws Exception {
2 if (context != null) {
3 String type = context.getStringAttribute("type");
4 Properties props = context.getChildrenAsProperties();
5 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
6 factory.setProperties(props);
7 return factory;
8 }
9 throw new BuilderException("Environment declaration requires a TransactionFactory.");
10 }
这边重点看下第5行代码,跟到最后可以发现,它其实是从typeAliasRegistry 这个map中根据type去获取class,进而得到实例,代码如下:
1 protected Class<?> resolveAlias(String alias) {
2 return typeAliasRegistry.resolveAlias(alias);
3 }
那这么的根据获取到是哪个类呢?其实我们上面说到过,其实一些默认的typeAlias,我们再看下关于这边的:
1 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
2 typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
3
4 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
5 typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
6 typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
这部分是在configuation中的,类初始化的时候就会加载,我们可以看到,transactionManager有两种配置,JDBC和MANAGED,分别对应于JdbcTransactionFactory和ManagedTransactionFactory。下面我们就来分析下这两个类,首先看下这两个类所在的目录解结构:
我们可以看出这边使用了工厂模式,其中
Transaction是事物接口,主要定义了如下接口:
Connection getConnection() throws SQLException; // 获取数据库连接
void commit() throws SQLException; // 提交
void rollback() throws SQLException; // 回滚
void close() throws SQLException; // 关闭数据库连接
Integer getTimeout() throws SQLException; // 获取设置的事物超时时间
TransactionFactory是事物工厂接口,主要定义了如下接口:
void setProperties(Properties props); // 设置属性
Transaction newTransaction(Connection conn); // 根据连接创建事物实例
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit); // 根据数据源,事物隔离级别,是否自动提交创建事物实例。
这样设计的目的就是为了便于扩展,以后要实现不同类型的事物只要实现这两个接口就行了。
上面配置文件中的JDBC和MANAGED其实就是对应于mybatis内置两种事物类型,JDBCTransaction和ManagedTransaction,
二者的不同之处在于:前者是直接使用JDK提供的JDBC来管理事务的各个环节:提交、回滚、关闭等操作,而后者则什么都不做,那么后者有什么意义呢,当然很重要。当我们单独使用MyBatis来构建项目时,我们要在Configuration配置文件中进行环境(environment)配置,在其中要设置事务类型为JDBC,意思是说MyBatis被单独使用时就需要使用JDBC类型的事务模型,因为在这个模型中定义了事务的各个方面,使用它可以完成事务的各项操作。而MANAGED类型的事务模型其实是一个托管模型,也就是说它自身并不实现任何事务功能,而是托管出去由其他框架来实现,你可能还不明白,这个事务的具体实现就交由如Spring之类的框架来实现,而且在使用SSM整合框架后已经不再需要单独配置环境信息(包括事务配置与数据源配置),因为在在整合jar包(mybatis-spring.jar)中拥有覆盖mybatis里面的这部分逻辑的代码,实际情况是即使你显式设置了相关配置信息,系统也会视而不见......托管的意义显而易见,正是为整合而设。我们学习MyBatis的目的正是由于其灵活性和与Spring等框架的无缝整合的能力,所以有关JDBC事务模块的内容明显不再是MyBatis功能中的重点,也许只有在单独使用MyBatis的少量系统中才会使用到。
但是还是得去了解下,我们先看下JdbcTransactionFactory,这个实现了TransactionFactory,将获取JDBCTransaction实例的过程隐藏了起来,其主要代码如下:
1 public class JdbcTransactionFactory implements TransactionFactory { 2 3 @Override 4 public void setProperties(Properties props) { 5 } 6 7 @Override 8 public Transaction newTransaction(Connection conn) { 9 return new JdbcTransaction(conn); 10 } 11 12 @Override 13 public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { 14 return new JdbcTransaction(ds, level, autoCommit); 15 } 16 }
我们先看setProperties方法,参数是Properties,这个方法是在解析事物标签的时候,如果有属性设置,就要执行,用于覆盖默认的配置,但是对于JdbcTransaction,即使你设置了,也不起作用,因为这边是是一个空方法,当然,我们一般不会设置。
下面两个方法名字一样,只是参数不一样,一个是根据连接去获取JdbcTransaction的实例,一个是根据数据源,事物隔离级别,是否自动提交来获取,讲这两个方法之前,我们先来看下,JdbcTransaction中相关提交、回滚、关闭操作的实现:
提交:
1 if (connection != null && !connection.getAutoCommit()) { 2 if (log.isDebugEnabled()) { 3 log.debug("Committing JDBC Connection [" + connection + "]"); 4 } 5 connection.commit(); 6 } 7 }
回滚:
1 public void rollback() throws SQLException { 2 if (connection != null && !connection.getAutoCommit()) { 3 if (log.isDebugEnabled()) { 4 log.debug("Rolling back JDBC Connection [" + connection + "]"); 5 } 6 connection.rollback(); 7 } 8 }
关闭:
1 public void close() throws SQLException { 2 if (connection != null) { 3 resetAutoCommit(); 4 if (log.isDebugEnabled()) { 5 log.debug("Closing JDBC Connection [" + connection + "]"); 6 } 7 connection.close(); 8 } 9 }
我们可以看到,执行这些方法的时候都是针对connection来操作的,首先我们就要获取connection,
获取连接:
1 public Connection getConnection() throws SQLException {
2 if (connection == null) {
3 openConnection();
4 }
5 return connection;
6 }
我们接着看openConnection方法:
1 protected void openConnection() throws SQLException { 2 if (log.isDebugEnabled()) { 3 log.debug("Opening JDBC Connection"); 4 } 5 connection = dataSource.getConnection(); 6 if (level != null) { 7 connection.setTransactionIsolation(level.getLevel()); 8 } 9 setDesiredAutoCommit(autoCommmit); 10 }
setDesiredAutoCommit的具体内容如下:
1 protected void setDesiredAutoCommit(boolean desiredAutoCommit) { 2 try { 3 if (connection.getAutoCommit() != desiredAutoCommit) { 4 if (log.isDebugEnabled()) { 5 log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); 6 } 7 connection.setAutoCommit(desiredAutoCommit); 8 } 9 } catch (SQLException e) { 10 // Only a very poorly implemented driver would fail here, 11 // and there's not much we can do about that. 12 throw new TransactionException("Error configuring AutoCommit. " 13 + "Your driver may not support getAutoCommit() or setAutoCommit(). " 14 + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); 15 } 16 }
从这个两个方法我们可以看出,connection可以从datasource中获取,并补充设置它的事物隔离级别,是否自动提交等属性。现在我们再来看一下JdbcTransactionFactory中那两个方法就不难理解了,说到底就是为了获得connection,进而去操作事物。
最后,我们再来看一个JdbcTransaction中的方法,
1 protected void resetAutoCommit() { 2 try { 3 if (!connection.getAutoCommit()) { 4 // MyBatis does not call commit/rollback on a connection if just selects were performed. 5 // Some databases start transactions with select statements 6 // and they mandate a commit/rollback before closing the connection. 7 // A workaround is setting the autocommit to true before closing the connection. 8 // Sybase throws an exception here. 9 if (log.isDebugEnabled()) { 10 log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); 11 } 12 connection.setAutoCommit(true); 13 } 14 } catch (SQLException e) { 15 if (log.isDebugEnabled()) { 16 log.debug("Error resetting autocommit to true " 17 + "before closing the connection. Cause: " + e); 18 } 19 } 20 }
这个方法是在关闭连接的时候调用的,mybatis默认的自动提交为true,如果将其设置为了false,就需要将其复位。那么自动提交的初始值是在哪边设置的呢?