文章目录
上篇文章 Mybatis系列(二)配置文件源码解析,对mybatis源码进行了简单解析。在mybatis配置文件中,configuration作为跟节点,下边还有很多子节点,本节我们就来介绍properties和eveironments节点。既然要说properties,那就不得不提一下Mybatis提供的三种properties配置方法:
- property子元素
- properties文件
- 程序代码传递
一、mybatis配置数据源的两种方式
第一种直接配置xml
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
</configuration>
第二种从外部指定properties配置文件
<configuration>
<properties resource="configuration.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
创建一个配置文件configuration.properties
,这也是目前项目中最常用的一种方式。
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_demo
username=root
password=root123
第三种程序代码配置
public static SqlSessionFactory getSessionFactory(){
SqlSessionFactory sqlSessionFactory = null;
String resource = "mybatis-config.xml";
InputStream inputStream;
try {
InputStream in = Resources.getResourceAsStream("configuration.properties");
Properties properties = new Properties();
properties.load(in);
String username = properties.getProperty("username");
String password = properties.getProperty("password");
properties.put("username",username);
properties.put("password",password);
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream,properties);
} catch (IOException e) {
e.printStackTrace();
}
return sqlSessionFactory;
}
如果三种配置方式都用了,哪种方式优先呢?那就让源码给我们答案吧。
二、propertiesElement源码
上节我们说到mybatis是通过XMLConfigBuilder
解析配置文件的,那么我们继续从这个方法说起。
private void parseConfiguration(XNode root) {
try {
//解析properties文件
this.propertiesElement(root.evalNode("properties"));
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.settingsElement(root.evalNode("settings"));
//解析environments文件
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
private void propertiesElement(XNode context) throws Exception {
//判断properties是否有值
if (context != null) {
//获取properties子节点数据作为默认数据
Properties defaults = context.getChildrenAsProperties();
//获取resource和url的属性值
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
//resource和url不能同时为空,否则会抛异常
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//通过流解析resource或url文件内容为Properties对象,put进默认的Properties对象,
//如果之前<properties>有子节点与resource或url的key值相同的,会被覆盖掉,这也就是官方说的,
//通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的是 properties 属性中指定的属性。
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//configuration对象是队XML文件的解析结果的承载对象,所有的解析结果都会set进configuration
Properties vars = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//把装有解析配置propertis对象set进解析器, 因为后面可能会用到
this.parser.setVariables(defaults);
//set进configuration对象
this.configuration.setVariables(defaults);
}
}
mybatis官方文档介绍:
通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的是 properties 属性中指定的属性。
源码分析也显而易见,resource/url 属性中指定的配置参数会覆盖掉properties 属性中指定的属性。
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
三、environments配置
environments顾名思义,是用来配置运行环境的,其实就是配置数据库,它下边又分为两个可配置的元素:事务管理器(transactionManager
)、数据源(dataSource
)。
下面来看environments节点解析,先来个配置:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 上面指定了数据库配置文件, 配置文件里面也是对应的这四个属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
environments元素节点可以配置多个environment子节点,在平时的开发中,开发环境和测试环境及正式环境肯定用的是不同的库,我们可以通过配置environments属性选择对应的environment。
接下来我们就看看XMLConfigBuilder
这个类是怎么解析environments的。
四、environmentsElement源码
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
判断environment是否为空,为空则获取default属性值
if (this.environment == null) {
this.environment = context.getStringAttribute("default");
}
//获取子节点的Node对象集合
Iterator i$ = context.getChildren().iterator();
while(i$.hasNext()) {
XNode child = (XNode)i$.next();
//获取子节点id
String id = child.getStringAttribute("id");
//判断子节点ID与environment是否相等,如果相等返回TRUE,否则FALSE
if (this.isSpecifiedEnvironment(id)) {
//Mybatis事务处理使用JDBC和MANAGED
//使用JDBC的事务管理机制:即利用java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等
//使用MANAGED的事务管理机制:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器如(JBOSS,Weblogic)来实现对事务的管理,比如我们常用Spring进行事务管理
TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
//environment节点下边就是dataSource节点
//datasource有三种类型下边我们会讲到
DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
//把事务管理与数据源参数放入EnvironMent对象
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
//将environment放到configuration
this.configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
//datasource解析方法
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
//datasource连接池
String type = context.getStringAttribute("type");
//子节点,将name和value属性set到properties对象
Properties props = context.getChildrenAsProperties();
//创建DataSourceFactory
DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
} else {
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
}
transactionManager 事务管理器
在Mybatis中,transactionManager
提供了两个实现类,它需要实现接口transaction
。
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
从方法可知,它主要工作就是提交(commit
),回滚(rollback
),和关闭(close
)数据库的事务。Mybatis为Transaction
提供了两个实现类JdbcTransaction
,ManagedTransaction
。因此它有两个工厂JdbcTransactionFactory
,ManagedTransactionFactory
,这个工厂要实现TransactionFactory
接口,通过它们会生成对应的Transaction
对象,于是就可以把事务管理器配置成
<transactionManager type="JDBC"/>
<transactionManager type="MANAGEN"/>
- JDBC使用的
JdbcTranscationFactory
生成的JdbcTransaction
对象实现。它是以JDBC的方式对数据库的提交和回滚进行操作。 - MANAGED使用的
ManagedTransactionFactory
生成的ManagedTransaction
对象实现。它的提交和回滚方法不用任何操作,而是把事务交给容器处理。在默认情况下,它会关闭连接,然后一些容器并不希望这样,因为需要将closeConnection
属性设置为false来阻止它默认的关闭行为。
不想用Mybatis的规则时,我们也可以自定义事务工厂。
datasource数据源
我们看源码datasource接口有三个实现类,分别是JndiDataSourceFactory
,PooledDataSourceFactory
,UnpooledDataSourceFactory
。
<dataSource type="UNPOOLED">
<dataSource type="POOLED">
<dataSource type="JNDI">
- UNPOOLED
这个数据源的实现只是每次被请求时打开和关闭连接。虽然一点慢,它对在及时可用连接方面没有性能要求的简单应用程序是一个很好的选择。 - POOLED
这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。
这是一种使得并发 Web 应用快速响应请求的流行处理方式。 - JNDI
这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI上下文的引用。
通过对源码的解读,相信大家对mybatis已经有了更深的认识。
还有一个问题,我们配置数据源的时候,是通过${driver}这种表达式,这又是怎么解析的呢?其实它是通过PropertyParser来解析的。
PropertyParser源码
public class PropertyParser {
public PropertyParser() {
}
public static String parse(String string, Properties variables) {
PropertyParser.VariableTokenHandler handler = new PropertyParser.VariableTokenHandler(variables);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
private static class VariableTokenHandler implements TokenHandler {
private Properties variables;
public VariableTokenHandler(Properties variables) {
this.variables = variables;
}
public String handleToken(String content) {
return this.variables != null && this.variables.containsKey(content) ? this.variables.getProperty(content) : "${" + content + "}";
}
}
}
五、附录:Mybatis配置文件元素一览表
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration> <!-- 配置 -->
<properties/> <!-- 属性 -->
<settings><!-- 设置 -->
<setting name="" value=""/>
</settings>
<typeAliases/><!-- 类型命名 -->
<typeHandlers/><!-- 类型处理器 -->
<objectFactory type=""/><!-- 对象工厂 -->
<plugins><!-- 插件 -->
<plugin interceptor=""></plugin>
</plugins>
<environments default=""><!-- 配置环境 -->
<environment id=""><!-- 环境变量 -->
<transactionManager type=""></transactionManager> <!-- 事务管理器 -->
<dataSource type=""></dataSource> <!-- 数据源 -->
</environment>
</environments>
<databaseIdProvider type=""/> <!-- 数据库厂商标识 -->
<mappers/><!-- 映射器 -->
</configuration>
properties 和 environments元素节点分析到此结束,下篇文章对其他节点进行分析。