org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction

15 篇文章 0 订阅
14 篇文章 0 订阅

错误场景

我自定义了一个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()方法是可以执行成功的,并且执行表达式发现事务是开启的,这就奇了怪了
我简单列一下找开启事务的位置思路:

  1. 首先已排除我目前使用工程主动开启了事务
  2. 找到Spring中获取事务的方法org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction
  3. 开始调试停到断点,查看方法的调用堆栈,里面有一些反射和切面相关的方法,找到最上层发现是SimpleJpaRepository导致的
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kw8WNhpS-1652870548016)(img_1.png)]
  4. 点进这个类一看,发现类头上有个@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即可

补充

网上说调用增删改时只开启读事务会抛异常,但是我实测是没有异常的,只不过这个操作的执行是不走事务的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值