本文主要是介绍【业务功能篇138】SpringBoot中的事务传播属性运用REQUIRED、REQUIRES_NEW、NESTED 只读属性运用readOnly,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
3.事务的传播属性
@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
日常开发中基本只会使用到REQUIRED(1),REQUIRES_NEW(4),NESTED(7)三种。
NESTED和REQUIRES_NEW是有区别的。NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而REQUIRES_NEW则可以拥有自己独立的隔离级别和锁等特性。
使用REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时,外部事务将继续执行。两个事务互不影响,两个事务不是一个真正的嵌套事务,同时它还需要JTA事务管理器的支持。
使用NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。嵌套事务开始执行时, 它将取得一个 savepoint,如果这个嵌套事务失败, 将回滚到此savepoint。潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
NESTED的实现主要依赖于数据库的保存点(SAVEPOINT)技术,SAVEPOINT记录了一个保存点,可以通过ROLLBACK TO SAVEPOINT来回滚到某个保存点。如果数据库支持保存点技术时就启用保存点技术;如果不支持就会新建一个事务去执行代码,也就相当于REQUIRES_NEW。
@Transactional注解支持9个属性的设置,其中使用较多的三个属性:readOnly、propagation、isolation。其中propagation属性用来枚举事务的传播行为,isolation用来设置事务隔离级别,readOnly进行读写事务控制。
最容易弄混淆的其实是PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全
commited 或 rolled back 而不依赖于外部事务,它拥有自己的隔离范围, 自己的锁, 等等.
当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
PROPAGATION_REQUIRES_NEW常用于日志记录,或者交易失败仍需要留痕
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正
的子事务. 潜套事务开始执行时, 它将取得一个 savepoint.
如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分,
只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于,
PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED
则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit,
这个规则同样适用于 roll back.
几个例子理解REQUIRED、REQUIRES_NEW、NESTED 的使用注意事项(TRY…CATCH配合使用)
1、REQUIRED的使用注意项
1.1 REQUIRED保证其处理过程同一个事务,如果调用的同一个类的配置的REQUIRED的方法,且此方法存在TRY CATCH 代码块, 如果此代码块出现异常,程序可以继续执行。
1.2 但如果调用的其他类的配置REQUIRED方法,且TRY CATCH住,则全部的提交全部回滚,且报出异常: Transaction rolled back because it has been marked as rollback-only
因为事务报出异常后要全部回滚,包括父类的调用。1.3 如果service中包含多个dao的方法,其都属于同一个事务,其中报错全部回滚,try catch住不影响程序代码的继续执行.
class A{//PROPAGATION_REQUIREDvoid methodA() {try{methodB(); //可以继续执行,因为是同一个类}catch(Exception ex){ex.printStrace();}try{methodC(); //报错Transaction rolled back because it has been marked as rollback-only//因为回滚整个事务,不能用try catch住.当然通过不会try catch一个事务的部分代码}catch(Exception ex){ex.printStrace();}}//PROPAGATION_REQUIREDvoid methodB() {}
}class B{//PROPAGATION_REQUIREDvoid methodC() {}
}2、NESTED的具体用途如下:
在此方法出现异常时,通过TRY CATCH 代码块包含住, 继续往下执行或者执行CATCH中的处理.
此点REUQIRED做不到, REQUIRED_NEW能做到, 但它是单独的事务,不与父类一块提交的。ServiceA {
/*** 事务属性配置为 PROPAGATION_REQUIRED*/void methodA() {try {//savepointServiceB.methodB(); //PROPAGATION_NESTED 级别} catch (SomeException) {// 执行其他业务, 如 ServiceC.methodC();
}}}
Spring中的7个事务传播行为:
事务行为 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,假设当前没有事务。就新建一个事务 |
PROPAGATION_SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
举例说明
ServiceA
ServiceA { void methodA() {ServiceB.methodB();}
}
ServiceB
ServiceB { void methodB() {}
}
1.PROPAGATION_REQUIRED
假如当前正要运行的事务不在另外一个事务里,那么就起一个新的事务 比方说,ServiceB.methodB的事务级别定义PROPAGATION_REQUIRED, 那么因为执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。这时调用ServiceB.methodB,ServiceB.methodB看到自己已经执行在ServiceA.methodA的事务内部。就不再起新的事务。而假如ServiceA.methodA执行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的不论什么地方出现异常。事务都会被回滚。即使ServiceB.methodB的事务已经被提交,可是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2.PROPAGATION_SUPPORTS
假设当前在事务中。即以事务的形式执行。假设当前不在一个事务中,那么就以非事务的形式执行
3.PROPAGATION_MANDATORY
必须在一个事务中执行。也就是说,他仅仅能被一个父事务调用。否则,他就要抛出异常
4.PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比方我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW。那么当运行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起。ServiceB.methodB会起一个新的事务。等待ServiceB.methodB的事务完毕以后,他才继续运行。
他与PROPAGATION_REQUIRED 的事务差别在于事务的回滚程度了。由于ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。假设ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚。ServiceB.methodB是不会回滚的。假设ServiceB.methodB失败回滚,假设他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5.PROPAGATION_NOT_SUPPORTED
当前不支持事务。比方ServiceA.methodA的事务级别是PROPAGATION_REQUIRED 。而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时。ServiceA.methodA的事务挂起。而他以非事务的状态执行完,再继续ServiceA.methodA的事务。
6.PROPAGATION_NEVER
不能在事务中执行。
如果ServiceA.methodA的事务级别是PROPAGATION_REQUIRED。 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。
7.PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
事务只读属性
在 Spring Boot 中,事务只读属性是指一个事务是否只读,即只能读取数据而不能修改数据。在只读事务中,如果尝试修改数据,则会抛出异常。只读事务可以提高事务的并发性能,因为在只读事务中,数据库不需要进行锁定,从而提高了并发度。
在 Spring Boot 中,只读事务是通过 @Transactional 注解的 readOnly 属性来实现的。如果将 readOnly 属性设置为 true,则表示该事务是只读事务,否则为读写事务。下面是一个使用 @Transactional 注解的例子:
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactional(readOnly = true)public User getUserById(Long userId) {return userRepository.findById(userId).orElse(null);}@Transactionalpublic void saveUser(User user) {userRepository.save(user);}}
在上面的例子中,getUserById() 方法是一个只读方法,因此将 readOnly 属性设置为 true。而 saveUser() 方法是一个写方法,因此不需要设置 readOnly 属性。
只读事务的原理
只读事务的实现原理与普通事务的实现原理类似,都是通过 AOP 机制来实现的。在 Spring Boot 中,只读事务的实现原理如下:
当一个方法被标记为只读事务时,Spring Boot 会创建一个新的只读事务,并将其绑定到当前线程上。
当方法执行完成后,Spring Boot 会提交或回滚事务,然后将事务与当前线程解绑,从而释放资源。
在只读事务中,因为不需要进行锁定操作,所以可以提高事务的并发性能。此外,只读事务还可以用于一些特殊场景,例如在数据库主从复制时,可以将只读操作发送到从数据库中执行,从而分担主数据库的压力。
如何使用只读事务
在 Spring Boot 中,只需要在方法上加上 @Transactional(readOnly = true) 注解即可将该方法设置为只读事务。下面是一个例子:
@Service
public class UserService {@Autowiredprivate UserRepository userRepository;@Transactional(readOnly = true)public User getUserById(Long userId) {return userRepository.findById(userId).orElse(null);}}
在上面的例子中,getUserById() 方法是一个只读方法,因此将 readOnly 属性设置为 true。
需要注意的是,只读事务只适用于读取数据的场景,如果需要修改数据,则需要使用读写事务。此外,只读事务不能保证数据的一致性,因为在只读事务中,数据可能已经被其他事务修改了,因此在使用只读事务时需要注意这一点。
结论
在 Spring Boot 中,只读事务是一种特殊的事务,它可以提高事务的并发性能。只读事务是通过 @Transactional 注解的 readOnly 属性来实现的。只读事务的实现原理与普通事务的实现原理类似,都是通过 AOP 机制来实现的。只需要在方法上加上 @Transactional(readOnly = true) 注解即可将该方法设置为只读事务。但需要注意的是,只读事务只适用于读取数据的场景,如果需要修改数据,则需要使用读写事务。此外,只读事务不能保证数据的一致性,因为在只读事务中,数据可能已经被其他事务修改了,因此在使用只读事务时需要注意这一点。
除了在方法上加上 @Transactional(readOnly = true) 注解之外,还可以在类上加上 @Transactional(readOnly = true) 注解,这样该类中所有的方法都将被设置为只读事务。下面是一个例子:
@Service
@Transactional(readOnly = true)
public class UserService {@Autowiredprivate UserRepository userRepository;public User getUserById(Long userId) {return userRepository.findById(userId).orElse(null);}public List<User> getAllUsers() {return userRepository.findAll();}@Transactionalpublic void saveUser(User user) {userRepository.save(user);}}
在上面的例子中,getUserById() 和 getAllUsers() 方法都是只读方法,因为在类上加上了 @Transactional(readOnly = true) 注解,而 saveUser() 方法是一个写方法,因此需要使用读写事务。
需要注意的是,在使用只读事务时,需要确保数据的一致性。如果在只读事务中读取了数据,然后在另一个事务中修改了该数据,那么在只读事务中再次读取该数据时,将会看到修改后的数据。因此,在使用只读事务时,需要根据实际情况来决定是否使用只读事务,并且需要对数据的一致性进行仔细的考虑。
总结
在本文中,我们介绍了 Spring Boot 中的事务只读属性是什么,原理以及如何使用。只读事务可以提高事务的并发性能,在读取数据时非常有用,但需要注意数据一致性的问题。只需要在方法上加上 @Transactional(readOnly = true) 注解即可将该方法设置为只读事务,也可以在类上加上 @Transactional(readOnly = true) 注解,将该类中所有的方法都设置为只读事务。在使用只读事务时,需要根据实际情况来决定是否使用只读事务,并且需要对数据的一致性进行仔细的考虑。
原文链接:
https://blog.csdn.net/albert_xjf/article/details/131434383
https://blog.csdn.net/z69183787/article/details/76208998
这篇关于【业务功能篇138】SpringBoot中的事务传播属性运用REQUIRED、REQUIRES_NEW、NESTED 只读属性运用readOnly的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!