[英]Diagnosing transactional exceptions in Spring Data
使用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 Dynamic Proxies通过动态生成实现代理目标的所有接口的对象以及使用原始类的实例来模仿目标类
除非另有明确说明,否则Spring尝试使用2)。 由于2)如果目标类未实现任何接口,则不起作用,必须选择选项1)。 退回到选项1)可能也会中断。 考虑最终方法(如Maarten所述)或范围比public
更严格的方法。
这些常规内容都记录在Spring参考中(查找“ cglib”,“ proxy”,“ transaction”等)。
回到您的示例(希望它不会被过分简化):
A)类FooService
包含annoted方法getNode
没有实现任何接口(由这实在是一个不好的做法的方式;你应该程序对一个接口,例如容易让你交换的实现),春已去了“ CGLIB方式”。 使用CGLIB应该可以工作,因为没有什么是final
,带注释的方法是public
,两次调用基类构造函数不会造成任何危害,...
b)从stacktrace调用,我们可以知道BarService
和VotingActivityBuilder
类是使用CGLIB代理的,因此通常可以正常工作。
c)如果由于@Transactional
注释而将提到的BarService
和VotingActivityBuilder
代理,则您已经成功设置了事务管理器并启用了注释驱动的事务(通过<tx:annotation-driven/>
或@EnableTransactionManagement
)。 尽管我担心这两个对象会因为其他原因被代理(告诉我们!:-))。 在后一种情况下,实例化事务管理器并启用注释驱动的事务(请参阅Maarten的答案)。
d)排除多个交易经理的情况(您只有一个,不是吗?)
e)查看FooService
,我们可以排除内部调用setNode
的情况(即从另一个FooService
非tx感知方法调用它)。 这样做将绕过带注释方法的代理版本。
f)我现在可以想象的最后一件事(但是不能从stacktrace或您提供的代码确定)是您的代码具有多个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>)
从堆栈跟踪情况来看,实例BarService
正在为一个非代理实例调用FooService
。 如果后者位于代理位置,则您将在stacktrace中看到CGLibEnhance
-ed类,在您的情况下将看到CGLibEnhance
以及负责启动事务的TransactionInterceptor
。
现在,为什么不对该实例进行代理,这很难说。 @EnableTransactionManagement
是否丢失? 实例不是由Spring管理的吗? 可以因为上完课而不能上课吗?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.