使用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
注释没有按照我的预期执行(即将我的调用包裹到{{1}在一个交易中)?
我更大的问题是:你怎么知道?堆栈跟踪中给出了哪些线索可能表明出现意外行为?我对Spring和Spring Data比较陌生,我确信这些问题本身并不难诊断,我只是在努力这样做,因为我不知道如何解释堆栈跟踪:我该怎么办?正在寻找痕迹,以便在出现这些问题时进行诊断?
(请让我知道回答这个问题需要进一步的代码/配置,我会发布它 - 我故意尽可能少地包括在内,希望你需要什么看到诊断这个特定问题将有助于我理解我将来需要考虑什么来诊断类似的异常。但我会说,template.findOne(Long id)
注释正在按预期工作同一应用程序上下文中的大量其他方法)
答案 0 :(得分:1)
在答案中包含一些想法:Spring(任何最新版本)通过实例化包含用“@Transactional”注释的方法的对象的代理来建立事务。这些代理(使用输入/退出代码处理事务包装原始功能)是以两种方式之一生成的:
1)通过CGLIB动态生成代理目标的子类的Java字节代码,覆盖带注释的方法,使用此子类的实例加上使用原始类的实例
2)通过Java Dynamic Proxies模拟目标类,动态生成实现代理目标的所有接口的对象,并使用原始类的实例
除非明确另有说明,否则Spring会尝试使用2)。如果目标类没有实现任何接口选项1)必须选择,则2)不起作用。回到选项1)也可能会破裂。考虑最终方法(如Maarten所提到的)或方法,其范围比public
更严格。
这些一般事项都记录在Spring引用中(查找“cglib”,“proxy”,“transaction”,...)。
回到你的例子(希望它不会被意外地过度简化):
a)包含带注释方法FooService
的类getNode
没有实现任何接口(顺便说一下,这实际上是一种不好的做法;你应该对一个接口进行编程,例如很容易让你交换实现),Spring必须走“CGLIB方式”。使用CGLIB应该工作,因为没有final
,注释方法是public
,调用基类构造函数两次不会造成任何伤害,...
b)从堆栈跟踪调用我们可以告诉我们使用CGLIB代理类BarService
和VotingActivityBuilder
,所以通常这可以工作。
c)如果由于BarService
注释而提及VotingActivityBuilder
和@Transactional
,则您已成功设置事务管理器并启用了注释驱动的事务(通过{{1} }或<tx:annotation-driven/>
)。虽然我担心这两个对象是由其他原因代理的(告诉我们!:-))。在后一种情况下,实例化一个事务管理器并启用注释驱动的事务(参见Maarten的回答)。
d)让我们排除多个tx经理的情况(你只有一个,不是吗?)
e)查看堆栈跟踪,我们可以排除内部调用@EnableTransactionManagement
的情况(即从另一个调用它,而不是setNode
的tx感知方法)。这样做会绕过带注释方法的代理版本。
f)我现在可以想象的最后一件事(但无法从堆栈跟踪或您提供的代码中确定无误),您的代码是否具有多个ApplicationContext(例如,Web-Apps通常具有“根上下文”,如以及具有亲子关系的“调度员背景”。如果将FooService
实例化为父上下文的一部分并将FooService
放在子上下文中,则不会生成tx逻辑。
我会去c)或f)。
PS:在tx注释方法@EnableTransactionManagement
中抛出异常的代理FooService
的堆栈跟踪在我的机器上看起来像
setNode
答案 1 :(得分:0)
从堆栈跟踪判断,BarService
的实例正在调用FooService
的非代理实例。如果后者代理,你会看到堆栈跟踪中的CGLibEnhance
- ed类和你的案例中的-IIRC-也是负责启动事务的TransactionInterceptor
。
现在为什么实例没有被代理,这很难说。 @EnableTransactionManagement
丢失了吗?该实例不是由Spring管理的吗?这个班级不能代理,因为它是最终的吗?