在使用 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) 从堆栈跟踪调用我们可以得知类 BarService
和VotingActivityBuilder
使用 CGLIB 进行代理,因此通常这确实有效。
c) 如果提到BarService
和VotingActivityBuilder
由于 @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/