事务的概念
事务说白了就是一序列业务操作的集合,而且这个集合要求ACID,所谓ACID就是:
1. 原子性(Atomicity)
- 含义:事务中的所有操作要么全部完成,要么全部不发生。
- 通俗理解:想象你在银行转账给朋友。如果你从自己的账户中扣除了钱,但是没有成功把钱加到朋友的账户里,那这笔交易就不应该发生。原子性确保了转账要么完全成功,要么完全失败,不会留下半途而废的状态。
2. 一致性(Consistency)
- 含义:事务执行后,数据必须处于一致的状态。
- 通俗理解:还是拿银行转账为例,如果你从账户中转走了100元,那么你的账户余额应该减少100元,而朋友的账户余额应该增加100元。一致性保证了转账前后的数据是正确的,不会有莫名其妙的钱凭空消失或出现。
3. 隔离性(Isolation)
- 含义:事务之间互不影响,就好像它们单独执行一样。
- 通俗理解:假设你和你的朋友在同一时间给同一个朋友转账,你们的转账不应该互相干扰。隔离性确保了你们的转账操作各自独立完成,就像你们是唯一在操作的人一样。
4. 持久性(Durability)
- 含义:一旦事务成功提交,它的结果就是永久的,即使系统崩溃也不会丢失。
- 通俗理解:想象你已经成功转账给了朋友,这时候突然停电了。持久性保证了即使在这种情况下,转账的结果仍然是有效的,你的账户余额和朋友的账户余额仍然会被正确更新。
Spring操作事务的方式(实操)
Spring操作事务的方式主要有以下两种:
- 声明式事务
- 编程式事务
编程式事务就是自己通过适用Spring提供的工具来建立事务,适合需要自定义事务的场景,不过现实开发大多情况下用不到,而且编程式事务编写的事务和业务代码高度耦合,不利于业务扩展,同时维护起来也不容易,如果有多个事务发生异常,那岂不是要挨个改一遍,于是我们先学习一下声明式事务
声明式事务管理
声明式事务管理允许你通过配置(如XML或注解)来指定哪些方法需要进行事务管理,而不需要在业务代码中显式地编写事务控制代码。这种方式可以减少事务相关代码的侵入性,并且易于维护
在Spring中,声明式事务管理最常见的实现方式是使用@Transactional
注解。你可以在类级别或方法级别应用此注解,以声明该类或方法需要被事务管理。
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
// 其他业务逻辑
}
@Transactional(readOnly = true)
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
在这个例子中,createUser
方法使用了@Transactional
注解,表示这个方法需要在一个事务中执行。如果方法中发生了异常,则事务将自动回滚,否则事务会在方法正常结束时提交。getUserById
方法使用了readOnly = true
属性,表明这是一个只读操作,不需要创建事务,但如果当前存在事务,则该方法将继续在当前事务中执行。
配置事务管理器
要启用声明式事务管理,你需要配置一个事务管理器。对于JPA或Hibernate,你可以使用JpaTransactionManager
;如果是普通的JDBC,则可以使用DataSourceTransactionManager
。
如果你使用Spring Boot,通常不需要手动配置事务管理器,因为它会自动为你配置好。但如果你使用XML配置或者自定义配置类,可以像下面这样配置:
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
事务传播行为
在使用@Transactional
注解时,还可以指定事务的传播行为,以控制不同事务方法之间的交互。Spring支持七种不同的传播行为,包括PROPAGATION_REQUIRED
(默认)、PROPAGATION_SUPPORTS
、PROPAGATION_MANDATORY
等。
@Transactional(propagation = Propagation.REQUIRED)
public void createUser(User user) {
userRepository.save(user);
}
@Transactional(propagation = Propagation.SUPPORTS)
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
1. PROPAGATION_REQUIRED
(默认)
- 描述: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- 场景: 假设你正在开发一个电商系统,其中有一个
transferMoney
方法用于从一个账户转账到另一个账户。这个方法应该在一个事务中执行,以确保转账操作要么全部成功,要么全部失败。
在这个场景中,如果@Service public class AccountService { @Autowired private AccountRepository accountRepository; @Transactional public void transferMoney(Account fromAccount, Account toAccount, BigDecimal amount) { fromAccount.debit(amount); toAccount.credit(amount); accountRepository.save(fromAccount); accountRepository.save(toAccount); } }
transferMoney
方法被另一个事务内的方法调用,它将加入那个事务;如果没有事务,则创建一个新的事务。
2. PROPAGATION_SUPPORTS
- 描述: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- 场景: 假设你有一个
getAccountBalance
方法用于获取账户余额。这个方法不需要在事务中执行,但如果它被事务中的方法调用,它可以加入事务。
在这个场景中,@Service public class AccountService { @Autowired private AccountRepository accountRepository; @Transactional(readOnly = true) public BigDecimal getAccountBalance(Account account) { return accountRepository.findBalanceByAccountId(account.getId()); } }
getAccountBalance
方法通常是非事务性的,但如果它被事务中的方法调用,它将加入那个事务。
3. PROPAGATION_MANDATORY
- 描述: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- 场景: 假设你有一个日志记录服务,它需要确保所有的日志记录都在事务中进行,以保证数据的一致性。
在这个场景中,如果@Service public class LoggingService { @Transactional(propagation = Propagation.MANDATORY) public void log(String message) { // 记录日志 } }
log
方法被非事务性的方法调用,它将抛出异常。
4. PROPAGATION_REQUIRES_NEW
- 描述: 总是创建一个新的事务,并且如果当前存在事务,则挂起当前事务。
- 场景: 假设你有一个
sendEmail
方法用于发送电子邮件确认。这个操作需要在一个独立的事务中执行,即使它被事务内的方法调用。
在这个场景中,@Service public class EmailService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void sendEmail(String email, String message) { // 发送邮件 } }
sendEmail
方法总是创建一个新的事务,即使它被事务内的方法调用。
5. PROPAGATION_NOT_SUPPORTED
- 描述: 以非事务方式执行操作,并挂起当前事务(如果存在)。
- 场景: 假设你有一个性能敏感的报表生成服务,它需要从数据库中读取大量数据。为了提高性能,你希望这个操作不在事务中执行。
在这个场景中,@Service public class ReportService { @Transactional(propagation = Propagation.NOT_SUPPORTED) public List<Report> generateReport() { // 生成报表 return reportRepository.findAll(); } }
generateReport
方法将以非事务方式执行,即使它被事务内的方法调用。
6. PROPAGATION_NEVER
- 描述: 以非事务方式执行操作,如果存在事务则抛出异常。
- 场景: 假设你有一个
pingDatabase
方法用于检查数据库连接是否可用。这个方法不能在事务中执行,因为它可能影响事务的一致性。
在这个场景中,如果@Service public class DatabaseHealthCheck { @Transactional(propagation = Propagation.NEVER) public boolean pingDatabase() { // 检查数据库连接 return databaseRepository.ping(); } }
pingDatabase
方法被事务内的方法调用,它将抛出异常。
7. PROPAGATION_NESTED
- 描述: 如果当前存在事务,则在嵌套事务内执行;如果当前不存在事务,则行为类似于
PROPAGATION_REQUIRED
。 - 场景: 假设你有一个
createUserAndProfile
方法用于创建用户并关联一个个人资料。为了确保用户创建成功后才能创建个人资料,你希望在用户创建成功后创建个人资料时使用嵌套事务。
在这个场景中,如果@Service public class UserService { @Autowired private UserRepository userRepository; @Autowired private ProfileRepository profileRepository; @Transactional public void createUserAndProfile(User user, Profile profile) { userRepository.save(user); createProfileForUser(user, profile); } @Transactional(propagation = Propagation.NESTED) public void createProfileForUser(User user, Profile profile) { profile.setUser(user); profileRepository.save(profile); } }
createUserAndProfile
方法在事务中被调用,createProfileForUser
方法将在嵌套事务中执行。如果createUserAndProfile
方法不在事务中被调用,则createProfileForUser
方法将创建一个新的事务。
事务隔离级别
1.ISOLATION_DEFAULT
(默认)
- 描述: 使用底层数据库系统的默认隔离级别。
- 场景: 如果你的应用不特别指定隔离级别,那么它将采用数据库的默认设置。例如,在MySQL中,默认的隔离级别通常是
READ_COMMITTED
。
2. ISOLATION_READ_UNCOMMITTED
- 描述: 允许脏读、不可重复读和幻读。
- 场景: 这个级别通常不推荐使用,因为它允许事务读取未提交的数据(脏读)。但在某些情况下,如果读取操作对实时性要求非常高且可以接受不一致的数据,可能会考虑使用此级别。例如,在一个实时监控系统中,为了减少延迟,可能会牺牲数据的一致性来换取更快的响应时间。
3. ISOLATION_READ_COMMITTED
(大多数数据库的默认值)
- 描述: 不允许脏读,但允许不可重复读和幻读。
- 场景: 这是大多数数据库系统的默认设置,适用于大多数情况。例如,在一个库存管理系统中,查询最新的库存量时,只需要读取已经提交的数据即可。
4. ISOLATION_REPEATABLE_READ
- 描述: 不允许脏读和不可重复读,但允许幻读。
- 场景: 这个级别确保事务能够重复读取相同的数据集。例如,在一个财务系统中,当你需要多次查询某个账户的余额时,希望每次查询的结果都是一样的(假设没有其他事务修改数据)。
5. ISOLATION_SERIALIZABLE
- 描述: 提供最高级别的隔离,不允许脏读、不可重复读和幻读。
- 场景: 这是最严格的隔离级别,确保事务完全独立地执行,就像它们是串行化的一样。虽然这能保证最高的数据一致性,但也可能导致较高的锁竞争和性能开销。例如,在一个银行转账系统中,为了保证资金转移的绝对安全性,可能会选择这个隔离级别。
按异常回滚
按指定异常回滚
@Transactional(rollbackFor = {异常类名})
不回滚哪些异常
@Transactional(noRollbackFor ={异常类名})
总结:
事务是具有ACID特性的一序列操作集合,Spring中管理事务的方法有编程事务和声明式事务,其中编程式事务由于其维护性低,可扩展性低,耦合度高的特点不常被使用,而声明式事务由于其简洁高效,低耦合的特性而被广泛使用,声明式事务可以使用xml方式配置也可以使用注解方式,其中注解方式最为简洁(尤其是在SpringBoot项目中),在以注解的方式中,总结了七种不同的事务传播方式以及五种事务隔离的方式,帮助大家在开发中使用事务得心应手。