事务-Transaction
文章目录
- 1. 基本概念
- 2. 事务配置
- 3. 事务的传播行为
- 4. 转账(案例)
- 4.1 准备工作
- 4.2 代码实现
1. 基本概念
-
定义:事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
-
Spring事务的作用:在数据层或业务层保障一系列的数据库操作同时成功或同时失败。
-
事务的操作主要有三步:
- 开启事务(一组操作开始前,开启事务):start transaction / begin ;
- 提交事务(这组操作全部成功后,提交事务):commit ;
- 回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;
-
Spring实现事务:Spring为了管理事务,提供了一个平台事务管理器
PlatformTransactionManager
PlatformTransactionManager
只是一个接口,Spring还为其提供了一个具体的实现(DataSourceTransactionManager
):我们只需要给它一个DataSource
对象,它就可以帮你去在业务层管理事务。其内部采用的是JDBC的事务。所以说如果你持久层采用的是JDBC相关的技术,就可以采用这个事务管理器来管理你的事务。而Mybatis内部采用的就是JDBC的事务,所以后期我们Spring整合Mybatis就采用的这个DataSourceTransactionManager
事务管理器。
-
如何在代码中实现事务:Spring框架当中就已经把事务控制的代码都已经封装好了,并不需要我们手动实现。我们使用了Spring框架,我们只需要通过一个简单的注解
@Transactional
就搞定了。-
@Transactional
作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。 -
@Transactional
注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。 -
@Transactional
注解书写位置:- 方法
- 当前方法交给Spring进行事务管理
- 类
- 当前类中所有的方法都交由Spring进行事务管理
- 接口
- 接口下所有的实现类当中所有的方法都交给Spring进行事务管理
- 方法
-
2. 事务配置
-
上面这些属性都可以在
@Transactional
注解的参数上进行设置。- readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
- timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
- rollbackFor:当出现指定异常进行事务回滚。
- noRollbackFor:当出现指定异常不进行事务回滚。
- rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串。
- noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串。
- isolation设置事务的隔离级别:
- DEFAULT:默认隔离级别, 会采用数据库的隔离级别
- READ_UNCOMMITTED:读未提交
- READ_COMMITTED: 读已提交
- REPEATABLE_READ:重复读取
- SERIALIZABLE: 串行化
注意事项:Spring的事务只会对
Error异常
和RuntimeException异常
及其子类进行事务回顾,其他的异常类型是不会回滚的。
3. 事务的传播行为
-
事务的传播行为:是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
-
我们要想控制事务的传播行为,在@Transactional注解的后面指定一个属性propagation,通过 propagation 属性来指定传播行为。接下来我们就来介绍一下常见的事务传播行为。
属性值 含义 REQUIRED 【默认值】需要事务,有则加入,无则创建新事务 REQUIRES_NEW 需要新事务,无论有无,总是创建新事务 SUPPORTS 支持事务,有则加入,无则在无事务状态中运行 NOT_SUPPORTED 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务 MANDATORY 必须有事务,否则抛异常 NEVER 必须没事务,否则抛异常 …
4. 转账(案例)
-
需求:银行转账操作保证两个账户的金额变动要么都成功,要么都失败,并提供操作日志,并保存在数据表中,便于后期数据追踪。
操作日志信息包含:
- 转账人、收账人、金额、执行状态、转账时间。
4.1 准备工作
-
添加相关依赖:
// Spring工程 <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> </dependencies>
// SpringBoot工程 <dependencies> <!--mybatis起步依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!--mysql驱动--> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> </dependencies>
-
按需进行如下配置:
Spring Boot 工程:无需手动添加
@EnableTransactionManagement
,只要引入事务相关依赖(如spring-boot-starter-jdbc
),Spring Boot 会自动配置事务管理。传统 Spring 工程:必须显式添加
@EnableTransactionManagement
并在 XML 中配置事务管理器(如<tx:annotation-driven/>
)@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class,MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig {}
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false jdbc.username=root jdbc.password=root
public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } //配置事务管理器,mybatis使用的是jdbc事务 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("com.itheima.domain"); ssfb.setDataSource(dataSource); return ssfb; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.itheima.dao"); return msc; } }
4.2 代码实现
-
数据库表结构(简略):
-- 账户表 CREATE TABLE account ( id BIGINT PRIMARY KEY, balance DECIMAL(15,2) NOT NULL ); -- 操作日志表 CREATE TABLE transfer_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, from_account BIGINT NOT NULL, to_account BIGINT NOT NULL, amount DECIMAL(15,2) NOT NULL, status VARCHAR(20) NOT NULL, create_time DATETIME NOT NULL );
-
核心代码分层设计:
- Service 层 - 转账业务与日志分离:
@Service public class TransferService { @Autowired private AccountService accountService; @Autowired private TransferLogService logService; // 外层事务:控制转账整体逻辑 @Transactional(propagation = Propagation.REQUIRED) public void transfer(Long fromId, Long toId, BigDecimal amount) { // 1. 记录日志(独立事务:无论转账成功与否都记录尝试) logService.logAttempt(fromId, toId, amount); try { // 2. 执行转账(内层事务) accountService.transferFunds(fromId, toId, amount); // 3. 记录成功日志(独立事务) logService.logSuccess(fromId, toId, amount); } catch (Exception e) { // 4. 记录失败日志(独立事务) logService.logFailure(fromId, toId, amount, e.getMessage()); throw e; // 抛出异常触发外层事务回滚 } } } @Service public class AccountService { @Transactional(propagation = Propagation.MANDATORY) // 必须在外层事务中调用 public void transferFunds(Long fromId, Long toId, BigDecimal amount) { // 扣减转出账户余额 accountRepository.deduct(fromId, amount); // 增加转入账户余额 accountRepository.add(toId, amount); } } @Service public class TransferLogService { // 独立事务:日志记录不受外层事务回滚影响 @Transactional(propagation = Propagation.REQUIRES_NEW) public void logAttempt(Long fromId, Long toId, BigDecimal amount) { logRepository.save(new TransferLog(fromId, toId, amount, "ATTEMPT")); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void logSuccess(...) { logRepository.save(new TransferLog(fromId, toId, amount, "Success")); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void logFailure(...) { logRepository.save(new TransferLog(fromId, toId, amount, "Failure")); } }
-
Repository 层(JPA 示例):
public interface AccountRepository extends JpaRepository<Account, Long> { @Modifying @Query("UPDATE Account a SET a.balance = a.balance - :amount WHERE a.id = :id") void deduct(@Param("id") Long id, @Param("amount") BigDecimal amount); @Modifying @Query("UPDATE Account a SET a.balance = a.balance + :amount WHERE a.id = :id") void add(@Param("id") Long id, @Param("amount") BigDecimal amount); } public interface TransferLogRepository extends JpaRepository<TransferLog, Long> {}