Implement Neo4jTransactionManager.
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
lombok.nonNull.exceptionType = IllegalArgumentException
|
||||
lombok.log.fieldName = LOG
|
||||
|
||||
|
||||
13
pom.xml
13
pom.xml
@@ -21,8 +21,8 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<version>2.2.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
<maven-surefire-plugin.version>3.0.0-M2</maven-surefire-plugin.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<mockito.version>2.23.4</mockito.version>
|
||||
<neo4j-java-driver.version>1.7.2</neo4j-java-driver.version>
|
||||
<neo4j.version>3.5.2</neo4j.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -151,6 +152,16 @@
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>neo4j</artifactId>
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-neo4j-rx-parent</artifactId>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
@@ -58,6 +58,12 @@
|
||||
<artifactId>rxjava</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.transaction</groupId>
|
||||
<artifactId>jta</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apiguardian</groupId>
|
||||
<artifactId>apiguardian-api</artifactId>
|
||||
@@ -72,6 +78,16 @@
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j.driver</groupId>
|
||||
<artifactId>neo4j-java-driver</artifactId>
|
||||
@@ -81,6 +97,10 @@
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
|
||||
@@ -18,42 +18,33 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.core;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.driver.v1.Driver;
|
||||
import org.neo4j.driver.v1.Session;
|
||||
import org.neo4j.driver.v1.StatementResult;
|
||||
import org.neo4j.driver.v1.Transaction;
|
||||
import org.neo4j.driver.v1.StatementRunner;
|
||||
import org.springframework.data.neo4j.core.transaction.DefaultNeo4jStatementRunnerSupplier;
|
||||
import org.springframework.data.neo4j.core.transaction.StatementRunnerSupplier;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link Neo4jOperations}. Uses the Neo4j Java driver to connect to and interact with the
|
||||
* database.
|
||||
*
|
||||
* @author Gerrit Meier
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
public class Neo4jTemplate implements Neo4jOperations {
|
||||
|
||||
private final Driver driver;
|
||||
private StatementRunnerSupplier<StatementRunner> statementRunnerSupplier;
|
||||
|
||||
@API(status = API.Status.STABLE, since = "1.0")
|
||||
public Neo4jTemplate(Driver driver) {
|
||||
this.driver = driver;
|
||||
this.statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier(driver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeQuery(String query) {
|
||||
Session session = driver.session();
|
||||
Transaction transaction = session.beginTransaction();
|
||||
try {
|
||||
StatementResult result = transaction.run(query);
|
||||
transaction.success();
|
||||
return result.list();
|
||||
} catch (Exception e) {
|
||||
transaction.failure();
|
||||
} finally {
|
||||
transaction.close();
|
||||
session.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
// TODO Let's see whether we can stick with the statementrunner or if we need to differentiate between reactive runner and default. Current 2.0 react version has two complete separate interfaces
|
||||
StatementRunner statementRunner = statementRunnerSupplier.get();
|
||||
StatementResult result = statementRunner.run(query);
|
||||
return result.list();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +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.session;
|
||||
|
||||
import org.neo4j.driver.v1.Driver;
|
||||
import org.neo4j.driver.v1.Session;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link Neo4jSessionFactory}.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
class DefaultNeo4jSessionFactory extends Neo4jSessionFactorySupport<Driver> {
|
||||
|
||||
@Override
|
||||
public Session getSession() {
|
||||
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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.session;
|
||||
|
||||
import org.neo4j.driver.v1.Driver;
|
||||
|
||||
/**
|
||||
* Support infrastructure for dealing with the Neo4j Driver.
|
||||
*
|
||||
* @param <D> Type of the driver.
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
abstract class Neo4jSessionFactorySupport<D extends Driver> implements Neo4jSessionFactory {
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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<StatementRunner> {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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.neo4j.driver.v1.Session;
|
||||
import org.neo4j.driver.v1.Transaction;
|
||||
import org.neo4j.driver.v1.TransactionConfig;
|
||||
import org.springframework.transaction.support.ResourceHolderSupport;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Neo4j specific {@link ResourceHolderSupport resource holder}, wrapping a {@link org.neo4j.driver.v1.Transaction}.
|
||||
* {@link Neo4jTransactionManager} binds instances of this class to the thread.
|
||||
* <p>
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
public class Neo4jResourceHolder extends ResourceHolderSupport {
|
||||
|
||||
private final Session session;
|
||||
|
||||
private final Transaction transaction;
|
||||
|
||||
public Neo4jResourceHolder(Session session) {
|
||||
this(session, TransactionConfig.empty());
|
||||
}
|
||||
|
||||
public Neo4jResourceHolder(Session session, TransactionConfig transactionConfig) {
|
||||
this.session = session;
|
||||
|
||||
this.transaction = this.session.beginTransaction(transactionConfig);
|
||||
}
|
||||
|
||||
Transaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
|
||||
Assert.state(hasActiveTransaction(), "Transaction must be open, but has already been closed.");
|
||||
Assert.state(!isRollbackOnly(), "Resource msut not be marked as rollback only.");
|
||||
|
||||
transaction.success();
|
||||
transaction.close();
|
||||
}
|
||||
|
||||
public void rollback() {
|
||||
|
||||
Assert.state(hasActiveTransaction(), "Transaction must be open, but has already been closed.");
|
||||
|
||||
transaction.failure();
|
||||
transaction.close();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
||||
Assert.state(hasActiveSession(), "Session must be open, but has already been closed.");
|
||||
|
||||
if (hasActiveTransaction()) {
|
||||
transaction.close();
|
||||
}
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
super.setRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetRollbackOnly() {
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean hasActiveSession() {
|
||||
|
||||
return session.isOpen();
|
||||
}
|
||||
|
||||
public boolean hasActiveTransaction() {
|
||||
|
||||
return transaction.isOpen();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.neo4j.driver.v1.Driver;
|
||||
import org.springframework.transaction.support.ResourceHolderSynchronization;
|
||||
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.
|
||||
*/
|
||||
class Neo4jSessionSynchronization
|
||||
extends ResourceHolderSynchronization<Neo4jResourceHolder, Object> {
|
||||
|
||||
private final Neo4jResourceHolder localResourceHolder;
|
||||
|
||||
Neo4jSessionSynchronization(Neo4jResourceHolder resourceHolder, Driver driver) {
|
||||
|
||||
super(resourceHolder, driver);
|
||||
this.localResourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#shouldReleaseBeforeCompletion()
|
||||
*/
|
||||
@Override
|
||||
protected boolean shouldReleaseBeforeCompletion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#processResourceAfterCommit(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void processResourceAfterCommit(Neo4jResourceHolder resourceHolder) {
|
||||
|
||||
super.processResourceAfterCommit(resourceHolder);
|
||||
|
||||
if (resourceHolder.hasActiveTransaction()) {
|
||||
resourceHolder.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#afterCompletion(int)
|
||||
*/
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
|
||||
if (status == TransactionSynchronization.STATUS_ROLLED_BACK && localResourceHolder.hasActiveTransaction()) {
|
||||
localResourceHolder.rollback();
|
||||
}
|
||||
|
||||
super.afterCompletion(status);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.transaction.support.ResourceHolderSynchronization#releaseResource(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected void releaseResource(Neo4jResourceHolder resourceHolder, Object resourceKey) {
|
||||
|
||||
if (resourceHolder.hasActiveSession()) {
|
||||
resourceHolder.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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.lang.Nullable;
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
import org.springframework.transaction.InvalidIsolationLevelException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.TransactionSystemException;
|
||||
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
import org.springframework.transaction.support.SmartTransactionObject;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Dedicated {@link org.springframework.transaction.PlatformTransactionManager} for native Neo4j transactions.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
public class Neo4jTransactionManager extends AbstractPlatformTransactionManager {
|
||||
|
||||
private final Driver driver;
|
||||
|
||||
@API(status = API.Status.STABLE, since = "1.0")
|
||||
public Neo4jTransactionManager(Driver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doGetTransaction() throws TransactionException {
|
||||
|
||||
Neo4jResourceHolder resourceHolder = (Neo4jResourceHolder) TransactionSynchronizationManager
|
||||
.getResource(driver);
|
||||
return new Neo4jTransactionObject(resourceHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
|
||||
|
||||
return extractNeo4jTransaction(transaction).hasResourceHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(transaction);
|
||||
|
||||
TransactionConfig transactionConfig = createTransactionConfigFrom(definition);
|
||||
boolean readOnly = definition.isReadOnly();
|
||||
AccessMode accessMode = readOnly ? AccessMode.READ : AccessMode.WRITE;
|
||||
List<String> bookmarks = Collections.emptyList(); // TODO Bookmarksupport
|
||||
|
||||
TransactionSynchronizationManager.setCurrentTransactionReadOnly(readOnly);
|
||||
|
||||
try {
|
||||
Session session = this.driver.session(accessMode, bookmarks);
|
||||
|
||||
Neo4jResourceHolder resourceHolder = new Neo4jResourceHolder(session, transactionConfig);
|
||||
transactionObject.setResourceHolder(resourceHolder);
|
||||
|
||||
resourceHolder.setSynchronizedWithTransaction(true);
|
||||
TransactionSynchronizationManager.bindResource(driver, resourceHolder);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
throw new TransactionSystemException(String.format("Could not open a new Neo4j session:", ex));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doSuspend(Object transaction) throws TransactionException {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(transaction);
|
||||
transactionObject.setResourceHolder(null);
|
||||
|
||||
return TransactionSynchronizationManager.unbindResource(driver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
|
||||
|
||||
TransactionSynchronizationManager.bindResource(driver, suspendedResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
|
||||
transactionObject.getRequiredResourceHolder().commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
|
||||
transactionObject.getRequiredResourceHolder().rollback();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(status);
|
||||
transactionObject.setRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doCleanupAfterCompletion(Object transaction) {
|
||||
|
||||
Neo4jTransactionObject transactionObject = extractNeo4jTransaction(transaction);
|
||||
transactionObject.getRequiredResourceHolder().close();
|
||||
|
||||
TransactionSynchronizationManager.unbindResource(driver);
|
||||
}
|
||||
|
||||
private static TransactionConfig createTransactionConfigFrom(TransactionDefinition definition) {
|
||||
|
||||
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
|
||||
throw new InvalidIsolationLevelException(
|
||||
"Neo4jTransactionManager is not allowed to support custom isolation levels.");
|
||||
}
|
||||
|
||||
if (definition.getPropagationBehavior() != TransactionDefinition.PROPAGATION_REQUIRED) {
|
||||
throw new IllegalTransactionStateException("Neo4jTransactionManager only supports 'required' propagation.");
|
||||
}
|
||||
|
||||
TransactionConfig.Builder builder = TransactionConfig.builder();
|
||||
if (definition.getTimeout() > 0) {
|
||||
builder = builder.withTimeout(Duration.ofSeconds(definition.getTimeout()));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Neo4jTransactionObject extractNeo4jTransaction(Object transaction) {
|
||||
|
||||
Assert.isInstanceOf(Neo4jTransactionObject.class, transaction,
|
||||
() -> String.format("Expected to find a %s but it turned out to be %s.", Neo4jTransactionObject.class,
|
||||
transaction.getClass()));
|
||||
|
||||
return (Neo4jTransactionObject) transaction;
|
||||
}
|
||||
|
||||
private static Neo4jTransactionObject extractNeo4jTransaction(DefaultTransactionStatus status) {
|
||||
|
||||
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";
|
||||
|
||||
// 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;
|
||||
|
||||
Neo4jTransactionObject(@Nullable Neo4jResourceHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usually called in {@link #doBegin(Object, TransactionDefinition)} which is called when there's
|
||||
* no existing transaction.
|
||||
*
|
||||
* @param resourceHolder A newly created resource holder with a fresh drivers session,
|
||||
*/
|
||||
void setResourceHolder(@Nullable Neo4jResourceHolder resourceHolder) {
|
||||
this.resourceHolder = resourceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal true} if a {@link Neo4jResourceHolder} is set.
|
||||
*/
|
||||
boolean hasResourceHolder() {
|
||||
return resourceHolder != null;
|
||||
}
|
||||
|
||||
Neo4jResourceHolder getRequiredResourceHolder() {
|
||||
|
||||
Assert.state(hasResourceHolder(), RESOURCE_HOLDER_NOT_PRESENT_MESSAGE);
|
||||
return resourceHolder;
|
||||
}
|
||||
|
||||
void setRollbackOnly() {
|
||||
|
||||
getRequiredResourceHolder().setRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return this.hasResourceHolder() && this.resourceHolder.isRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
|
||||
TransactionSynchronizationUtils.triggerFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.core.session;
|
||||
package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import org.neo4j.driver.v1.Session;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Interface for factories creating {@link org.neo4j.driver.v1.Session Neo4j driver sessions}.
|
||||
* Used for obtaining {@link org.neo4j.driver.v1.Session session bound (standard or reactive)} bound statement runners.
|
||||
* <p>
|
||||
* <strong>Note:</strong> Intended for internal usage only.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
public interface Neo4jSessionFactory {
|
||||
|
||||
Session getSession();
|
||||
@FunctionalInterface
|
||||
public interface StatementRunnerSupplier<T> extends Supplier<T> {
|
||||
}
|
||||
@@ -2,6 +2,6 @@
|
||||
* Core infrastructure for providing Neo4j sessions to Spring Data Neo4j.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.data.neo4j.core.session;
|
||||
package org.springframework.data.neo4j.core.transaction;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.config;
|
||||
|
||||
import static org.springframework.data.neo4j.repository.config.Neo4jRepositoryConfigurationExtension.*;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
@@ -31,6 +33,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.data.neo4j.core.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Annotation to activate Neo4j repositories. If no base package is configured through either {@link #value()},
|
||||
@@ -77,7 +80,13 @@ public @interface EnableNeo4jRepositories {
|
||||
/**
|
||||
* Configures the name of the {@link Neo4jTemplate} bean to be used with the repositories detected.
|
||||
*/
|
||||
String neo4jTemplateRef() default "neo4jTemplate";
|
||||
String neo4jTemplateRef() default DEFAULT_NEO4J_TEMPLATE_BEAN_NAME;
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories
|
||||
* discovered through this annotation. Defaults to {@code transactionManager}.
|
||||
*/
|
||||
String transactionManagerRef() default DEFAULT_TRANSACTION_MANAGER_BEAN_NAME;
|
||||
|
||||
/**
|
||||
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.repository.config;
|
||||
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.data.neo4j.repository.support.Neo4jRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
|
||||
/**
|
||||
* This dedicated Neo4j repository extension will be registered via {@link Neo4jRepositoriesRegistrar} and then provide
|
||||
@@ -35,6 +35,14 @@ class Neo4jRepositoryConfigurationExtension extends RepositoryConfigurationExten
|
||||
|
||||
private static final String MODULE_PREFIX = "neo4j";
|
||||
|
||||
/**
|
||||
* See {@link AbstractBeanDefinition#INFER_METHOD}.
|
||||
*/
|
||||
static final String GENERATE_BEAN_NAME = "(generated)";
|
||||
|
||||
static final String DEFAULT_NEO4J_TEMPLATE_BEAN_NAME = "neo4jTemplate";
|
||||
static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config14.RepositoryConfigurationExtension#getRepositoryFactoryBeanClassName()
|
||||
@@ -55,13 +63,15 @@ class Neo4jRepositoryConfigurationExtension extends RepositoryConfigurationExten
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
|
||||
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
|
||||
|
||||
AnnotationAttributes attributes = config.getAttributes();
|
||||
builder.addPropertyValue("transactionManager",
|
||||
source.getAttribute("transactionManagerRef").orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME));
|
||||
|
||||
builder.addPropertyReference("neo4jOperations", attributes.getString("neo4jTemplateRef"));
|
||||
builder.addPropertyReference("neo4jOperations",
|
||||
source.getAttribute("neo4jTemplateRef").orElse(DEFAULT_NEO4J_TEMPLATE_BEAN_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,17 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.core.Neo4jOperations;
|
||||
import org.springframework.data.neo4j.repository.Neo4jRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Repository base implementation for Neo4j.
|
||||
*
|
||||
* @author Gerrit Meier
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Repository
|
||||
@Transactional(readOnly = true)
|
||||
class SimpleNeo4jRepository<T, ID> implements Neo4jRepository<T, ID> {
|
||||
|
||||
private final Neo4jOperations neo4jOperations;
|
||||
@@ -51,11 +56,13 @@ class SimpleNeo4jRepository<T, ID> implements Neo4jRepository<T, ID> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <S extends T> S save(S entity) {
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <S extends T> Iterable<S> saveAll(Iterable<S> entities) {
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
}
|
||||
@@ -86,23 +93,31 @@ class SimpleNeo4jRepository<T, ID> implements Neo4jRepository<T, ID> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteById(ID id) {
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void delete(T entity) {
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteAll(Iterable<? extends T> entities) {
|
||||
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteAll() {
|
||||
throw new UnsupportedOperationException("Not there yet.");
|
||||
|
||||
for (T element : findAll()) {
|
||||
delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.neo4j.driver.v1.AccessMode;
|
||||
import org.neo4j.driver.v1.Driver;
|
||||
import org.neo4j.driver.v1.Session;
|
||||
import org.neo4j.driver.v1.StatementRunner;
|
||||
import org.neo4j.driver.v1.Transaction;
|
||||
import org.neo4j.driver.v1.TransactionConfig;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultNeo4jStatementRunnerSupplierTest {
|
||||
|
||||
@Mock
|
||||
private Driver driver;
|
||||
@Mock
|
||||
private Session session;
|
||||
@Mock
|
||||
private Transaction transaction;
|
||||
@Mock
|
||||
UserTransaction userTransaction;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
AtomicBoolean sessionIsOpen = new AtomicBoolean(true);
|
||||
AtomicBoolean transactionIsOpen = new AtomicBoolean(true);
|
||||
|
||||
when(driver.session(AccessMode.WRITE, Collections.emptyList())).thenReturn(session);
|
||||
|
||||
when(session.beginTransaction(any(TransactionConfig.class))).thenReturn(transaction);
|
||||
doAnswer(invocation -> {
|
||||
sessionIsOpen.set(false);
|
||||
return null;
|
||||
}).when(session).close();
|
||||
when(session.isOpen()).thenAnswer(invocation -> sessionIsOpen.get());
|
||||
|
||||
doAnswer(invocation -> {
|
||||
transactionIsOpen.set(false);
|
||||
return null;
|
||||
}).when(transaction).close();
|
||||
when(transaction.isOpen()).thenAnswer(invocation -> transactionIsOpen.get());
|
||||
}
|
||||
|
||||
@Nested
|
||||
class BasedOnJtaTransactions {
|
||||
|
||||
@Test
|
||||
void shouldParticipateInOngoingTransactionWithCommit() throws Exception {
|
||||
|
||||
when(userTransaction.getStatus()).thenReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE,
|
||||
Status.STATUS_ACTIVE);
|
||||
|
||||
JtaTransactionManager txManager = new JtaTransactionManager(userTransaction);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
StatementRunnerSupplier<StatementRunner> statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier(
|
||||
driver);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(driver)).isFalse();
|
||||
|
||||
StatementRunner statementRunner = statementRunnerSupplier.get();
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(driver)).isTrue();
|
||||
}
|
||||
});
|
||||
|
||||
verify(userTransaction).begin();
|
||||
|
||||
verify(driver).session(AccessMode.WRITE, Collections.emptyList());
|
||||
|
||||
verify(session, times(2)).isOpen();
|
||||
verify(session).beginTransaction(any(TransactionConfig.class));
|
||||
verify(session).close();
|
||||
|
||||
verify(transaction, times(3)).isOpen();
|
||||
verify(transaction).success();
|
||||
verify(transaction).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldParticipateInOngoingTransactionWithRollback() throws Exception {
|
||||
|
||||
when(userTransaction.getStatus()).thenReturn(Status.STATUS_NO_TRANSACTION, Status.STATUS_ACTIVE,
|
||||
Status.STATUS_ACTIVE);
|
||||
|
||||
JtaTransactionManager txManager = new JtaTransactionManager(userTransaction);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
StatementRunnerSupplier<StatementRunner> statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier(
|
||||
driver);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(driver)).isFalse();
|
||||
|
||||
@SuppressWarnings({ "unused" })
|
||||
StatementRunner statementRunner = statementRunnerSupplier.get();
|
||||
|
||||
assertThat(TransactionSynchronizationManager.hasResource(driver)).isTrue();
|
||||
|
||||
transactionStatus.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(userTransaction).begin();
|
||||
verify(userTransaction).rollback();
|
||||
|
||||
verify(driver).session(AccessMode.WRITE, Collections.emptyList());
|
||||
|
||||
verify(session, times(2)).isOpen();
|
||||
verify(session).beginTransaction(any(TransactionConfig.class));
|
||||
verify(session).close();
|
||||
|
||||
verify(transaction, times(3)).isOpen();
|
||||
verify(transaction).failure();
|
||||
verify(transaction).close();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class BasedOnNeo4jTransactions {
|
||||
@Test
|
||||
public void shouldParticipateInOngoingTransaction() {
|
||||
|
||||
Neo4jTransactionManager txManager = new Neo4jTransactionManager(driver);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
|
||||
StatementRunnerSupplier<StatementRunner> statementRunnerSupplier = new DefaultNeo4jStatementRunnerSupplier(
|
||||
driver);
|
||||
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
|
||||
|
||||
assertThat(TransactionSynchronizationManager.isSynchronizationActive()).isTrue();
|
||||
assertThat(transactionStatus.isNewTransaction()).isTrue();
|
||||
assertThat(TransactionSynchronizationManager.hasResource(driver)).isTrue();
|
||||
|
||||
@SuppressWarnings({ "unused" })
|
||||
StatementRunner statementRunner = statementRunnerSupplier.get();
|
||||
|
||||
transactionStatus.setRollbackOnly();
|
||||
}
|
||||
});
|
||||
|
||||
verify(driver).session(AccessMode.WRITE, Collections.emptyList());
|
||||
|
||||
verify(session).isOpen();
|
||||
verify(session).beginTransaction(any(TransactionConfig.class));
|
||||
verify(session).close();
|
||||
|
||||
verify(transaction, times(2)).isOpen();
|
||||
verify(transaction).failure();
|
||||
verify(transaction).close();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void verifyTransactionSynchronizationManagerState() {
|
||||
|
||||
assertTrue(TransactionSynchronizationManager.getResourceMap().isEmpty());
|
||||
assertFalse(TransactionSynchronizationManager.isSynchronizationActive());
|
||||
assertNull(TransactionSynchronizationManager.getCurrentTransactionName());
|
||||
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||
assertNull(TransactionSynchronizationManager.getCurrentTransactionIsolationLevel());
|
||||
assertFalse(TransactionSynchronizationManager.isActualTransactionActive());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.neo4j.driver.v1.AccessMode;
|
||||
import org.neo4j.driver.v1.Driver;
|
||||
import org.neo4j.driver.v1.Session;
|
||||
import org.neo4j.driver.v1.StatementResult;
|
||||
import org.neo4j.driver.v1.Transaction;
|
||||
import org.neo4j.driver.v1.TransactionConfig;
|
||||
import org.springframework.data.neo4j.core.Neo4jTemplate;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class Neo4jTransactionManagerTest {
|
||||
|
||||
@Mock
|
||||
private Driver driver;
|
||||
@Mock
|
||||
private Session session;
|
||||
@Mock
|
||||
private Transaction transaction;
|
||||
@Mock
|
||||
private StatementResult statementResult;
|
||||
|
||||
@Test
|
||||
public void triggerCommitCorrectly() {
|
||||
|
||||
when(driver.session(AccessMode.WRITE, Collections.emptyList())).thenReturn(session);
|
||||
when(session.beginTransaction(any(TransactionConfig.class))).thenReturn(transaction);
|
||||
when(transaction.run(anyString())).thenReturn(statementResult);
|
||||
when(session.isOpen()).thenReturn(true);
|
||||
when(transaction.isOpen()).thenReturn(true, false);
|
||||
|
||||
Neo4jTransactionManager txManager = new Neo4jTransactionManager(driver);
|
||||
TransactionStatus txStatus = txManager.getTransaction(new DefaultTransactionDefinition());
|
||||
|
||||
Neo4jTemplate template = new Neo4jTemplate(driver);
|
||||
template.executeQuery("RETURN 1");
|
||||
|
||||
txManager.commit(txStatus);
|
||||
|
||||
verify(driver).session(AccessMode.WRITE, Collections.emptyList());
|
||||
|
||||
verify(session).isOpen();
|
||||
verify(session).beginTransaction(any(TransactionConfig.class));
|
||||
|
||||
verify(transaction, times(2)).isOpen();
|
||||
verify(transaction).success();
|
||||
verify(transaction).close();
|
||||
|
||||
verify(session).close();
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,12 @@ 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.transaction.Neo4jTransactionManager;
|
||||
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.testcontainers.containers.Neo4jContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
@@ -45,7 +48,8 @@ import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
@Testcontainers
|
||||
class RepositoryIT {
|
||||
|
||||
@Container private static Neo4jContainer neo4jContainer = new Neo4jContainer().withAdminPassword(null);
|
||||
@Container
|
||||
private static Neo4jContainer neo4jContainer = new Neo4jContainer().withAdminPassword(null);
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
@@ -75,16 +79,26 @@ class RepositoryIT {
|
||||
|
||||
@Configuration
|
||||
@EnableNeo4jRepositories
|
||||
@EnableTransactionManagement
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public Neo4jOperations neo4jTemplate() {
|
||||
String boltUrl = neo4jContainer.getBoltUrl();
|
||||
Driver driver = GraphDatabase.driver(boltUrl, AuthTokens.none());
|
||||
public Driver driver() {
|
||||
|
||||
return new Neo4jTemplate(driver);
|
||||
String boltUrl = neo4jContainer.getBoltUrl();
|
||||
return GraphDatabase.driver(boltUrl, AuthTokens.none());
|
||||
}
|
||||
|
||||
}
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
|
||||
return new Neo4jTransactionManager(driver());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Neo4jOperations neo4jTemplate() {
|
||||
|
||||
return new Neo4jTemplate(driver());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ package org.springframework.data.neo4j.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -74,7 +76,8 @@ class SimpleNeo4jRepositoryTest {
|
||||
|
||||
@Test
|
||||
void deleteAll1NotImplemented() {
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> repository.deleteAll(null));
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> repository.deleteAll(Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user