From 0e40d9053c60f68a5be1e5046b7baa272ebbe8db Mon Sep 17 00:00:00 2001 From: Gerrit Meier Date: Fri, 5 Apr 2024 11:51:31 +0200 Subject: [PATCH] GH-2888 - Respect multiple Neo4jTransactionManagers. There might be different transaction managers in place for different Neo4jTemplates. The change is also applied to the ReactiveNeo4jTemplate. Co-authored-by: Michael Simons --- .../data/neo4j/core/Neo4jTemplate.java | 39 +++++++++++++++---- .../neo4j/core/ReactiveNeo4jTemplate.java | 34 ++++++++++++++-- .../domain1/Domain1Config.java | 7 ++-- .../domain2/Domain2Config.java | 7 ++-- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index 2df2bed1b..55dd9603e 100644 --- a/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -158,8 +158,17 @@ public final class Neo4jTemplate implements this(neo4jClient, neo4jMappingContext, EntityCallbacks.create()); } + public Neo4jTemplate(Neo4jClient neo4jClient, Neo4jMappingContext neo4jMappingContext, PlatformTransactionManager transactionManager) { + this(neo4jClient, neo4jMappingContext, EntityCallbacks.create(), transactionManager); + } + public Neo4jTemplate(Neo4jClient neo4jClient, Neo4jMappingContext neo4jMappingContext, EntityCallbacks entityCallbacks) { + this(neo4jClient, neo4jMappingContext, entityCallbacks, null); + } + + public Neo4jTemplate(Neo4jClient neo4jClient, Neo4jMappingContext neo4jMappingContext, + EntityCallbacks entityCallbacks, @Nullable PlatformTransactionManager platformTransactionManager) { Assert.notNull(neo4jClient, "The Neo4jClient is required"); Assert.notNull(neo4jMappingContext, "The Neo4jMappingContext is required"); @@ -170,6 +179,7 @@ public final class Neo4jTemplate implements this.eventSupport = EventSupport.useExistingCallbacks(neo4jMappingContext, entityCallbacks); this.renderer = Renderer.getDefaultRenderer(); this.elementIdOrIdFunction = SpringDataCypherDsl.elementIdOrIdFunction.apply(null); + setTransactionManager(platformTransactionManager); } ProjectionFactory getProjectionFactory() { @@ -1102,9 +1112,22 @@ public final class Neo4jTemplate implements this.elementIdOrIdFunction = SpringDataCypherDsl.elementIdOrIdFunction.apply(cypherDslConfiguration.getDialect()); this.cypherGenerator.setElementIdOrIdFunction(elementIdOrIdFunction); - PlatformTransactionManager transactionManager = beanFactory.getBeanProvider(PlatformTransactionManager.class).getIfUnique(() -> beanFactory.getBean(Neo4jTransactionManager.class)); - this.transactionTemplate = new TransactionTemplate(transactionManager); - this.transactionTemplateReadOnly = new TransactionTemplate(transactionManager, readOnlyTransactionDefinition); + if (this.transactionTemplate != null && this.transactionTemplateReadOnly != null) { + return; + } + PlatformTransactionManager transactionManager = null; + var it = beanFactory.getBeanProvider(PlatformTransactionManager.class).stream().iterator(); + while (it.hasNext()) { + PlatformTransactionManager transactionManagerCandidate = it.next(); + if (transactionManagerCandidate instanceof Neo4jTransactionManager neo4jTransactionManager) { + if (transactionManager != null) { + throw new IllegalStateException("Multiple Neo4jTransactionManagers are defined in this context. " + + "If this in intended, please pass the transaction manager to use with this Neo4jTemplate in the constructor"); + } + transactionManager = neo4jTransactionManager; + } + } + setTransactionManager(transactionManager); } // only used for the CDI configuration @@ -1112,10 +1135,12 @@ public final class Neo4jTemplate implements this.renderer = rendererFromCdiConfiguration; } - // only used for the CDI configuration - public void setTransactionManager(PlatformTransactionManager platformTransactionManager) { - this.transactionTemplate = new TransactionTemplate(platformTransactionManager); - this.transactionTemplateReadOnly = new TransactionTemplate(platformTransactionManager, readOnlyTransactionDefinition); + public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager) { + if (transactionManager == null) { + return; + } + this.transactionTemplate = new TransactionTemplate(transactionManager); + this.transactionTemplateReadOnly = new TransactionTemplate(transactionManager, readOnlyTransactionDefinition); } @Override diff --git a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java index 6d490a03e..50b2ffdac 100644 --- a/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java +++ b/src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java @@ -150,9 +150,14 @@ public final class ReactiveNeo4jTemplate implements private ProjectionFactory projectionFactory; private Renderer renderer; + private Function elementIdOrIdFunction; public ReactiveNeo4jTemplate(ReactiveNeo4jClient neo4jClient, Neo4jMappingContext neo4jMappingContext) { + this(neo4jClient, neo4jMappingContext, null); + } + + public ReactiveNeo4jTemplate(ReactiveNeo4jClient neo4jClient, Neo4jMappingContext neo4jMappingContext, @Nullable ReactiveTransactionManager transactionManager) { Assert.notNull(neo4jClient, "The Neo4jClient is required"); Assert.notNull(neo4jMappingContext, "The Neo4jMappingContext is required"); @@ -163,6 +168,7 @@ public final class ReactiveNeo4jTemplate implements this.eventSupport = ReactiveEventSupport.useExistingCallbacks(neo4jMappingContext, ReactiveEntityCallbacks.create()); this.renderer = Renderer.getDefaultRenderer(); this.elementIdOrIdFunction = SpringDataCypherDsl.elementIdOrIdFunction.apply(null); + setTransactionManager(transactionManager); } ProjectionFactory getProjectionFactory() { @@ -1182,10 +1188,32 @@ public final class ReactiveNeo4jTemplate implements this.renderer = Renderer.getRenderer(cypherDslConfiguration); this.elementIdOrIdFunction = SpringDataCypherDsl.elementIdOrIdFunction.apply(cypherDslConfiguration.getDialect()); this.cypherGenerator.setElementIdOrIdFunction(elementIdOrIdFunction); - ReactiveTransactionManager reactiveTransactionManager = beanFactory.getBeanProvider(ReactiveTransactionManager.class) - .getIfUnique(() -> beanFactory.getBean(ReactiveNeo4jTransactionManager.class)); - this.transactionalOperatorReadOnly = TransactionalOperator.create(reactiveTransactionManager, readOnlyTransactionDefinition); + + if (this.transactionalOperator != null && this.transactionalOperatorReadOnly != null) { + return; + } + + ReactiveTransactionManager reactiveTransactionManager = null; + var iter = beanFactory.getBeanProvider(ReactiveTransactionManager.class).stream().iterator(); + while (iter.hasNext()) { + ReactiveTransactionManager transactionManagerCandidate = iter.next(); + if (transactionManagerCandidate instanceof ReactiveNeo4jTransactionManager reactiveNeo4jTransactionManager) { + if (reactiveTransactionManager != null) { + throw new IllegalStateException("Multiple ReactiveNeo4jTransactionManagers are defined in this context. " + + "If this in intended, please pass the transaction manager to use with this ReactiveNeo4jTemplate in the constructor"); + } + reactiveTransactionManager = reactiveNeo4jTransactionManager; + } + } + setTransactionManager(reactiveTransactionManager); + } + + private void setTransactionManager(@Nullable ReactiveTransactionManager reactiveTransactionManager) { + if (reactiveTransactionManager == null) { + return; + } this.transactionalOperator = TransactionalOperator.create(reactiveTransactionManager); + this.transactionalOperatorReadOnly = TransactionalOperator.create(reactiveTransactionManager, readOnlyTransactionDefinition); } @Override diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java index 6b6882f38..da18d076c 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain1/Domain1Config.java @@ -63,9 +63,10 @@ public class Domain1Config { @Primary @Bean public Neo4jOperations domain1Template( @Qualifier("domain1Client") Neo4jClient domain1Client, - @Qualifier("domain1Context") Neo4jMappingContext domain1Context + @Qualifier("domain1Context") Neo4jMappingContext domain1Context, + @Qualifier("domain1Manager") PlatformTransactionManager domain1TransactionManager ) { - return new Neo4jTemplate(domain1Client, domain1Context); + return new Neo4jTemplate(domain1Client, domain1Context, domain1TransactionManager); } @Primary @Bean @@ -78,7 +79,7 @@ public class Domain1Config { @Primary @Bean public DatabaseSelectionProvider domain1Selection() { - return () -> DatabaseSelection.undecided(); + return DatabaseSelection::undecided; } @Primary @Bean diff --git a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java index 9c62560b9..309616aa5 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java +++ b/src/test/java/org/springframework/data/neo4j/integration/multiple_ctx_imperative/domain2/Domain2Config.java @@ -62,9 +62,10 @@ public class Domain2Config { @Bean public Neo4jOperations domain2Template( @Qualifier("domain2Client") Neo4jClient domain2Client, - @Qualifier("domain2Context") Neo4jMappingContext domain2Context + @Qualifier("domain2Context") Neo4jMappingContext domain2Context, + @Qualifier("domain2Manager") PlatformTransactionManager domain2TransactionManager ) { - return new Neo4jTemplate(domain2Client, domain2Context); + return new Neo4jTemplate(domain2Client, domain2Context, domain2TransactionManager); } @Bean @@ -77,7 +78,7 @@ public class Domain2Config { @Bean public DatabaseSelectionProvider domain2Selection() { - return () -> DatabaseSelection.undecided(); + return DatabaseSelection::undecided; } @Bean