搞懂 MyBatis 的事务管理机制

1,191 阅读8分钟

MyBatis 是一款优秀的持久层框架,相信很多 Java 后端开发人员对它都不会陌生。在实际项目开发中,事务管理是非常重要的一环,而 MyBatis 也为我们提供了便捷的事务管理机制。

本文将从以下方面详细解析 MyBatis 的事务管理机制:

  1. 事务概述
  2. MyBatis 实现事务的方式
  3. 事务实现源码分析

一、事务概述

事务是指要么全部执行成功,要么全部回滚的一组操作。在数据库中,一般使用 ACID 规则来约束事务,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。在 MyBatis 中,也遵循了这一规则。

MyBatis中的事务是指一系列数据库操作,这些操作要么全部执行成功,要么全部执行失败。如果操作过程中发生错误,所有对数据库的修改都将被回滚,即还原到最初状态。事务是确保数据一致性和完整性的关键机制之一。

在MyBatis中,我们可以通过三种方式来管理事务:

  1. 编程式管理事务:在代码中显式开启、提交或回滚事务。

  2. 声明式管理事务:通过AOP代理实现事务管理,可以让代码更简洁,更容易维护。

  3. 注解式管理事务:通过注解方式管理事务,是声明式管理事务的一种扩展方式。

以上三种方式,最常用的是声明式管理事务,它可以将事务管理和业务逻辑分离,降低代码的耦合度,提高代码的可读性和可维护性。在MyBatis中,声明式事务管理需要借助Spring框架来实现。

在使用声明式事务管理时,我们需要在Spring配置文件中配置TransactionManager和TransactionProxyFactoryBean两个bean,其中TransactionManager负责事务管理,TransactionProxyFactoryBean负责AOP代理。

下面是一个示例:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

<bean id="userMapperProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager" ref="transactionManager" />
    <property name="target" ref="userMapper" />
    <property name="transactionAttributes">
        <props>
            <prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="delete*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
</bean>

在编写完上述配置后,我们就可以对UserMapper接口中的方法进行事务管理了。例如,在UserService类中,我们可以注入userMapperProxy,并调用其中的方法:

@Service
public class UserService {

    @Autowired
    UserMapper userMapperProxy;

    @Transactional
    public void addUser(User user) {
        userMapperProxy.addUser(user);
    }
}

在代码中,通过@Transactional注解声明了该方法需要进行事务管理,当方法执行过程中发生异常时,事务将会回滚。

MyBatis支持多种事务管理方式,但是建议使用声明式事务管理,因为它能够最大程度地降低代码的耦合度,并提高代码的可读性和可维护性。

二、MyBatis 实现事务的方式

MyBatis 实现事务也有多种方式,其中最常用的方式有两种:编程式事务和声明式事务。接下来我们将分别来介绍这两种方式。

1. 编程式事务

编程式事务就是通过编写代码来手动管理事务,主要包括以下几个步骤:

1)获取 SqlSession 对象;

2)设置自动提交为 false;

3)执行数据库操作;

4)提交事务;

5)关闭 SqlSession。

代码示例如下:

SqlSession sqlSession = sqlSessionFactory.openSession(false);
try {
    // 执行数据库操作
    sqlSession.insert("insertUser", user);
    sqlSession.update("updateUser", user);
    
    // 提交事务
    sqlSession.commit();
} catch (Exception e) {
    // 回滚事务
    sqlSession.rollback();
} finally {
    // 关闭 SqlSession
    sqlSession.close();
}

以上代码中,通过设置自动提交为 false 来关闭自动提交事务的方式,然后在 try 块中执行需要进行事务管理的数据库操作,如果出现异常则回滚事务,否则提交事务,并在 finally 中关闭 SqlSession。

2. 声明式事务

声明式事务就是在配置文件中通过事务管理器来管理事务,这种方式更加灵活和方便,我们只需要在配置文件中配置好即可。MyBatis 内置了两个事务管理器:JDBC 和 Spring。

(1)JDBC 事务管理器

JDBC 事务管理器是 MyBatis 内置的一个事务管理器,在 SqlSessionFactory 的配置文件中进行配置:

<transactionManager type="JDBC" />

(2)Spring 事务管理器

Spring 事务管理器是基于 Spring 框架的事务管理器,需要引入 Spring 的包,并在 MyBatis 的配置文件中配置:

<transactionManager type="SPRING" />

在使用 Spring 事务管理器时,还需要在 Spring 的配置文件中配置事务管理器和数据源,具体可以参考 Spring 的文档。

三、事务源码理解

(1)TransactionFactory

在 MyBatis 中,事务工厂需要实现 org.apache.ibatis.transaction.TransactionFactory 接口,该接口有一个方法:

Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

该方法用于创建一个新的 Transaction 实例。

例如,如果你要使用 JDBC 来管理事务,可以使用默认提供的 JdbcTransactionFactory 类来创建事务工厂。以下是一个创建事务工厂的示例代码:

import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

import javax.sql.DataSource;

public class MyTransactionFactory {
    public static TransactionFactory createTransactionFactory() {
        return new JdbcTransactionFactory();
    }
}

以上代码中,我们使用 JdbcTransactionFactory 创建一个新的事务工厂。实际上,在 MyBatis 的配置文件中,可以直接引用这个工厂。

例如:

<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>
  <!-- Mapper 配置 -->
</configuration>

以上代码中,我们使用了 <transactionManager type="JDBC"/> 来配置事务管理器的类型,并且在 <dataSource> 标签中指定了数据源的类型为 POOLED,也就是一个连接池数据源。

在实际应用中,还可以通过自定义事务工厂来创建其他类型的事务。例如,如果你要使用 Atomikos 来管理事务,可以自定义一个 AtomikosTransactionFactory 类来创建事务工厂。

(2)JdbcTransaction

在 MyBatis 中,Transaction 接口的默认实现是 JdbcTransaction 类。这个类的创建是在 JdbcTransactionFactory 工厂类中完成的。

以下是 JdbcTransactionFactory 工厂类中用于创建 Transaction 实例的代码:

@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
  return new JdbcTransaction(dataSource, level, autoCommit);
}

在这段代码中,我们可以看到工厂方法 newTransaction() 的实现。该方法接收三个参数:数据源、事务隔离级别和是否自动提交。

newTransaction 方法中,我们使用这些参数创建一个新的 JdbcTransaction 实例,并将其返回。JdbcTransaction 类中实现了 Transaction 接口,因此可以直接返回。

以下是 JdbcTransaction 类的主要源码:

public class JdbcTransaction implements Transaction {

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) throws SQLException {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
    openConnection();
    setDesiredAutoCommit();
    setDesiredTransactionIsolation();
  }

  // ...其他方法

}

JdbcTransaction 类中,我们创建了一个基本的事务对象,它包含了一些属性,例如事务管理器、连接、事务隔离级别等等。在构造函数中,我们使用传入的参数来初始化这些属性值。

除了构造函数之外,还有一些其他的方法用于对事务进行操作,例如提交、回滚、关闭等。这些方法都是从 Transaction 接口中继承来的。

因此,在 MyBatis 中,创建一个新的事务就是通过事务工厂和相应的创建方法来创建一个特定类型的事务对象。而在事务对象中,我们可以对事务进行各种操作。

(3)ManagedTransaction

在 MyBatis 中,除了 JdbcTransaction 之外,还提供了另外一种称为 ManagedTransaction 的事务实现,它是一种由容器或框架来管理的事务,而不是 MyBatis 内部来管理。

ManagedTransaction 接口的默认实现是 SpringManagedTransaction 类。该类实现了 MyBatis 的 Transaction 接口,但其本身并不处理任何事务逻辑,而是委托给 Spring 框架进行处理。

以下是 SpringManagedTransaction 类的主要源码:

public class SpringManagedTransaction implements Transaction {

  private final DataSource dataSource;
  private Connection connection;
  private boolean isConnectionTransactional;
  private boolean autoCommit;

  public SpringManagedTransaction(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  // ...其他方法

}

SpringManagedTransaction 类中,我们使用传入的数据源来创建一个新的事务对象。在 getConnection() 方法中,如果当前连接不存在,则会打开一个新的连接并返回;否则直接返回已经存在的连接。

需要注意的是,SpringManagedTransaction 并不自行管理事务,而是将事务管理工作交给了 Spring 框架。因此,如果要使用 SpringManagedTransaction,我们需要在 Spring 配置文件中配置相应的事务管理器,例如 DataSourceTransactionManagerJpaTransactionManager 等。

总之,相比于 JdbcTransactionManagedTransaction 更适合在容器或框架中使用,而不是在 MyBatis 内部使用。如果你使用 Spring 或其他类似的框架来管理事务,则可以考虑使用 ManagedTransaction 来实现 MyBatis 的事务管理。

四、测试用例

接下来我们将通过一个示例来详细介绍 MyBatis 的事务管理机制。假设我们需要执行一个复杂的业务操作,需要同时更新多个表,我们就可以使用编程式事务来管理事务。

在 Mapper.xml 文件中,编写如下 SQL 语句:

<insert id="insertUser" parameterType="com.example.User">
    insert into user (id, name, age) values (#{id}, #{name}, #{age})
</insert>

<update id="updateAccount" parameterType="com.example.Account">
    update account set balance = #{balance} where id = #{id}
</update>

然后我们编写一个 Service 类来调用 Mapper 文件中的方法,代码如下:

public class UserService {
    private SqlSessionFactory sqlSessionFactory;

    public UserService(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public void addUserAndAccount(User user, Account account) {
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        try {
            // 执行数据库操作
            sqlSession.insert("insertUser", user);
            sqlSession.update("updateAccount", account);
            
            // 提交事务
            sqlSession.commit();
        } catch (Exception e) {
            // 回滚事务
            sqlSession.rollback();
        } finally {
            // 关闭 SqlSession
            sqlSession.close();
        }
    }
}

以上代码中,我们通过 openSession(false) 来获取 SqlSession 对象,并设置自动提交为 false,然后在 try 块中执行数据库操作,如果出现异常则回滚事务,否则提交事务,并在 finally 中关闭 SqlSession。在实际开发中,我们可以将 SqlSession 放入 Spring 管理中,这样就更加方便了。

以上就是 MyBatis 的事务管理机制的详细介绍和代码示例,希望能对大家有所帮助。