MyBatis源码分析-(一)事务管理机制源码分析

本文最后更新于:March 18, 2023 pm

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

目录

首先简单的回顾一下MyBatis的初始使用过程:

  • 创建Maven项目并引入Mybatis和MySQL连接依赖。

  • 创建并编写MyBatis主配置文件:mybatis-config.xml,并配置数据源、事务等信息。

  • 创建并编写对应的Mapper文件:xxxxMapper.xml,编写SQL语句。

  • 在主配置文件中声明Mapper文件。

  • 然后主函数中进行使用,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.tothefor.MybatisMember;

    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;

    import java.io.InputStream;
    import java.util.List;

    /**
    * @Author DragonOne
    * @Date 2023/3/10 22:27
    * @墨水记忆 www.tothefor.com
    */
    public class Main {
    public static void main(String[] args) throws Exception{
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
    SqlSession session = build.openSession();

    int insert = session.insert("insertTest");
    System.out.println(insert);
    session.commit();
    }
    }

在这里,我们需要注意MyBatis的几个核心对象:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。

面试题:在MyBatis中,负责执行SQL语句的对象叫什么?

  • 答案:SqlSession。

但是,如何才能获取到SqlSession呢?要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory来生产SqlSession;但是,要想获取SqlSessionFactory对象,又需要先获取SqlSessionFactoryBuilder对象,通过SqlSessionFactoryBuilder对象的build方法获取SqlSessionFactory对象。具体代码可见初始示例,核心代码如下:

1
2
3
4
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); // 文件路径相对根路径(resource目录)
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
SqlSession session = build.openSession();

其中,SqlSessionFactoryBuilder对象的build方法需要InputStream流,获取InputStream流的方式很多。上面示例中是通过MyBatis包中的一个Resources工具类进行获取的,当然也可以自行直接进行new,如下:

1
2
3
4
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream inputStream = new FileInputStream("mybatis-config.xml"); // 需要文件绝对路径
SqlSessionFactory build = sqlSessionFactoryBuilder.build(inputStream);
SqlSession session = build.openSession();

还有一种获取方式,通过系统的类加载器进行获取,如下所示:

1
2
3
4
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml"); // 文件路径相对根路径(resource目录)
SqlSessionFactory build = sqlSessionFactoryBuilder.build(resourceAsStream);
SqlSession session = build.openSession();

我们对MyBatis提供的Resources工具类进行源码查看可知,其底层实现就是通过调用类加载器。源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();

public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream((ClassLoader)null, resource);
}

public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader); // 核心
if (in == null) {
throw new IOException("Could not find resource " + resource);
} else {
return in;
}
}

其中的classLoaderWrapper的实现方式为:

1
2
3
4
5
6
7
8
9
10
11
12
public class ClassLoaderWrapper {
ClassLoader defaultClassLoader;
ClassLoader systemClassLoader;

ClassLoaderWrapper() {
try {
this.systemClassLoader = ClassLoader.getSystemClassLoader();
} catch (SecurityException var2) {
}

}
}

📚小知识:一般情况下,名称中带resource的都是以根节点(resource目录)作为查找起点的。

事务管理机制

MyBatis的主配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jwgl?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/XsxkbMapper1.xml"/>
</mappers>
</configuration>

在最开始的实例中可以看见,最后一行是session.commit();。那么这是干嘛的呢?是用来提交事务的,因为MyBatis中默认是需要手动进行提交的。

在MyBatis中,通过主配置文件的

1
2
3
4
<environment id="development">
<transactionManager type="JDBC"/>
...
</environment>

进行事务的管理,其中type支持两种值:

  • JDBC。
  • MANAGED。

如果type的值为JDBC,则表示MyBatis框架自行管理事务,使用原生的JDBC代码进行事务的管理。比如:

1
2
coon.setAutoCommit(false); // 是否自动提交。也就是是否开启是否。如果自动提交,则表示关闭事务;如果手动提交,则表示开启事务。
coon.commit(); // 提交事务

但是,在MyBatis的使用中,我们发现并没有使用过这两句代码,主要是因为被封装了。

1
2
SqlSession session = build.openSession(); // 底层是:coon.setAutoCommit(false);
session.commit(); // 底层是:coon.commit();

提示:以上仅是type为JDBC时,采用了JDBC的事务管理器。

相反,如果type为MANAGED,则表示使用MANAGED事务管理器。也就是表示MyBatis不再负责事务的管理了,而是交给了其他容器来负责,比如:Spring。如果配置值为MANAGED,但又没有其他进行管理,则表示事务没有开启。

源码分析

JDBC

手动提交

首先,我们在openSession()处打一个断点:

1
SqlSession session = build.openSession();

然后DeBug进去,可以看见如下:

1
2
3
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

这里面,需要注意最后一个参数为false,这也表示是否自动提交。然后再进入openSessionFromDataSource里面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

在这里面,我们可以看见一个事务管理器接口:

1
Transaction tx = null;

而该事务管理器接口有两个实现类,刚好对应type的两个值:

1
2
public class JdbcTransaction implements Transaction
public class ManagedTransaction implements Transaction

然后一步一步往下走,一直走到:

1
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

这里就表示创建一个事务管理器。

然后再进入newTransaction:

1
2
3
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit, this.skipSetAutoCommitOnClose);
}

这里,我们就可以看见,其实是通过JDBC实现的,接下来继续往下走,直到JDBC的构造方法:

1
2
3
4
5
6
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit, boolean skipSetAutoCommitOnClose) {
this.dataSource = ds;
this.level = desiredLevel;
this.autoCommit = desiredAutoCommit;
this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose;
}

在这里,就是将desiredAutoCommit赋值给autoCommit的,表示是否需要自动提交,这里为false。

所以,当type为JDBC时,底层其实就是使用的传统JDBC进行事务的处理。

自动提交

接下来,再看看设置自动提交的源码过程,同时打断点:

1
SqlSession session = build.openSession(true);

然后同样的方式,一直到:

1
2
3
4
5
6
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit, boolean skipSetAutoCommitOnClose) {
this.dataSource = ds;
this.level = desiredLevel;
this.autoCommit = desiredAutoCommit;
this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose;
}

然后找到该类(JdbcTransaction)中的openConnection()方法:

1
2
3
4
5
6
7
8
9
10
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}

然后查看:

1
setDesiredAutoCommit(autoCommit);

源码如下(删除无用代码):

1
2
3
4
5
6
7
8
9
10
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
...
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
...
}
}

这里非常重要。

首先,看一下判断条件:

1
connection.getAutoCommit() != desiredAutoCommit

我们知道,这时的desiredAutoCommit是为true的,表示自动提交。那connection.getAutoCommit()的值是什么呢?

首先,我们需要知道,如果在JDBC的代码中没有执行过:connection.setAutoCommit(false)的话,那么默认是为true的。即:JDBC中默认事务是自动提交的。

而且,我们也并没有设置过,所以,此时的 connection.getAutoCommit() 是为true的。那么,

1
connection.getAutoCommit() != desiredAutoCommit

的结果为false才对。即然为false了,那源码中的:

1
connection.setAutoCommit(desiredAutoCommit);

就不会被执行。虽然没有被执行,但是默认就是设置的为true,所以,并没有影响。

总结

  • 只要setCommit是true,则表示没有开启事务;为false表示开启事务。