如何避免掉进【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 exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}commitTransactionAfterReturning(txInfo);//3提交事务return retVal;}
注:123处的代码注释,你自己跟一下源码,记忆会更深刻,就能找到DataourceTransactionManager
事务不生效——声明为final
@Transactionalpublic final void add(User user) {userMapper.insert(user);}我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。
事务不生效——声明为private
@Transactionalprvate 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
@Overridepublic RestfulResponse<String> newFile(String fileId, String extraInfo, SecurityUserInfo userDto) {// 保存文件到阿里云ossString 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{@Transactionalpublic 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只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。
事务不生效——自己吞了异常
@Transactionalpublic void add(User user) {try{userMapper.insert(user);} catch(Exception e){e.printStack();}}一旦出现异常,就被catch捕住了,也就不会向外抛了,前面我们开spring底层源时看到这么一段代码,目标方法抛出的异常会捕获,然后回滚事务。如下代码:
catch (Throwable ex) {// 回滚事务target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}
事务不生效——抛出的异常不正确
@Transactionalpublic 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此类是具体实现
@Overridepublic boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);}意思异常类型必须是RuntimeException和Error这两个类型及子类型,Exception不在这个范围内,所以抛出的Exception事务是不会回滚的
事务不生效——多线程调用
@Autowiredprivate SportService sportService;@Transactionalpublic void add(User user) {userMapper.insert(user);new Thread(() -> {sportService.doLikeThing();}).start();}
@Servicepublic class SportService{@Transactionalpublic 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回滚,不影响外层呢,
把上面的第一段代码换成如下:
@Transactionalpublic void add(User user) {userMapper.insert(user);try{sportService.doLikeThing();} catch(Exception e) {log.error(e);}}上面捕获了就可以了,他就不会向上继续抛了。
总结:
上面的事务不生效的场景,大家有没有似曾相识的感觉 ,大部分都是比较容易入过
坑的。大家注意。仔细把这篇看完,以后基本就不会习惯性掉进深坑了。
快乐三连击:【点赞,在看,分享】 好东西又分享出来
