事务控制
在后端开发中,控制事务 是一个很常见也很重要的点。
因为我们经常会遇到多个操作的集合 需要帮他们封装成一个原子性的操作。这也就是事务的核心意义。
spring事务的原理:AOP
(不懂AOP的去看篇:理解AOP)
我们知道AOP的原理是什么:就是创建一个代理对象 来包裹原有的对象。 然后对原有对象进行增强。
所以我们的要一个方法进行事务增强。
道理是一样的 就是创建一个代理对象 在这个代理对象里面 对原方法进行事务的控制。
具体怎么做呢 我们展示一下 比较常见的注解 来实现
声明式事务
@Transactional
public void doTransactionalOperation() {
// 事务操作的代码
}
这个注解有四个属性 很重要:
隔离级别(Isolation)
:用于控制事务之间的隔离程度,即一个事务的操作对其他事务的可见性和影响。常见的隔离级别有:
READ_UNCOMMITTED(读取未提交):最低的隔离级别,允许事务读取未提交的数据,可能会导致脏读(Dirty Read)。
READ_COMMITTED(读取已提交):允许事务读取已提交的数据,避免脏读,但可能会导致不可重复读(Non-repeatable Read)。
REPEATABLE_READ(可重复读):确保在事务执行期间多次读取同一数据时,数据保持一致,避免不可重复读,但可能会导致幻读(Phantom Read)。
SERIALIZABLE(串行化):最高的隔离级别,强制事务串行执行,避免脏读、不可重复读和幻读,但性能较低。
传播行为(Propagation)
:用于控制事务的传播方式,即事务方法调用其他事务方法时的行为。常见的传播行为有:
REQUIRED(默认值):如果当前存在事务,就加入到当前事务中,如果不存在事务,则创建一个新的事务。
REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则挂起当前事务。
SUPPORTS:如果当前存在事务,就加入到当前事务中,如果不存在事务,则以非事务方式执行。
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务。
MANDATORY:必须在一个已存在的事务中执行,否则抛出异常。
NEVER:必须在一个非事务环境中执行,否则抛出异常。
只读属性(ReadOnly)
:用于指定事务是否为只读事务。如果设置为只读事务,事务期间不允许对数据进行修改操作,可以提升一定的性能。
超时时间(Timeout)
:用于指定事务的超时时间,即事务在指定的时间内必须完成,否则将被强制回滚。超时时间一般以秒为单位。
其他三个都好理解 我们重点说一下 这个传播行为:
新手第一次接触可能会绕住,这一篇中先省略,后面专门写一篇:
【spring核心】Spring中的事务传播
来细讲
事务失效
什么情况下事务会失效?? 我总结一下比较常见的四种场景
- @Transactional 修饰私有方法的时候 可能会失效 (注意这里时可能)
JDK动态代理只能代理实现了接口的类的公共方法,无法代理私有方法。因此,当我们在私有方法上添加@Transactional注解时,由于动态代理的限制,事务注解不会被织入到代理对象中,导致事务管理失效。 - 只有代理对象 调用才会生效
这个是我们平常遇到的最多的情况
注意:
如果在同一个类中的方法调用,且这些方法都在同一个类的实例上调用,而且这些方法都没有被 @Transactional 或者类级别的 @Transactional 注解修饰,那么 Spring 事务将不会生效。
Spring 事务是通过 AOP(面向切面编程)来实现的。通常情况下,Spring 在对带有 @Transactional 注解的方法进行代理时,会在方法调用前开启事务,在方法调用后根据方法的执行情况来决定提交或回滚事务。
但是,如果在同一个类的方法调用中,事务代理并不会生效,这是因为 Spring 默认情况下使用的是基于代理的 AOP,它是通过 JDK 动态代理或者 CGLIB 来生成代理对象的。对于同一个类的方法调用,Spring 事务只会在代理对象中生效,而不会作用在同一个类的实例对象上。
什么意思呢 你通过bean注入 从容器里面取出来的对象 调用它的方法 才会生效,
如果知识一个类内部的方法 比如下面的m1 就是一个单纯的内部方法,用它去调用是不会生效的。
@Component
public class UserService {
public void m1(){
this.m2();
}
@Transactional
public void m2(){
//执行db操作
}
如果没有获取UserService 的bean 直接外部调用m2方法 它时不会生效的。
大家在写代码 处理事务的时候 要千万注意这一点 当你的一个方法上面加了事务注解 这时候 你要留意调用这个方法的 调用者是实例对象还是代理对象
- 异常类型错误
并不是任何异常情况下,spring都会回滚事务,默认情况下,RuntimeException和Error的情况下,spring事务才会回滚。
也可以自定义回滚的异常类型
- 必须在同一个线程里面
这点也要注意 ,如果你在异常方法里面 新new了一个线程,
那么事务也会失效