错误场景
我自定义了一个Jpa接口类继承了JpaRepository。
接口类中定义了一个根据用户id删除的方法如下:
/**
* 根据用户id删除验证码.
* @param userId 用户id
*/
void deleteByUserId(Integer userId);
我通过mq消费者调用service类中方法A,其中方法A内调用了这个Jpa接口的deleteByUserId,抛出异常
原因分析
网上搜了一下说是未开启事务的导致的,首先service类中的方法A我没有加@Transactional注解,事务这块都是统一走的applicationContext.xml中配置的transactionManger和aop,
我看了一下切面里定义切点的方法,发现拦截点是在controller包下而不是service包下。而mq类的调用位置确实没有经过controller
并且我在调试过程中TransactionSynchronizationManager.isActualTransactionActive()
执行这个表达式发现没有开启的事务,说明网上的答案靠谱。
事务开启检查是在org.springframework.orm.jpa.SharedEntityManagerCreator.SharedEntityManagerInvocationHandler.invoke中做的,贴部分源码
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
else if (transactionRequiringMethods.contains(method.getName())) {
// We need a transactional target now, according to the JPA spec.
// Otherwise, the operation would get accepted but remain unflushed...
if (target == null || (!TransactionSynchronizationManager.isActualTransactionActive() &&
!target.getTransaction().isActive())) {
throw new TransactionRequiredException("No EntityManager with actual transaction available " +
"for current thread - cannot reliably process '" + method.getName() + "' call");
}
}
...
}
transactionRequiringMethods中需要检查的方法
transactionRequiringMethods.add("joinTransaction");
transactionRequiringMethods.add("flush");
transactionRequiringMethods.add("persist");
transactionRequiringMethods.add("merge");
transactionRequiringMethods.add("remove");
transactionRequiringMethods.add("refresh");
但是我发现有相同方式调用Jpa接口的save()方法是可以执行成功的,并且执行表达式发现事务是开启的,这就奇了怪了
我简单列一下找开启事务的位置思路:
- 首先已排除我目前使用工程主动开启了事务
- 找到Spring中获取事务的方法org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction
- 开始调试停到断点,查看方法的调用堆栈,里面有一些反射和切面相关的方法,找到最上层发现是SimpleJpaRepository导致的
- 点进这个类一看,发现类头上有个
@Transactional(readOnly = true)
,可以看到事务是在这里开启的
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
因为save()我们实际上调用的是SimpleJpaRepository原生的,而deleteByUserId()是我们自定义的,导致一个有事务一个没事务,
主要注意的是SimpleJpaRepository中事务是readOnly = true,相当于只有查询语句才走事务,我们save()还是不走事务的,只不过他开启事务了SharedEntityManagerInvocationHandler中的检查不报错…
解决对策
在对应的service方法加上@Transactional
即可
补充
网上说调用增删改时只开启读事务会抛异常,但是我实测是没有异常的,只不过这个操作的执行是不走事务的。