¿Cuál es la diferencia entre la anotación @Transactional y rollbackFor = Exception.class? 3 razones y soluciones para el error de anotación @Transactional

1. Primero, preparé un dato en Mysql

imagen

2. Comience la prueba de manera simple y grosera

1. Nuestro propósito es cambiar delflag a 0 y simplemente preparar el sql

 <update id="test">
        UPDATE tbl_users set delflag='0' where account='admin'
 </update>

2. Vamos a probarlo primero. @TransactionalEl código es el siguiente. Todo el mundo sabe que 2/0 lanzará una excepción.

  @Override
  @Transactional
    public Ret test(){
        int i = articleMapper.test();
        int a = 2/0;
        if(i > 0){
            ResultUtil.success();
        }
        return ResultUtil.error();
    }

3. Ejecute la prueba i=1 para indicar que la actualización fue exitosa, no se preocupe, sigamos hasta el punto de interrupción y bajemos.

imagen4. Como era de esperar, cuando la ejecución llegó a la línea 54, ocurrió un error.java.lang.ArithmeticException: /by zero

imagen5. Los estudiantes cuidadosos encontrarán que ArithmeticExceptionesta clase de excepción se RuntimeExceptionhereda

Y @Transactionalla excepción de reversión predeterminada esRuntimeException

imagen6. Hicimos clic en RuntimeExceptionesta clase para descubrir que lo que encontramos RuntimeExceptionfue Exceptionheredado

Y todas las clases de excepción son básicamente heredadas, incluida la excepción RuntimeExceptionjusto arribajava.lang.ArithmeticException

Entonces, siempre que RuntimeExceptionla RuntimeExceptionexcepción lanzada por las siguientes subclases @Transactionalse pueda revertir

imagen7. En este momento, veamos si el valor de la base de datos se ha modificado con éxito, es obvio que los datos se han revertido y no se han modificado a 0.

inserte la descripción de la imagen aquí

1. A continuación, estamos probando @Transactionalel código de excepción que no se puede transferir de la siguiente manera

try catchLo usamos directamente para capturar excepciones y luego lanzar Exceptionexcepciones personalizadas en la captura

@Override
@Transactional
public Ret test() throws Exception {
    
    
    int i = articleMapper.test();

    try {
    
    
        int a = 2 / 0;
    } catch (Exception e) {
    
    
        throw new Exception();
    }
    if (i > 0) {
    
    
        ResultUtil.success();
    }
    return ResultUtil.error();
}

2. La excepción lanzada directamente por ok es la excepción que especificamos.Vamos java.lang.Exceptiona la base de datos

inserte la descripción de la imagen aquí

3. La base de datos se actualiza a 0, lo que indica que @Transactional no puede revertir la excepción
inserte la descripción de la imagen aquí

en conclusión

@TransactionalSolo se puede revertir RuntimeExceptiony RuntimeExceptionlas excepciones lanzadas por las siguientes subclases no se pueden Exceptionrevertir

Si necesita admitir Exceptionla excepción de reversión, utilice@Transactional(rollbackFor = Exception.class)

Si está agregando, eliminando y modificando aquí, sugiero que todos lo usen@Transactional(rollbackFor = Exception.class)




Introducción a los escenarios de falla transaccional

Cuando el primer tipo de modificador de método de anotación de anotación transaccional no es público, @Transactionalla anotación no funcionará. Por ejemplo el siguiente código.

Defina una implementación de anotación incorrecta @Transactional, modificando un método de acceso predeterminado

@Component  
public class TestServiceImpl {
    
      
    @Resource  
    TestMapper testMapper;  
      
    @Transactional  
    void insertTestWrongModifier() {
    
      
        int re = testMapper.insert(new Test(10,20,30));  
        if (re > 0) {
    
      
            throw new NeedToInterceptException("need intercept");  
        }  
        testMapper.insert(new Test(210,20,30));  
    }  
  
}  

En el mismo paquete, cree un nuevo objeto de llamada para acceder.

@Component  
public class InvokcationService {
    
      
    @Resource  
    private TestServiceImpl testService;  
    public void invokeInsertTestWrongModifier(){
    
      
        //调用@Transactional标注的默认访问符方法  
        testService.insertTestWrongModifier();  
    }  
}  

caso de prueba

@RunWith(SpringRunner.class)  
@SpringBootTest  
public class DemoApplicationTests {
    
      
   @Resource  
   InvokcationService invokcationService;  
  
   @Test  
   public void  testInvoke(){
    
      
      invokcationService.invokeInsertTestWrongModifier();  
   }  
}  

El método de acceso anterior hace que la transacción no se abra, por lo que cuando el método genera una excepción, testMapper.insert(new Test(10,20,30));la operación no se revierte. Si TestServiceImpl#insertTestWrongModifierel método se cambia a público, la transacción se abrirá normalmente y testMapper.insert(new Test(10,20,30));se revertirá.

el segundo

Llame al método marcado dentro de la clase llamándolo dentro de la clase @Transactional. En este caso, la transacción no se abrirá. El código de ejemplo es el siguiente.

establecer una llamada interna

@Component  
public class TestServiceImpl implements TestService {
    
      
    @Resource  
    TestMapper testMapper;  
  
    @Transactional  
    public void insertTestInnerInvoke() {
    
      
        //正常public修饰符的事务方法  
        int re = testMapper.insert(new Test(10,20,30));  
        if (re > 0) {
    
      
            throw new NeedToInterceptException("need intercept");  
        }  
        testMapper.insert(new Test(210,20,30));  
    }  
  
  
    public void testInnerInvoke(){
    
      
        //类内部调用@Transactional标注的方法。  
        insertTestInnerInvoke();  
    }  
  
}  

caso de prueba.

@RunWith(SpringRunner.class)  
@SpringBootTest  
public class DemoApplicationTests {
    
      
  
   @Resource  
   TestServiceImpl testService;  
  
   /**  
    * 测试内部调用@Transactional标注方法  
    */  
   @Test  
   public void  testInnerInvoke(){
    
      
       //测试外部调用事务方法是否正常  
      //testService.insertTestInnerInvoke();  
       //测试内部调用事务方法是否正常  
      testService.testInnerInvoke();  
   }  
}

El anterior es el código de prueba utilizado. Ejecute la prueba para saber que el método de transacción de llamada externa puede iniciar la transacción y testMapper.insert(new Test(10,20,30))la operación se revertirá;

Luego ejecute otro caso de prueba, llame a un método para llamar al @Transactionalmétodo de transacción marcado internamente dentro de la clase, el resultado de la operación es que la transacción no se abrirá normalmente y testMapper.insert(new Test(10,20,30))la operación se guardará en la base de datos sin revertirse.

tercero

La excepción se captura dentro del método de transacción y no se lanza ninguna excepción nueva, por lo que la operación de transacción no se revertirá. El código de ejemplo es el siguiente.

@Component  
public class TestServiceImpl implements TestService {
    
      
    @Resource  
    TestMapper testMapper;  
  
    @Transactional  
    public void insertTestCatchException() {
    
      
        try {
    
      
            int re = testMapper.insert(new Test(10,20,30));  
            if (re > 0) {
    
      
                //运行期间抛异常  
                throw new NeedToInterceptException("need intercept");  
            }  
            testMapper.insert(new Test(210,20,30));  
        }catch (Exception e){
    
      
            System.out.println("i catch exception");  
        }  
    }  
      
}  

El código del caso de prueba es el siguiente.

@RunWith(SpringRunner.class)  
@SpringBootTest  
public class DemoApplicationTests {
    
      
  
   @Resource  
   TestServiceImpl testService;  
  
   @Test  
   public void testCatchException(){
    
      
      testService.insertTestCatchException();  
   }  
}   

Al ejecutar el caso de prueba, se encontró que, aunque se lanzó una excepción, la excepción se capturó y no se eliminó del método, y testMapper.insert(new Test(210,20,30))la operación no se revirtió.

Los tres anteriores son las razones principales por las que @Transactionallas anotaciones no funcionan y @Transactionalfallan. @TransactionalLo siguiente combina las anotaciones en primavera para realizar el análisis del código fuente y por qué @Transactionallas anotaciones no funcionan.

@La anotación transaccional no funciona en el análisis de principios

La primera

@TransactionalCuando el modificador del método de anotación no es público, @Transactionalla anotación no funcionará. El motivo del análisis aquí es que @Transactionalse basa en la implementación dinámica de proxy. @TransactionalEl método de implementación se analiza en el principio de implementación de anotaciones. En el proceso de inicialización de beans, @Transactionalse crean objetos proxy para instancias de beans con anotaciones. Hay un @Transactionalproceso de información de anotación de escaneo de primavera. Desafortunadamente, se refleja en el código fuente. @TransactionalSi el modificador del método marcado no es público, entonces la información del método predeterminado @Transactionalestá vacía, por lo que el objeto proxy no se creará para el bean o el proxy. no se realizará la llamada para el método.

@TransactionalEn el principio de implementación de anotaciones, se introduce cómo determinar si un bean crea un objeto proxy.La lógica general es. Cree una BeanFactoryTransactionAttributeSourceAdvisorinstancia de corte de puntos aop de acuerdo con Spring, recorra el objeto de método de la clase de bean actual y juzgue si la información de anotación en el método está incluida.Si @Transactionalalgún método del bean contiene @Transactionalinformación de anotación, entonces se adapta a este BeanFactoryTransactionAttributeSourceAdvisorpunto -punto de corte. Debe crear un objeto proxy y luego la lógica del proxy administra la lógica de apertura y cierre de la transacción por nosotros.

En el código fuente de Spring, al interceptar el proceso de creación del bean y buscar el punto de corte de la adaptación del bean, se utiliza el siguiente método. El propósito es encontrar la información @ en el método. Si existe, significa que el punto de corte se puede aplicar (canApply) al Transactionalbean BeanFactoryTransactionAttributeSourceAdvisor,

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    
      
   Assert.notNull(pc, "Pointcut must not be null");  
   if (!pc.getClassFilter().matches(targetClass)) {
    
      
      return false;  
   }  
  
   MethodMatcher methodMatcher = pc.getMethodMatcher();  
   if (methodMatcher == MethodMatcher.TRUE) {
    
      
      // No need to iterate the methods if we're matching any method anyway...  
      return true;  
   }  
  
   IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;  
   if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
    
      
      introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;  
   }  
  
    //遍历class的方法对象  
   Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));  
   classes.add(targetClass);  
   for (Class<?> clazz : classes) {
    
      
      Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);  
      for (Method method : methods) {
    
      
         if ((introductionAwareMethodMatcher != null &&  
               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||  
             //适配查询方法上的@Transactional注解信息    
             methodMatcher.matches(method, targetClass)) {
    
      
            return true;  
         }  
      }  
   }  
  
   return false;  
}  

Podemos romper el punto en el método anterior, depurar el código de seguimiento paso a paso y, finalmente, el código anterior llamará al siguiente método para juzgar. Los puntos de interrupción en los siguientes métodos y mirar hacia atrás en la pila de llamadas de métodos también es una buena forma de rastrear.

AbstractFallbackTransactionAttributeSource#getTransactionAttribute
  • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
    
      
   // Don't allow no-public methods as required.  
   //非public 方法,返回@Transactional信息一律是null  
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    
      
      return null;  
   }  
   //后面省略.......  
 }  

ningún objeto proxy creado

Por lo tanto, si los modificadores de todos los métodos no son públicos, no se creará ningún objeto proxy. Tome el código de prueba al principio como ejemplo, si el testService del modificador normal es el objeto proxy creado por cglib en la imagen a continuación.

imagen

Si los métodos de la clase no son públicos, no será un objeto proxy.

sin llamadas de proxy

Considere una situación como la que se muestra en el siguiente código. Ambos métodos están @Transactionalanotados, pero uno tiene un modificador público y el otro no, en este caso podemos prever que se creará un objeto proxy, porque hay al menos un @Transactionalmétodo de anotación con un modificador público.

Una vez que se crea el objeto proxy, insertTestWrongModifier¿se iniciará la transacción? La respuesta es no.

@Component  
public class TestServiceImpl implements TestService {
    
      
    @Resource  
    TestMapper testMapper;  
  
    @Override  
    @Transactional  
    public void insertTest() {
    
      
        int re = testMapper.insert(new Test(10,20,30));  
        if (re > 0) {
    
      
            throw new NeedToInterceptException("need intercept");  
        }  
        testMapper.insert(new Test(210,20,30));  
    }  
      
    @Transactional  
    void insertTestWrongModifier() {
    
      
        int re = testMapper.insert(new Test(10,20,30));  
        if (re > 0) {
    
      
            throw new NeedToInterceptException("need intercept");  
        }  
        testMapper.insert(new Test(210,20,30));  
    }  
}  

La razón es que cuando el objeto de proxy dinámico llama a la lógica de proxy, en la función de intercepción del objeto de proxy creado por cglib, CglibAopProxy.DynamicAdvisedInterceptor#intercepthay una lógica de la siguiente manera, el propósito es obtener la lógica aop de la adaptación del método actual que necesita ser ejecutado por el objeto proxy actual.

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);  

Para que la anotación @Transactional encuentre el proceso lógico aop, de manera similar, también se ejecuta una vez

AbstractFallbackTransactionAttributeSource#getTransactionAttribute
  • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

Es decir, es necesario encontrar una información de anotación sobre el método @Transactional, si no hay información de anotación, @Transactionalno se ejecutará la lógica de agente correspondiente al agente y se ejecutará directamente el método. Sin @Transactionalla lógica del proxy de anotación, la transacción no se puede abrir, lo cual también se menciona en el artículo anterior.

el segundo

Llame al método marcado dentro de la clase llamándolo dentro de la clase @Transactional. En este caso, la transacción no se abrirá.

Después de un análisis detallado del primer tipo, también se debe adivinar la razón por la cual la gestión de transacciones no está habilitada en esta situación;

Dado que la gestión de transacciones se implementa en función de la lógica de proxy del objeto de proxy dinámico, si el método de transacción dentro de la clase se llama dentro de la clase, el proceso de llamar al método de transacción no se llama a través del objeto de proxy, sino directamente a través de este. objeto para llamar al método, el objeto de proxy omitido no debe tener lógica de proxy.

De hecho, podemos jugar así, y la llamada interna también puede realizar la apertura de la transacción.El código es el siguiente.

@Component  
public class TestServiceImpl implements TestService {
    
      
    @Resource  
    TestMapper testMapper;  
  
    @Resource  
    TestServiceImpl testServiceImpl;  
  
  
    @Transactional  
    public void insertTestInnerInvoke() {
    
      
        int re = testMapper.insert(new Test(10,20,30));  
        if (re > 0) {
    
      
            throw new NeedToInterceptException("need intercept");  
        }  
        testMapper.insert(new Test(210,20,30));  
    }  
  
  
    public void testInnerInvoke(){
    
      
        //内部调用事务方法  
        testServiceImpl.insertTestInnerInvoke();  
    }  
  
}  

Lo anterior es el uso de objetos proxy para llamadas de transacciones, por lo que la gestión de transacciones se puede habilitar, pero en la operación real, nadie estará inactivo y jugará así ~

tercero

La excepción se captura dentro del método de transacción y no se lanza ninguna excepción nueva, por lo que la operación de transacción no se revertirá.

En este caso, puede ser más común para nosotros, y el problema radica en la lógica del proxy. Primero veamos cómo la lógica del proxy dinámico en el código fuente maneja los asuntos por nosotros.

TransactionAspectSupport#invokeWithinTransaction

el código se muestra a continuación.

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)  
      throws Throwable {
    
      
  
   // If the transaction attribute is null, the method is non-transactional.  
   final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);  
   final PlatformTransactionManager tm = determineTransactionManager(txAttr);  
   final String joinpointIdentification = methodIdentification(method, targetClass);  
  
   if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    
      
      // Standard transaction demarcation with getTransaction and commit/rollback calls.  
       //开启事务  
      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);  
      Object retVal = null;  
      try {
    
      
         // This is an around advice: Invoke the next interceptor in the chain.  
         // This will normally result in a target object being invoked.  
          //反射调用业务方法  
         retVal = invocation.proceedWithInvocation();  
      }  
      catch (Throwable ex) {
    
      
         // target invocation exception  
          //异常时,在catch逻辑中回滚事务  
         completeTransactionAfterThrowing(txInfo, ex);  
         throw ex;  
      }  
      finally {
    
      
         cleanupTransactionInfo(txInfo);  
      }  
       //提交事务  
      commitTransactionAfterReturning(txInfo);  
      return retVal;  
   }  
  
   else {
    
      
     //....................  
   }  
}          

Entonces, después de leer el código anterior, está claro de un vistazo que si la transacción quiere retroceder, debe poder detectar la excepción aquí. Si la excepción se detecta a la mitad, la transacción no se retrotraerá.

Las situaciones anteriores se resumen.

Supongo que te gusta

Origin blog.csdn.net/qq_43842093/article/details/131605760
Recomendado
Clasificación