Mybatis框架源码解读
本内容均属原创内容,转载请注明出处:https://www.jianshu.com/p/dc9fd739d829
有一起学习的小伙伴可以加老薛qq:1811112688 一起努力。
从今天开始,我们需要详细花一段时间系统的深入学习一下MyBatis框架底层的内容,我们会从一下几个方向展开讨论:
- MyBatis执行过程
- MyBatis的Executor、StatementHandler、ParameterHandler、ResultSetHandler
- MyBatis的一级缓存,二级缓存
- MyBatis事务管理机制
- 锁机制
(一):MyBatis执行过程
1-1:编写测试用例
1-1-1: 环境要求
测试用例环境:
Maven:3.6 Idea:2018-3 jdk:11 Mybatis:3.4.6 MySql:8.0
MySql驱动包:8.0.13
1-1-2:测试用例配置
1-1-2-1:pom文件配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mage</groupId>
<artifactId>helloMybatis01</artifactId>
<version>1.0-SNAPSHOT</version>
<name>helloMybatis01</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.11</maven.compiler.source>
<maven.compiler.target>1.11</maven.compiler.target>
</properties>
<dependencies>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<!-- junit测试包 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 日志文件管理包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
</dependencies>
<!-- 加载资源配置文件 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
1-1-2-2:mybatis.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>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 数据库连接相关配置 ,这里动态获取config.properties文件中的内容-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/user" />
<property name="username" value="root" />
<property name="password" value="mage1234" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.mage.dao.UserDao"></mapper>
</mappers>
</configuration>
1-1-2-3:UserDao.java
public interface UserDao {
public User queryById(int id);
}
1-1-2-4:UserDao.xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mage.dao.UserDao" >
<sql id="QueryUserSql">
select * from t_user
</sql>
<select id="queryById" resultType="com.mage.vo.User" parameterType="int">
<include refid="QueryUserSql"></include>
where id = #{id}
</select>
</mapper>
1-1-2-5:测试类
public class HiMyBatis{
private SqlSession session;
@Before
public void start() throws IOException {
//1:读取配置信息 加载配置
InputStream is = Resources.getResourceAsStream("mybatis.xml");
//2:构建SqlSessionFactory回话工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//3:获取SqlSession回话
this.session = sqlSessionFactory.openSession();
}
@After
public void destory(){
if(session!=null){
//关闭会话
session.close();
}
}
@Test
public void test() throws IOException {
//4:获取代理对象
UserDao dao = session.getMapper(UserDao.class);
//5:获取查询结果
User user = dao.queryById(1);
System.out.println(user);
}
}
1-2:MyBatis层次结构
1-2-1:SqlSession:
接收开发人员提供Statement Id 和参数.并返回操作结果:
主要负责【Connection获取】和【Statement对象管理方案】
Statement对象管理方案
- 1)简单管理方案:一个Statement接口对象只执行一次。执行完毕 就会Statement接口对象进行销毁。
- 2)可重用方案: 使用一个Map集合,关键字就是一条Sql语句。对应
内容Statement接口对象,等到SqlSession再次接收到相同命令时,就从map集合找到对应Statement接口使用。
map.put("select * from order", Statement1) - 3)批处理管理方案:将多个Statement包含的SQL语句,交给一个Statement对象 输送到数据库,形成批处理操作
1-2-2: Executor:
MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
Executor的继承关系
- BaseExecutor:抽象类;减轻Executor接口实现难度。
- CachingExecutour:提高查询效率,在查询时首先到缓存中寻找对应的数据。如果有直接返回,MyBatis框架默认情况下使用执行器缓存执行器,
如果缓存执行器没有得到对应结果时,才会交给其他的执行器执行
1-2-3: StatementHandler:
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
1-2-4: ParameterHandler:
负责对用户传递的参数转换成JDBC Statement 所需要的参数
1-2-5: ResultSetHandler:
负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
1-2-6 : TypeHandler:
负责java数据类型和jdbc数据类型之间的映射和转换
1-1-7: MappedStatement:
维护了一条<select|update|delete|insert>节点的封装
1-1-8: SqlSource:
负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回BoundSql表示动态生成的SQL语句以及相应的参数信息
1-1-9: Configuration:
MyBatis所有的配置信息都维持在Configuration对象之中
1-3:MyBatis 架构图
PS:这个架构图,通过理解MyBatis的层级结构尝试理解就可以了。其实和层级结构图阐述的思想是一致的。
1-4:SqlSessionFactory接口:
作用:主要负责【Connection获取】和【Statement对象管理方案】
1-4-1:SqlSessionFactory的类图
1-4-2:SqlSessionManager分析
1-4-2-1:源码
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
//请注意这里
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal();
//第二步:内部构建SqlSeesion的代理对象
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
}
//第一步:假设调用当前构造器
public static SqlSessionManager newInstance(Reader reader) {
return new SqlSessionManager((new SqlSessionFactoryBuilder()).build(reader, (String)null, (Properties)null));
}
}
1-4-2-2:源码解读
这里老薛将大部分的构造器删除掉了,因为其实核心的代码已经在上面了,就是调用private的构造器创建对象
- [ ] 第一步:读取流数据,调用私有的构造器,创建返回SqlSession对象
- [ ] 第二步:这里私有的构造器内部就是通过java提供的动态代理创建了一个与之对应的SqlSession的代理对象。
- [ ] 第三步:耐心的读一下代理对象的创建方式,其实和我们之前聊过的动态代理,和多级代理 一回顾其实很容易。
- [ ] 监控
SqlSession.class
这个接口中的所有方法,这里的方法都是核心业务方法,一旦被发现执行,则交由SqlSessionManager中的内部类SqlSessionInterceptor对象去实现。
1-4-2-2-1:SqlSessionInterceptor类的源码:
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//注意这里的sqlSession的创建
SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
return method.invoke(sqlSession, args);
} catch (Throwable var12) {
throw ExceptionUtil.unwrapThrowable(var12);
}
} else {
//如果代理对象没有将sqlSession对象创建则会重新创建一个
SqlSession autoSqlSession = SqlSessionManager.this.openSession();
Object var7;
try {
Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
var7 = result;
} catch (Throwable var13) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(var13);
} finally {
autoSqlSession.close();
}
return var7;
}
}
}
1-4-2-2-2:SqlSessionInterceptor源码解读:
1:这里明显是一个代理实现类对象
2:在内部类中的SqlSession对象其实是从外部类中的ThreadLocal中获取的。
3:如果ThreadLocal中不存在,根据跟踪源码,我们发现其实是通过创建的SqlSessionFactory重新带开了一个SqlSession对象,那么重点就在于创建的SqlSessionFactory是那个实现类了。
public SqlSession openSession() {
return this.sqlSessionFactory.openSession();
}
1-4-2-2-3:为什么目前SqlSessionManager用的不多了?
原因就在与SqlSessionManager中的SqlSession为了保护线程安全,通过ThreadLocal做了线程安全,但是一般情况下,我们的MyBatis框架会和Spring框架一起使用,而此时SqlSession对象的线程安全就显得不那么好用了。
1-4-3:DefaultSqlSessionFactory分析:
1-4-3-1:源码
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
//1:会将配置文件读取到Configuration对象中
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
//方法1
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
//方法2
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
DefaultSqlSession var8;
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException var13) {
autoCommit = true;
}
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
Transaction tx = transactionFactory.newTransaction(connection);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var14, var14);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
}
1-4-3-2:源码解读
这里老薛也将大部分的内容都删减了一下,留了一个对外可以访问的构造器以及两个核心方法。所有的构造器的调用都是通过从数据源或者是链接中创建返回的。也就是两个私有方法中获取的。这里老薛解读一个:openSessionFromDataSource();方法
- 第一步:首先会将Configuration对象创建好,其实在MyBatis框架启动时,就会读取核心配置文件,即mybatis.xml文件,通过XMLConfigBuilder对象去将xml文件中的数据信息挨个进行填充。
- 第二步:读取Configuration中的Environment,其实就是我们配置的环境,可以获取到配置的数据源、事物提交方式等信息。
- 第三步:获取到TransactionFactory对象以及Executor执行器对象。
- 第四步:通过配置对象、执行器对象创建DefaultSqlSession
ps:最后留个小作业:
1:查看一下SqlSession接口的实现类和源码,你是否发现相似之处呢?
2:通过run我们的第一个代码,和对于层架结构图以及结构图,你是否可以通过DeBug方式,画出来整个程序的流程图呢?
参考内容:http://www.mybatis.org/mybatis-3/zh/
以及MyBatis技术内幕和深入浅出MyBatis技术原理书籍