Skip to content

Commit 5b901a5

Browse files
committed
#80 fix nested transaction behaviour keeping collections
1 parent 7cd8dbb commit 5b901a5

File tree

4 files changed

+81
-26
lines changed

4 files changed

+81
-26
lines changed

src/main/java/com/arangodb/springframework/transaction/ArangoTransactionManager.java

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public ArangoTransactionManager(ArangoOperations operations, QueryTransactionBri
3131
* Creates a new transaction object. Any synchronized resource will be reused.
3232
*/
3333
@Override
34-
protected Object doGetTransaction() {
34+
protected ArangoTransactionObject doGetTransaction() {
3535
DbName database = operations.getDatabaseName();
3636
if (logger.isDebugEnabled()) {
3737
logger.debug("Create new transaction for database " + database);
@@ -58,10 +58,13 @@ protected void doBegin(Object transaction, TransactionDefinition definition) thr
5858
}
5959
ArangoTransactionObject tx = (ArangoTransactionObject) transaction;
6060
tx.configure(definition);
61-
DbName key = operations.getDatabaseName();
62-
TransactionSynchronizationManager.unbindResourceIfPossible(key);
63-
TransactionSynchronizationManager.bindResource(key, tx.getResource());
64-
bridge.setCurrentTransaction(collections -> tx.getOrBegin(collections).getStreamTransactionId());
61+
bridge.setCurrentTransaction(collections -> {
62+
try {
63+
return tx.getOrBegin(collections).getStreamTransactionId();
64+
} catch (ArangoDBException error) {
65+
throw new TransactionSystemException("Cannot begin transaction", error);
66+
}
67+
});
6568
}
6669

6770
/**
@@ -118,8 +121,36 @@ protected void doSetRollbackOnly(DefaultTransactionStatus status) throws Transac
118121
tx.setRollbackOnly();
119122
}
120123

124+
@Override
125+
protected DefaultTransactionStatus newTransactionStatus(TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) {
126+
return super.newTransactionStatus(definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
127+
}
128+
129+
/**
130+
* Bind the resource for the first new transaction created.
131+
*/
132+
@Override
133+
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
134+
super.prepareSynchronization(status, definition);
135+
if (status.isNewTransaction()) {
136+
ArangoTransactionResource resource = ((ArangoTransactionObject) status.getTransaction()).getResource();
137+
resource.increaseReferences();
138+
if (resource.isSingleReference()) {
139+
TransactionSynchronizationManager.bindResource(operations.getDatabaseName(), resource);
140+
}
141+
}
142+
}
143+
144+
/**
145+
* Unbind the resource for the last transaction completed.
146+
*/
121147
@Override
122148
protected void doCleanupAfterCompletion(Object transaction) {
123-
TransactionSynchronizationManager.unbindResourceIfPossible(operations.getDatabaseName());
149+
ArangoTransactionResource resource = ((ArangoTransactionObject) transaction).getResource();
150+
if (resource.isSingleReference()) {
151+
TransactionSynchronizationManager.unbindResource(operations.getDatabaseName());
152+
}
153+
resource.decreasedReferences();
124154
}
155+
125156
}

src/main/java/com/arangodb/springframework/transaction/ArangoTransactionObject.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.arangodb.springframework.transaction;
22

3+
import com.arangodb.ArangoDBException;
34
import com.arangodb.ArangoDatabase;
45
import com.arangodb.entity.StreamTransactionEntity;
56
import com.arangodb.entity.StreamTransactionStatus;
67
import com.arangodb.model.StreamTransactionOptions;
78
import org.apache.commons.logging.Log;
89
import org.apache.commons.logging.LogFactory;
910
import org.springframework.lang.Nullable;
10-
import org.springframework.transaction.IllegalTransactionStateException;
1111
import org.springframework.transaction.TransactionDefinition;
1212
import org.springframework.transaction.interceptor.TransactionAttribute;
1313
import org.springframework.transaction.support.SmartTransactionObject;
@@ -53,7 +53,7 @@ void configure(TransactionDefinition definition) {
5353
}
5454
}
5555

56-
ArangoTransactionResource getOrBegin(Collection<String> collections) {
56+
ArangoTransactionResource getOrBegin(Collection<String> collections) throws ArangoDBException {
5757
addCollections(collections);
5858
if (resource.getStreamTransactionId() != null) {
5959
return getResource();
@@ -70,13 +70,13 @@ ArangoTransactionResource getOrBegin(Collection<String> collections) {
7070
return getResource();
7171
}
7272

73-
void commit() {
73+
void commit() throws ArangoDBException {
7474
if (isStatus(StreamTransactionStatus.running)) {
7575
database.commitStreamTransaction(resource.getStreamTransactionId());
7676
}
7777
}
7878

79-
void rollback() {
79+
void rollback() throws ArangoDBException {
8080
if (isStatus(StreamTransactionStatus.running)) {
8181
database.abortStreamTransaction(resource.getStreamTransactionId());
8282
}
@@ -104,15 +104,16 @@ public String toString() {
104104

105105
private void addCollections(Collection<String> collections) {
106106
if (resource.getStreamTransactionId() != null) {
107-
if (!resource.getCollectionNames().containsAll(collections)) {
107+
if (!resource.getCollectionNames().containsAll(collections) && logger.isDebugEnabled()) {
108108
Set<String> additional = new HashSet<>(collections);
109109
additional.removeAll(resource.getCollectionNames());
110-
throw new IllegalTransactionStateException("Stream transaction already started on collections " + resource.getCollectionNames() + ", no additional collections allowed: " + additional);
110+
logger.debug("Stream transaction already started on collections " + resource.getCollectionNames() + ", assuming additional collections are read only: " + additional);
111111
}
112+
} else {
113+
Set<String> all = new HashSet<>(resource.getCollectionNames());
114+
all.addAll(collections);
115+
resource.setCollectionNames(all);
112116
}
113-
HashSet<String> all = new HashSet<>(resource.getCollectionNames());
114-
all.addAll(collections);
115-
resource.setCollectionNames(all);
116117
}
117118

118119
private boolean isStatus(StreamTransactionStatus status) {

src/main/java/com/arangodb/springframework/transaction/ArangoTransactionResource.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ArangoTransactionResource {
1818
private Set<String> collectionNames;
1919

2020
private boolean rollbackOnly;
21+
int references = 0;
2122

2223
ArangoTransactionResource(@Nullable String streamTransactionId, Set<String> collectionNames, boolean rollbackOnly) {
2324
this.streamTransactionId = streamTransactionId;
@@ -48,4 +49,16 @@ boolean isRollbackOnly() {
4849
void setRollbackOnly(boolean rollbackOnly) {
4950
this.rollbackOnly = rollbackOnly;
5051
}
52+
53+
void increaseReferences() {
54+
++references;
55+
}
56+
57+
boolean isSingleReference() {
58+
return references <= 1;
59+
}
60+
61+
void decreasedReferences() {
62+
--references;
63+
}
5164
}

src/test/java/com/arangodb/springframework/transaction/ArangoTransactionManagerTest.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import static org.hamcrest.MatcherAssert.assertThat;
3333
import static org.hamcrest.Matchers.empty;
3434
import static org.hamcrest.Matchers.is;
35+
import static org.hamcrest.Matchers.hasItem;
3536
import static org.hamcrest.Matchers.hasItems;
37+
import static org.hamcrest.Matchers.not;
3638
import static org.hamcrest.Matchers.nullValue;
3739
import static org.mockito.ArgumentMatchers.any;
3840
import static org.mockito.Mockito.when;
@@ -95,7 +97,7 @@ public void getTransactionReturnsNewTransactionWithoutStreamTransaction() {
9597
public void nestedGetTransactionReturnsNewTransactionWithFormerCollections() {
9698
DefaultTransactionAttribute first = new DefaultTransactionAttribute();
9799
first.setLabels(Collections.singleton("foo"));
98-
underTest.getTransaction(first);
100+
TransactionStatus outer = underTest.getTransaction(first);
99101
DefaultTransactionAttribute second = new DefaultTransactionAttribute();
100102
second.setLabels(Collections.singleton("bar"));
101103
TransactionStatus inner = underTest.getTransaction(second);
@@ -141,13 +143,19 @@ public void nestedGetTransactionReturnsExistingTransactionWithFormerCollections(
141143

142144
DefaultTransactionAttribute second = new DefaultTransactionAttribute();
143145
second.setLabels(Collections.singleton("bar"));
144-
TransactionStatus inner = underTest.getTransaction(second);
145-
assertThat(inner.isNewTransaction(), is(false));
146-
ArangoTransactionObject transactionObject = getTransactionObject(inner);
147-
assertThat(transactionObject.getResource().getStreamTransactionId(), is("123"));
148-
underTest.commit(inner);
146+
TransactionStatus inner1 = underTest.getTransaction(second);
147+
assertThat(inner1.isNewTransaction(), is(false));
148+
ArangoTransactionObject tx1 = getTransactionObject(inner1);
149+
assertThat(tx1.getResource().getStreamTransactionId(), is("123"));
150+
underTest.commit(inner1);
151+
TransactionStatus inner2 = underTest.getTransaction(second);
152+
assertThat(inner2.isNewTransaction(), is(false));
153+
ArangoTransactionObject tx2 = getTransactionObject(inner1);
154+
assertThat(tx2.getResource().getStreamTransactionId(), is("123"));
155+
underTest.commit(inner2);
149156
underTest.commit(outer);
150157
verify(database).commitStreamTransaction("123");
158+
assertThat(TransactionSynchronizationManager.getResource(DATABASE_NAME), nullValue());
151159
}
152160

153161
@Test
@@ -163,14 +171,16 @@ public void getTransactionWithMultipleBridgeCallsWorksForKnownCollections() {
163171
assertThat(collections.getWrite(), hasItems("baz", "foo"));
164172
}
165173

166-
@Test(expected = IllegalTransactionStateException.class)
167-
public void getTransactionWithMultipleBridgeCallsFailsForAdditionalCollection() {
174+
@Test
175+
public void getTransactionWithMultipleBridgeCallsIgnoresAdditionalCollections() {
168176
DefaultTransactionAttribute definition = new DefaultTransactionAttribute();
169-
definition.setLabels(Collections.singleton("baz"));
177+
definition.setLabels(Collections.singleton("bar"));
170178
definition.setTimeout(20);
171-
underTest.getTransaction(definition);
179+
TransactionStatus state = underTest.getTransaction(definition);
172180
beginTransaction("123", "foo");
173-
beginPassed.getValue().apply(Collections.singletonList("bar"));
181+
beginPassed.getValue().apply(Collections.singletonList("baz"));
182+
assertThat(getTransactionObject(state).getResource().getCollectionNames(), hasItems("foo", "bar"));
183+
assertThat(getTransactionObject(state).getResource().getCollectionNames(), not(hasItem("baz")));
174184
}
175185

176186
@Test(expected = InvalidIsolationLevelException.class)

0 commit comments

Comments
 (0)