Mybatis流程分享-II 构建数据库 Environment 信息
序言
通过 章节一 的讨论,我们知道Mybatis内部对于xml的解析过程具体如下:
可以看到,在配置文件的解析过程中其会首先调用SqlSessionFactortyBuilder中的build方法,接着再调用XMLConfigBuilder的parse()方法,并最终返回一个Configuraion对象。进一步,又会调用SqlSessionFactortyBuilder中重载的build方法,返回一个SqlSessionFactory对象。
在这一章节中,我们主要先看一下 Configuraion 和 SqlSessionFactory 这两个类
核心配置 Configuraion 配置管家
在分析这个类之前,我们先来看一下这个类是在何时进行生成的,具体位置如下
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//Step1.把配置文件输入流转成XMLConfigBuild解析类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//Step2.解析类并返回一个SQLSessionFactory类
return build(parser.parse());
} catch (Exception e) {
.....
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//Step1.1 主动生成一个Configuration类
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
在进行Step1中,也是就把配置文件输入流转成XMLConfigBuild解析类这个步骤,我们可以看到在解析的过程成,会主动生成了一个Configuraion类,并且生成这个类的时候,同时还携带了大量的默认配置信息。
这些配置信息在后续同样扮演了很重要的角色。
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
此刻,对于xml配置文件中的信息就已经开始有一个配置管家Configuration 开始进行接管了,但是这个时候的管家还只存在一些默认信息,更多配置信息则需要经过后续步骤的填充
XMLConfigBuilder的parse()方法
在前面,我们知道Configuration 的创建是在XMLConfigurationBuild的构建过程中,此时的Configuration只具有一个原始的框架。而如果parse方法中没有对这个Configuration进行配置的补充,则上方的语句完全不需要执行return build(parser.parse()) ,只需要执行 return build(parser.getConfiguration()) 就行,因此,我们可以断定, parser.parse() 一定存在对 Configuration 配置的补充修饰
XMLConfigBuilderr # parseConfiguration
private void parseConfiguration(XNode root) {
// 暂时忽略对于其他标签的处理,重点关注environments标签
environmentsElement(root.evalNode("environments"));
}
对于Mybatis配置文件<configuration>标签下的environments通常会有如下的配置信息:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<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>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
其中属性的含义如下:
id配置环境的名称信息transactionManager指明数据库事务实现方式datoSource配置数据库连接池信息
了解了XML文件中,environments 这个节点配置的信息,我们再来看这个 environmentsElement 就相对简单许多
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 逐个遍历Environments标签下的environment标签
for (XNode child : context.getChildren()) {
// 获取environment的id属性信息
String id = child.getStringAttribute("id");
// 判断是否存在指定环境&&是否是当前环境
if (isSpecifiedEnvironment(id)) {
// 获取transactionManager管理工厂
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);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
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 类中,而这个类也被保存在Configuration 中,此时这个配置中也存在了环境信息的配置
TransactionFactory 事务工厂
在解析 transactionManager 标签的时候,我们能够知道我们设置的事务管理器的类型,
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
......
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
protected <T> Class<? extends T> resolveClass(String alias) {
.......
return resolveAlias(alias);
}
protected <T> Class<? extends T> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
如果在xml中我们没有进行自定义其他的配置的话,Mybatis就会通过这个 事务管理器的type类型,从typeAliasRegistry 找到对应的管理器。
而这个 typeAliasRegistry 变量,实际在生成 Configuration 这个类的时候,就已经存在了默认配置
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
}
从此处看出,在 MyBatis 中有两种类型的事务管理器(也就是 type="[ JDBC|MANAGED]")environments
JDBC – 这个配置直接使用了 JDBC 的提交和回滚功能,它依赖从数据源获得的连接来管理事务作用域。默认情况下,为了与某些驱动程序兼容,它在关闭连接时启用自动提交。然而,对于某些驱动程序来说,启用自动提交不仅是不必要的,而且是一个代价高昂的操作。因此,从 3.5.10 版本开始,你可以通过将 "skipSetAutoCommitOnClose" 属性设置为 "true" 来跳过这个步骤。例如:
<transactionManager type="JDBC">
<property name="skipSetAutoCommitOnClose" value="true"/>
</transactionManager>
MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:
<transactionManager type="MANAGED">
<property name="closeConnection" value="false"/>
</transactionManager>
DataSourceFactory 数据源工厂
DataSourceFactory 和 TransactionFactory 有点类似,也是通过获取数据源type类型,然后再获取Configraution中默认的数据源类型。
从Configruation的默认配置中来看,数据源有以下三种 数据源类型信息
UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:
-
driver– 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。 -
url– 这是数据库的 JDBC URL 地址。 -
username– 登录数据库的用户名。 -
password– 登录数据库的密码。 -
defaultTransactionIsolationLevel– 默认的连接事务隔离级别。 -
defaultNetworkTimeout– 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看java.sql.Connection#setNetworkTimeout()的 API 文档以获取更多信息。作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如: -
driver.encoding=UTF8
这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8 的 encoding 属性给数据库驱动。
POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。
除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections– 在任意时间可存在的活动(正在使用)连接数量,默认值:10poolMaximumIdleConnections– 任意时间可能存在的空闲连接数。poolMaximumCheckoutTime– 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)poolTimeToWait– 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。poolMaximumLocalBadConnectionTolerance– 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections与poolMaximumLocalBadConnectionTolerance之和。 默认值:3(新增于 3.4.5)poolPingQuery– 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。poolPingEnabled– 是否启用侦测查询。若开启,需要设置poolPingQuery属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。poolPingConnectionsNotUsedFor– 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。
JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:
initial_context– 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。data_source– 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。
当所有信息全部解析完毕后,其会将配置文件中设定的内容设定到Environment对象中;然后,再将配置好的Environment对象设定到XMLConfigBuilder的成员变量Configuration之中。
事实上,
Configuration 对象是一个核心组件,负责管理 MyBatis 的配置信息和各种资源。例如:
- 配置信息管理:
Configuration对象负责管理MyBatis的全局配置信息,包括数据库连接信息、缓存配置、插件配置等。 - 映射信息管理:
MyBatis通过Configuration对象管理与数据库表的映射关系。映射信息包括 SQL 语句与方法的映射关系,以及查询结果与Java对象之间的映射配置。 - Mapper 注册:
Configuration对象负责将Mapper接口与对应的映射配置关联起来。通过它,你可以将Mapper接口与xml文件或注解进行关联,从而实现SQL语句的映射。
所以Configuration更像一个 “大管家” 一样,统筹管理着Mybatis的配置信息。 明白了Configuration对象的作用后,我们继续来看parse方法中build(parse.parse())方法的逻辑,方法逻辑如下:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
述代码逻辑很简单,就是将配置信息configuration传入build方法,然后new一个DefaultSqlSessionFactory进行返回。
接下来,我们继续来看DefaultSqlSessionFactory的究竟又是何物。
SqlSessionFactory相关内容
对于DefaultSqlSessionFactory而言,其有如下的类结构关系:
可以看到,DefaultSqlSessionFactory类结构关系非常简单。其只实现了一个SqlSessionFactory 接口。而SqlSessionFactory 接口内容如下所示:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
// 其他为openSession方法的重载
}
可以看到,SqlSessionFactory作为一个接口,其内部只定义了一个openSession方法,而该方法的返回值一个SqlSession对象。
因此对于SqlSessionFactory而言,只需记住:是一个SqlSession的工厂,通过该工厂可以获取SqlSession对象。
此处实际上是一个工厂模式的典型应用。具体来看,在SqlSessionFactory内部定义了创建SqlSession的创建方式。从而使得SqlSession的创建和使用进行了分析。
(tip:SqlSession相关内容我们会在后续文章中分析,此处可以先选择性的忽视)
DefaultSqlSessionFactory作为SqlSessionFactory的唯一实现。接下来,我们便看看其内部又有哪些逻辑。DefaultSqlSessionFactory的相关内容:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
// mybatis会将所有配置信息归结到configuration之中
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
/**
* 获取一个sqlSession信息
**/
@Override
public SqlSession openSession() {
// 调用openSessionFromDataSource返回一个SqlSession对象
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
// .......省略其他无关代码
}
可以看到DefaultSqlSessionFactory中的openSession方法,从而将构建SqlSession的逻辑委托给方法openSessionFromDataSource来完成。
总结
至此,我们对Mybaits的Configuration和SqlSessionFactory进行了介绍和分析,总结来看:
Configuration用于保存配置文件的配置信息;SqlSessionFactory作为SqlSession的工厂,用于SqlSession的创建。
此外,解析配置文件、构建SqlSessinonFactory的整体调用过程如下图所示。而这张图可以看做是本文的核心内容的总结。

浙公网安备 33010602011771号