问题
This is similar to what is discussed at Unable to use two Neo4j Instances with Spring boot/Spring data neo4j but I don't have two databases. I have downloaded the spring-data neo4j sample java application from the git repo and want to execute a dynamic query instead of executing a static query via repository interface.
I am facing an issue of null transaction manager.
Here's my interface :
public interface SearchRepositoryCustom {
Iterable<Movie> searchByCriteria();
}
Here's my custom repo impl:
@Repository
@Transactional
public class SearchRepositoryImpl implements SearchRepositoryCustom {
@Autowired
private SessionFactory sessionFactory;
@Override
public Iterable<Movie> searchByCriteria() {
String query = "MATCH (m:Movie)<-[r:ACTED_IN]-(a:Person) RETURN m,r,a LIMIT 10";
return sessionFactory.openSession().query(Movie.class, query, Collections.emptyMap());
}
}
Here's my configuration :
@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(basePackages = "movies.spring.data.neo4j.repositories")
public class Neo4jPersistenceConfig {
@Bean
@ConfigurationProperties("spring.data.neo4j")
public Neo4jProperties neo4jProperties() {
return new Neo4jProperties();
}
@Bean
public org.neo4j.ogm.config.Configuration userConfiguration() {
return neo4jProperties().createConfiguration();
}
@Bean
public SessionFactory getSessionFactory() {
return new SessionFactory(userConfiguration(), "movies.spring.data.neo4j.domain");
}
@Bean
public Neo4jTransactionManager transactionManager() {
return new Neo4jTransactionManager(getSessionFactory());
}
}
Since I have only one TransactionManager and One SessionFactory (as I have only one Neo4j instance) I don't need to name the beans separately.
I am seeing the following exception :
org.neo4j.ogm.exception.core.TransactionManagerException: Transaction is not current for this thread
at org.neo4j.ogm.session.transaction.DefaultTransactionManager.rollback(DefaultTransactionManager.java:86) ~[neo4j-ogm-core-3.1.0.jar:3.1.0]
at org.neo4j.ogm.transaction.AbstractTransaction.rollback(AbstractTransaction.java:65) ~[neo4j-ogm-api-3.1.0.jar:3.1.0]
at org.neo4j.ogm.drivers.bolt.transaction.BoltTransaction.rollback(BoltTransaction.java:61) ~[neo4j-ogm-bolt-driver-3.1.0.jar:3.1.0]
at org.neo4j.ogm.transaction.AbstractTransaction.close(AbstractTransaction.java:144) ~[neo4j-ogm-api-3.1.0.jar:3.1.0]
at org.springframework.data.neo4j.transaction.Neo4jTransactionManager.doCleanupAfterCompletion(Neo4jTransactionManager.java:379) ~[spring-data-neo4j-5.0.5.RELEASE.jar:5.0.5.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:1007) ~[spring-tx-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:793) ~[spring-tx-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714) ~[spring-tx-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532) ~[spring-tx-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304) ~[spring-tx-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98) ~[spring-tx-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185) ~[spring-aop-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) ~[spring-aop-5.0.4.RELEASE.jar:5.0.4.RELEASE]
at movies.spring.data.neo4j.repositories.SearchRepositoryImpl$$EnhancerBySpringCGLIB$$d2631bcd.searchByCriteria(<generated>) ~[classes/:na]
at movies.spring.data.neo4j.controller.MovieController.advGlobal(MovieController.java:54) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_171]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_171]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171]
Even if I actually go ahead and declare the name of the beans and mark the method transactional by specifying the name of the transactionManager, I still get the same error consistently.
Java version : 1.8
neo4j version : 3.4.6
What am I missing?
回答1:
Gerrit is right. I'd like to add the two options we have here. We provide an injectable Session
that is bound to the current thread and is integrated with Springs transactions. Just auto wire that instead of the SessionFactory
and you're good to go with your solution. Please note that I'm using constructor injection as recommended throughout all Spring projects:
@Repository
@Transactional
class SearchRepositoryImpl implements SearchRepositoryCustom {
private final Session session;
public SearchRepositoryImpl(Session session) {
this.session = session;
}
@Override
public Iterable<ThingEntity> searchByCriteria() {
String query = "MATCH (t:ThingEntity) RETURN t LIMIT 10";
return session.query(ThingEntity.class, query, Map.of());
}
}
I have used another domain to create a concise example project, but the idea stays the same.
For a simple use case like that I fully agree with Gerrit and would use the @Query
annotation on a declarative Spring Data Neo4j repository like this:
interface ThingRepository extends Neo4jRepository<ThingEntity, Long> {
@Query("MATCH (t:ThingEntity) RETURN t LIMIT 10")
public Iterable<ThingEntity> searchByCriteria();
}
The usage is the same, as demonstrated here:
@Component
class ExampleUsage implements CommandLineRunner {
private final ThingRepository thingRepository;
private final SearchRepositoryCustom searchRepositoryCustom;
public ExampleUsage(ThingRepository thingRepository, SearchRepositoryCustom searchRepositoryCustom) {
this.thingRepository = thingRepository;
this.searchRepositoryCustom = searchRepositoryCustom;
}
@Override
public void run(String... args) {
this.thingRepository.save(new ThingEntity(1));
this.thingRepository.save(new ThingEntity(2));
var things = this.searchRepositoryCustom.searchByCriteria();
things.forEach(System.out::println);
things = this.thingRepository.searchByCriteria();
things.forEach(System.out::println);
}
}
You'll find the complete application as a gist: Use Spring Data Neo4js injectable OGM Session. I have used Java 10 instead of 8 as we approaching EOL for Java 8, but that doesn't change the repository implementations. Apart from that, tested with Spring Boot 2.0.4, Spring Data Kay and OGM 3.1.0.
Edit: In regard to the comment: The injectable session is a proxy. The field itself is final, but the proxy opens sessions as needed and then delegates to it.
回答2:
You are mixing up Neo4j-OGM‘s SessionFactory
/Session
and the @Transactional
support of Spring (Data Neo4j). The latter will create a new transaction of which the OGM code is not aware and tries to create a fresh transaction.
If you use Spring Data Neo4j you can also define the query within your entity repository with a @Query
annotated method.
The other solution would be to remove the @Transactional
annotation in your service layer and create it manually if you plan to execute multiple operations (Not needed for one because OGM will create a transaction implicit if it does not exist).
来源:https://stackoverflow.com/questions/52051625/custom-repository-implementation-for-neo4j-doesnt-work