Skip to content

Commit b5e9fa5

Browse files
committed
TransactionActionSupport evaluates Vavr Try failure as rollback-only
Closes gh-20361
1 parent afbe7b3 commit b5e9fa5

File tree

3 files changed

+178
-10
lines changed

3 files changed

+178
-10
lines changed

spring-tx/spring-tx.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies {
1010
optional("javax.resource:javax.resource-api:1.7.1")
1111
optional("javax.transaction:javax.transaction-api:1.3")
1212
optional("com.ibm.websphere:uow:6.0.2.17")
13+
optional("io.vavr:vavr:0.10.0")
1314
testCompile("org.aspectj:aspectjweaver:${aspectjVersion}")
1415
testCompile("org.codehaus.groovy:groovy:${groovyVersion}")
1516
testCompile("org.eclipse.persistence:javax.persistence:2.2.0")

spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java

+44-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.Properties;
2121
import java.util.concurrent.ConcurrentMap;
2222

23+
import io.vavr.control.Try;
2324
import org.apache.commons.logging.Log;
2425
import org.apache.commons.logging.LogFactory;
2526

@@ -79,6 +80,12 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
7980
*/
8081
private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
8182

83+
/**
84+
* Vavr library present on the classpath?
85+
*/
86+
private static final boolean vavrPresent = ClassUtils.isPresent(
87+
"io.vavr.control.Try", TransactionAspectSupport.class.getClassLoader());
88+
8289
/**
8390
* Holder to support the {@code currentTransactionStatus()} method,
8491
* and to support communication between different cooperating advices
@@ -287,7 +294,8 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targe
287294
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
288295
// Standard transaction demarcation with getTransaction and commit/rollback calls.
289296
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
290-
Object retVal = null;
297+
298+
Object retVal;
291299
try {
292300
// This is an around advice: Invoke the next interceptor in the chain.
293301
// This will normally result in a target object being invoked.
@@ -301,6 +309,15 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targe
301309
finally {
302310
cleanupTransactionInfo(txInfo);
303311
}
312+
313+
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
314+
// Set rollback-only in case of Vavr failure matching our rollback rules...
315+
TransactionStatus status = txInfo.getTransactionStatus();
316+
if (status != null && txAttr != null) {
317+
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
318+
}
319+
}
320+
304321
commitTransactionAfterReturning(txInfo);
305322
return retVal;
306323
}
@@ -313,7 +330,12 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targe
313330
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
314331
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
315332
try {
316-
return invocation.proceedWithInvocation();
333+
Object retVal = invocation.proceedWithInvocation();
334+
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
335+
// Set rollback-only in case of Vavr failure matching our rollback rules...
336+
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
337+
}
338+
return retVal;
317339
}
318340
catch (Throwable ex) {
319341
if (txAttr.rollbackOn(ex)) {
@@ -712,4 +734,23 @@ public String toString() {
712734
}
713735
}
714736

737+
738+
/**
739+
* Inner class to avoid a hard dependency on the Vavr library at runtime.
740+
*/
741+
private static class VavrDelegate {
742+
743+
public static boolean isVavrTry(Object retVal) {
744+
return (retVal instanceof Try);
745+
}
746+
747+
public static Object evaluateTryFailure(Object retVal, TransactionAttribute txAttr, TransactionStatus status) {
748+
return ((Try<?>) retVal).onFailure(ex -> {
749+
if (txAttr.rollbackOn(ex)) {
750+
status.setRollbackOnly();
751+
}
752+
});
753+
}
754+
}
755+
715756
}

spring-tx/src/test/java/org/springframework/transaction/annotation/AnnotationTransactionInterceptorTests.java

+133-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.transaction.annotation;
1818

19+
import io.vavr.control.Try;
1920
import org.junit.Test;
2021

2122
import org.springframework.aop.framework.ProxyFactory;
@@ -123,12 +124,12 @@ public void withMultiMethodOverride() {
123124
}
124125

125126
@Test
126-
public void withRollback() {
127+
public void withRollbackOnRuntimeException() {
127128
ProxyFactory proxyFactory = new ProxyFactory();
128-
proxyFactory.setTarget(new TestWithRollback());
129+
proxyFactory.setTarget(new TestWithExceptions());
129130
proxyFactory.addAdvice(this.ti);
130131

131-
TestWithRollback proxy = (TestWithRollback) proxyFactory.getProxy();
132+
TestWithExceptions proxy = (TestWithExceptions) proxyFactory.getProxy();
132133

133134
try {
134135
proxy.doSomethingErroneous();
@@ -147,6 +148,88 @@ public void withRollback() {
147148
}
148149
}
149150

151+
@Test
152+
public void withCommitOnCheckedException() {
153+
ProxyFactory proxyFactory = new ProxyFactory();
154+
proxyFactory.setTarget(new TestWithExceptions());
155+
proxyFactory.addAdvice(this.ti);
156+
157+
TestWithExceptions proxy = (TestWithExceptions) proxyFactory.getProxy();
158+
159+
try {
160+
proxy.doSomethingElseWithCheckedException();
161+
fail("Should throw Exception");
162+
}
163+
catch (Exception ex) {
164+
assertGetTransactionAndCommitCount(1);
165+
}
166+
}
167+
168+
@Test
169+
public void withRollbackOnCheckedExceptionAndRollbackRule() {
170+
ProxyFactory proxyFactory = new ProxyFactory();
171+
proxyFactory.setTarget(new TestWithExceptions());
172+
proxyFactory.addAdvice(this.ti);
173+
174+
TestWithExceptions proxy = (TestWithExceptions) proxyFactory.getProxy();
175+
176+
try {
177+
proxy.doSomethingElseWithCheckedExceptionAndRollbackRule();
178+
fail("Should throw Exception");
179+
}
180+
catch (Exception ex) {
181+
assertGetTransactionAndRollbackCount(1);
182+
}
183+
}
184+
185+
@Test
186+
public void withVavrTrySuccess() {
187+
ProxyFactory proxyFactory = new ProxyFactory();
188+
proxyFactory.setTarget(new TestWithVavrTry());
189+
proxyFactory.addAdvice(this.ti);
190+
191+
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
192+
193+
proxy.doSomething();
194+
assertGetTransactionAndCommitCount(1);
195+
}
196+
197+
@Test
198+
public void withVavrTryRuntimeException() {
199+
ProxyFactory proxyFactory = new ProxyFactory();
200+
proxyFactory.setTarget(new TestWithVavrTry());
201+
proxyFactory.addAdvice(this.ti);
202+
203+
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
204+
205+
proxy.doSomethingErroneous();
206+
assertGetTransactionAndRollbackCount(1);
207+
}
208+
209+
@Test
210+
public void withVavrTryCheckedException() {
211+
ProxyFactory proxyFactory = new ProxyFactory();
212+
proxyFactory.setTarget(new TestWithVavrTry());
213+
proxyFactory.addAdvice(this.ti);
214+
215+
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
216+
217+
proxy.doSomethingErroneousWithCheckedException();
218+
assertGetTransactionAndCommitCount(1);
219+
}
220+
221+
@Test
222+
public void withVavrTryCheckedExceptionAndRollbackRule() {
223+
ProxyFactory proxyFactory = new ProxyFactory();
224+
proxyFactory.setTarget(new TestWithVavrTry());
225+
proxyFactory.addAdvice(this.ti);
226+
227+
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
228+
229+
proxy.doSomethingErroneousWithCheckedExceptionAndRollbackRule();
230+
assertGetTransactionAndRollbackCount(1);
231+
}
232+
150233
@Test
151234
public void withInterface() {
152235
ProxyFactory proxyFactory = new ProxyFactory();
@@ -352,21 +435,64 @@ public void doSomethingCompletelyElse() {
352435
}
353436

354437

355-
@Transactional(rollbackFor = IllegalStateException.class)
356-
public static class TestWithRollback {
438+
@Transactional
439+
public static class TestWithExceptions {
357440

358441
public void doSomethingErroneous() {
359442
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
360443
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
361444
throw new IllegalStateException();
362445
}
363446

364-
@Transactional(rollbackFor = IllegalArgumentException.class)
365447
public void doSomethingElseErroneous() {
366448
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
367449
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
368450
throw new IllegalArgumentException();
369451
}
452+
453+
@Transactional
454+
public void doSomethingElseWithCheckedException() throws Exception {
455+
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
456+
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
457+
throw new Exception();
458+
}
459+
460+
@Transactional(rollbackFor = Exception.class)
461+
public void doSomethingElseWithCheckedExceptionAndRollbackRule() throws Exception {
462+
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
463+
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
464+
throw new Exception();
465+
}
466+
}
467+
468+
469+
@Transactional
470+
public static class TestWithVavrTry {
471+
472+
public Try<String> doSomething() {
473+
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
474+
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
475+
return Try.success("ok");
476+
}
477+
478+
public Try<String> doSomethingErroneous() {
479+
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
480+
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
481+
return Try.failure(new IllegalStateException());
482+
}
483+
484+
public Try<String> doSomethingErroneousWithCheckedException() {
485+
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
486+
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
487+
return Try.failure(new Exception());
488+
}
489+
490+
@Transactional(rollbackFor = Exception.class)
491+
public Try<String> doSomethingErroneousWithCheckedExceptionAndRollbackRule() {
492+
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
493+
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
494+
return Try.failure(new Exception());
495+
}
370496
}
371497

372498

0 commit comments

Comments
 (0)