简介:
MyBatis 初始化的主要工作是加载井解析 mybatis-config.xml 配置文件、映射配置文件以及相关的注解信息。
mybatis-config.xml配置内容:
<?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 resource="db.properties"></properties>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<environments default="development">
<environment id="development">
<!-- 事物管理器类型 -->
<transactionManager type="JDBC"/>
<!--连接池类型-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--配置mapper映射文件-->
<mappers>
<mapper resource="CollegeMapper.xml"/>
</mappers>
</configuration>
测试解析配置文件
public class MapperTest {
public static void main(String[] args) throws IOException {
// 1 使用Resources加载全局配置文件,封装成抽象类Reader ,Resources 类正如其名,会帮助你从类路径下、文件系统或一个 web URL 中加载资源文件。
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 2 创建DefaultSqlSessionFactory
SqlSessionFactory factory = builder.build(reader);
// 3 创建会话SqlSession
SqlSession session = factory.openSession();
// 获取Mapper对象
CollegeMapper collegeMapper = session.getMapper(CollegeMapper.class);
List<College> colleges = collegeMapper.selectCollegeAdminListAll();
System.out.println(colleges);
session.commit();
session.close();
}
}
运行MapperTest :
MyBatis的初始化步骤:
1 构建DefaultSqlSessionFactory对象
MyBatis的初始化入口是 SqlSessionFactoryBuilder.build()方法,该方法最终返回一个DefaultSqlSessionFactory对象。我们先来看下SqlSessionFactoryBuilder 类(部分方法已省略)的源码:
public class SqlSessionFactoryBuilder {
// 构建DefaultSqlSessionFactory对象
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
// 构建DefaultSqlSessionFactory对象的具体执行方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 读取配置文件,创建XMLConfigBuilder 实例。
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析配置文件,创建DefaultSqlSessionFactory对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//根据Configuration 创建DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
获取DefaultSqlSessionFactory步骤:
第一步:实例化XMLConfigBuilder 。
SqlSessionFactoryBuilder中使用XMLConfigBuilder 对象来解析mybatis-config.xml 配置文件 ,我们来看下XMLConfigBuilder结构。
XMLConfigBuilder 继承BaseBuilder,是mybatis专门用来解析全局配置文件的,而BaseBuilder跟mybatis初始化有着莫大的关系,我们先来看下BaseBuilder基本信息。
(1) BaseBuilder类图如下:
其中XMLMapperBuilder是解析 Mapper 映射器;XMLStatementBuilder用来解析增删改查标签。
(2)BaseBuilder的核心字段解释:
Configuration 是 MyBatis初始化过程的核心对象MyBatis 中几乎全部的配置信息会保存到Configuration 对象中。Configuration 类里面有很多的属性,有很多是跟 mybatis-config里面的标签直接对应的。
protected final Configuration configuration;
// 记录配置文件中的所有别名信息。
protected final TypeAliasRegistry typeAliasRegistry;
// 加载配置文件中自定义的 Type Handler
protected final TypeHandlerRegistry typeHandlerRegistry;
以上三个BaseBuilder的成员变量都是在 MyBatis 初始化过程中创建的全局唯一的对象。
第二步: XMLConfigBuilder 获取Configuration 对象
真正解析mybatis-config配置文件是通过XMLConfigBuilder.parse()方法,具体源码如下:
// 首先会检查是不是已经解析过,也就是说在应用的生命周期里面,mybatis-config配置文件只需要解析一次,生成的 Configuration 对象也会存在应用的整个生命周期中。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在 mybatis-config.xml 配置文件中查找<configuration>节点,并开始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 具体解析 mybatis-config.xml方法
private void parseConfiguration(XNode root) {
try {
// 解析<properties>节点
propertiesElement(root.evalNode("properties"));
// 解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
//设置 vfsimpl 字段。loadCustomVfs 是获取 Vitual File System 的自定义实现类,比如我们要读取本地文件,或者 FTP 远程文件的时候,就可以用到自定义的 VFS 类。我们根据<settings>标签里面的<vfsImpl>标签,生成了一个抽象类 VFS 的子类,并且赋值到 Configuration中。
loadCustomVfs(settings);
// loadCustomLogImpl 是根据<logImpl>标签获取日志的实现类,我们可以用到很多的日志的方案,包括 LOG4J,LOG4J2,SLF4J 等等。这里生成了一个 Log 接口的实现类,并且赋值到 Configuration 中。
loadCustomLogImpl(settings);
// 解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins>节点。<plugins>标签里面只有<plugin>标签,<plugin>标签里面只有<property>标签。标签解析完以后,会生成一个 Interceptor 对象,并且添加到 Configuration 的InterceptorChain 属性里面,它是一个 List。
pluginElement(root.evalNode("plugins"));
// 解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
// 解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 将 settings 佳设置到 Configuration 中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析<environments>节点。一个 environment 就是对应一个数据源,所以在这里我们会根据配置的<transactionManager>创建一个事务工厂,根据<dataSource>标签创建一个数据源,最后把这两个对象设置成 Environment 对象的属性,放到Configuration 里面。
environmentsElement(root.evalNode("environments"));
// 解析<databaseIdProvider>节点,生成 DatabaseIdProvider 对象(用来支持不同厂商的数据库)。
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析<typeHandlers>节点。跟 TypeAlias 一样,TypeHandler 有两种配置方式,一种是单独配置一个类,一种是指定一个 package。最后我们得到的是 JavaType 和 JdbcType,以及用来做相互映射的 TypeHandler 之间的映射关系。最后存放在 TypeHandlerRegistry 对象里面。
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析<mappers>节点,将包内的映射器接口实现全部注册为映射器。
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
第三步:根据Configuration 创建DefaultSqlSessionFactory
创建DefaultSqlSessionFactory源码如下:
//根据Configuration创建DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
创建DefaultSqlSessionFactory的流程图如下:
2 创建会话SqlSession
跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建。具体的创建操作是由DefaultSqlSessionFactory. openSessionFromDataSource()来创建DefaultSqlSession对象。源码如下:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
// 创建SqlSession 具体实现方法。顾名思义就是从全局配置文件中的DataSource获取sqlSession。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 从 Configuration 里面拿到 Enviroment
final Environment environment = configuration.getEnvironment();
// 根据Enviroment获取事物工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建Transaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建Executor ,用来执行sql。
final Executor executor = configuration.newExecutor(tx, execType);
// 返回DefaultSqlSession实例,属性包括 Configuration、Executor 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
根据源码,创建sqlsession可分为如下步骤:
第一步:创建事物 Transaction
Transaction 的类图结构:
产生事物的工厂如下:
如果配置的是 JDBC,则会使用 Connection 对象的 commit()、rollback()、close()管理事务。
如果配置成 MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。因为我们跑的是本地程序,如果配置成 MANAGE 不会有任何事务。
如 果 是 Spring + MyBatis , 则 没 有 必 要 配 置 , 因 为 我 们 会 直 接 在applicationContext.xml 里面配置数据源和事务管理器,覆盖 MyBatis 的配置。
第二步:创建Executor执行器
Executor 的基本类型有三种:SIMPLE、BATCH、REUSE,默认是 SIMPLE(settingsElement()读取默认值),他们都继承了抽象类 BaseExecutor。类图结构如下:
三种类型的区别(通过 update()方法对比):
SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map 内,供下一次使用。简言之,就是重复使用 Statement 对象。
BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行
executeBatch()批处理。与 JDBC 批处理相同。
如果全局配置文件配置了 cacheEnabled=ture,会用装饰器模式对 executor 进行包装:new CachingExecutor(executor)。
包装完毕后,会执行:executor = (Executor) interceptorChain.pluginAll(executor);对 executor 进行包装。
第三步:创建属性包括 Configuration、Executor 对象的DefaultSqlSession实例。
获取DefaultSqlSession源码如下:
return new DefaultSqlSession(configuration, executor, autoCommit);
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
创建DefaultSqlSession时序图如下:
总结:mybatis配置文件的解析大致分为两种 。一个是mybatis-config.xml 全局配置文件的解析。另外就是可能有很多个的 Mapper.xml 文件的解析,也包括在 Mapper 接口类上面定义的注解。本文只是讲解mybatis如何解析mybatis-config.xml,以及如何创建会话的过程,后面会对Mapper.xml解析,如何创建Mapper对象,以及sql如何执行进行详细讲解。