spring事务失效的几种情况与原因
1.service没有托管给spring
代码语言:javascript复制public class TranInvalidCaseWithoutInjectSpring {
private UserService userService;
public TranInvalidCaseWithoutInjectSpring(UserService userService) {
this.userService = userService;
}
@Transactional
public boolean add(User user){
boolean isSuccess = userService.save(user);
int i = 1 % 0;
return isSuccess;
}
}
代码语言:javascript复制@Test
public void testServiceWithoutInjectSpring(){
boolean randomBoolean = new Random().nextBoolean();
TranInvalidCaseWithoutInjectSpring tranInvalidCaseWithoutInjectSpring;
if(randomBoolean){
tranInvalidCaseWithoutInjectSpring = applicationContext.getBean(TranInvalidCaseWithoutInjectSpring.class);
System.out.println("service已经被spring托管");
}else{
tranInvalidCaseWithoutInjectSpring = new TranInvalidCaseWithoutInjectSpring(userService);
System.out.println("service没被spring托管");
}
boolean isSuccess = tranInvalidCaseWithoutInjectSpring.add(user);
Assert.assertTrue(isSuccess);
}
失效原因: spring事务生效的前提是,service必须是一个bean对象
解决方案: 将service注入spring
2.抛出受检异常
代码语言:javascript复制@Service
public class TranInvalidCaseByThrowCheckException {
@Autowired
private UserService userService;
@Transactional
public boolean add(User user) throws FileNotFoundException {
boolean isSuccess = userService.save(user);
new FileInputStream("1.txt");
return isSuccess;
}
}
代码语言:javascript复制 @Test
public void testThrowCheckException() throws Exception{
boolean randomBoolean = new Random().nextBoolean();
boolean isSuccess = false;
TranInvalidCaseByThrowCheckException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseByThrowCheckException.class);
if(randomBoolean){
System.out.println("配置@Transactional(rollbackFor = Exception.class)");
isSuccess = tranInvalidCaseByThrowCheckException.save(user);
}else{
System.out.println("配置@Transactional");
tranInvalidCaseByThrowCheckException.add(user);
}
Assert.assertTrue(isSuccess);
}
失效原因: spring默认只会回滚非检查异常和error异常
解决方案: 配置rollbackFor
3.业务自己捕获了异常
代码语言:javascript复制 @Transactional
public boolean add(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
}
return isSuccess;
}
代码语言:javascript复制 @Test
public void testCatchExecption() throws Exception{
boolean randomBoolean = new Random().nextBoolean();
boolean isSuccess = false;
TranInvalidCaseWithCatchException tranInvalidCaseByThrowCheckException = applicationContext.getBean(TranInvalidCaseWithCatchException.class);
if(randomBoolean){
randomBoolean = new Random().nextBoolean();
if(randomBoolean){
System.out.println("将异常原样抛出");
tranInvalidCaseByThrowCheckException.save(user);
}else{
System.out.println("设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();");
tranInvalidCaseByThrowCheckException.addWithRollBack(user);
}
}else{
System.out.println("业务自己捕获了异常");
tranInvalidCaseByThrowCheckException.add(user);
}
Assert.assertTrue(isSuccess);
}
失效原因: spring事务只有捕捉到了业务抛出去的异常,才能进行后续的处理,如果业务自己捕获了异常,则事务无法感知。
解决方案: 1、将异常原样抛出; 2、设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
4.切面顺序导致
代码语言:javascript复制@Service
public class TranInvalidCaseWithAopSort {
@Autowired
private UserService userService;
@Transactional
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
}
代码语言:javascript复制@Aspect
@Component
@Slf4j
public class AopAspect {
@Around(value = " execution (* com.github.lybgeek.transcase.aopsort..*.*(..))")
public Object around(ProceedingJoinPoint pjp){
try {
System.out.println("这是一个切面");
return pjp.proceed();
} catch (Throwable throwable) {
log.error("{}",throwable);
}
return null;
}
}
失效原因: spring事务切面的优先级顺序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异常,则会同业务自己捕获异常的那种场景一样
解决方案: 1、在切面中将异常原样抛出; 2、在切面中设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
5.非public方法
代码语言:javascript复制@Service
public class TranInvalidCaseWithAccessPerm {
@Autowired
private UserService userService;
@Transactional
protected boolean save(User user){
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
}
代码语言:javascript复制public class TranInvalidCaseWithAccessPermTest {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class);
TranInvalidCaseWithAccessPerm tranInvalidCaseWithAccessPerm = context.getBean(TranInvalidCaseWithAccessPerm.class);
boolean isSuccess = tranInvalidCaseWithAccessPerm.save(UserUtils.getUser());
System.out.println(isSuccess);
}
}
失效原因: spring事务默认生效的方法权限都必须为public
解决方案:
1、将方法改为public;
2、修改TansactionAttributeSource,将publicMethodsOnly改为false
6.父子容器
失效原因: 子容器扫描范围过大,将未加事务配置的serivce扫描进来
解决方案: 1、父子容器个扫个的范围; 2、不用父子容器,所有bean都交给同一容器管理
注: 因为示例是使用springboot,而springboot启动默认没有父子容器,只有一个容器,因此就该场景就演示示例了
7.方法用final修饰
代码语言:javascript复制 @Transactional
public final boolean add(User user, UserService userService) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,植入事务功能
解决方案: 1、方法不要用final修饰
8.方法用static修饰
代码语言:javascript复制 @Transactional
public static boolean save(User user, UserService userService) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 原因和final一样
解决方案: 1、方法不要用static修饰
9.调用本类方法
代码语言:javascript复制 public boolean save(User user) {
return this.saveUser(user);
}
@Transactional
public boolean saveUser(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 本类方法不经过代理,无法进行增强
解决方案: 1、注入自己来调用; 2、使用@EnableAspectJAutoProxy(exposeProxy = true) AopContext.currentProxy()
10.多线程调用
代码语言:javascript复制 @Transactional(rollbackFor = Exception.class)
public boolean save(User user) throws ExecutionException, InterruptedException {
Future<Boolean> future = executorService.submit(() -> {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new Exception();
}
return isSuccess;
});
return future.get();
}
失效原因: 因为spring的事务是通过数据库连接来实现,而数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务
11.错误的传播行为
代码语言:javascript复制 @Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
失效原因: 使用的传播特性不支持事务
12.使用了不支持事务的存储引擎
失效原因: 使用了不支持事务的存储引擎。比如mysql中的MyISAM
13.数据源没有配置事务管理器
注: 因为springboot,他默认已经开启事务管理器。org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration。因此示例略过
14.被代理的类过早实例化
代码语言:javascript复制@Service
public class TranInvalidCaseInstantiatedTooEarly implements BeanPostProcessor , Ordered {
@Autowired
private UserService userService;
@Transactional
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 % 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}
@Override
public int getOrder() {
return 1;
}
}
失效原因: 当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器增强
总结
本文列举了14种spring事务失效的场景,其实这14种里面有很多都是归根结底都是属于同一类问题引起,比如因为动态代理原因、方法限定符原因、异常类型原因等