spring AbstractReactiveTransactionAspectTests 源码

  • 2022-08-08
  • 浏览 (374)

spring AbstractReactiveTransactionAspectTests 代码

文件路径:/spring-tx/src/test/java/org/springframework/transaction/interceptor/AbstractReactiveTransactionAspectTests.java

/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.interceptor;

import java.lang.reflect.Method;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.ReactiveTransaction;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.reactive.TransactionContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;

/**
 * Abstract support class to test {@link TransactionAspectSupport} with reactive methods.
 *
 * @author Mark Paluch
 * @author Juergen Hoeller
 */
public abstract class AbstractReactiveTransactionAspectTests {

	protected Method getNameMethod;

	protected Method setNameMethod;

	protected Method exceptionalMethod;


	@BeforeEach
	public void setup() throws Exception {
		getNameMethod = TestBean.class.getMethod("getName");
		setNameMethod = TestBean.class.getMethod("setName", String.class);
		exceptionalMethod = TestBean.class.getMethod("exceptional", Throwable.class);
	}


	@Test
	public void noTransaction() throws Exception {
		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);

		DefaultTestBean tb = new DefaultTestBean();
		TransactionAttributeSource tas = new MapTransactionAttributeSource();

		// All the methods in this class use the advised() template method
		// to obtain a transaction object, configured with the when PlatformTransactionManager
		// and transaction attribute source
		TestBean itb = (TestBean) advised(tb, rtm, tas);

		checkReactiveTransaction(false);
		itb.getName();
		checkReactiveTransaction(false);

		// expect no calls
		verifyNoInteractions(rtm);
	}

	/**
	 * Check that a transaction is created and committed.
	 */
	@Test
	public void transactionShouldSucceed() throws Exception {
		TransactionAttribute txatt = new DefaultTransactionAttribute();

		MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
		tas.register(getNameMethod, txatt);

		ReactiveTransaction status = mock(ReactiveTransaction.class);
		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);
		// expect a transaction
		given(rtm.getReactiveTransaction(txatt)).willReturn(Mono.just(status));
		given(rtm.commit(status)).willReturn(Mono.empty());

		DefaultTestBean tb = new DefaultTestBean();
		TestBean itb = (TestBean) advised(tb, rtm, tas);

		itb.getName()
				.as(StepVerifier::create)
				.verifyComplete();

		verify(rtm).commit(status);
	}

	/**
	 * Check that two transactions are created and committed.
	 */
	@Test
	public void twoTransactionsShouldSucceed() throws Exception {
		TransactionAttribute txatt = new DefaultTransactionAttribute();

		MapTransactionAttributeSource tas1 = new MapTransactionAttributeSource();
		tas1.register(getNameMethod, txatt);
		MapTransactionAttributeSource tas2 = new MapTransactionAttributeSource();
		tas2.register(setNameMethod, txatt);

		ReactiveTransaction status = mock(ReactiveTransaction.class);
		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);
		// expect a transaction
		given(rtm.getReactiveTransaction(txatt)).willReturn(Mono.just(status));
		given(rtm.commit(status)).willReturn(Mono.empty());

		DefaultTestBean tb = new DefaultTestBean();
		TestBean itb = (TestBean) advised(tb, rtm, new TransactionAttributeSource[] {tas1, tas2});

		itb.getName()
				.as(StepVerifier::create)
				.verifyComplete();

		Mono.from(itb.setName("myName"))
				.as(StepVerifier::create)
				.verifyComplete();

		verify(rtm, times(2)).commit(status);
	}

	/**
	 * Check that a transaction is created and committed.
	 */
	@Test
	public void transactionShouldSucceedWithNotNew() throws Exception {
		TransactionAttribute txatt = new DefaultTransactionAttribute();

		MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
		tas.register(getNameMethod, txatt);

		ReactiveTransaction status = mock(ReactiveTransaction.class);
		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);
		// expect a transaction
		given(rtm.getReactiveTransaction(txatt)).willReturn(Mono.just(status));
		given(rtm.commit(status)).willReturn(Mono.empty());

		DefaultTestBean tb = new DefaultTestBean();
		TestBean itb = (TestBean) advised(tb, rtm, tas);

		itb.getName()
				.as(StepVerifier::create)
				.verifyComplete();

		verify(rtm).commit(status);
	}


	@Test
	public void rollbackOnCheckedException() throws Throwable {
		doTestRollbackOnException(new Exception(), true, false);
	}

	@Test
	public void noRollbackOnCheckedException() throws Throwable {
		doTestRollbackOnException(new Exception(), false, false);
	}

	@Test
	public void rollbackOnUncheckedException() throws Throwable {
		doTestRollbackOnException(new RuntimeException(), true, false);
	}

	@Test
	public void noRollbackOnUncheckedException() throws Throwable {
		doTestRollbackOnException(new RuntimeException(), false, false);
	}

	@Test
	public void rollbackOnCheckedExceptionWithRollbackException() throws Throwable {
		doTestRollbackOnException(new Exception(), true, true);
	}

	@Test
	public void noRollbackOnCheckedExceptionWithRollbackException() throws Throwable {
		doTestRollbackOnException(new Exception(), false, true);
	}

	@Test
	public void rollbackOnUncheckedExceptionWithRollbackException() throws Throwable {
		doTestRollbackOnException(new RuntimeException(), true, true);
	}

	@Test
	public void noRollbackOnUncheckedExceptionWithRollbackException() throws Throwable {
		doTestRollbackOnException(new RuntimeException(), false, true);
	}

	/**
	 * Check that the when exception thrown by the target can produce the
	 * desired behavior with the appropriate transaction attribute.
	 * @param ex exception to be thrown by the target
	 * @param shouldRollback whether this should cause a transaction rollback
	 */
	@SuppressWarnings("serial")
	protected void doTestRollbackOnException(
			final Exception ex, final boolean shouldRollback, boolean rollbackException) throws Exception {

		TransactionAttribute txatt = new DefaultTransactionAttribute() {
			@Override
			public boolean rollbackOn(Throwable t) {
				assertThat(t).isSameAs(ex);
				return shouldRollback;
			}
		};

		Method m = exceptionalMethod;
		MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
		tas.register(m, txatt);

		ReactiveTransaction status = mock(ReactiveTransaction.class);
		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);
		// Gets additional call(s) from TransactionControl

		given(rtm.getReactiveTransaction(txatt)).willReturn(Mono.just(status));

		TransactionSystemException tex = new TransactionSystemException("system exception");
		if (rollbackException) {
			if (shouldRollback) {
				given(rtm.rollback(status)).willReturn(Mono.error(tex));
			}
			else {
				given(rtm.commit(status)).willReturn(Mono.error(tex));
			}
		}
		else {
			given(rtm.commit(status)).willReturn(Mono.empty());
			given(rtm.rollback(status)).willReturn(Mono.empty());
		}

		DefaultTestBean tb = new DefaultTestBean();
		TestBean itb = (TestBean) advised(tb, rtm, tas);

		itb.exceptional(ex)
				.as(StepVerifier::create)
				.expectErrorSatisfies(actual -> {
					if (rollbackException) {
						assertThat(actual).isEqualTo(tex);
					}
					else {
						assertThat(actual).isEqualTo(ex);
					}
				}).verify();

		if (!rollbackException) {
			if (shouldRollback) {
				verify(rtm).rollback(status);
			}
			else {
				verify(rtm).commit(status);
			}
		}
	}

	/**
	 * Simulate a transaction infrastructure failure.
	 * Shouldn't invoke target method.
	 */
	@Test
	public void cannotCreateTransaction() throws Exception {
		TransactionAttribute txatt = new DefaultTransactionAttribute();

		Method m = getNameMethod;
		MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
		tas.register(m, txatt);

		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);
		// Expect a transaction
		CannotCreateTransactionException ex = new CannotCreateTransactionException("foobar", null);
		given(rtm.getReactiveTransaction(txatt)).willThrow(ex);

		DefaultTestBean tb = new DefaultTestBean() {
			@Override
			public Mono<String> getName() {
				throw new UnsupportedOperationException(
						"Shouldn't have invoked target method when couldn't create transaction for transactional method");
			}
		};
		TestBean itb = (TestBean) advised(tb, rtm, tas);

		itb.getName()
				.as(StepVerifier::create)
				.expectError(CannotCreateTransactionException.class)
				.verify();
	}

	/**
	 * Simulate failure of the underlying transaction infrastructure to commit.
	 * Check that the target method was invoked, but that the transaction
	 * infrastructure exception was thrown to the client
	 */
	@Test
	public void cannotCommitTransaction() throws Exception {
		TransactionAttribute txatt = new DefaultTransactionAttribute();

		Method m = setNameMethod;
		MapTransactionAttributeSource tas = new MapTransactionAttributeSource();
		tas.register(m, txatt);
		// Method m2 = getNameMethod;
		// No attributes for m2

		ReactiveTransactionManager rtm = mock(ReactiveTransactionManager.class);

		ReactiveTransaction status = mock(ReactiveTransaction.class);
		given(rtm.getReactiveTransaction(txatt)).willReturn(Mono.just(status));
		UnexpectedRollbackException ex = new UnexpectedRollbackException("foobar", null);
		given(rtm.commit(status)).willReturn(Mono.error(ex));
		given(rtm.rollback(status)).willReturn(Mono.empty());

		DefaultTestBean tb = new DefaultTestBean();
		TestBean itb = (TestBean) advised(tb, rtm, tas);

		String name = "new name";

		Mono.from(itb.setName(name))
				.as(StepVerifier::create)
				.consumeErrorWith(throwable -> {
					assertThat(throwable.getClass()).isEqualTo(RuntimeException.class);
					assertThat(throwable.getCause()).isEqualTo(ex);
				})
				.verify();

		// Should have invoked target and changed name

		itb.getName()
				.as(StepVerifier::create)
				.expectNext(name)
				.verifyComplete();
	}

	private void checkReactiveTransaction(boolean expected) {
		Mono.deferContextual(Mono::just)
				.handle((context, sink) -> {
					if (context.hasKey(TransactionContext.class) != expected) {
						fail("Should have thrown NoTransactionException");
					}
					sink.complete();
				})
				.block();
	}


	protected Object advised(
			Object target, ReactiveTransactionManager rtm, TransactionAttributeSource[] tas) throws Exception {

		return advised(target, rtm, new CompositeTransactionAttributeSource(tas));
	}

	/**
	 * Subclasses must implement this to create an advised object based on the
	 * when target. In the case of AspectJ, the  advised object will already
	 * have been created, as there's no distinction between target and proxy.
	 * In the case of Spring's own AOP framework, a proxy must be created
	 * using a suitably configured transaction interceptor
	 * @param target the target if there's a distinct target. If not (AspectJ),
	 * return target.
	 * @return transactional advised object
	 */
	protected abstract Object advised(
			Object target, ReactiveTransactionManager rtm, TransactionAttributeSource tas) throws Exception;


	public interface TestBean {

		Mono<String> getName();

		Publisher<Void> setName(String name);

		Mono<Void> exceptional(Throwable t);
	}


	public class DefaultTestBean implements TestBean {

		private String name;

		@Override
		public Mono<String> getName() {
			return Mono.justOrEmpty(name);
		}

		@Override
		public Publisher<Void> setName(String name) {
			return Mono.fromRunnable(() -> this.name = name);
		}

		@Override
		public Mono<Void> exceptional(Throwable t) {
			if (t != null) {
				return Mono.error(t);
			}
			return Mono.empty();
		}
	}

}

相关信息

spring 源码目录

相关文章

spring AbstractTransactionAspectTests 源码

spring BeanFactoryTransactionTests 源码

spring ImplementsNoInterfaces 源码

spring MapTransactionAttributeSource 源码

spring MyRuntimeException 源码

spring PlatformTransactionManagerFacade 源码

spring ReactiveTransactionInterceptorTests 源码

spring RollbackRuleAttributeTests 源码

spring RuleBasedTransactionAttributeTests 源码

spring TransactionAttributeEditorTests 源码

0  赞