Skip to content
This repository was archived by the owner on Mar 10, 2025. It is now read-only.

Commit 3c69480

Browse files
feat(core): Support beginTransaction with TransactionConfig duration (#620)
* Configure timeout before beginning neo4j transactions * Create a new neo4j session if transaction propagation requires a new one * Add integration tests
1 parent d63b4ea commit 3c69480

File tree

7 files changed

+80
-2
lines changed

7 files changed

+80
-2
lines changed

examples/test-data-service/grails-app/services/example/LibraryService.groovy

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package example
22

33
import grails.gorm.transactions.Transactional
4+
import org.springframework.transaction.annotation.Propagation
45

56
@Transactional
67
class LibraryService {
@@ -14,6 +15,16 @@ class LibraryService {
1415
bookService.get(id)
1516
}
1617

18+
@Transactional(timeout = 60, propagation = Propagation.REQUIRES_NEW)
19+
Book getBookWithLongTimeout(Serializable id) {
20+
Book.executeQuery('CALL grails.sleep(4000) MATCH (n:Book) WHERE ( ID(n)=$1 ) RETURN n as data', [id])
21+
}
22+
23+
@Transactional(timeout = 1, propagation = Propagation.REQUIRES_NEW)
24+
Book getBookWithShortTimeout(Serializable id) {
25+
Book.executeQuery('CALL grails.sleep(4000) MATCH (n:Book) WHERE ( ID(n)=$1 ) RETURN n as data', [id])
26+
}
27+
1728
Person addMember(String firstName, String lastName) {
1829
assert personService != null
1930
personService.save(firstName, lastName)

examples/test-data-service/grails-app/services/example/TestService.groovy

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ class TestService {
88
libraryService.bookExists(id)
99
}
1010

11+
Book testDataServiceWithLongTimeout(Serializable id) {
12+
libraryService.getBookWithLongTimeout(id)
13+
}
14+
15+
Book testDataServiceWithShortTimeout(Serializable id) {
16+
libraryService.getBookWithShortTimeout(id)
17+
}
18+
1119
Person save(String firstName, String lastName) {
1220
libraryService.addMember(firstName, lastName)
1321
}

examples/test-data-service/src/integration-test/groovy/example/TestServiceSpec.groovy

+17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package example
22

33
import grails.gorm.transactions.Rollback
44
import grails.testing.mixin.integration.Integration
5+
import org.neo4j.driver.exceptions.ClientException
56
import org.springframework.beans.factory.annotation.Autowired
67
import spock.lang.Specification
78

@@ -23,6 +24,22 @@ class TestServiceSpec extends Specification {
2324
noExceptionThrown()
2425
}
2526

27+
void "test transaction with long timeout"() {
28+
when:
29+
testService.testDataServiceWithLongTimeout()
30+
31+
then:
32+
noExceptionThrown()
33+
}
34+
35+
void "test transaction with short timeout"() {
36+
when:
37+
testService.testDataServiceWithShortTimeout()
38+
39+
then:
40+
thrown(ClientException)
41+
}
42+
2643
void "test autowire by type"() {
2744

2845
expect:

grails-datastore-gorm-neo4j/src/main/groovy/org/grails/datastore/gorm/neo4j/Neo4jDatastoreTransactionManager.java

+5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ protected void doBegin(Object o, TransactionDefinition definition) throws Transa
5858
try {
5959
session = (Neo4jSession) txObject.getSessionHolder().getSession();
6060

61+
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
62+
session = (Neo4jSession) getDatastore().connect();
63+
txObject.setSession(session);
64+
}
65+
6166
if (definition.isReadOnly()) {
6267
// Just set to NEVER in case of a new Session for this transaction.
6368
session.setFlushMode(FlushModeType.COMMIT);

grails-datastore-gorm-neo4j/src/main/groovy/org/grails/datastore/gorm/neo4j/Neo4jTransaction.groovy

+13-2
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ import groovy.util.logging.Slf4j
1919
import org.neo4j.driver.AccessMode
2020
import org.neo4j.driver.Driver
2121
import org.neo4j.driver.Session
22+
import org.neo4j.driver.TransactionConfig
2223
import org.grails.datastore.mapping.transactions.Transaction
2324
import org.neo4j.driver.SessionConfig
2425
import org.springframework.transaction.TransactionDefinition
2526
import org.springframework.transaction.support.DefaultTransactionDefinition
2627

28+
import java.time.Duration
29+
2730
/**
2831
* Represents a Neo4j transaction
2932
*
@@ -48,7 +51,11 @@ class Neo4jTransaction implements Transaction<org.neo4j.driver.Transaction>, Clo
4851

4952
log.debug("TX START: Neo4J beginTx()")
5053
this.boltSession = boltDriver.session(SessionConfig.builder().withDefaultAccessMode(transactionDefinition.readOnly ? AccessMode.READ : AccessMode.WRITE).build())
51-
transaction = boltSession.beginTransaction()
54+
final TransactionConfig.Builder config = TransactionConfig.builder()
55+
if (transactionDefinition.timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
56+
config.withTimeout(Duration.ofSeconds(transactionDefinition.timeout))
57+
}
58+
transaction = boltSession.beginTransaction(config.build())
5259
this.transactionDefinition = transactionDefinition
5360
this.sessionCreated = sessionCreated
5461
}
@@ -98,6 +105,10 @@ class Neo4jTransaction implements Transaction<org.neo4j.driver.Transaction>, Clo
98105
}
99106

100107
void setTimeout(int timeout) {
101-
throw new UnsupportedOperationException()
108+
log.debug("TX TIMEOUT: Neo4j tx.setTimeout({})", timeout);
109+
// Neo4j tx config is immutable
110+
if (timeout != transactionDefinition.timeout) {
111+
log.warn("Transaction timeout for '{}' was already configured to {} seconds", transactionDefinition.name, transactionDefinition.timeout)
112+
}
102113
}
103114
}

grails-datastore-gorm-neo4j/src/main/groovy/org/grails/datastore/gorm/neo4j/util/EmbeddedNeo4jServer.java

+1
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ public static ServerControls start(String host, int port, File dataLocation, Map
174174
}
175175

176176
return serverBuilder
177+
.withProcedure(GrailsProcedures.class)
177178
.newServer();
178179
}
179180

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.grails.datastore.gorm.neo4j.util;
2+
3+
import org.neo4j.procedure.Description;
4+
import org.neo4j.procedure.Name;
5+
import org.neo4j.procedure.Procedure;
6+
7+
/**
8+
* Grails procedures for Neo4j embedded server.
9+
*/
10+
public class GrailsProcedures {
11+
12+
/**
13+
* Procedure for Neo4j that sleeps for the specified number of milliseconds.
14+
* @param millis the length of time to sleep in milliseconds
15+
*/
16+
@Procedure("grails.sleep")
17+
@Description("Sleep for the specified number of milliseconds.")
18+
public void sleep(@Name(value = "millis", defaultValue = "1000") Long millis) {
19+
try {
20+
Thread.sleep(millis);
21+
} catch (Exception e) {
22+
e.printStackTrace();
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)