1、什么是 MyBatis ?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybaits 整体结构
整体上可以把 Mybatis 分为三层
API 接口层:对开发提供的业务层,使用本地 API 调用来操作业务数据库
数据处理层:负责具体的 SQL 查找,语句解析(参数映射),SQL 执行,以及结果映射,主要用来完成一次数据库请求操作。
基础支撑层:负责基础的功能组件支撑,包括有连接管理,事务管理,配置加载以及缓存处理等。将这些公用的组件提取出来,负责为上游业务提供基础支撑。
2、入门案例
也可参考官方:mybatis.org/mybatis-3/z…
代码示列:
使用上面的例子,就可以完成 Mybatis 操纵数据库得到业务数据。
总结下分为四个步骤:
- 获取配置文件(也可以是配置建造器),从配置中得到 SqlSessionFactory
- 从 SqlSessionFactory 获取 SqlSession
- 通过 SqlSession.getMapper 进行 CRUD 和事务操作
- 关闭 session
思考:Mybatis 在上面四步操作中做了什么?MyBatissql 语句的执行解析过程?
3、执行过程分析
3.1 构建 SqlSessionFactory
我们一步一步来,根据代码进行查看
String resource = "mybatis-config.xml";
//将 XML 配置文件构建为 Configuration 配置类
Reader reader = Resources.getResourceAsReader(resource);
// 通过加载配置文件流构建一个 SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
复制代码
首先上面两行代码是从配置文件中进行构建 SqlSessionFactory,进入到 build... 中看看是如何构建出的
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
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.
}
}
}
复制代码
这里看出 SqlSessionFactoryBuilder 会加载配置并解析 xml 配置文件,先看下 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)-->
<properties resource="db.properties"></properties>
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="******"/>
</properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
<!--全局设置(settings)-->
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
<!--类型别名(typeAliases)-->
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
<!--类型处理器(typeHandlers)-->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
<!--对象工厂(objectFactory)-->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
<!--插件(plugins)-->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
<!--环境配置(environments)-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
<!--数据库厂商标识(databaseIdProvider)-->
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
<!--映射器(mappers)-->
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
</configuration>
复制代码
接下来可以看解析配置的核心方法
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
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;
}
复制代码
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
/**
* 解析 properties 节点
* <properties resource="mybatis/db.properties" />
*/
propertiesElement(root.evalNode("properties"));
/**
* 解析我们的 mybatis-config.xml 中的 settings 节点
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* 基本没有用过该属性,VFS 含义是虚拟文件系统
*/
loadCustomVfs(settings);
/**
* 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
*/
loadCustomLogImpl(settings);
/**
* 解析我们的别名
* <typeAliases> <typeAlias alias="Author" type="cn.zcy.pojo.Author"/>
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 解析我们的插件(比如分页插件)
*/
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置 settings 和默认值
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
/**
* 解析我们的 mybatis 环境 <environments>
*/
environmentsElement(root.evalNode("environments"));
/**
* 解析数据库厂商<databaseIdProvider>
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**
* 解析我们的类型处理器节点<typeHandlers>*/
typeHandlerElement(root.evalNode("typeHandlers"));
/**
* 最重要的就是解析我们的 mapper
* resource/url/class/package;
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
复制代码
以上过程执行完成后会得到一个包含 Configuration 对象,其中 Configuration 包含所有的配置信息。
总结
获取 SqlSessionFactory 的流程:
- 通过 SqlSessionFactoryBuilder 解析 Mybatis 所有的属性配置,生成一个包含所有的配置信息的 Configuration 对象。
- 然后用 Configuration 生成一个 DefaultSqlSessionFactory,整个过程的时序图如下
3.2 获取 SqlSession
拿到 DefaultSqlSessionFactory 后就可以开启 session,获取 SqlSession 后可以执行 CURD 操作,接下来看看 SqlSession session = sqlMapper.openSession() 的过程;
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//获取环境变量
final Environment environment = configuration.getEnvironment();
//获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
/**
* 创建一个 sql 执行器对象
* 一般情况下 若我们的 mybaits 的全局配置文件的 cacheEnabled 默认为 ture 就返回
* 一个 cacheExecutor,若关闭的话返回的就是一个 SimpleExecutor
*/
final Executor executor = configuration.newExecutor(tx, execType);
// 建返回一个 DeaultSqlSessoin 对象返回
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();
}
}
复制代码
这里会根据 SQL 执行器类型 execType,获取一个 sql 执行器,用来进行 SQL 语句操作
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
/**
* 判断执行器的类型
* 批量的执行器
*/
if (ExecutorType.BATCH == executorType) {
e