4.3 MyBatis 事务管理


文档摘要

4.3 MyBatis 事务管理 4.3 MyBatis 事务管理 4.3.1 事务的概念和重要性 事务(Transaction)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的操作序列构成,要么全部执行成功,要么全部不执行。事务是维护数据库完整性的重要手段,保证了数据的一致性。 事务的四大特性(ACID): 原子性(Atomicity): 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 一致性(Consistency): 事务必须使数据库从一个一致性状态变换到另一个一致性状态。 隔离性(Isolation): 并发执行的事务之间互不干扰,一个事务的内部操作对其他事务是不可见的。 持久性(Durability): 一旦事务提交,其所做的修改将永久保存在数据库中。

4.3 MyBatis 事务管理

4.3 MyBatis 事务管理

4.3.1 事务的概念和重要性

事务(Transaction)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的操作序列构成,要么全部执行成功,要么全部不执行。事务是维护数据库完整性的重要手段,保证了数据的一致性。

事务的四大特性(ACID):

  • 原子性(Atomicity): 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  • 一致性(Consistency): 事务必须使数据库从一个一致性状态变换到另一个一致性状态。

  • 隔离性(Isolation): 并发执行的事务之间互不干扰,一个事务的内部操作对其他事务是不可见的。

  • 持久性(Durability): 一旦事务提交,其所做的修改将永久保存在数据库中。

4.3.2 MyBatis 事务管理方式

MyBatis 提供了两种事务管理方式:

  • JDBC 事务管理: 使用 JDBC 的 java.sql.Connection 对象来管理事务,依赖于数据库驱动提供的事务支持。

  • Managed 事务管理: 将事务管理委托给容器(如 Spring),MyBatis 不负责事务的提交或回滚。

mybatis-config.xml 文件中,可以通过 <transactionManager> 元素配置事务管理器:

<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="autoCommit" value="false"/> </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>
  • type="JDBC": 指定使用 JDBC 事务管理器。autoCommit 属性设置为 false,表示禁用自动提交,需要手动提交或回滚事务。

  • type="MANAGED": 指定使用 Managed 事务管理器。 MyBatis 不会管理事务的生命周期,而是依赖容器来管理。

选择哪种事务管理方式?

  • 如果你的应用运行在 Spring 等容器中,推荐使用 MANAGED 事务管理,将事务控制交给容器,利用容器提供的事务管理功能,如声明式事务。

  • 如果你的应用是独立的,没有容器支持,可以使用 JDBC 事务管理,手动控制事务的提交和回滚。

4.3.3 JDBC 事务管理实践

使用 JDBC 事务管理时,需要在代码中手动获取 SqlSession,并控制事务的提交或回滚。

示例:

import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; import java.io.IOException; public class JdbcTransactionExample { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = org.apache.ibatis.io.Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = null; try { session = sqlSessionFactory.openSession(); // 1. 获取 SqlSession // 2. 执行数据库操作 session.insert("UserMapper.insertUser", new User("testUser", "testPassword")); session.update("UserMapper.updateUser", new User("testUser", "newPassword")); // 3. 提交事务 session.commit(); System.out.println("事务提交成功"); } catch (Exception e) { // 4. 回滚事务 if (session != null) { session.rollback(); System.out.println("事务回滚成功"); } e.printStackTrace(); } finally { // 5. 关闭 SqlSession if (session != null) { session.close(); } } } static class User { private String username; private String password; public User(String username, String password) { this.username = username; this.password = password; } // Getters and setters } }

代码详解:

  1. 获取 SqlSession: 通过 sqlSessionFactory.openSession() 获取 SqlSession 对象。

  2. 执行数据库操作: 使用 session.insert(), session.update(), session.delete(), session.selectOne(), session.selectList() 等方法执行数据库操作。

  3. 提交事务: 使用 session.commit() 提交事务,将所有操作持久化到数据库。

  4. 回滚事务: 如果发生异常,使用 session.rollback() 回滚事务,撤销所有操作,保证数据一致性。

  5. 关闭 SqlSession: 无论事务提交或回滚,都需要在 finally 块中关闭 SqlSession,释放资源。

注意事项:

  • 务必在 try-catch-finally 块中处理事务,确保事务的提交或回滚,以及资源的释放。

  • 如果使用 JDBC 事务管理,需要手动控制事务的生命周期,容易出错。

4.3.4 Managed 事务管理实践 (Spring 集成)

当 MyBatis 与 Spring 集成时,通常使用 Managed 事务管理,将事务控制交给 Spring 容器。Spring 提供了声明式事务管理,通过注解或 XML 配置即可实现事务控制,无需手动编写事务管理代码。

1. Spring 配置文件 (applicationContext.xml):

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 数据源配置 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- SqlSessionFactory 配置 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!-- Mapper 接口扫描 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 事务管理器配置 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启用注解驱动的事务管理 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- Service Bean --> <bean id="userService" class="com.example.service.UserServiceImpl"> <property name="userMapper" ref="userMapper"/> </bean> <!-- Mapper Bean --> <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="com.example.mapper.UserMapper"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>

2. MyBatis 配置文件 (mybatis-config.xml):

<configuration> <environments default="development"> <environment id="development"> <transactionManager type="MANAGED"/> <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> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>

注意 transactionManagertype 属性设置为 MANAGED

3. UserMapper 接口:

package com.example.mapper; import com.example.model.User; public interface UserMapper { int insertUser(User user); int updateUser(User user); User selectUserById(int id); }

4. UserService 接口和实现:

package com.example.service; import com.example.model.User; public interface UserService { void addUser(User user); }
package com.example.service.impl; import com.example.mapper.UserMapper; import com.example.model.User; import com.example.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @Transactional public void addUser(User user) { userMapper.insertUser(user); // 模拟异常,测试事务回滚 if (user.getId() == 1) { throw new RuntimeException("模拟异常,测试事务回滚"); } userMapper.updateUser(user); } }

5. 使用 @Transactional 注解:

在需要事务控制的方法上添加 @Transactional 注解,Spring 会自动管理该方法的事务。

代码详解:

  • Spring 配置: 配置数据源、SqlSessionFactory、Mapper 接口扫描器和事务管理器。

  • mybatis-config.xmltransactionManagertype 设置为 MANAGED,告诉 MyBatis 将事务管理交给容器。

  • @Transactional 注解:UserServiceImpl.addUser() 方法上添加 @Transactional 注解,表示该方法需要事务支持。如果方法执行过程中发生异常,Spring 会自动回滚事务。

测试:

import com.example.model.User; import com.example.service.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService"); User user = new User(); user.setId(1); user.setUsername("testUser"); user.setPassword("testPassword"); try { userService.addUser(user); } catch (Exception e) { System.out.println("事务已回滚"); } } }

优点:

  • 代码简洁,无需手动编写事务管理代码。

  • 利用 Spring 容器的事务管理功能,如事务传播行为、事务隔离级别等。

  • 降低了出错的风险,提高了开发效率。

4.3.5 事务传播行为

事务传播行为是指当一个事务方法被另一个事务方法调用时,事务应该如何传播。Spring 提供了多种事务传播行为,可以在 @Transactional 注解的 propagation 属性中指定。

常见的事务传播行为:

  • REQUIRED(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

  • REQUIRES_NEW: 无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。

  • SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。

  • NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。

  • MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  • NEVER: 以非事务方式执行操作,如果当前存在事务,则抛出异常。

  • NESTED: 如果当前存在事务,则创建一个嵌套事务,嵌套事务可以独立于外部事务进行提交或回滚。

示例:

@Transactional(propagation = Propagation.REQUIRES_NEW) public void methodA() { // ... } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { methodA(); // 调用 methodA // ... }

在上面的示例中,methodA 使用 REQUIRES_NEW 传播行为,会创建一个新的事务,即使 methodB 已经存在一个事务。methodB 使用 REQUIRED 传播行为,会加入当前事务。

4.3.6 事务隔离级别

事务隔离级别是指多个并发事务之间互相隔离的程度。数据库提供了多种事务隔离级别,可以在 @Transactional 注解的 isolation 属性中指定。

常见的事务隔离级别:

  • DEFAULT: 使用数据库的默认隔离级别。

  • READ_UNCOMMITTED: 允许读取未提交的数据,可能导致脏读、不可重复读和幻读。

  • READ_COMMITTED: 允许读取已提交的数据,可以避免脏读,但可能导致不可重复读和幻读。

  • REPEATABLE_READ: 确保在同一个事务中多次读取同一数据的结果是一致的,可以避免脏读和不可重复读,但可能导致幻读。

  • SERIALIZABLE: 提供最高的隔离级别,确保事务串行执行,可以避免脏读、不可重复读和幻读,但性能较低。

示例:

@Transactional(isolation = Isolation.READ_COMMITTED) public void methodC() { // ... }

在上面的示例中,methodC 使用 READ_COMMITTED 隔离级别,允许读取已提交的数据,可以避免脏读。

4.3.7 最佳实践

  • 选择合适的事务管理方式: 如果应用运行在 Spring 等容器中,推荐使用 Managed 事务管理。如果应用是独立的,可以使用 JDBC 事务管理。

  • 使用声明式事务管理: 尽量使用 Spring 的声明式事务管理,通过 @Transactional 注解或 XML 配置来实现事务控制,避免手动编写事务管理代码。

  • 选择合适的事务传播行为和隔离级别: 根据业务需求选择合适的事务传播行为和隔离级别,避免不必要的性能损耗。

  • 控制事务范围: 事务范围不宜过大,尽量将事务控制在必要的操作范围内,避免长时间锁定资源。

  • 处理异常: 在事务方法中捕获异常,并根据异常类型决定是否回滚事务。

  • 避免长事务: 长事务会长时间占用数据库资源,影响系统性能。尽量避免长事务,可以将长事务拆分成多个短事务。

  • 使用合适的数据库连接池: 选择性能良好的数据库连接池,如 HikariCP、Druid 等,提高数据库连接的效率。


发布者: 作者: 转发
评论区 (0)
U