解析environments元素,完成Mybatis中的多环境配置
在完成枯燥的基于settings配置Configuration对象的过程之后,就到了解析environments标签,配置Mybatis的多环境的过程了。
Mybatis默认是支持多环境配置的,在Mybatis中有一个Environment的对象,该对象有三个简单的参数:
/**
* Mybatis环境容器
*
* @author Clinton Begin
*/
public final class Environment {
// 环境唯一标志
private final String id;
// 事务工厂
private final TransactionFactory transactionFactory;
// 数据源
private final DataSource dataSource;
}
其中id是当前环境的唯一标志,属于语义化属性。
transactionFactory属性对应的是TransactionFactory对象,他是一个事务创建工厂,用于创建Transaction对象。
Transaction对象包装了JDBC的Connection,用于处理数据库链接的生命周期,包括链接的:创建,提交/回滚和关闭。
dataSource属性对应的是DataSource对象,指向了一个JDBC数据源。
在Mybatis关于environments的DTD定义是这样的:
<!--ELEMENT environments (environment+)-->
<!--ATTLIST environments
default CDATA #REQUIRED
-->
在environments中必须指定default属性的值,他是默认环境的ID,用于指定默认使用的环境配置,同时在environments
中允许出现一个或多个environment子元素。
<!--ELEMENT environment (transactionManager,dataSource)-->
<!--ATTLIST environment
id CDATA #REQUIRED
-->
environment元素有一个必填的ID属性,是当前环境配置的唯一标志,同时他下面必须配置一个transactionManager和
dataSource子元素。
transactionManager
其中transactionManager用来配置当前环境的事务管理器,其DTD定义如下:
<!--ELEMENT transactionManager (property*)-->
<!--ATTLIST transactionManager
type CDATA #REQUIRED
-->
transactionManager有一个必填的type属性,表示使用的事务管理器的类型,在Mybatis中默认提供了两种类型的事务管
理器:JDBC和MANAGED。 这两个简短的名称依托于Mybatis的类型别名机制,他们是在Configuration的无参构造方法中注册的:
public Configuration() {
// 注册别名
// 注册JDBC别名
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
// 注册事务管理别名
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
...
}
其中JDBC对应的JdbcTransactionFactory创建的JdbcTransaction对象直接使用了默认的JDBC的提交和回滚设置,依赖于从数据源得到的链接来管理事务作用域。
而MANAGED对应的ManagedTransactionFactory创建的ManagedTransaction对象是Transaction的实现之一,它会忽略提交和回滚请求,但是会响应关闭连接的请求,不过,我们可以通过参数closeConnection来控制他如何处理关闭连接的请求,当closeConnection的值为false时,他将会忽略掉关闭链接的请求。
<transactionmanager type="MANAGED">
<property name="closeConnection" value="false" />
</transactionmanager>
在上面的示例中,我们可以发现transactionManager也允许配置property标签,用于自定义的事务管理器的参数。
dataSource
dataSource元素用来配置JDBC数据源,他的DTD定义如下:
<!--ELEMENT dataSource (property*)-->
<!--ATTLIST dataSource
type CDATA #REQUIRED
-->
dataSource标签同样有一个必填的type属性,它用于指向JDBC数据源的具体实例,在Mybatis中默认提供了三种类型的数据源:
UNPOOLED,POOLED和JNDI。
同样,这三个简短的名称也是Mybatis的类型别名,其注册也是在Configuration对象的无参构造中:
public Configuration() {
// 注册别名
...
// 注册JNDI别名
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
// 注册池化数据源别名
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
// 注册为池化的数据源
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
...
}
其中JNDI对应的JndiDataSourceFactory的作用是基于JNDI加载一个可用的DataSource。
POOLED对应的PooledDataSourceFactory用于获取一个PooledDataSource。
PooledDataSource是一个简单的,同步的,线程安全的数据库连接池,通过复用JDBC的Connection,避免了重复创建新的链接实例所必须的初始化和认证时间,提高了应用程序对数据库访问的并发能力。
UNPOOLED对应的UnpooledDataSourceFactory用于获取一个UnpooledDataSource,UnpooledDataSource是一个简单的数据源,他对每一次获取链接的请求都会打开一个新的链接。
dataSource标签下允许出现多个property子标签,这些property将会转换成Properties用于初始化和配置对应的DataSource.
在了解了每一个元素标签的作用之后,我们继续回到解析environments的代码上来。
调用解析的入口(XmlConfigBuilder):
private void parseConfiguration(XNode root) {
// ...
// 加载多环境源配置,寻找当前环境(默认为default)对应的事务管理器和数据源
environmentsElement(root.evalNode("environments"));
// ...
}
解析并构建Mybatis的Environment对象:
/**
* 解析environments节点
*
* @param context environments节点
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 配置默认环境
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
// 获取环境唯一标志
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// 配置默认数据源
// 创建事务工厂
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 创建数据源工厂
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 创建数据源
DataSource dataSource = dsFactory.getDataSource();
// 通过环境唯一标志,事务工厂,数据源构建一个环境容器
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 配置Mybatis当前的环境容器
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
解析environments标签的过程并不是很复杂,他首先通过environments的default属性获取用户指定的默认环境标志。
然后遍历environments下的所有environment节点,通过environment节点的id属性获取待处理环境environment的唯一标志,
如果当前标志和用户指定的默认环境标志一致的话,则处理该environment节点,否则跳过处理。
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
} else if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
} else if (environment.equals(id)) {
return true;
}
return false;
}
处理environment节点的操作,主要是通过解析元素transactionManager和dataSource,获取对应的TransactionFactory
对象以及DataSourceFactory对象。
dataSource元素的解析过程和transactionManager近乎一致,都是先解析出type属性对应的实际类型,然后通过反射获取对象实例,最后调用实例对象的setProperties()方法完成自定义的参数的同步工作。
关于transactionManager的解析过程:
/**
* 根据environments>environment>transactionManager节点配置事务管理机制
*
* @param context environments>environment>transactionManager节点内容
*/
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
// 获取事务类型
String type = context.getStringAttribute("type");
// 获取事务参数配置
Properties props = context.getChildrenAsProperties();
// 创建事务工厂
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
// 设置定制参数
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
关于dataSource的解析过程:
/**
* 解析dataSource元素
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 获取dataSource的type属性指向的DataSourceFactory别名。
String type = context.getStringAttribute("type");
// 获取用户定义配置的参数集合
Properties props = context.getChildrenAsProperties();
// 解析别名对应的具体类型,并反射获取实体。
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
// 设值
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
不同于TransactionFactory,在DataSourceFactory被初始化之后,Mybatis会调用DataSourceFactory的getDataSource()方法获取具体的DataSource实例。
到这里,配置Environment对象的必备元素已经全部得到,接下来通过使用Environment对象的构建器来构建一个
Environment对象,并将该Environment实例同步到Mybatis的配置类Configuration中的environment属性中。
至此,整个environments元素的解析工作也已经完成。
在继续解析后续元素前,让我们先来了解一下TransactionFactory和DataSourceFactory相关的信息。
首先我们看一下TransactionFactory:
/**
* 用于创建Transaction对象的实例
*
* @author Clinton Begin
*/
public interface TransactionFactory {
/**
* 配置事务工厂的自定义属性
* @param props 自定义属性
*/
void setProperties(Properties props);
/**
* 通过已有的链接创建一个事务
* @param conn 现有的数据库链接
* @return Transaction 事务
* @since 3.1.0
*/
Transaction newTransaction(Connection conn);
/**
* 从指定的数据源中创建一个事务
* @param dataSource 数据源
* @param level 事务级别
* @param autoCommit 是否自动提交
* @return Transaction 事务
* @since 3.1.0
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
从方法定义上我们可以看出,TransactionFactory是创建Transaction对象的工厂实例,在mybatis中Transaction对象封装了原生的Connection对象,
接管了对Connection对象的控制权,包括:创建链接,提交事务,回滚事务,关闭链接以及获取事务超时时间。
/**
* 包装数据库连接。
* 处理连接生命周期,包括:创建,准备,提交/回滚和关闭
*
* @author Clinton Begin
*/
public interface Transaction {
/**
* 获取一个数据库链接
*
* @return 数据库链接
*/
Connection getConnection() throws SQLException;
/**
* 提交
*/
void commit() throws SQLException;
/**
* 回滚
*/
void rollback() throws SQLException;
/**
* 关闭链接
*/
void close() throws SQLException;
/**
* 获取事务超时时间
*/
Integer getTimeout() throws SQLException;
}
TransactionFactory有两种默认实现,分别是:JdbcTransactionFactory和ManagedTransactionFactory。
JdbcTransactionFactory负责创建JdbcTransaction对象实例,ManagedTransactionFactory负责创建ManagedTransaction对象实例。
> 像这种工厂和产品皆被抽象出来的工厂定义,通常我们称之为抽象工厂,对应着设计模式中的抽象工厂模式。
JdbcTransactionFactory的实现比较简单,仅仅是调用JdbcTransaction不同的构造方法完成JdbcTransaction对象的初始化工作而已,同时,他不会处理用户传入的自定义属性配置。
/**
* 创建一个JdbcTransaction实例
*
* @author Clinton Begin
*
* @see JdbcTransaction
*/
public class JdbcTransactionFactory implements TransactionFactory {
@Override
public void setProperties(Properties props) {
}
@Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
ManagedTransactionFactory略有不同的地方在于,它会读取用户传入的名为closeConnection的自定义属性,并在创建ManagedTransaction对象实例时使用该属性完成ManagedTransaction对象的初始化工作。
/**
* 创建一个ManagedTransactionFactory实例
*
* @author Clinton Begin
*
* @see ManagedTransaction
*/
public class ManagedTransactionFactory implements TransactionFactory {
private boolean closeConnection = true;
@Override
public void setProperties(Properties props) {
if (props != null) {
String closeConnectionProperty = props.getProperty("closeConnection");
if (closeConnectionProperty != null) {
closeConnection = Boolean.valueOf(closeConnectionProperty);
}
}
}
@Override
public Transaction newTransaction(Connection conn) {
return new ManagedTransaction(conn, closeConnection);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
// 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.
return new ManagedTransaction(ds, level, closeConnection);
}
}
JdbcTransaction功能的实现依赖于从数据源得到的链接,他会将提交和回滚等请求直接委托给JDBC原生的Connection对象来完成。
而ManagedTransaction作为Transaction的另一种实现,不同于JdbcTransaction,
当对链接的操作请求到来时,ManagedTransaction默认会忽略所有的提交和回滚请求,同时针对关闭链接的请求,会根据创建ManagedTransaction对象时传入的closeConnection来决定是否受理。
之后我们简单看一下DataSourceFactory相关的类图:
在前面我们大概了解了DataSourceFactory不同实现类的大致作用,因为这里涉及到数据库连接池相关的知识,所以更详细的DataSourceFactory相关的信息,将会在扩展数据库连接池相关的知识时给出。
至此,基本完成了environments元素的处理工作。
关注我,一起学习更多知识
来源:oschina
链接:https://my.oschina.net/u/3101282/blog/4326191