diff --git a/etc/adr/adr-003.adoc b/etc/adr/adr-003.adoc new file mode 100644 index 000000000..45d5a09f8 --- /dev/null +++ b/etc/adr/adr-003.adoc @@ -0,0 +1,21 @@ +== ADR 3: Public classes that are part of internal API only must be final + +=== Status + +accepted, open + +=== Context + +Due to the fact that we are not yet on the module path, we need to have some classes public defined that are not meant +to be part of the public API. + +=== Decision + +Those classes should be marked `@API(status = API.Status.INTERNAL, since = "1.0")` as well as made final to at least +prevent people from inheriting from them. +We need to add architecture rules in jQAssistant. + +=== Consequences + +Potentially problems with some Spring proxies. +Need to be solved on a case by case incident. diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java new file mode 100644 index 000000000..19ab19d24 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/DefaultNodeManager.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core; + +import org.apiguardian.api.API; +import org.neo4j.driver.v1.StatementRunner; +import org.neo4j.driver.v1.Transaction; +import org.springframework.data.neo4j.core.context.DefaultPersistenceContext; +import org.springframework.data.neo4j.core.context.PersistenceContext; +import org.springframework.data.neo4j.core.schema.Schema; +import org.springframework.lang.Nullable; + +/** + * @author Michael J. Simons + */ +@API(status = API.Status.INTERNAL, since = "1.0") +class DefaultNodeManager implements NodeManager { + + private final PersistenceContext persistenceContext = new DefaultPersistenceContext(); + + private final Schema schema; + + private final Neo4jTemplate neo4jTemplate; + + private final Transaction transaction; + + DefaultNodeManager(Schema schema, StatementRunner statementRunner) { + this.schema = schema; + this.neo4jTemplate = new Neo4jTemplate(() -> statementRunner); + this.transaction = statementRunner instanceof Transaction ? (Transaction) statementRunner : null; + } + + @Override + @Nullable + public Transaction getTransaction() { + return transaction; + } + + @Override + public Object executeQuery(String query) { + + return neo4jTemplate.executeQuery(query); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java index 8a7ffbc0f..fe2a417e7 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java @@ -18,11 +18,12 @@ */ package org.springframework.data.neo4j.core; +import java.util.function.Supplier; + import org.neo4j.driver.v1.Driver; import org.neo4j.driver.v1.StatementResult; import org.neo4j.driver.v1.StatementRunner; -import org.springframework.data.neo4j.core.transaction.DefaultNeo4jStatementRunnerSupplier; -import org.springframework.data.neo4j.core.transaction.StatementRunnerSupplier; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils; /** * Default implementation of {@link Neo4jOperations}. Uses the Neo4j Java driver to connect to and interact with the @@ -33,10 +34,14 @@ import org.springframework.data.neo4j.core.transaction.StatementRunnerSupplier; */ public class Neo4jTemplate implements Neo4jOperations { - private StatementRunnerSupplier statementRunnerSupplier; + private Supplier statementRunnerSupplier; public Neo4jTemplate(Driver driver) { - this.statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier(driver); + this(() -> Neo4jTransactionUtils.retrieveTransactionalStatementRunner(driver)); + } + + Neo4jTemplate(Supplier statementRunnerSupplier) { + this.statementRunnerSupplier = statementRunnerSupplier; } @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java new file mode 100644 index 000000000..db7c8aa76 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManager.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core; + +import org.apiguardian.api.API; +import org.neo4j.driver.v1.Transaction; +import org.springframework.lang.Nullable; + +/** + * Entry point for creating queries that return managed Nodes. The node manager is not supposed to be kept around + * for longer than necessary. Try to keep your transactions short to avoid memory pressure due to keeping track of + * managed nodes. + * + * @author Michael J. Simons + */ +@API(status = API.Status.STABLE, since = "1.0") +public interface NodeManager { + + /** + * Clears all managed entities and flushes any open state to the underlying storage. + */ + default void flush() { + } + + @Nullable + Transaction getTransaction(); + + @API(status = API.Status.EXPERIMENTAL) + Object executeQuery(String query); +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java new file mode 100644 index 000000000..324211dd0 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/NodeManagerFactory.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import org.apiguardian.api.API; +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.StatementRunner; +import org.springframework.data.neo4j.core.schema.Scanner; +import org.springframework.data.neo4j.core.schema.Schema; +import org.springframework.lang.Nullable; + +/** + * Creates ready to use instances of {@link NodeManager}. + * + * @author Michael J. Simons + */ +@API(status = API.Status.STABLE, since = "1.0") +@Slf4j +public final class NodeManagerFactory { + + /** + * Driver that is used to create new sessions, either by directly invoking it or through Springs transactional utils. + */ + private final Driver driver; + + /** + * The initial set of classes that will be scanned in {@link #initialize()} to build the schema for node managers + * belonging to this factory. + */ + private final Set> initialPersistentClasses; + + /** The scanner used to scan the initial set of persistent classes for creating a schema. */ + private Scanner scanner = new NoopScanner(); + + @Nullable + private Schema schema; + + private Function statementRunnerProvider = sourceDriver -> sourceDriver + .session(AccessMode.WRITE, Collections.emptyList()).beginTransaction(); + + /** + * Creates a new instance of a factory producing {@link NodeManager node managers}. When used in a transactional setup, + * i.e. with the {@link org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager}, make sure to use + * the same {@link Driver driver instance} for both the node and the transaction manager. + *

+ * Spring Boots autoconfiguration for SDN RX will make sure that the same driver is used for both concerns. + * + * @param driver The driver used to obtain statement runners from when creating instances of node managers. + * @param initialPersistentClasses The set of classes that should be initially scanned + */ + public NodeManagerFactory(Driver driver, Class... initialPersistentClasses) { + this.driver = driver; + + this.initialPersistentClasses = new HashSet<>(); + Arrays.stream(initialPersistentClasses).forEach(this.initialPersistentClasses::add); + } + + /** + * Creates a new node manager. The returned manager is supposed to have a short lifetime. When used in a Spring setup, + * this method should not called directly by client code. Instead the client code should use an injected instance of + * {@link NodeManager}, which will participate in Springs application based transaction or in JTA transactions. + * + * @return A new node manager + */ + public NodeManager createNodeManager() { + + Objects.requireNonNull(schema, "A schema is required. Did you call #initialize() before using this factory?"); + return new DefaultNodeManager(schema, statementRunnerProvider.apply(driver)); + } + + /** + * Configures a provider for extracting sessions/transactions from a Neo4j driver. This method is not to be called + * from application code and only used by internal API. + * + * @param statementRunnerProvider A required provider of statement runners + */ + @API(status = API.Status.INTERNAL, since = "1.0") + public void setStatementRunnerProvider(Function statementRunnerProvider) { + + Objects.requireNonNull(statementRunnerProvider, "A node manager factory requires a provider of statement runners."); + this.statementRunnerProvider = statementRunnerProvider; + } + + /** + * Configures the scanner used to build a schema for domain objects. This method is not to be called from application + * code and only used by internal API. + * + * @param scanner + */ + @API(status = API.Status.INTERNAL, since = "1.0") + public void setScanner(Scanner scanner) { + + Objects.requireNonNull(scanner, "A node manager factory requires a scanner."); + this.scanner = scanner; + } + + /** + * This initializes this factory and is usually called by Springs infrastructure and only useful as standalone call + * when the node manager factory is used without Spring. + */ + public void initialize() { + + log.info("Initializing schema with {} persistent classes", this.initialPersistentClasses.size()); + this.schema = scanner.scan(Collections.unmodifiableSet(this.initialPersistentClasses)); + } + + /** + * A noop implementation of a Schema scanner, only provided to create instances of a {@link NodeManagerFactory} that + * are in a valid state without booting up a whole context. + */ + private static class NoopScanner implements Scanner { + + @Override + public Schema scan(Collection> persistentClasses) { + return new Schema(); + } + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContextImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java similarity index 91% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContextImpl.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java index 37a47c3bd..3f46405e7 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/PersistenceContextImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/context/DefaultPersistenceContext.java @@ -21,5 +21,5 @@ package org.springframework.data.neo4j.core.context; /** * @author Michael J. Simons */ -class PersistenceContextImpl implements PersistenceContext { +public class DefaultPersistenceContext implements PersistenceContext { } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentEntityImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java similarity index 89% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentEntityImpl.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java index ac522e3c9..f1e4c1b22 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentEntityImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentEntity.java @@ -25,12 +25,12 @@ import org.springframework.data.util.TypeInformation; /** * @author Michael J. Simons */ -class Neo4jPersistentEntityImpl extends BasicPersistentEntity +class DefaultNeo4jPersistentEntity extends BasicPersistentEntity implements Neo4jPersistentEntity { private final String primaryLabel; - Neo4jPersistentEntityImpl(TypeInformation information) { + DefaultNeo4jPersistentEntity(TypeInformation information) { super(information); Node nodeAnnotation = this.findAnnotation(Node.class); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentPropertyImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java similarity index 91% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentPropertyImpl.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java index 0678b8a51..f772d0009 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentPropertyImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4jPersistentProperty.java @@ -27,7 +27,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; /** * @author Michael J. Simons */ -class Neo4jPersistentPropertyImpl extends AnnotationBasedPersistentProperty +class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProperty implements Neo4jPersistentProperty { /** @@ -37,7 +37,7 @@ class Neo4jPersistentPropertyImpl extends AnnotationBasedPersistentProperty owner, SimpleTypeHolder simpleTypeHolder) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java index 0f11cfe20..84624335f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImpl.java @@ -19,6 +19,7 @@ package org.springframework.data.neo4j.core.mapping; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Optional; @@ -50,12 +51,14 @@ public class MappingContextBasedScannerImpl implements Scanner { } @Override - public Schema scan() { + public Schema scan(Collection> persistentClasses) { final Schema schema = new Schema(); - neo4jMappingContext.getPersistentEntities().forEach(m -> - schema.registerNodeDescription(describeAsNode(m)) - ); + persistentClasses.forEach(clazz -> { + Neo4jPersistentEntity m = neo4jMappingContext.getPersistentEntity(clazz); + schema.registerNodeDescription(describeAsNode(m)); + }); + return schema; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java index 6d8f0ef1a..fdbbcdc70 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java @@ -37,7 +37,7 @@ public class Neo4jMappingContext extends AbstractMappingContext Neo4jPersistentEntity createPersistentEntity(TypeInformation typeInformation) { - return new Neo4jPersistentEntityImpl<>(typeInformation); + return new DefaultNeo4jPersistentEntity<>(typeInformation); } /* @@ -48,6 +48,6 @@ public class Neo4jMappingContext extends AbstractMappingContext neo4jPersistentProperties, SimpleTypeHolder simpleTypeHolder) { - return new Neo4jPersistentPropertyImpl(property, neo4jPersistentProperties, simpleTypeHolder); + return new DefaultNeo4jPersistentProperty(property, neo4jPersistentProperties, simpleTypeHolder); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java index c21a1aecb..d961faaf8 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/schema/Scanner.java @@ -18,6 +18,8 @@ */ package org.springframework.data.neo4j.core.schema; +import java.util.Collection; + import org.apiguardian.api.API; /** @@ -32,7 +34,8 @@ public interface Scanner { /** * Scans the relevant classes and creates a schema. * + * @param persistentClasses The classes to scan * @return The new schema. */ - Schema scan(); + Schema scan(Collection> persistentClasses); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/DefaultNeo4jStatementRunnerSupplier.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/DefaultNeo4jStatementRunnerSupplier.java deleted file mode 100644 index d18445f6a..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/DefaultNeo4jStatementRunnerSupplier.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019 "Neo4j," - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.data.neo4j.core.transaction; - -import lombok.RequiredArgsConstructor; - -import java.util.Collections; - -import org.neo4j.driver.v1.AccessMode; -import org.neo4j.driver.v1.Driver; -import org.neo4j.driver.v1.StatementRunner; -import org.springframework.transaction.support.TransactionSynchronizationManager; - -/** - * @author Michael J. Simons - */ -@RequiredArgsConstructor -public class DefaultNeo4jStatementRunnerSupplier implements StatementRunnerSupplier { - - private final Driver driver; - - @Override - public StatementRunner get() { - - if (!TransactionSynchronizationManager.isSynchronizationActive()) { - return driver.session(); - } - - // Try existing transaction - Neo4jResourceHolder resourceHolder = (Neo4jResourceHolder) TransactionSynchronizationManager - .getResource(driver); - - if (resourceHolder != null) { - - return resourceHolder.getTransaction(); - } - - // Manually create a new synchronization - resourceHolder = new Neo4jResourceHolder(driver.session(AccessMode.WRITE, Collections.emptyList())); - - TransactionSynchronizationManager.registerSynchronization( - new Neo4jSessionSynchronization(resourceHolder, driver)); - resourceHolder.setSynchronizedWithTransaction(true); - TransactionSynchronizationManager.bindResource(driver, resourceHolder); - - return resourceHolder.getTransaction(); - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jResourceHolder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jConnectionHolder.java similarity index 92% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jResourceHolder.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jConnectionHolder.java index f49a0925a..c330bf909 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jResourceHolder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jConnectionHolder.java @@ -32,17 +32,17 @@ import org.springframework.util.Assert; * * @author Michael J. Simons */ -public class Neo4jResourceHolder extends ResourceHolderSupport { +public class Neo4jConnectionHolder extends ResourceHolderSupport { private final Session session; private final Transaction transaction; - Neo4jResourceHolder(Session session) { + Neo4jConnectionHolder(Session session) { this(session, TransactionConfig.empty()); } - Neo4jResourceHolder(Session session, TransactionConfig transactionConfig) { + Neo4jConnectionHolder(Session session, TransactionConfig transactionConfig) { this.session = session; this.transaction = this.session.beginTransaction(transactionConfig); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jSessionSynchronization.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jSessionSynchronization.java index 6b6022756..b1883ed8f 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jSessionSynchronization.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jSessionSynchronization.java @@ -24,17 +24,17 @@ import org.springframework.transaction.support.TransactionSynchronization; /** * Neo4j specific {@link ResourceHolderSynchronization} for resource cleanup at the end of a transaction when - * participating in a non-native Neoj4 transaction, such as a Jta transaction. + * participating in a non-native Neo4j transaction, such as a Jta transaction. */ class Neo4jSessionSynchronization - extends ResourceHolderSynchronization { + extends ResourceHolderSynchronization { - private final Neo4jResourceHolder localResourceHolder; + private final Neo4jConnectionHolder localConnectionHolder; - Neo4jSessionSynchronization(Neo4jResourceHolder resourceHolder, Driver driver) { + Neo4jSessionSynchronization(Neo4jConnectionHolder connectionHolder, Driver driver) { - super(resourceHolder, driver); - this.localResourceHolder = resourceHolder; + super(connectionHolder, driver); + this.localConnectionHolder = connectionHolder; } /* @@ -51,7 +51,7 @@ class Neo4jSessionSynchronization * @see org.springframework.transaction.support.ResourceHolderSynchronization#processResourceAfterCommit(java.lang.Object) */ @Override - protected void processResourceAfterCommit(Neo4jResourceHolder resourceHolder) { + protected void processResourceAfterCommit(Neo4jConnectionHolder resourceHolder) { super.processResourceAfterCommit(resourceHolder); @@ -67,8 +67,8 @@ class Neo4jSessionSynchronization @Override public void afterCompletion(int status) { - if (status == TransactionSynchronization.STATUS_ROLLED_BACK && localResourceHolder.hasActiveTransaction()) { - localResourceHolder.rollback(); + if (status == TransactionSynchronization.STATUS_ROLLED_BACK && localConnectionHolder.hasActiveTransaction()) { + localConnectionHolder.rollback(); } super.afterCompletion(status); @@ -79,7 +79,7 @@ class Neo4jSessionSynchronization * @see org.springframework.transaction.support.ResourceHolderSynchronization#releaseResource(java.lang.Object, java.lang.Object) */ @Override - protected void releaseResource(Neo4jResourceHolder resourceHolder, Object resourceKey) { + protected void releaseResource(Neo4jConnectionHolder resourceHolder, Object resourceKey) { if (resourceHolder.hasActiveSession()) { resourceHolder.close(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java index 486d40d79..7a4c1abca 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java @@ -18,15 +18,23 @@ */ package org.springframework.data.neo4j.core.transaction; +import lombok.extern.slf4j.Slf4j; + import java.time.Duration; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.apiguardian.api.API; import org.neo4j.driver.v1.AccessMode; import org.neo4j.driver.v1.Driver; import org.neo4j.driver.v1.Session; import org.neo4j.driver.v1.TransactionConfig; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.data.neo4j.core.NodeManagerFactory; import org.springframework.lang.Nullable; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.InvalidIsolationLevelException; @@ -41,23 +49,44 @@ import org.springframework.transaction.support.TransactionSynchronizationUtils; import org.springframework.util.Assert; /** - * Dedicated {@link org.springframework.transaction.PlatformTransactionManager} for native Neo4j transactions. + * Dedicated {@link org.springframework.transaction.PlatformTransactionManager} for native Neo4j transactions. This + * transaction manager will synchronize a pair of a native Neo4j session/transaction and one instance of a + * {@link org.springframework.data.neo4j.core.NodeManager} with the transaction. * * @author Michael J. Simons */ -public class Neo4jTransactionManager extends AbstractPlatformTransactionManager { +@Slf4j +public class Neo4jTransactionManager extends AbstractPlatformTransactionManager implements BeanFactoryAware { private final Driver driver; + @Nullable + private NodeManagerFactory nodeManagerFactory; + @API(status = API.Status.STABLE, since = "1.0") public Neo4jTransactionManager(Driver driver) { this.driver = driver; } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + try { + this.setNodeManagerFactory(beanFactory.getBean(NodeManagerFactory.class)); + } catch (NoSuchBeanDefinitionException ex) { + log.warn("Found no instance of {}", NodeManagerFactory.class); + } + } + + public void setNodeManagerFactory(@Nullable NodeManagerFactory nodeManagerFactory) { + + // TODO Check if the NodeManager uses the same datasource aka driver as we do + this.nodeManagerFactory = nodeManagerFactory; + } + @Override protected Object doGetTransaction() throws TransactionException { - Neo4jResourceHolder resourceHolder = (Neo4jResourceHolder) TransactionSynchronizationManager + Neo4jConnectionHolder resourceHolder = (Neo4jConnectionHolder) TransactionSynchronizationManager .getResource(driver); return new Neo4jTransactionObject(resourceHolder); } @@ -83,13 +112,21 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager try { Session session = this.driver.session(accessMode, bookmarks); - Neo4jResourceHolder resourceHolder = new Neo4jResourceHolder(session, transactionConfig); - transactionObject.setResourceHolder(resourceHolder); + Neo4jConnectionHolder connectionHolder = new Neo4jConnectionHolder(session, transactionConfig); + connectionHolder.setSynchronizedWithTransaction(true); + transactionObject.setResourceHolder(connectionHolder); + TransactionSynchronizationManager.bindResource(this.driver, connectionHolder); - resourceHolder.setSynchronizedWithTransaction(true); - TransactionSynchronizationManager.bindResource(driver, resourceHolder); + if (this.nodeManagerFactory != null) { + NodeManagerHolder nodeManagerHolder = new NodeManagerHolder(this.nodeManagerFactory.createNodeManager()); + nodeManagerHolder.setSynchronizedWithTransaction(true); + transactionObject.setNodeManagerHolder(nodeManagerHolder); + TransactionSynchronizationManager.bindResource(this.nodeManagerFactory, nodeManagerHolder); + } } catch (Exception ex) { + ex.printStackTrace(); throw new TransactionSystemException(String.format("Could not open a new Neo4j session: %s", ex.getMessage())); + } } @@ -134,8 +171,14 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager Neo4jTransactionObject transactionObject = extractNeo4jTransaction(transaction); transactionObject.getRequiredResourceHolder().close(); + transactionObject.getNodeManagerHolder() + .map(NodeManagerHolder::getNodeManager) + .ifPresent(nodeManager -> nodeManager.flush()); TransactionSynchronizationManager.unbindResource(driver); + if (this.nodeManagerFactory != null) { + TransactionSynchronizationManager.unbindResource(this.nodeManagerFactory); + } } private static TransactionConfig createTransactionConfigFrom(TransactionDefinition definition) { @@ -171,16 +214,21 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager return extractNeo4jTransaction(status.getTransaction()); } + static class Neo4jTransactionObject implements SmartTransactionObject { - private static final String RESOURCE_HOLDER_NOT_PRESENT_MESSAGE = "Neo4jResourceHolder is required but not present. o_O"; + private static final String RESOURCE_HOLDER_NOT_PRESENT_MESSAGE = "Neo4jConnectionHolder is required but not present. o_O"; // The resource holder is null when the call to TransactionSynchronizationManager.getResource // in Neo4jTransactionManager.doGetTransaction didn't return a corresponding resource holder. // If it is null, there's no existing session / transaction. - private @Nullable Neo4jResourceHolder resourceHolder; + @Nullable + private Neo4jConnectionHolder resourceHolder; - Neo4jTransactionObject(@Nullable Neo4jResourceHolder resourceHolder) { + @Nullable + private NodeManagerHolder nodeManagerHolder; + + Neo4jTransactionObject(@Nullable Neo4jConnectionHolder resourceHolder) { this.resourceHolder = resourceHolder; } @@ -190,23 +238,31 @@ public class Neo4jTransactionManager extends AbstractPlatformTransactionManager * * @param resourceHolder A newly created resource holder with a fresh drivers session, */ - void setResourceHolder(@Nullable Neo4jResourceHolder resourceHolder) { + void setResourceHolder(@Nullable Neo4jConnectionHolder resourceHolder) { this.resourceHolder = resourceHolder; } + void setNodeManagerHolder(@Nullable NodeManagerHolder nodeManagerHolder) { + this.nodeManagerHolder = nodeManagerHolder; + } + /** - * @return {@literal true} if a {@link Neo4jResourceHolder} is set. + * @return {@literal true} if a {@link Neo4jConnectionHolder} is set. */ boolean hasResourceHolder() { return resourceHolder != null; } - Neo4jResourceHolder getRequiredResourceHolder() { + Neo4jConnectionHolder getRequiredResourceHolder() { Assert.state(hasResourceHolder(), RESOURCE_HOLDER_NOT_PRESENT_MESSAGE); return resourceHolder; } + Optional getNodeManagerHolder() { + return Optional.ofNullable(nodeManagerHolder); + } + void setRollbackOnly() { getRequiredResourceHolder().setRollbackOnly(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java new file mode 100644 index 000000000..c85cb08e1 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtils.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.transaction; + +import java.util.Collections; + +import org.neo4j.driver.v1.AccessMode; +import org.neo4j.driver.v1.Driver; +import org.neo4j.driver.v1.StatementRunner; +import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.NodeManagerFactory; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +/** + * Internal use only. + */ +public final class Neo4jTransactionUtils { + + public static StatementRunner retrieveTransactionalStatementRunner(Driver driver) { + + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + return driver.session(); + } + + // Try existing transaction + Neo4jConnectionHolder connectionHolder = (Neo4jConnectionHolder) TransactionSynchronizationManager + .getResource(driver); + + if (connectionHolder != null) { + + return connectionHolder.getTransaction(); + } + + // Manually create a new synchronization + connectionHolder = new Neo4jConnectionHolder(driver.session(AccessMode.WRITE, Collections.emptyList())); + connectionHolder.setSynchronizedWithTransaction(true); + + TransactionSynchronizationManager.registerSynchronization( + new Neo4jSessionSynchronization(connectionHolder, driver)); + + TransactionSynchronizationManager.bindResource(driver, connectionHolder); + + return connectionHolder.getTransaction(); + } + + public static NodeManager retrieveTransactionalNodeManager(NodeManagerFactory nodeManagerFactory) { + + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + return nodeManagerFactory.createNodeManager(); + } + + // Try existing transaction + NodeManagerHolder nodeManagerHolder = (NodeManagerHolder) TransactionSynchronizationManager + .getResource(nodeManagerFactory); + + if (nodeManagerHolder != null) { + + return nodeManagerHolder.getNodeManager(); + } + + // Manually create a new synchronization + nodeManagerHolder = new NodeManagerHolder(nodeManagerFactory.createNodeManager()); + nodeManagerHolder.setSynchronizedWithTransaction(true); + + TransactionSynchronizationManager.registerSynchronization( + new NodeManagerSynchronization(nodeManagerHolder, nodeManagerFactory)); + + TransactionSynchronizationManager.bindResource(nodeManagerFactory, nodeManagerHolder); + + return nodeManagerHolder.getNodeManager(); + } + + private Neo4jTransactionUtils() { + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/StatementRunnerSupplier.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java similarity index 56% rename from spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/StatementRunnerSupplier.java rename to spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java index 4d2a2934c..44d13ff18 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/StatementRunnerSupplier.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerHolder.java @@ -18,15 +18,30 @@ */ package org.springframework.data.neo4j.core.transaction; -import java.util.function.Supplier; +import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.lang.Nullable; +import org.springframework.transaction.support.ResourceHolderSupport; +import org.springframework.util.Assert; /** - * Used for obtaining {@link org.neo4j.driver.v1.Session session bound (standard or reactive)} bound statement runners. + * Dedicated holder for storing a NodeManager inside a transaction. *

* Note: Intended for internal usage only. * * @author Michael J. Simons */ -@FunctionalInterface -public interface StatementRunnerSupplier extends Supplier { +public class NodeManagerHolder extends ResourceHolderSupport { + + @Nullable + private final NodeManager nodeManager; + + public NodeManagerHolder(@Nullable NodeManager nodeManager) { + this.nodeManager = nodeManager; + } + + public NodeManager getNodeManager() { + Assert.state(this.nodeManager != null, "No NodeManager available"); + return this.nodeManager; + } + } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java new file mode 100644 index 000000000..881f1d565 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/transaction/NodeManagerSynchronization.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.core.transaction; + +import org.springframework.data.neo4j.core.NodeManagerFactory; +import org.springframework.transaction.support.ResourceHolderSynchronization; + +/** + * TODO Update Licence header https + * + * @author Michael J. Simons + */ +public class NodeManagerSynchronization + extends ResourceHolderSynchronization { + + private final NodeManagerHolder localNodeManagerHolder; + + NodeManagerSynchronization(NodeManagerHolder nodeManagerHolder, NodeManagerFactory nodeManagerFactory) { + + super(nodeManagerHolder, nodeManagerFactory); + this.localNodeManagerHolder = nodeManagerHolder; + } + + @Override + protected void flushResource(NodeManagerHolder resourceHolder) { + resourceHolder.getNodeManager().flush(); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java index 90cdedc3b..364fdc552 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/EnableNeo4jRepositories.java @@ -31,7 +31,7 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.core.annotation.AliasFor; -import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean; import org.springframework.transaction.PlatformTransactionManager; @@ -78,9 +78,9 @@ public @interface EnableNeo4jRepositories { Class repositoryFactoryBeanClass() default Neo4jRepositoryFactoryBean.class; /** - * Configures the name of the {@link Neo4jTemplate} bean to be used with the repositories detected. + * Configures the name of the {@link NodeManager} bean to be used with the repositories detected. */ - String neo4jTemplateRef() default DEFAULT_NEO4J_TEMPLATE_BEAN_NAME; + String nodeManagerFactoryRef() default DEFAULT_NODE_MANAGER_FACTORY_BEAN_NAME; /** * Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java index cecf03300..6f62637e9 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/config/Neo4jRepositoryConfigurationExtension.java @@ -24,9 +24,12 @@ import java.util.Collections; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.core.schema.Node; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean; +import org.springframework.data.neo4j.repository.support.NodeManagerFactoryBean; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; import org.springframework.data.repository.config.RepositoryConfigurationSource; @@ -48,9 +51,14 @@ public class Neo4jRepositoryConfigurationExtension extends RepositoryConfigurati */ static final String GENERATE_BEAN_NAME = "(generated)"; - static final String DEFAULT_NEO4J_TEMPLATE_BEAN_NAME = "neo4jTemplate"; + static final String DEFAULT_NODE_MANAGER_FACTORY_BEAN_NAME = "nodeManagerFactory"; static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager"; + /** + * Holds the name of the shared NodeManagerBean created from the factory with the configured name. + */ + private String generatedNodeManagerBeanName; + /* * (non-Javadoc) * @see org.springframework.data.repository.config14.RepositoryConfigurationExtension#getRepositoryFactoryBeanClassName() @@ -89,7 +97,57 @@ public class Neo4jRepositoryConfigurationExtension extends RepositoryConfigurati builder.addPropertyValue("transactionManager", source.getAttribute("transactionManagerRef").orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)); - builder.addPropertyReference("neo4jOperations", - source.getAttribute("neo4jTemplateRef").orElse(DEFAULT_NEO4J_TEMPLATE_BEAN_NAME)); + builder.addPropertyReference("nodeManager", this.generatedNodeManagerBeanName); + } + + @Override + public void registerBeansForRoot(BeanDefinitionRegistry registry, + RepositoryConfigurationSource config) { + + // Mapping context + AbstractBeanDefinition neo4jMappingContextBeanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(Neo4jMappingContext.class) + .getBeanDefinition(); + String nameOfMappingContextBean = registerWithSourceAndGeneratedBeanName( + neo4jMappingContextBeanDefinition, registry, config); + + // Augmented node manager factory (creating injectable, shared instances of NodeManager) + String nameOfNodeManagerFactory = config.getAttribute("nodeManagerFactoryRef") + .orElse(DEFAULT_NODE_MANAGER_FACTORY_BEAN_NAME); + AbstractBeanDefinition sharedSessionCreatorBeanDefinition = BeanDefinitionBuilder + .rootBeanDefinition(NodeManagerFactoryBean.class) + .addConstructorArgReference(nameOfNodeManagerFactory) + .addConstructorArgReference(nameOfMappingContextBean) + .getBeanDefinition(); + this.generatedNodeManagerBeanName = registerWithSourceAndGeneratedBeanName( + sharedSessionCreatorBeanDefinition, registry, config); + } + + /** + * Uses a generated bean name if {@code configuredBeanName} is equal to {@link #GENERATE_BEAN_NAME}, otherwise uses + * the configured bean name to register the new bean. Does not check if there's already a bean under the configured + * name but throws a {@link org.springframework.beans.factory.BeanDefinitionStoreException}. Checks whether a bean is + * already registered under {@code configuredBeanName} in the given {@link BeanDefinitionRegistry} and uses a + * generated name for registering the bean instead. If not, the suggested bean name is used. + * + * @param bean must not be {@literal null}. + * @param registry must not be {@literal null}. + * @param configuredBeanName must not be {@literal null} or empty. + * @param source must not be {@literal null}. + * @return the bean name used for registering the given {@link AbstractBeanDefinition} + * @throws org.springframework.beans.factory.BeanDefinitionStoreException if the BeanDefinition is invalid or if there + * is already a BeanDefinition for the specified bean name * (and we are not allowed to override it) + */ + private static String registerWithGeneratedNameOrUseConfigured(AbstractBeanDefinition bean, + BeanDefinitionRegistry registry, String configuredBeanName, Object source) { + + String registeredBeanName = configuredBeanName; + if (GENERATE_BEAN_NAME.equals(configuredBeanName)) { + registeredBeanName = registerWithSourceAndGeneratedBeanName(bean, registry, source); + } else { + bean.setSource(source); + registry.registerBeanDefinition(configuredBeanName, bean); + } + return registeredBeanName; } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java index 3fdc80d15..f390a8bf3 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartTreeNeo4jQuery.java @@ -18,7 +18,7 @@ */ package org.springframework.data.neo4j.repository.query; -import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; @@ -26,10 +26,11 @@ import org.springframework.data.repository.query.RepositoryQuery; * Implementation of {@link RepositoryQuery} for derived finder methods. * * @author Gerrit Meier - **/ + * @author Michael J. Simons + */ public class PartTreeNeo4jQuery extends AbstractNeo4jQuery { - public PartTreeNeo4jQuery(Neo4jQueryMethod queryMethod, Neo4jOperations neo4jOperations) { + public PartTreeNeo4jQuery(Neo4jQueryMethod queryMethod, NodeManager nodeManager) { } @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java index 2e6222516..5ce499521 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StringBasedNeo4jQuery.java @@ -18,7 +18,7 @@ */ package org.springframework.data.neo4j.repository.query; -import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; @@ -26,21 +26,22 @@ import org.springframework.data.repository.query.RepositoryQuery; * Implementation of {@link RepositoryQuery} for String based custom Cypher query. * * @author Gerrit Meier + * @author Michael J. Simons */ public class StringBasedNeo4jQuery extends AbstractNeo4jQuery { private final Neo4jQueryMethod queryMethod; - private final Neo4jOperations neo4jOperations; + private final NodeManager nodeManager; - public StringBasedNeo4jQuery(Neo4jQueryMethod queryMethod, Neo4jOperations neo4jOperations) { + public StringBasedNeo4jQuery(Neo4jQueryMethod queryMethod, NodeManager nodeManager) { this.queryMethod = queryMethod; - this.neo4jOperations = neo4jOperations; + this.nodeManager = nodeManager; } @Override public Object execute(Object[] parameters) { - return neo4jOperations.executeQuery(queryMethod.getAnnotatedQuery()); + return nodeManager.executeQuery(queryMethod.getAnnotatedQuery()); } @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java index c2f8943fd..d7781e0a1 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java @@ -21,7 +21,7 @@ package org.springframework.data.neo4j.repository.support; import java.lang.reflect.Method; import java.util.Optional; -import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.query.Neo4jQueryMethod; import org.springframework.data.neo4j.repository.query.PartTreeNeo4jQuery; @@ -41,13 +41,14 @@ import org.springframework.data.repository.query.RepositoryQuery; * Factory to create {@link Neo4jRepository} instances. * * @author Gerrit Meier + * @author Michael J. Simons */ -class Neo4jRepositoryFactory extends RepositoryFactorySupport { +final class Neo4jRepositoryFactory extends RepositoryFactorySupport { - private final Neo4jOperations neo4jOperations; + private final NodeManager nodeManager; - Neo4jRepositoryFactory(Neo4jOperations neo4jOperations) { - this.neo4jOperations = neo4jOperations; + Neo4jRepositoryFactory(NodeManager nodeManager) { + this.nodeManager = nodeManager; } @Override @@ -57,7 +58,7 @@ class Neo4jRepositoryFactory extends RepositoryFactorySupport { @Override protected Object getTargetRepository(RepositoryInformation metadata) { - return getTargetRepositoryViaReflection(metadata, neo4jOperations); + return getTargetRepositoryViaReflection(metadata, nodeManager); } @Override @@ -73,18 +74,18 @@ class Neo4jRepositoryFactory extends RepositoryFactorySupport { protected Optional getQueryLookupStrategy(Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { - return Optional.of(new Neo4jQueryLookupStrategy(neo4jOperations, evaluationContextProvider)); + return Optional.of(new Neo4jQueryLookupStrategy(nodeManager, evaluationContextProvider)); } private class Neo4jQueryLookupStrategy implements QueryLookupStrategy { - private final Neo4jOperations neo4jOperations; + private final NodeManager nodeManager; private final QueryMethodEvaluationContextProvider evaluationContextProvider; - private Neo4jQueryLookupStrategy(Neo4jOperations neo4jOperations, + private Neo4jQueryLookupStrategy(NodeManager nodeManager, QueryMethodEvaluationContextProvider evaluationContextProvider) { - this.neo4jOperations = neo4jOperations; + this.nodeManager = nodeManager; this.evaluationContextProvider = evaluationContextProvider; } @@ -97,10 +98,10 @@ class Neo4jRepositoryFactory extends RepositoryFactorySupport { Neo4jQueryMethod queryMethod = new Neo4jQueryMethod(method, metadata, factory); if (queryMethod.hasAnnotatedQuery()) { - return new StringBasedNeo4jQuery(queryMethod, neo4jOperations); + return new StringBasedNeo4jQuery(queryMethod, nodeManager); } - return new PartTreeNeo4jQuery(queryMethod, neo4jOperations); + return new PartTreeNeo4jQuery(queryMethod, nodeManager); } } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java index 7a6a90080..b452be392 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactoryBean.java @@ -20,7 +20,7 @@ package org.springframework.data.neo4j.repository.support; import java.io.Serializable; -import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; @@ -35,7 +35,7 @@ import org.springframework.data.repository.core.support.TransactionalRepositoryF public class Neo4jRepositoryFactoryBean, S, ID extends Serializable> extends TransactionalRepositoryFactoryBeanSupport { - private Neo4jOperations neo4jOperations; + private NodeManager nodeManager; /** * Creates a new {@link TransactionalRepositoryFactoryBeanSupport} for the given repository interface. @@ -46,13 +46,13 @@ public class Neo4jRepositoryFactoryBean, S, ID exten super(repositoryInterface); } - public void setNeo4jOperations(Neo4jOperations neo4jOperations) { - this.neo4jOperations = neo4jOperations; + public void setNodeManager(NodeManager nodeManager) { + this.nodeManager = nodeManager; } @Override protected RepositoryFactorySupport doCreateRepositoryFactory() { - return new Neo4jRepositoryFactory(neo4jOperations); + return new Neo4jRepositoryFactory(nodeManager); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java new file mode 100644 index 000000000..0cf0904f2 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/NodeManagerFactoryBean.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.repository.support; + +import org.apiguardian.api.API; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.NodeManagerFactory; +import org.springframework.data.neo4j.core.mapping.MappingContextBasedScannerImpl; +import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils; + +/** + * This is a shim that integrates a {@link NodeManagerFactory} with Spring Datas infrastructure. It takes in the factory + * that needs to be provided by the user and the mapping context provided by our infrastructure. The node manager factory + * is than augment by our {@link Neo4jTransactionUtils} so that it doesn't use unmanaged native transaction, but managed + * native transaction. Furthermore, the default noop scanner is replaced by a scanner based on our Neo4j mapping context. + * + * @author Gerrit Meier + * @author Michael J. Simons + */ +@API(status = API.Status.INTERNAL, since = "1.0") +public final class NodeManagerFactoryBean implements InitializingBean, FactoryBean { + private final NodeManagerFactory target; + + private final Neo4jMappingContext neo4jMappingContext; + + public NodeManagerFactoryBean(NodeManagerFactory target, Neo4jMappingContext neo4jMappingContext) { + + this.target = target; + this.neo4jMappingContext = neo4jMappingContext; + } + + @Override + public NodeManager getObject() { + return SharedNodeManagerCreator.createSharedNodeManager(this.target); + } + + @Override + public Class getObjectType() { + return NodeManager.class; + } + + @Override + public void afterPropertiesSet() { + target.setStatementRunnerProvider(Neo4jTransactionUtils::retrieveTransactionalStatementRunner); + target.setScanner(new MappingContextBasedScannerImpl(this.neo4jMappingContext)); + target.initialize(); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java new file mode 100644 index 000000000..f2ca993d9 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SharedNodeManagerCreator.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2019 "Neo4j," + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.neo4j.repository.support; + +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + +import org.springframework.data.neo4j.core.NodeManager; +import org.springframework.data.neo4j.core.NodeManagerFactory; +import org.springframework.data.neo4j.core.transaction.Neo4jTransactionUtils; +import org.springframework.lang.Nullable; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.ReflectionUtils; + +/** + * @author Michael J. Simons + */ +@Slf4j +final class SharedNodeManagerCreator { + + private static final Set TRANSACTION_REQUIRING_METHODS; + + static { + Set tmp = new HashSet<>(); + + tmp.add("save"); + tmp.add("delete"); + + TRANSACTION_REQUIRING_METHODS = Collections.unmodifiableSet(tmp); + } + + public static NodeManager createSharedNodeManager(NodeManagerFactory nodeManagerFactory) { + + return (NodeManager) Proxy.newProxyInstance(SharedNodeManagerCreator.class.getClassLoader(), + new Class[] { NodeManager.class }, new SharedNodeManagerInvocationHandler(nodeManagerFactory)); + } + + private static class SharedNodeManagerInvocationHandler implements InvocationHandler, Serializable { + + private final NodeManagerFactory targetFactory; + + SharedNodeManagerInvocationHandler(NodeManagerFactory targetFactory) { + this.targetFactory = targetFactory; + } + + @Override + @Nullable + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + String methodName = method.getName(); + switch (methodName) { + case "equals": + // Only consider equal when proxies are identical. + return (proxy == args[0]); + case "hashCode": + // Use hashCode of proxy. + return hashCode(); + case "toString": + // Deliver toString without touching a target Session. + return "Shared Session proxy for target factory [" + this.targetFactory + "]"; + default: + Function methodCall = + targetSession -> ReflectionUtils.invokeMethod(method, targetSession, args); + return invokeInTransaction(methodName, methodCall); + } + } + + private Object invokeInTransaction(String methodName, Function methodCall) { + + // Determine current Session: either the transactional one + // managed by the factory or a temporary one for the given invocation. + NodeManager target = Neo4jTransactionUtils.retrieveTransactionalNodeManager(this.targetFactory); + + if (TRANSACTION_REQUIRING_METHODS.contains(methodName)) { + if (target == null || (!TransactionSynchronizationManager.isActualTransactionActive() + && target.getTransaction() != null)) { + throw new IllegalStateException("No NodeManager with actual transaction available " + + "for current thread - cannot reliably process '" + methodName + "' call"); + } + } + + // Regular Session operations. + boolean isNewSession = false; + if (target == null) { + log.debug("Creating new Session for shared Session invocation"); + target = this.targetFactory.createNodeManager(); + isNewSession = true; + } + + // Invoke method on current Session. + try { + return methodCall.apply(target); + } finally { + if (isNewSession) { + target.flush(); + } + } + } + } + + private SharedNodeManagerCreator() { + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java index ceebc17b0..c84107ab2 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/support/SimpleNeo4jRepository.java @@ -24,7 +24,7 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.neo4j.core.Neo4jOperations; +import org.springframework.data.neo4j.core.NodeManager; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -39,10 +39,10 @@ import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly = true) class SimpleNeo4jRepository implements Neo4jRepository { - private final Neo4jOperations neo4jOperations; + private final NodeManager nodeManager; - SimpleNeo4jRepository(Neo4jOperations neo4jOperations) { - this.neo4jOperations = neo4jOperations; + SimpleNeo4jRepository(NodeManager nodeManager) { + this.nodeManager = nodeManager; } @Override diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentEntityImplTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4JPersistentEntityTest.java similarity index 96% rename from spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentEntityImplTest.java rename to spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4JPersistentEntityTest.java index e3f6b6040..0da61ef8c 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jPersistentEntityImplTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/DefaultNeo4JPersistentEntityTest.java @@ -26,7 +26,7 @@ import org.springframework.data.neo4j.core.schema.Node; /** * @author Michael J. Simons */ -public class Neo4jPersistentEntityImplTest { +public class DefaultNeo4JPersistentEntityTest { private Neo4jMappingContext mappingContext = new Neo4jMappingContext(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java index 017f53111..87037f81c 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/mapping/MappingContextBasedScannerImplTest.java @@ -49,7 +49,7 @@ class MappingContextBasedScannerImplTest { neo4jMappingContext.initialize(); final Scanner scanner = new MappingContextBasedScannerImpl(neo4jMappingContext); - Schema schema = scanner.scan(); + Schema schema = scanner.scan(new HashSet<>(Arrays.asList(BikeNode.class, UserNode.class))); Optional optionalUserNodeDescription = schema.getNodeDescription("User"); assertThat(optionalUserNodeDescription) diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/transaction/DefaultNeo4jStatementRunnerSupplierTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtilsTest.java similarity index 88% rename from spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/transaction/DefaultNeo4jStatementRunnerSupplierTest.java rename to spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtilsTest.java index 3a6266f50..ec74b6ba2 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/transaction/DefaultNeo4jStatementRunnerSupplierTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionUtilsTest.java @@ -54,7 +54,7 @@ import org.springframework.transaction.support.TransactionTemplate; */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class DefaultNeo4jStatementRunnerSupplierTest { +class Neo4jTransactionUtilsTest { @Mock private Driver driver; @@ -90,11 +90,8 @@ class DefaultNeo4jStatementRunnerSupplierTest { @Test void shouldWorkWithoutSynchronizations() { - StatementRunnerSupplier statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier( - driver); - @SuppressWarnings({ "unused" }) - StatementRunner statementRunner = statementRunnerSupplier.get(); + StatementRunner statementRunner = Neo4jTransactionUtils.retrieveTransactionalStatementRunner(driver); verify(driver).session(); verifyNoMoreInteractions(driver, session, transaction); @@ -107,8 +104,6 @@ class DefaultNeo4jStatementRunnerSupplierTest { Neo4jTransactionManager txManager = new Neo4jTransactionManager(driver); TransactionTemplate txTemplate = new TransactionTemplate(txManager); - StatementRunnerSupplier statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier( - driver); txTemplate.execute(new TransactionCallbackWithoutResult() { @@ -120,7 +115,8 @@ class DefaultNeo4jStatementRunnerSupplierTest { assertThat(TransactionSynchronizationManager.hasResource(driver)).isTrue(); @SuppressWarnings({ "unused" }) - StatementRunner statementRunner = statementRunnerSupplier.get(); + StatementRunner statementRunner = Neo4jTransactionUtils + .retrieveTransactionalStatementRunner(driver); transactionStatus.setRollbackOnly(); } @@ -142,8 +138,6 @@ class DefaultNeo4jStatementRunnerSupplierTest { Neo4jTransactionManager txManager = new Neo4jTransactionManager(driver); TransactionTemplate txTemplate = new TransactionTemplate(txManager); - StatementRunnerSupplier statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier( - driver); txTemplate.execute(new TransactionCallbackWithoutResult() { @@ -151,7 +145,8 @@ class DefaultNeo4jStatementRunnerSupplierTest { protected void doInTransactionWithoutResult(TransactionStatus outerStatus) { @SuppressWarnings({ "unused" }) - StatementRunner outerStatementRunner = statementRunnerSupplier.get(); + StatementRunner outerStatementRunner = Neo4jTransactionUtils + .retrieveTransactionalStatementRunner(driver); assertThat(outerStatus.isNewTransaction()).isTrue(); txTemplate.execute(new TransactionCallbackWithoutResult() { @@ -162,7 +157,8 @@ class DefaultNeo4jStatementRunnerSupplierTest { assertThat(innerStatus.isNewTransaction()).isFalse(); @SuppressWarnings({ "unused" }) - StatementRunner innerStatementRunner = statementRunnerSupplier.get(); + StatementRunner innerStatementRunner = Neo4jTransactionUtils + .retrieveTransactionalStatementRunner(driver); } }); @@ -193,8 +189,6 @@ class DefaultNeo4jStatementRunnerSupplierTest { JtaTransactionManager txManager = new JtaTransactionManager(userTransaction); TransactionTemplate txTemplate = new TransactionTemplate(txManager); - StatementRunnerSupplier statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier( - driver); txTemplate.execute(new TransactionCallbackWithoutResult() { @@ -206,7 +200,8 @@ class DefaultNeo4jStatementRunnerSupplierTest { assertThat(TransactionSynchronizationManager.hasResource(driver)).isFalse(); @SuppressWarnings({ "unused" }) - StatementRunner statementRunner = statementRunnerSupplier.get(); + StatementRunner statementRunner = Neo4jTransactionUtils + .retrieveTransactionalStatementRunner(driver); assertThat(TransactionSynchronizationManager.hasResource(driver)).isTrue(); } @@ -233,8 +228,6 @@ class DefaultNeo4jStatementRunnerSupplierTest { JtaTransactionManager txManager = new JtaTransactionManager(userTransaction); TransactionTemplate txTemplate = new TransactionTemplate(txManager); - StatementRunnerSupplier statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier( - driver); txTemplate.execute(new TransactionCallbackWithoutResult() { @@ -246,7 +239,8 @@ class DefaultNeo4jStatementRunnerSupplierTest { assertThat(TransactionSynchronizationManager.hasResource(driver)).isFalse(); @SuppressWarnings({ "unused" }) - StatementRunner statementRunner = statementRunnerSupplier.get(); + StatementRunner statementRunner = Neo4jTransactionUtils + .retrieveTransactionalStatementRunner(driver); assertThat(TransactionSynchronizationManager.hasResource(driver)).isTrue(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java index cef268283..a51c641cf 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/PersonRepository.java @@ -23,9 +23,11 @@ import java.util.List; import org.neo4j.driver.v1.Record; import org.springframework.data.neo4j.repository.Neo4jRepository; import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.transaction.annotation.Transactional; public interface PersonRepository extends Neo4jRepository { + @Transactional @Query("RETURN 1") List customQuery(); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java index 9dc6ef2b8..4468b06e2 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/integration/RepositoryIT.java @@ -31,8 +31,7 @@ import org.neo4j.driver.v1.Record; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.data.neo4j.core.Neo4jOperations; -import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.data.neo4j.core.NodeManagerFactory; import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager; import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories; import org.springframework.test.context.ContextConfiguration; @@ -90,15 +89,15 @@ class RepositoryIT { } @Bean - public PlatformTransactionManager transactionManager() { + public NodeManagerFactory nodeManagerFactory(Driver driver) { - return new Neo4jTransactionManager(driver()); + return new NodeManagerFactory(driver, Person.class); } @Bean - public Neo4jOperations neo4jTemplate() { + public PlatformTransactionManager transactionManager(Driver driver) { - return new Neo4jTemplate(driver()); + return new Neo4jTransactionManager(driver); } } }