Skip to content

Commit e8d506c

Browse files
GH-2860 - Add explicit transaction boundaries.
If no transactional boundaries were set by the user, Spring Data Neo4j would create new transactions (default read/write) for the underlying database operations. In cases where multiple statements are required to execute a SDN operation this would mean that multiple transaction would have been created. This commit fixes this problem and creates new transaction if no transaction was defined around the invocation of those units-of-work. The change will introduce a breaking change: All pure read operations in SDN (like Neo4jTemplate#findAll) will now happen in read-only transactions. If they contain custom statements with write operations, they need to get wrapped in an explicit write transaction. Closes #2860 Co-authored-by: Michael Simons <[email protected]>
1 parent f5d93fa commit e8d506c

File tree

9 files changed

+414
-308
lines changed

9 files changed

+414
-308
lines changed

src/main/java/org/springframework/data/neo4j/config/Neo4jCdiConfigurationSupport.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ public Configuration cypherDslConfiguration() {
7575
public Neo4jOperations neo4jOperations(
7676
@Any Instance<Neo4jClient> neo4jClient,
7777
@Any Instance<Neo4jMappingContext> mappingContext,
78-
@Any Instance<Configuration> cypherDslConfiguration
78+
@Any Instance<Configuration> cypherDslConfiguration,
79+
@Any Instance<PlatformTransactionManager> transactionManager
7980
) {
8081
Neo4jTemplate neo4jTemplate = new Neo4jTemplate(resolve(neo4jClient), resolve(mappingContext));
8182
neo4jTemplate.setCypherRenderer(Renderer.getRenderer(resolve(cypherDslConfiguration)));
83+
neo4jTemplate.setTransactionManager(resolve(transactionManager));
8284
return neo4jTemplate;
8385
}
8486

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+275-198
Large diffs are not rendered by default.

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

+103-85
Large diffs are not rendered by default.

src/main/java/org/springframework/data/neo4j/core/transaction/Neo4jTransactionManager.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
246246

247247
// Otherwise we open a session and synchronize it.
248248
Session session = driver.session(Neo4jTransactionUtils.defaultSessionConfig(targetDatabase, asUser));
249-
Transaction transaction = session.beginTransaction(TransactionConfig.empty());
249+
Transaction transaction = session.beginTransaction(Neo4jTransactionUtils.createTransactionConfigFrom(TransactionDefinition.withDefaults(), -1));
250250
// Manually create a new synchronization
251251
connectionHolder = new Neo4jTransactionHolder(new Neo4jTransactionContext(targetDatabase, asUser), session, transaction);
252252
connectionHolder.setSynchronizedWithTransaction(true);

src/main/java/org/springframework/data/neo4j/core/transaction/ReactiveNeo4jTransactionManager.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public static Mono<ReactiveTransaction> retrieveReactiveTransaction(
215215
return Mono.defer(() -> {
216216

217217
ReactiveSession session = driver.session(ReactiveSession.class, Neo4jTransactionUtils.defaultSessionConfig(targetDatabase, asUser));
218-
return Mono.fromDirect(session.beginTransaction(TransactionConfig.empty())).map(tx -> {
218+
return Mono.fromDirect(session.beginTransaction(Neo4jTransactionUtils.createTransactionConfigFrom(TransactionDefinition.withDefaults(), -1))).map(tx -> {
219219

220220
ReactiveNeo4jTransactionHolder newConnectionHolder = new ReactiveNeo4jTransactionHolder(
221221
new Neo4jTransactionContext(targetDatabase, asUser), session, tx);

src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java

+14-11
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
5050
import org.springframework.transaction.PlatformTransactionManager;
5151
import org.springframework.transaction.annotation.EnableTransactionManagement;
52+
import org.springframework.transaction.support.TransactionTemplate;
5253

5354
import java.util.ArrayList;
5455
import java.util.Arrays;
@@ -880,20 +881,22 @@ void saveWeirdHierarchy() {
880881
}
881882

882883
@Test
883-
void updatingFindShouldWork() {
884+
void updatingFindShouldWork(@Autowired PlatformTransactionManager transactionManager) {
884885
Map<String, Object> params = new HashMap<>();
885886
params.put("wrongName", "Siemons");
886887
params.put("correctName", "Simons");
887-
Optional<Person> optionalResult = neo4jTemplate
888-
.findOne("MERGE (p:Person {lastName: $wrongName}) ON MATCH set p.lastName = $correctName RETURN p",
889-
params, Person.class);
890-
891-
assertThat(optionalResult).hasValueSatisfying(
892-
updatedPerson -> {
893-
assertThat(updatedPerson.getLastName()).isEqualTo("Simons");
894-
assertThat(updatedPerson.getAddress()).isNull(); // We didn't fetch it
895-
}
896-
);
888+
new TransactionTemplate(transactionManager).executeWithoutResult(tx -> {
889+
Optional<Person> optionalResult = neo4jTemplate
890+
.findOne("MERGE (p:Person {lastName: $wrongName}) ON MATCH set p.lastName = $correctName RETURN p",
891+
params, Person.class);
892+
893+
assertThat(optionalResult).hasValueSatisfying(
894+
updatedPerson -> {
895+
assertThat(updatedPerson.getLastName()).isEqualTo("Simons");
896+
assertThat(updatedPerson.getAddress()).isNull(); // We didn't fetch it
897+
}
898+
);
899+
});
897900
}
898901

899902
@Test

src/test/java/org/springframework/data/neo4j/integration/imperative/RepositoryIT.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,12 @@ void aggregateThroughQueryIntoCustomObjectDTOShouldWork(@Autowired PersonReposit
499499
}
500500

501501
@Test // DATAGRAPH-1429
502-
void queryAggregatesShouldWorkWithTheTemplate(@Autowired Neo4jTemplate template) {
502+
void queryAggregatesShouldWorkWithTheTemplate(@Autowired Neo4jTemplate template, @Autowired PlatformTransactionManager transactionManager) {
503+
new TransactionTemplate(transactionManager).executeWithoutResult(tx -> {
503504

504-
List<Person> people = template.findAll("unwind range(1,5) as i with i create (p:Person {firstName: toString(i)}) return p", Person.class);
505-
assertThat(people).extracting(Person::getFirstName).containsExactly("1", "2", "3", "4", "5");
505+
List<Person> people = template.findAll("unwind range(1,5) as i with i create (p:Person {firstName: toString(i)}) return p", Person.class);
506+
assertThat(people).extracting(Person::getFirstName).containsExactly("1", "2", "3", "4", "5");
507+
});
506508
}
507509

508510
@Test

src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.springframework.data.neo4j.test.Neo4jReactiveTestConfiguration;
5858
import org.springframework.transaction.ReactiveTransactionManager;
5959
import org.springframework.transaction.annotation.EnableTransactionManagement;
60+
import org.springframework.transaction.reactive.TransactionalOperator;
6061
import reactor.core.publisher.Flux;
6162
import reactor.test.StepVerifier;
6263

@@ -533,9 +534,10 @@ void saveAllProjectionShouldWork(@Autowired ReactiveNeo4jTemplate template) {
533534
}
534535

535536
@Test
536-
void saveAllAsWithOpenProjectionShouldWork(@Autowired ReactiveNeo4jTemplate template) {
537+
void saveAllAsWithOpenProjectionShouldWork(@Autowired ReactiveNeo4jTemplate template, @Autowired ReactiveTransactionManager transactionManager) {
537538

538539
// Using a query on purpose so that the address is null
540+
TransactionalOperator.create(transactionManager).transactional(
539541
template.findOne("MATCH (p:Person {lastName: $lastName}) RETURN p",
540542
Collections.singletonMap("lastName", "Siemons"), Person.class)
541543
.zipWith(template.findOne("MATCH (p:Person {lastName: $lastName}) RETURN p",
@@ -550,7 +552,7 @@ void saveAllAsWithOpenProjectionShouldWork(@Autowired ReactiveNeo4jTemplate temp
550552
p2.setFirstName("Helga");
551553
p2.setLastName("Schneider");
552554
return template.saveAllAs(Arrays.asList(p1, p2), OpenProjection.class);
553-
})
555+
}))
554556
.map(OpenProjection::getFullName)
555557
.sort()
556558
.as(StepVerifier::create)
@@ -832,13 +834,15 @@ void saveAllAsWithClosedProjectionShouldWork(@Autowired ReactiveNeo4jTemplate te
832834
}
833835

834836
@Test
835-
void updatingFindShouldWork() {
837+
void updatingFindShouldWork(@Autowired ReactiveTransactionManager transactionManager) {
836838
Map<String, Object> params = new HashMap<>();
837839
params.put("wrongName", "Siemons");
838840
params.put("correctName", "Simons");
839-
neo4jTemplate
840-
.findOne("MERGE (p:Person {lastName: $wrongName}) ON MATCH set p.lastName = $correctName RETURN p",
841-
params, Person.class)
841+
TransactionalOperator.create(transactionManager)
842+
.transactional(
843+
neo4jTemplate
844+
.findOne("MERGE (p:Person {lastName: $wrongName}) ON MATCH set p.lastName = $correctName RETURN p",
845+
params, Person.class))
842846
.as(StepVerifier::create)
843847
.consumeNextWith(updatedPerson -> {
844848

src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveRepositoryIT.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -702,9 +702,9 @@ void aggregateThroughQueryIntoListShouldWork(@Autowired ReactivePersonRepository
702702
}
703703

704704
@Test // DATAGRAPH-1429
705-
void queryAggregatesShouldWorkWithTheTemplate(@Autowired ReactiveNeo4jTemplate template) {
705+
void queryAggregatesShouldWorkWithTheTemplate(@Autowired ReactiveNeo4jTemplate template, @Autowired ReactiveTransactionManager reactiveTransactionManager) {
706706

707-
Flux<Person> people = template.findAll("unwind range(1,5) as i with i create (p:Person {firstName: toString(i)}) return p", Person.class);
707+
Flux<Person> people = TransactionalOperator.create(reactiveTransactionManager).transactional(template.findAll("unwind range(1,5) as i with i create (p:Person {firstName: toString(i)}) return p", Person.class));
708708

709709
StepVerifier.create(people.map(Person::getFirstName))
710710
.expectNext("1", "2", "3", "4", "5")

0 commit comments

Comments
 (0)