java - 诊断 Spring Data 中的事务异常

标签 java spring spring-data spring-data-neo4j

在使用 Spring Data Neo4j(使用简单映射模式)时,我偶尔会遇到 NotInTransactionException 被抛出到使用 @Transactional 注释的方法中,并且发现自己在尝试诊断这些异常时费尽了心思。例如下面的方法:

@Service 
public class FooService { 
  @Autowired Neo4jTemplate template;

  //GraphPersisted is an interface containing a single method: Long getId()
  //ModelNode is an empty interface implemented by my @NodeEntity classes 

  @Transactional
  public <T extends ModelNode> T getNode(GraphPersisted g, Class<T> clazz){          
    return template.repositoryFor(clazz).findOne(g.getId()); //NotInTransactionException!!
  }
}

抛出以下内容:

Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: nested exception is org.neo4j.graphdb.NotInTransactionException
at org.springframework.data.neo4j.support.Neo4jExceptionTranslator.translateExceptionIfPossible(Neo4jExceptionTranslator.java:51)
at org.springframework.data.neo4j.support.Neo4jTemplate.translateExceptionIfPossible(Neo4jTemplate.java:447)
at org.springframework.data.neo4j.support.Neo4jTemplate.getNode(Neo4jTemplate.java:481)
at org.springframework.data.neo4j.repository.NodeGraphRepositoryImpl.getById(NodeGraphRepositoryImpl.java:33)
at org.springframework.data.neo4j.repository.NodeGraphRepositoryImpl.getById(NodeGraphRepositoryImpl.java:24)
at org.springframework.data.neo4j.repository.AbstractGraphRepository.findOne(AbstractGraphRepository.java:127)
at org.springframework.data.neo4j.repository.AbstractGraphRepository.findOne(AbstractGraphRepository.java:51)
at net.mypkg.myapp.core.FooService.getNode(FooService.java:28)
at net.mypkg.myapp.citizenry.BarService.getCitNode(BarService.java:136)
at net.mypkg.myapp.citizenry.BarService.loadCitizens(BarService.java:81)
at net.mypkg.myapp.citizenry.BarService$$FastClassBySpringCGLIB$$792b7a4e.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:640)
at net.mypkg.myapp.citizenry.BarService$$EnhancerBySpringCGLIB$$59949515.loadCitizens(<generated>)
at net.mypkg.myapp.creator.builders.VotingActivityBuilder.makeVotesFor(VotingActivityBuilder.java:46)
at net.mypkg.myapp.creator.builders.VotingActivityBuilder.build(VotingActivityBuilder.java:35)
at net.mypkg.myapp.creator.builders.VotingActivityBuilder$$FastClassBySpringCGLIB$$6871225a.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:640)
at net.mypkg.myapp.creator.builders.VotingActivityBuilder$$EnhancerBySpringCGLIB$$7f5827a1.build(<generated>)
at net.mypkg.myapp.creator.Creator.create(Creator.java:33)
at net.mypkg.myapp.creator.CreatorDriver.run(CreatorDriver.java:52)
at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:634)
... 5 more

我最直接的问题是:为什么会抛出此异常?为什么我的 @Transactional 注释没有执行我期望的操作(即将我对 template.findOne(Long id) 的调用包装在事务中)?

我更大的问题是:你怎么知道?堆栈跟踪中给出了哪些线索可能表明意外行为是从哪里产生的?我对 Spring 和 Spring Data 比较陌生,我确信这些问题本质上并不难以诊断,我只是在努力这样做,因为我不知道如何解释堆栈跟踪:当这些问题出现时,我应该在跟踪中寻找什么来诊断这些问题?

(请让我知道回答这个问题需要哪些进一步的代码/配置,我将发布它 - 我故意包含尽可能少的内容,希望您需要查看的内容来诊断此特定问题将帮助我了解将来诊断类似异常时需要查看的内容。不过,我会说,@Transactional 注释在同一应用程序上下文中的大量其他方法中按预期工作)

最佳答案

将一些想法总结为一个答案:Spring(任何最新版本)通过实例化包含用“@Transactional”注释的方法的对象的代理来建立事务。这些代理(用处理事务的输入/退出代码包装原始功能)通过以下两种方式之一生成:

1) 通过CGLIB,通过使用该子类的实例加上原始类的实例,为代理目标的子类动态生成Java字节代码覆盖带注释的方法

2) 通过 Java 动态代理通过动态生成实现代理目标的所有接口(interface)的对象并使用原始类的实例来模仿目标类

除非另有明确说明,否则 Spring 会尝试使用 2)。如果目标类没有实现任何接口(interface),则 2) 不起作用,必须选择选项 1)。退回到选项 1) 也可能会失败。考虑 final方法(如 Maarten 提到的)或范围比 public 更严格的方法。 .

这些一般内容都记录在 Spring 引用中(查找“cglib”、“proxy”、“transaction”...)。

回到你的例子(希望它不会意外地过度简化):

a) 类 FooService包含带注释的方法 getNode不实现任何接口(interface)(顺便说一句,这确实是一个不好的做法;您应该针对接口(interface)进行编程,例如轻松地允许您交换实现),Spring 必须走“CGLIB 方式”。使用 CGLIB 应该可以工作,因为什么都没有 final ,注释的方法为public ,调用基类构造函数两次不会造成任何损害,...

b) 从堆栈跟踪调用我们可以得知类 BarServiceVotingActivityBuilder使用 CGLIB 进行代理,因此通常这确实有效。

c) 如果提到BarServiceVotingActivityBuilder由于 @Transactional 被代理注释,您已成功设置事务管理器并启用注释驱动事务(通过 <tx:annotation-driven/>@EnableTransactionManagement )。尽管我担心这两个对象因其他原因被代理(告诉我们!:-))。在后一种情况下,实例化事务管理器并启用注释驱动的事务(参见 Maarten 的回答)。

d) 让我们排除多个交易管理器的情况(你只有一个,不是吗?)

e) 通过查看堆栈跟踪,我们可以排除内部调用 setNode 的情况。 (即从 FooService 的另一个非 tx 感知方法调用它)。这样做会绕过带注释方法的代理版本。

f)我现在能想象的最后一件事(但不能从堆栈跟踪或您提供的代码中确定),您的代码是否具有多个 ApplicationContext (例如,Web 应用程序通常具有“根上下文”以及具有父子关系的“调度程序上下文”)。如果你实例化FooService作为父上下文的一部分并放置 @EnableTransactionManagement在您的子上下文中,将不会生成任何 tx 逻辑。

我会选择 c) 或 f)。

PS:代理的堆栈跟踪 FooService在 tx 带注释的方法中抛出异常 setNode在我的机器上看起来像

java.lang.IllegalArgumentException: This is for testing purposes.
  at eu.example.service.FooService.getNode(FooService.java:94)
  at eu.example.service.FooService$$FastClassByCGLIB$$837ba2c0.invoke(<generated>)
  at o.s.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
  at o.s.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:698)
  at o.s.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
  at o.s.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
  at o.s.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
  at o.s.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
  at o.s.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
  at o.s.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631)
  at eu.example.service.FooService$$EnhancerByCGLIB$$e1fb8939.getNode(<generated>)

关于java - 诊断 Spring Data 中的事务异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25492986/

相关文章:

java - CDI 生成方法 InjectionPoint 为 Null

java - Spring MVC 无法解析 .jsp 文件

java - 将Wiremock部署到Weblogic作为 war 时的验证问题

java - 使用 Springs MVC 返回 JSON 数据哪种方式更好以及为什么

java - Spring 数据 : multiple repository interfaces into a single 'repository' service class

java - Spring Data Mongo - 如何映射继承的 POJO 实体?

java - 可通过方法参数运行

spring - 创建 session 无状态使用

java - 未找到具有配置名称的 Spring 配置

spring-data-jpa - 使用Spring CrudRepository时'不等于'条件