前面研究了一下MyBatis中TypeAliases类型命名的注册过程,下面研究一下MyBatis中XML配置environments环境来创建事务和数据源的流程。
还是先从解析MyBatis配置文件configuration节点及子节点时,调用environmentsElement来解析environments。
/**
* MyBatis配置环境,MyBatis支持多种环境,这中机制适用于多种数据库之中
*/
environmentsElement(root.evalNode("environments"));
直接看源码,我以注释的形式进行解释说明。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
/**
* 如果我们在方法中没有传递environment参数,就解析MyBatis-config配置文件的
*/
if (environment == null) {
/**
* 获取配置文件default的值<environments default="development">
* environment = development
*/
environment = context.getStringAttribute("default");
}
/**
* 解析environment节点以及一下节点,这里可以说明,Mybatis是支持双数据源的
*/
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
/**
*
* isSpecifiedEnvironment 判断 environment 和 id是否为空,如果为空,抛出一场。如果相等返回true,否则返回false
*
*/
if (isSpecifiedEnvironment(id)) {
/**
* <transactionManager type="JDBC"/>
* 事务
*/
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
/**
* <dataSource type="POOLED">
*数据源
*/
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
/**
* 将dataSource设置进configuration对象
*/
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
事物的声明:
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
/**
* <transactionManager type="JDBC"/>
* MyBatis支持两种的事物管理,也就是 type="[JDBC|MANAGED]"
* JDBC --- 这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的链接来管理事物的作用域
* MANAGED ---- 这个配置从来不提交或者回滚一个链接,而是让容器来管理事物的整个生命周期(eg JEE应用服务器的上下文) 默认情况下它会关闭,然而一些容器并不希望这样,因此需要将closeConnection 属性设置为false,来阻止它的默认关闭。
* eg:<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
*/
String type = context.getStringAttribute("type");
/**
* 获取transactionMagger 中properties属性
*/
Properties props = context.getChildrenAsProperties();
/**
* resolveClass(type)这个我们主要说一下,回想一下我们创建Configuration对象时他的默认构造方法给类型命名,有如下代码:
* typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
* typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
*resolveClass(type)返回JdbcTransactionFactory Calss,使用newInstance通过反射创建TransactionFactory对象
*/
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
数据源:
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
/**
* <dataSource type="POOLED">
* MyBatis有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):
* UNPOOLED--不使用任何数据库连接池来管理数据库连接
* POOLED--使用Mybatis自带的数据库连接池来管理数据库连接
* JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。
*/
String type = context.getStringAttribute("type");
/**
* <property name="driver" value="${driver}"/>
* <property name="url" value="${url}"/>
* <property name="username" value="${username}"/>
* <property name="password" value="${password}"/>
*/
Properties props = context.getChildrenAsProperties();
/**
* 这里直接参考事物的讲解
* typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
* typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
* typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
*/
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
以上我们简单的看了一下通过解析environments节点以及一下子节点,来初始化数据源和事物,但是MyBatis是如何通过${driver}这个获取对应的参数呢?我们看如下代码
/**
* @author Clinton Begin
* @author Kazuki Shimizu
* 解析${}这种形式的表达式
*/
public class PropertyParser {
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
/**
* The special property key that indicate whether enable a default value on placeholder.
* <p>
* The default value is {@code false} (indicate disable a default value on placeholder)
* If you specify the {@code true}, you can specify key and default value on placeholder (e.g. {@code ${db.username:postgres}}).
* </p>
* @since 3.4.2
*/
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
/**
* The special property key that specify a separator for key and default value on placeholder.
* <p>
* The default separator is {@code ":"}.
* </p>
* @since 3.4.2
*/
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
private static final String ENABLE_DEFAULT_VALUE = "false";
private static final String DEFAULT_VALUE_SEPARATOR = ":";
private PropertyParser() {
// Prevent Instantiation
}
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
private final boolean enableDefaultValue;
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
private String getPropertyValue(String key, String defaultValue) {
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
if (variables != null) {
String key = content;
if (enableDefaultValue) {
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
return "${" + content + "}";
}
}
}
MyBaits通过PropertyParser这个类解析${driver}表达式。