文章目录
- 警觉 @Transactional 的坑
-
- 标记了 @Transactional 的 private 办法——不失效
- 本类中的办法调用本类中 @Transactional 办法——不失效
- 代码中手动解决了异样——不失效
- 即便抛出异样,也有可能不失效
写在后面
Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API (JPA) 等事务 API,实现了统一的编程模型,而 Spring 的申明式事务性能更是提供了极其不便的事务配置形式,配合 Spring Boot 的主动配置,大多数 Spring Boot 我的项目只须要在办法上标记 @Transactional 注解,即可一键开启办法的事务性配置。
在应用 @Transactional 注解开启申明式事务时,第一个最容易疏忽的问题是,很可能事务并没有失效。
而应用 @Transactional 注解开启申明式事务时,不光须要留神其有没有失效,其余的坑也十分多,在这里列举一下以示警觉。
警觉 @Transactional 的坑
标记了 @Transactional 的 private 办法——不失效
Controller 类调用 UserService 的 createUserPrivate 办法时,因为 createUserPrivate 办法时 private 的,即便加上了 @Transactional,申明式事务也不会失效。
除非非凡配置(比方应用 AspectJ 动态织入实现 AOP),否则只有定义在 public 办法上的 @Transactional 能力失效。
起因是,Spring 默认通过动静代理的形式实现 AOP,对指标办法进行加强,private 办法无奈代理到,Spring 天然也无奈动静加强事务处理逻辑。
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
// 标记了 @Transactional 的 private 办法
@Transactional
private void createUserPrivate(UserEntity entity) {userRepository.save(entity);
if (entity.getName().contains("test"))
throw new RuntimeException("invalid username!");
}
}
本类中的办法调用本类中 @Transactional 办法——不失效
必须通过代理过的类从内部调用指标办法能力失效。
也就是说,只有除了标注了 @Transactional 办法存在的类 的其余类,调用该办法,申明式事务才会失效。
public int createUserWrong2(String name) {this.createUserPublic(new UserEntity(name));
return userRepository.findByName(name).size();}
// 标记了 @Transactional 的 public 办法
@Transactional
public void createUserPublic(UserEntity entity) {userRepository.save(entity);
if (entity.getName().contains("test"))
throw new RuntimeException("invalid username!");
}
然而!以下本人调用本人的办法是能够失效的,然而不合乎分层标准:
本身注入一个本身,而后调用本身的类,理论开发最好不要这样搞。。
以下才是规范的调用形式:
// Controller 中
@GetMapping("right")
public int right2(@RequestParam("name") String name) {
try {userService.createUserPublic(new UserEntity(name));
} catch (Exception ex) {log.error("create user failed because {}", ex.getMessage());
}
return userService.getUserCount(name);
}
// Service 中
@Transactional
public void createUserPublic(UserEntity entity) {userRepository.save(entity);
if (entity.getName().contains("test"))
throw new RuntimeException("invalid username!");
}
咱们再通过一张图来回顾下 this 自调用、通过 self 调用,以及在 Controller 中调用 UserService 三种实现的区别:
通过 this 自调用,没有机会走到 Spring 的代理类;后两种改良计划调用的是 Spring 注入的 UserService,通过代理调用才有机会对 createUserPublic 办法进行动静加强。
代码中手动解决了异样——不失效
只有异样流传出了标记了 @Transactional 注解的办法,事务能力回滚。在 Spring 的 TransactionAspectSupport 里有个 invokeWithinTransaction 办法,外面就是处理事务的逻辑。能够看到,只有捕捉到异样能力进行后续事务处理,以下是 spring 源码:
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {cleanupTransactionInfo(txInfo);
}
手动解决了异样,并没有抛出,以下代码中申明式事务是不会失效的:
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public int createUserWrong1(String name) {
try {this.createUserPrivate(new UserEntity(name));
throw new RuntimeException("error");
} catch (Exception ex) {log.error("create user failed because {}", ex.getMessage());
}
}
}
解决方案:手动回滚
加上 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@Transactional public void createUserRight1(String name) {
try {userRepository.save(new UserEntity(name));
throw new RuntimeException("error");
} catch (Exception ex) {log.error("create user failed", ex);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
即便抛出异样,也有可能不失效
默认状况下,呈现 RuntimeException(非受检异样)或 Error 的时候,Spring 才会回滚事务。
关上 Spring 的 DefaultTransactionAttribute 类能看到如下代码块,能够发现相干证据,通过正文也能看到 Spring 这么做的起因,大略的意思是受检异样个别是业务异样,或者说是相似另一种办法的返回值,呈现这样的异样可能业务还能实现,所以不会被动回滚;而 Error 或 RuntimeException 代表了非预期的后果,应该回滚:
/**
* The default behavior is as with EJB: rollback on unchecked exception
* ({@link RuntimeException}), assuming an unexpected outcome outside of any
* business rules. Additionally, we also attempt to rollback on {@link Error} which
* is clearly an unexpected outcome as well. By contrast, a checked exception is
* considered a business exception and therefore a regular expected outcome of the
* transactional business method, i.e. a kind of alternative return value which
* still allows for regular completion of resource operations.
* <p>This is largely consistent with TransactionTemplate's default behavior,
* except that TransactionTemplate also rolls back on undeclared checked exceptions
* (a corner case). For declarative transactions, we expect checked exceptions to be
* intentionally declared as business exceptions, leading to a commit by default.
* @see org.springframework.transaction.support.TransactionTemplate#execute
*/
@Override
public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}
以下代码中事务是不会失效的:
@Service
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
// 即便出了受检异样也无奈让事务回滚
@Transactional
public void createUserWrong2(String name) throws IOException {userRepository.save(new UserEntity(name));
otherTask();}
// 因为文件不存在,肯定会抛出一个 IOException
private void otherTask() throws IOException {Files.readAllLines(Paths.get("file-that-not-exist"));
}
}
解决方案,在注解中申明,冀望遇到所有的 Exception 都回滚事务(来冲破默认不回滚受检异样的限度):
加上 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来笼罩其默认设置。
@Transactional(rollbackFor = Exception.class)
public void createUserRight2(String name) throws IOException {userRepository.save(new UserEntity(name));
otherTask();}