首页 > 其他分享 >Spring Tx (六) (Spring事务失效的情况)

Spring Tx (六) (Spring事务失效的情况)

时间:2022-11-06 15:33:47浏览次数:55  
标签:事务 Tx Spring void Transactional public 失效 userModel class


文章目录

  • ​​1.访问权限问题​​
  • ​​2.方法被final修饰​​
  • ​​3.方法内部调用​​
  • ​​3.1.新增加一个service方法​​
  • ​​3.2.在该Service类中注入自己​​
  • ​​3.3.通过AopContent类​​
  • ​​4.未被spring管理​​
  • ​​5.多线程调用​​
  • ​​6.表不支持事务​​
  • ​​7.未开启事务​​
  • ​​8.错误的传播特性​​
  • ​​9.被异常吞并了​​
  • ​​10.手动抛了别的异常​​
  • ​​11.自定义了回滚异常​​
  • ​​12.嵌套事务回滚多了​​
  • ​​13.大事务问题​​
  • ​​14.编程式事务​​

1.访问权限问题

@Service
public class UserService {

@Transactional
private void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}

在add方法上面的权限是private,事务失效

说白了,在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}

// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}

// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}

if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}

如果权限不是public的话事务失效

2.方法被final修饰

@Service
public class UserService {

@Transactional
public final void add(UserModel userModel){
saveData(userModel);
updateData(userModel);
}
}

因为事务的底层用到的是aop,aop的动态代理,cjlib或者是jdk动态代理,如果加上final的话,那么在代理类中无法重写该方法。事务失效

如果某个方法是static的,同样无法通过动态代理,变成事务方法。

3.方法内部调用

有的时候我们需要在一个方法中调用另外一个事务方法

@Service
public class UserService {

@Autowired
private UserMapper userMapper;


public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}

@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}

因为updateStatus拥有事务的能力是因为Spring aop生成了代理对象,但是这种方法直接调用的是this的方法,所以事务失效

因此,一个同类的方法调用,会导致事务失效

解决方法

3.1.新增加一个service方法

这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。

@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;

public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}

@Servcie
public class ServiceB {

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}

}

3.2.在该Service类中注入自己

@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;

public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}

3.3.通过AopContent类

在该Service类中使用AopContext.currentProxy()获取代理对象

@Servcie
public class ServiceA {

public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}

@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}

4.未被spring管理

//@Service
public class UserService {

@Transactional
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}

5.多线程调用

@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}

@Service
public class RoleService {

@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}

这两个事务方法不在一个线程中,获取数据库不一样,是两个不同的事务。

同一个事务代表的是获取的数据库是同一个

解决方法:异步编排技术

6.表不支持事务

存储引擎如果是myisam的话就不支持事务

7.未开启事务

springboot默认是开启事务的。

不过如果是普通是ssm的话,需要在xml中配置事务开启

<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切点把事务切进去 -->
<aop:config>
<aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>

8.错误的传播特性

我们在使用@Transactional注解时,是可以指定propagation参数的。

propagation:REQUIRED ,REQUIRED_NEW, NESTED, SUPPORT, NOT_SUPPORT, NEVER, MANDATORY

@Service
public class UserService {

@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}

如果add的传播行为为NEVER的话,那么不支持事务,如果有事务的话会抛出异常

9.被异常吞并了

@Slf4j
@Service
public class UserService {

@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

没有手动抛出异常的话,那么事务不会回滚。

10.手动抛了别的异常

即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。

@Slf4j
@Service
public class UserService {

@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}

默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。

11.自定义了回滚异常

在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。

@Slf4j
@Service
public class UserService {

@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}

如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。

即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

这是为什么呢?

因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception或Throwable。

12.嵌套事务回滚多了

public class UserService {

@Autowired
private UserMapper userMapper;

@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}

@Service
public class RoleService {

@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。

因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

@Slf4j
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {

userMapper.insertUser(userModel);
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}

可以将内部嵌套事务放在try/catch中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。

13.大事务问题

通常情况下,我们会在方法上@Transactional注解,填加事务功能,比如:

@Service
public class UserService {

@Autowired
private RoleService roleService;

@Transactional
public void add(UserModel userModel) throws Exception {
query1();
query2();
query3();
roleService.save(userModel);
update(userModel);
}
}


@Service
public class RoleService {

@Autowired
private RoleService roleService;

@Transactional
public void save(UserModel userModel) throws Exception {
query4();
query5();
query6();
saveData(userModel);
}
}

上面的案例中只有

roleService.save(userModel);
update(userModel);
saveData(userModel);

需要事务

不过这样的写法会让所有的查询方法也被包含在一个事务中。
如果查询过多的话,调用层级很深,那么会耗费很多时间。

14.编程式事务

@Autowired
private TransactionTemplate transactionTemplate;

...

public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}

这种方法的好处:

  • 避免由于spring aop问题,导致事务失效的问题。
  • 能够更小粒度的控制事务的范围,更直观。


标签:事务,Tx,Spring,void,Transactional,public,失效,userModel,class
From: https://blog.51cto.com/c959c/5827271

相关文章

  • Spring Tx (五) (分布式事务及解决方案)
    文章目录​​1.2PC​​​​1.1可能会存在哪些问题​​​​2.三阶段提交(3PC)​​​​3.补偿事务(TCC)​​​​3.1TCC解决了2PC的问题​​​​4.本地消息表​​​​5.消息事......
  • springboot整合项目-商城新增收货地址功能
    新增收货地址持久层1.新增规划sql语句insertintot_adress(字段列表)values(值列表)2.一个用户的收货地址规定最多只能由20条数据对应,在插入用户数据之前先做......
  • Spring Tx (七) (大事务问题)
    大事务引发的问题:死锁,锁等待,回滚时间长接口超时,数据库主从延迟,并发情况下数据库连接池被打满1.@Transactional注解是通过Spring的AOP起作用的,但是如果使用不当,事务......
  • Springboot2.x 结合 redis 实现ip请求次数限制
    参考https://cloud.tencent.com/developer/article/1607647SpringBoot整合Redis代码详解,四步搞定!https://blog.csdn.net/jinyangbest/article/details/98205802sprin......
  • SpringBoot 自动装配原理
    SpingBoot如何实现自动配置​​SpringBoot​​都需要创建一个​​mian​​启动类,而启动类都含有​​@SpringBootApplication​​注解,从启动类,一步步探索源码。@SpringBoo......
  • Spring三级缓存解决循环依赖
    前提知识1、解决循环依赖的核心依据:实例化和初始化步骤是分开执行的2、实现方式:三级缓存3、lambda表达式的延迟执行特性spring源码执行逻辑核心方法refresh(), populateBea......
  • Spring Boot 中使用 Swagger
    前后端分离开发,后端需要编写接⼝说明⽂档,会耗费⽐较多的时间。swagger是⼀个⽤于⽣成服务器接⼝的规范性⽂档,并且能够对接⼝进⾏测试的⼯具。作用⽣成接⼝说明⽂档对接⼝......
  • Springcloud之服务注册中心-----Eureka开发步骤
    一、Eureka(netflix开发的)A.开发服务端server1.创建项目并引入依赖<!--sprinngboot-web--><dependency><groupId>......
  • SpringBoot 自动装配原理
    早期的Spring项目需要添加需要配置繁琐的xml,比如MVC、事务、数据库连接等繁琐的配置。SpringBoot的出现就无需这些繁琐的配置,因为SpringBoot基于约定大于配置的理念,在......
  • 600002 TXT 建筑图纸代号
    常用结构构件代号序号名称代号1板B2层面板WB3空心板KB4槽形板CB5折板ZB6密肋板MB7楼梯板TB8盖板或沟盖板GB9档雨板......