🌴简单了解Spring事务
🍉一、什么是事务,有什么作用?
事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作。在数据库管理系统中,事务是对数据库执行的一组操作的逻辑单元,它被视为一个独立的工作单元,要么完全执行,要么完全不执行。事务是确保数据库数据的一致性、完整性和持久性的重要机制之一。
事务具有以下四个关键属性,通常被称为ACID属性:
-
原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败。如果在事务执行期间发生了错误,所有的更改将被回滚,数据库状态将被还原到事务开始之前的状态。
-
一致性(Consistency):事务执行后,数据库的状态必须满足所有的约束和规则,保证数据的一致性。这意味着事务执行的结果必须是有效的,不能违反数据库的完整性约束。
-
隔离性(Isolation):并发执行的多个事务之间应该相互隔离,使得每个事务感觉到它是在系统中独立执行的,即使在实际执行中,多个事务同时运行。
-
持久性(Durability):一旦事务成功完成并提交,其结果应该是永久性的,即使系统发生故障,数据库也应该能够恢复到事务成功完成后的状态。
事务的使用可以确保数据库操作的一致性和可靠性。例如,在进行银行转账时,必须保证从一个账户扣款和向另一个账户存款是原子操作,即使在系统发生故障时也不会出现资金损失或数据不一致的情况。因此,事务是数据库管理系统中的重要概念,用于确保数据的完整性和可靠性。
本文讲的是Spring事务,Spring 只提供统一事务管理接口,实现都是数据库自己去实现提交、回滚。
🥝二、事务的种类
现在都用声明式事务,编程式事务停留在大学教学使用仅做了解吧
编程式事务
编程式事务管理是指通过编写代码显式地管理事务的开启、提交、回滚和关闭等操作。下面是 Spring 中编程式事务管理的基本步骤:
- 获取事务管理器(PlatformTransactionManager):首先,需要获取一个适当的事务管理器实例,该实例负责管理事务的生命周期。Spring 提供了各种类型的事务管理器,如 DataSourceTransactionManager、JpaTransactionManager 等,具体选择取决于你使用的持久化技术。
- 开启事务:在执行需要进行事务管理的代码之前,通过调用事务管理器的方法来开启一个事务。
- 执行事务操作:在事务开启之后,执行需要在同一个事务中执行的数据库操作。这些操作可以是针对数据库的增删改查等操作。
- 提交或回滚事务:根据业务逻辑的执行结果,决定是提交事务还是回滚事务。如果操作成功,调用事务管理器的提交方法提交事务;如果发生异常或操作失败,调用事务管理器的回滚方法回滚事务。
- 关闭事务:当事务执行完成后,需要关闭事务。通常,Spring 会在执行完提交或回滚操作后自动关闭事务,但也可以显式地调用事务管理器的关闭方法来手动关闭事务。
下面是一个简单的示例,演示了如何在 Spring 中使用编程式事务管理:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private UserRepository userRepository;
public void transferMoney(int fromUserId, int toUserId, double amount) {
TransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(definition);
try {
User fromUser = userRepository.findById(fromUserId);
User toUser = userRepository.findById(toUserId);
if (fromUser.getBalance() >= amount) {
fromUser.setBalance(fromUser.getBalance() - amount);
toUser.setBalance(toUser.getBalance() + amount);
userRepository.save(fromUser);
userRepository.save(toUser);
transactionManager.commit(status);
} else {
throw new InsufficientBalanceException("Insufficient balance");
}
} catch (Exception e) {
transactionManager.rollback(status);
throw new TransactionFailedException("Transaction failed", e);
}
}
}
声明式事务
通过@Transactional
注解即可,spring帮你管理
@Service
public class UserService {
@Transactional
public void transferMoney(int fromUserId, int toUserId, double amount) {
...
}
}
后文的隔离级别、传播机制都可以通过这个注解指定
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
🍅三、事务的隔离级别
事务的隔离级别是指多个事务并发执行时,各个事务之间的相互影响程度的规定。数据库管理系统需要考虑并发执行时可能产生的数据一致性问题,而隔离级别就是为了解决这些问题而定义的。
常见的数据库隔离级别有四种:
-
读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务尚未提交的数据。这种隔离级别可能导致脏读(Dirty Read),即一个事务读取到了另一个事务未提交的数据。
-
读已提交(Read Committed):确保一个事务只能读取到已提交的数据。这种隔离级别可以避免脏读,但仍然可能出现不可重复读(Non-Repeatable Read)问题,即一个事务在多次读取同一数据时,由于其他事务的更新操作,可能读取到了不同的值。
-
可重复读(Repeatable Read):确保一个事务在多次读取同一数据时,所读取到的值是一致的。在该隔离级别下,事务在读取数据时会对所涉及的数据加锁,防止其他事务修改数据,因此可以避免不可重复读问题。但仍然可能出现幻读(Phantom Read)问题,即一个事务在读取数据时,由于其他事务的插入或删除操作,可能读取到了新增或删除的数据。
-
串行化(Serializable):最高的隔离级别,确保每个事务串行执行,相当于对所有的数据操作加上了锁。这种隔离级别可以避免所有的并发问题,包括脏读、不可重复读和幻读,但性能通常会受到影响。
不同的隔离级别在提供数据一致性和并发性方面有不同的权衡。通常来说,隔离级别越高,数据的一致性就越好,但并发性就越差,因为需要更多的加锁操作来保证数据的一致性。在选择隔离级别时,需要根据具体的业务需求和系统性能进行权衡取舍。在大多数情况下,使用默认的隔离级别(通常是读已提交)即可满足需求。
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
spring的隔离级别枚举类里面多了一个DEFAULT,通常用默认的就行了。
🍊四、事务的传播机制
事务的传播机制是指在多个事务方法相互调用的情况下,各个方法之间事务如何传播和交互的规则。Spring 提供了丰富的事务传播行为,以满足不同业务场景下的需求。
以下是 Spring 提供的事务传播行为:
-
PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为,适用于大多数业务场景。
-
PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,则以非事务方式执行。
-
PROPAGATION_MANDATORY:强制要求当前存在事务,如果当前没有事务,则抛出异常。
-
PROPAGATION_REQUIRES_NEW:创建一个新的事务,并且暂停当前事务(如果存在)。如果当前存在事务,则新事务将在它结束后恢复。
-
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则暂停事务。
-
PROPAGATION_NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
-
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则执行与 REQUIRED 类似的操作。但是,如果嵌套事务内部发生异常,只会回滚嵌套事务,而不会回滚外部事务。
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
🥑五、事务不生效的清况
- spring事务拦截器,aop只会获取public修饰的方法上的@transactional注解信息
- 调用其他类方法才会走代理类
- 不合适的传播机制
- 抛出的不是rollbackFor设置的指定异常
针对第三种情况(传播机制设置错误),补充说明,Spring事务如果要问,绕不开这个:
[ZxFkService]
public void noTransactional() {
ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
insert(build);
zxSxService.MethodOfOtherClass();
}
[ZxSxService]
@Transactional
public void MethodOfOtherClass() {
ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
zxsxMapper.insert(build);
if (build.getBh().equals("001")) {
throw new RuntimeException("error");
}
}
正确答案:fk记录不回滚,sx回滚
[ZxFkService]
@Transactional
public void noTransactional() {
ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
insert(build);
zxSxService.MethodOfOtherClass();
}
[ZxSxService]
public void MethodOfOtherClass() {
ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
zxsxMapper.insert(build);
if (build.getBh().equals("001")) {
throw new RuntimeException("error");
}
}
正确答案:都回滚
[ZxFkService]
@Transactional
public void noTransactional() {
ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
insert(build);
zxSxService.MethodOfOtherClass();
}
[ZxSxService]
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodOfOtherClass() {
ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
zxsxMapper.insert(build);
if (build.getBh().equals("001")) {
throw new RuntimeException("error");
}
}
正确答案:都回滚,看似是两个事务,一个主事务,一个子事务,但是没有捕获异常,就会发生回滚.
[ZxFkService]
@Transactional
public void noTransactional() {
ZxFk build = ZxFk.builder().bh("001").ajBh("001").build();
insert(build);
try {
zxSxService.MethodOfOtherClass();
} catch (Exception e) {
// 防止抛出异常主事务回滚
}
}
[ZxSxService]
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void MethodOfOtherClass() {
ZxSx build = ZxSx.builder().bh("001").ajBh("001").build();
zxsxMapper.insert(build);
if (build.getBh().equals("001")) {
throw new RuntimeException("error");
}
}
正确答案:主事务不会回滚,子事务回滚
🌵六、事务的实现原理
Spring 的声明式事务是通过 AOP和代理机制实现
第一步,在 Bean 初始化阶段创建代理对象:
Spring 容器在初始化单例 Bean 的时候,会遍历所有的 BeanPostProcessor 实现类,执行postProcessAfterInitialization 方法。
在执行 postProcessAfterInitialization 方法时会遍历容器中所有的切面,查找与当前 Bean 匹配的切面,这里会获取事务的属性切面,也就是 @Transactional
注解及其属性值。
然后根据得到的切面创建一个代理对象,默认使用 JDK 动态代理创建代理,如果目标类是接口,则使用 JDK 动态代理,否则使用 Cglib。
第二步,在执行目标方法时进行事务增强操作:
当通过代理对象调用 Bean 方法的时候,会触发对应的 AOP 增强拦截器,声明式事务是一种环绕增强,对应接口为MethodInterceptor
,事务增强对该接口的实现为TransactionInterceptor
事务拦截器TransactionInterceptor
在invoke
方法中,通过调用父类TransactionAspectSupport
的invokeWithinTransaction
方法进行事务处理,包括开启事务、事务提交、异常回滚等。