【问题标题】:Diagnosing transactional exceptions in Spring Data诊断 Spring Data 中的事务异常
【发布时间】:2014-08-25 19:22:20
【问题描述】:

在使用 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 注释在大型同一应用程序上下文中的其他方法的数量)

【问题讨论】:

  • 很难说,但对我来说,您的 FooService 似乎没有被代理(不像 BarServiceVotingActivityBuilder)因此不能用 tx 功能装饰。不过,可能还差得很远。试图重现类似的堆栈跟踪但不能;可能是由于 Spring 版本不同。你的是什么?
  • @Hille:很有趣。您是否从以下事实推断出这一点:对 MethodProxy.invoke() 的调用与 BarServiceVotingActivityBuilder 方法相关,但与 FooService 方法无关?你能解释一下这在诊断上告诉我什么吗? PS 我使用的是 SDN 版本 3.1.2(最新稳定版本),它依赖于 Spring Data 版本 1.4.3
  • 没错,我查看了堆栈跟踪,并没有看到与FooService 相关的“调用”。另一种判断方法是从上到下查看堆栈跟踪:您在FooService.getNode 上运行而没有命中任何代理。
  • 是否有任何答案对您的问题有所启发?还有什么不清楚的地方吗?

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


【解决方案1】:

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

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

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

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

这些一般的东西都记录在 Spring 参考中(查找“cglib”、“proxy”、“transaction”……)。

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

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

b) 从堆栈跟踪调用我们可以看出 BarServiceVotingActivityBuilder 类是使用 CGLIB 代理的,所以通常这确实有效。

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

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

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

f) 我现在能想象的最后一件事(但不能从堆栈跟踪或您提供的代码中确定),您的代码是否具有多个 ApplicationContext(例如,Web-Apps 通常具有“根上下文”作为以及具有父子关系的“调度程序上下文”)。如果您将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>)

【讨论】:

  • 这是一个绝妙的答案。我打算坚持并提出这个赏金,希望能准确地吸引你在这个答案中投入的思想水平,但现在没有必要。继续努力,@Hille!
【解决方案2】:

从堆栈跟踪来看,BarService 的实例正在调用FooService 的非代理实例。如果后者是代理,您会在堆栈跟踪中看到CGLibEnhance-ed 类,在您的情况下,您还会看到负责启动事务的TransactionInterceptor

现在为什么实例没有被代理,这很难说。 @EnableTransactionManagement 失踪了吗?实例不是由 Spring 管理的吗?类不能因为是final就不能被代理吗?

【讨论】:

    猜你喜欢
    • 2018-02-04
    • 2018-01-14
    • 2015-02-27
    • 1970-01-01
    • 2021-08-19
    • 2015-09-26
    • 2019-05-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多