如何避免掉进spring事务不生效的坑中
Posted 与你同在架构之路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何避免掉进spring事务不生效的坑中相关的知识,希望对你有一定的参考价值。
大家好,我是宇哥
前言
你之前开发中,有没有遇到过一些加了事务@Transactional注解,但事务没有生效的的情况?反正我之前就遇到过,今天就总结整理一下。下面我们看看都有哪些导致事务不生效,也防止入坑。
spring事务的原理
从所周知spring事务底层是基于aop思想的,对目标类中的方法,进行动态代理实现的事务控制。我们看看底层核心代码TransactionAspectSupport类
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//1开启事务 Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// 2执行目标方法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);
}
commitTransactionAfterReturning(txInfo);//3提交事务
return retVal;
}
注:123处的代码注释,你自己跟一下源码,记忆会更深刻,就能找到DataourceTransactionManager
事务不生效——声明为final
@Transactional
public final void add(User user) {
userMapper.insert(user);
}
我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。
事务不生效——声明为private
@Transactional
prvate void add(User user) {
userMapper.insert(user);
}
我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。
你怎么知道的源码:TransactionalRepositoryProxyPostProcessor
private TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
....
事务不生效——内部方法调用
控制层直接调用方法是newFile
@Override
public RestfulResponse<String> newFile(String fileId, String extraInfo, SecurityUserInfo userDto) {
// 保存文件到阿里云oss
String url = ossService.putObject(objectName, ControllerConstant.EMPTY_TABLE_COMPRESSED_SSJSON.getBytes());
// 保存文件和记录加入一个事务控制中
saveFileAndHistory(fileId, userDto, objectName, url);
}
@Transactional// 保存基本信息
public void saveFileAndHistory(String fileId, SecurityUserInfo userDto, String targetObjectName, String url) {
saveFile(fileId, null, userDto);
tableService.saveOssUrlToFileHistory(fileId, 0L, url);
}
前面我们说过,spring底层是基于aop的动态代理实现,所以只能针对被调用的目标对象上@Transactional注解加上事务的代码,而saveFileAndHistory方法是内部方法,this(对象)调用的,所以不会产生事务代码。
事务不生效——当前类没有被spring管理
//@Service 或者加了,没有扫描到注解
public class UserService{
@Transactional
public void add(User user) {
userMapper.insert(user);
}
}
我们可以看到UserService类没有定义@Service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。
事务不生效——错误的spring事务传播特性(一般不会碰到)
@Transactional(propagation = Propagation.NEVER)
public void add(User user) {
userMapper.insert(user);
}
我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。
事务不生效——数据库不支持事
msql8以前的版本数据库引擎是支持myslam和innerdb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。
事务不生效——自己吞了异常
@Transactional
public void add(User user) {
try{
userMapper.insert(user);
} catch(Exception e){
e.printStack();
}
}
一旦出现异常,就被catch捕住了,也就不会向外抛了,前面我们开spring底层源时看到这么一段代码,目标方法抛出的异常会捕获,然后回滚事务。如下代码:
catch (Throwable ex) {
// 回滚事务target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
事务不生效——抛出的异常不正确
@Transactional
public void add(User user) {
try{
userMapper.insert(user);
} catch(Exception e){
throw new Exception(e);
}
}
有人可能发现不对啊,上面刚刚看到补的是Throwable啊,而抛的Exception是它的子,没问题啊。这个理解没问题,但是看@Transactional注解里如下
/**
* Defines zero (0) or more exception {@link Class classes}, which must be
* subclasses of {@link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>默认情况By default, a transaction will be rolling back on {@link RuntimeException}
* and {@link Error} but not on checked exceptions(不是检查异常) (business exceptions). See
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
* for a detailed explanation.
* <p>This is the preferred way to construct a rollback rule (in contrast to
* {@link #rollbackForClassName}), matching the exception class and its subclasses.
* <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}.
* @see #rollbackForClassName
* @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
*/
Class<? extends Throwable>[] rollbackFor() default {};
中间一段说的DefaultTransactionAttribute此类是具体实现
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
意思异常类型必须是RuntimeException和Error这两个类型及子类型,Exception不在这个范围内,所以抛出的Exception事务是不会回滚的
事务不生效——多线程调用
@Autowired
private SportService sportService;
@Transactional
public void add(User user) {
userMapper.insert(user);
new Thread(() -> {
sportService.doLikeThing();
}).start();
}
@Service
public class SportService{
@Transactional
public void doLikeThing(){
sportMapper.insert(x);
}
}
我们看到调用add方法是在主线程上,而调用doLikeThing方法另外一个线程,由于他们不在一个线程,拿到的数据库连接也不一样,因
为spring事务是基于连接来实现的,所以两个方法分别在不同的事务中。互相
不受影响
事务不生效——嵌套事务多回滚了
private SportService sportService;
public void add(User user) {
userMapper.insert(user);
sportService.doLikeThing();
}
public class SportService{
public void doLikeThing(){
sportMapper.insert(x);
}
}
这种就是内部事务嵌套,有时我们可能想doLikeThing方法异常,只回滚它自已
但,add方法也回滚了。原因很简单就是
doLikeThing 没有捕获异常,直接向上抛到外层代理,自然外层事务也会回滚。
那么有没有什么办法,只让doLikeThing回滚,不影响外层呢,
把上面的第一段代码换成如下:
@Transactional
public void add(User user) {
userMapper.insert(user);
try{
sportService.doLikeThing();
} catch(Exception e) {
log.error(e);
}
}
上面捕获了就可以了,他就不会向上继续抛了。
总结:
上面的事务不生效的场景,大家有没有似曾相识的感觉 ,大部分都是比较容易入过
坑的。大家注意。仔细把这篇看完,以后基本就不会习惯性掉进深坑了。
快乐三连击:【点赞,在看,分享】 好东西又分享出来
以上是关于如何避免掉进spring事务不生效的坑中的主要内容,如果未能解决你的问题,请参考以下文章