TransactionActionSupport evaluates Vavr Try failure as rollback-only
Closes gh-20361
This commit is contained in:
parent
afbe7b31bb
commit
b5e9fa5f1e
|
@ -10,6 +10,7 @@ dependencies {
|
||||||
optional("javax.resource:javax.resource-api:1.7.1")
|
optional("javax.resource:javax.resource-api:1.7.1")
|
||||||
optional("javax.transaction:javax.transaction-api:1.3")
|
optional("javax.transaction:javax.transaction-api:1.3")
|
||||||
optional("com.ibm.websphere:uow:6.0.2.17")
|
optional("com.ibm.websphere:uow:6.0.2.17")
|
||||||
|
optional("io.vavr:vavr:0.10.0")
|
||||||
testCompile("org.aspectj:aspectjweaver:${aspectjVersion}")
|
testCompile("org.aspectj:aspectjweaver:${aspectjVersion}")
|
||||||
testCompile("org.codehaus.groovy:groovy:${groovyVersion}")
|
testCompile("org.codehaus.groovy:groovy:${groovyVersion}")
|
||||||
testCompile("org.eclipse.persistence:javax.persistence:2.2.0")
|
testCompile("org.eclipse.persistence:javax.persistence:2.2.0")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import io.vavr.control.Try;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
@ -79,6 +80,12 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||||
*/
|
*/
|
||||||
private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
|
private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vavr library present on the classpath?
|
||||||
|
*/
|
||||||
|
private static final boolean vavrPresent = ClassUtils.isPresent(
|
||||||
|
"io.vavr.control.Try", TransactionAspectSupport.class.getClassLoader());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holder to support the {@code currentTransactionStatus()} method,
|
* Holder to support the {@code currentTransactionStatus()} method,
|
||||||
* and to support communication between different cooperating advices
|
* and to support communication between different cooperating advices
|
||||||
|
@ -287,7 +294,8 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||||
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
|
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
|
||||||
// Standard transaction demarcation with getTransaction and commit/rollback calls.
|
// Standard transaction demarcation with getTransaction and commit/rollback calls.
|
||||||
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
|
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
|
||||||
Object retVal = null;
|
|
||||||
|
Object retVal;
|
||||||
try {
|
try {
|
||||||
// This is an around advice: Invoke the next interceptor in the chain.
|
// This is an around advice: Invoke the next interceptor in the chain.
|
||||||
// This will normally result in a target object being invoked.
|
// This will normally result in a target object being invoked.
|
||||||
|
@ -301,6 +309,15 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||||
finally {
|
finally {
|
||||||
cleanupTransactionInfo(txInfo);
|
cleanupTransactionInfo(txInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
|
||||||
|
// Set rollback-only in case of Vavr failure matching our rollback rules...
|
||||||
|
TransactionStatus status = txInfo.getTransactionStatus();
|
||||||
|
if (status != null && txAttr != null) {
|
||||||
|
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commitTransactionAfterReturning(txInfo);
|
commitTransactionAfterReturning(txInfo);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
@ -313,7 +330,12 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||||
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
|
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
|
||||||
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
|
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
|
||||||
try {
|
try {
|
||||||
return invocation.proceedWithInvocation();
|
Object retVal = invocation.proceedWithInvocation();
|
||||||
|
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
|
||||||
|
// Set rollback-only in case of Vavr failure matching our rollback rules...
|
||||||
|
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
catch (Throwable ex) {
|
catch (Throwable ex) {
|
||||||
if (txAttr.rollbackOn(ex)) {
|
if (txAttr.rollbackOn(ex)) {
|
||||||
|
@ -712,4 +734,23 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class to avoid a hard dependency on the Vavr library at runtime.
|
||||||
|
*/
|
||||||
|
private static class VavrDelegate {
|
||||||
|
|
||||||
|
public static boolean isVavrTry(Object retVal) {
|
||||||
|
return (retVal instanceof Try);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object evaluateTryFailure(Object retVal, TransactionAttribute txAttr, TransactionStatus status) {
|
||||||
|
return ((Try<?>) retVal).onFailure(ex -> {
|
||||||
|
if (txAttr.rollbackOn(ex)) {
|
||||||
|
status.setRollbackOnly();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.transaction.annotation;
|
package org.springframework.transaction.annotation;
|
||||||
|
|
||||||
|
import io.vavr.control.Try;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.aop.framework.ProxyFactory;
|
import org.springframework.aop.framework.ProxyFactory;
|
||||||
|
@ -123,12 +124,12 @@ public class AnnotationTransactionInterceptorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withRollback() {
|
public void withRollbackOnRuntimeException() {
|
||||||
ProxyFactory proxyFactory = new ProxyFactory();
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
proxyFactory.setTarget(new TestWithRollback());
|
proxyFactory.setTarget(new TestWithExceptions());
|
||||||
proxyFactory.addAdvice(this.ti);
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
TestWithRollback proxy = (TestWithRollback) proxyFactory.getProxy();
|
TestWithExceptions proxy = (TestWithExceptions) proxyFactory.getProxy();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
proxy.doSomethingErroneous();
|
proxy.doSomethingErroneous();
|
||||||
|
@ -147,6 +148,88 @@ public class AnnotationTransactionInterceptorTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withCommitOnCheckedException() {
|
||||||
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
proxyFactory.setTarget(new TestWithExceptions());
|
||||||
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
|
TestWithExceptions proxy = (TestWithExceptions) proxyFactory.getProxy();
|
||||||
|
|
||||||
|
try {
|
||||||
|
proxy.doSomethingElseWithCheckedException();
|
||||||
|
fail("Should throw Exception");
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
assertGetTransactionAndCommitCount(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withRollbackOnCheckedExceptionAndRollbackRule() {
|
||||||
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
proxyFactory.setTarget(new TestWithExceptions());
|
||||||
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
|
TestWithExceptions proxy = (TestWithExceptions) proxyFactory.getProxy();
|
||||||
|
|
||||||
|
try {
|
||||||
|
proxy.doSomethingElseWithCheckedExceptionAndRollbackRule();
|
||||||
|
fail("Should throw Exception");
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
assertGetTransactionAndRollbackCount(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withVavrTrySuccess() {
|
||||||
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
proxyFactory.setTarget(new TestWithVavrTry());
|
||||||
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
|
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
|
||||||
|
|
||||||
|
proxy.doSomething();
|
||||||
|
assertGetTransactionAndCommitCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withVavrTryRuntimeException() {
|
||||||
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
proxyFactory.setTarget(new TestWithVavrTry());
|
||||||
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
|
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
|
||||||
|
|
||||||
|
proxy.doSomethingErroneous();
|
||||||
|
assertGetTransactionAndRollbackCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withVavrTryCheckedException() {
|
||||||
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
proxyFactory.setTarget(new TestWithVavrTry());
|
||||||
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
|
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
|
||||||
|
|
||||||
|
proxy.doSomethingErroneousWithCheckedException();
|
||||||
|
assertGetTransactionAndCommitCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withVavrTryCheckedExceptionAndRollbackRule() {
|
||||||
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
proxyFactory.setTarget(new TestWithVavrTry());
|
||||||
|
proxyFactory.addAdvice(this.ti);
|
||||||
|
|
||||||
|
TestWithVavrTry proxy = (TestWithVavrTry) proxyFactory.getProxy();
|
||||||
|
|
||||||
|
proxy.doSomethingErroneousWithCheckedExceptionAndRollbackRule();
|
||||||
|
assertGetTransactionAndRollbackCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withInterface() {
|
public void withInterface() {
|
||||||
ProxyFactory proxyFactory = new ProxyFactory();
|
ProxyFactory proxyFactory = new ProxyFactory();
|
||||||
|
@ -352,8 +435,8 @@ public class AnnotationTransactionInterceptorTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Transactional(rollbackFor = IllegalStateException.class)
|
@Transactional
|
||||||
public static class TestWithRollback {
|
public static class TestWithExceptions {
|
||||||
|
|
||||||
public void doSomethingErroneous() {
|
public void doSomethingErroneous() {
|
||||||
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
@ -361,12 +444,55 @@ public class AnnotationTransactionInterceptorTests {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(rollbackFor = IllegalArgumentException.class)
|
|
||||||
public void doSomethingElseErroneous() {
|
public void doSomethingElseErroneous() {
|
||||||
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void doSomethingElseWithCheckedException() throws Exception {
|
||||||
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void doSomethingElseWithCheckedExceptionAndRollbackRule() throws Exception {
|
||||||
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public static class TestWithVavrTry {
|
||||||
|
|
||||||
|
public Try<String> doSomething() {
|
||||||
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
|
return Try.success("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Try<String> doSomethingErroneous() {
|
||||||
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
|
return Try.failure(new IllegalStateException());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Try<String> doSomethingErroneousWithCheckedException() {
|
||||||
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
|
return Try.failure(new Exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Try<String> doSomethingErroneousWithCheckedExceptionAndRollbackRule() {
|
||||||
|
assertTrue(TransactionSynchronizationManager.isActualTransactionActive());
|
||||||
|
assertFalse(TransactionSynchronizationManager.isCurrentTransactionReadOnly());
|
||||||
|
return Try.failure(new Exception());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue