diff --git a/spring-tx/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java b/spring-tx/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java index 772787433261..f7b373a527a8 100644 --- a/spring-tx/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java +++ b/spring-tx/src/main/java/org/springframework/dao/support/PersistenceExceptionTranslationInterceptor.java @@ -16,6 +16,9 @@ package org.springframework.dao.support; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Map; import org.aopalliance.intercept.MethodInterceptor; @@ -27,6 +30,8 @@ import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.OrderComparator; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -167,8 +172,21 @@ protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(L // Find all translators, being careful not to activate FactoryBeans. Map pets = BeanFactoryUtils.beansOfTypeIncludingAncestors( beanFactory, PersistenceExceptionTranslator.class, false, false); + + List translators = new ArrayList<>(pets.values()); + if (translators.size() > 1) { + Comparator comparatorToUse = null; + if (beanFactory instanceof DefaultListableBeanFactory) { + comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator(); + } + if (comparatorToUse == null) { + comparatorToUse = OrderComparator.INSTANCE; + } + translators.sort(comparatorToUse); + } + ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator(); - for (PersistenceExceptionTranslator pet : pets.values()) { + for (PersistenceExceptionTranslator pet : translators) { cpet.addDelegate(pet); } return cpet; diff --git a/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java b/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java index 351e971b3ea8..65b3d0392c25 100644 --- a/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java +++ b/spring-tx/src/test/java/org/springframework/dao/annotation/PersistenceExceptionTranslationInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2020 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. @@ -16,19 +16,34 @@ package org.springframework.dao.annotation; +import java.util.ArrayList; +import java.util.List; + +import org.aopalliance.intercept.MethodInvocation; +import org.junit.jupiter.api.Test; + import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslationInterceptor; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.stereotype.Repository; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + /** * Tests for standalone usage of a PersistenceExceptionTranslationInterceptor, as explicit advice bean in a BeanFactory * rather than applied as part of a PersistenceExceptionTranslationAdvisor. * * @author Juergen Hoeller + * @author Tadaya Tsuyukubo */ public class PersistenceExceptionTranslationInterceptorTests extends PersistenceExceptionTranslationAdvisorTests { @@ -42,4 +57,50 @@ protected void addPersistenceExceptionTranslation(ProxyFactory pf, PersistenceEx } } + @Test + void detectPersistenceExceptionTranslators() throws Throwable { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + bf.registerBeanDefinition("peti", new RootBeanDefinition(PersistenceExceptionTranslationInterceptor.class)); + + List callOrder = new ArrayList<>(); + bf.registerSingleton("pet20", new CallOrderAwareExceptionTranslator(20, callOrder)); + bf.registerSingleton("pet10", new CallOrderAwareExceptionTranslator(10, callOrder)); + bf.registerSingleton("pet30", new CallOrderAwareExceptionTranslator(30, callOrder)); + + PersistenceExceptionTranslationInterceptor interceptor = bf.getBean("peti", PersistenceExceptionTranslationInterceptor.class); + interceptor.setAlwaysTranslate(true); + + RuntimeException exception = new RuntimeException(); + MethodInvocation invocation = mock(MethodInvocation.class); + given(invocation.proceed()).willThrow(exception); + + assertThatThrownBy(() -> interceptor.invoke(invocation)).isSameAs(exception); + + assertThat(callOrder).containsExactly(10, 20, 30); + } + + private static class CallOrderAwareExceptionTranslator implements PersistenceExceptionTranslator, Ordered { + + private final int order; + + private final List callOrder; + + public CallOrderAwareExceptionTranslator(int order, List callOrder) { + this.order = order; + this.callOrder = callOrder; + } + + @Override + public DataAccessException translateExceptionIfPossible(RuntimeException ex) { + callOrder.add(this.order); + return null; + } + + @Override + public int getOrder() { + return this.order; + } + } + }