Primavera - @Transactional - ¿Qué sucede en segundo plano?

334

¿Quiero saber qué sucede realmente cuando anotas un método con @Transactional? Por supuesto, sé que Spring envolverá ese método en una Transacción.

Pero tengo las siguientes dudas:

  1. ¿Escuché que Spring crea una clase proxy ? ¿Alguien puede explicar esto con más profundidad ? ¿Qué reside realmente en esa clase de proxy? ¿Qué pasa con la clase real? ¿Y cómo puedo ver la clase proxy creada por Spring?
  2. También leí en los documentos de Spring que:

Nota: Dado que este mecanismo se basa en proxies, solo se interceptarán las llamadas de método 'externas' que ingresen a través del proxy . Esto significa que 'auto-invocación', es decir, un método dentro del objeto de destino que llama a algún otro método del objeto de destino, no conducirá a una transacción real en tiempo de ejecución, incluso si el método invocado está marcado con @Transactional!

Fuente: http://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

¿Por qué solo las llamadas a métodos externos estarán en Transacción y no a los métodos de autoinvocación?

pico
fuente
2
La discusión relevante está aquí: stackoverflow.com/questions/3120143/…
dma_k

Respuestas:

255

Este es un gran tema. El documento de referencia de Spring le dedica varios capítulos. Recomiendo leer los sobre programación y transacciones orientadas a aspectos , ya que el soporte de transacciones declarativas de Spring utiliza AOP en su base.

Pero a un nivel muy alto, Spring crea proxies para las clases que declaran @Transactional en la clase misma o en los miembros. El proxy es principalmente invisible en tiempo de ejecución. Proporciona una forma para que Spring inyecte comportamientos antes, después o alrededor de las llamadas al método en el objeto que se está representando. La administración de transacciones es solo un ejemplo de los comportamientos que se pueden enganchar. Las verificaciones de seguridad son otro. Y también puede proporcionar el suyo propio para cosas como el registro. Entonces, cuando anota un método con @Transactional , Spring crea dinámicamente un proxy que implementa las mismas interfaces que la clase que está anotando. Y cuando los clientes hacen llamadas a su objeto, las llamadas se interceptan y los comportamientos se inyectan a través del mecanismo proxy.

Las transacciones en EJB funcionan de manera similar, por cierto.

Como observó, a través del mecanismo proxy solo funciona cuando entran llamadas de algún objeto externo. Cuando realiza una llamada interna dentro del objeto, realmente está haciendo una llamada a través de la referencia " this ", que omite el proxy. Sin embargo, hay formas de solucionar ese problema. Explico un enfoque en esta publicación del foro en el que uso un BeanFactoryPostProcessor para inyectar una instancia del proxy en clases de "autorreferencia" en tiempo de ejecución. Guardo esta referencia en una variable miembro llamada " yo ". Luego, si necesito hacer llamadas internas que requieren un cambio en el estado de la transacción del hilo, dirijo la llamada a través del proxy (por ejemplo, " me.someMethod ()".) La publicación del foro explica con más detalle. Tenga en cuenta que el código BeanFactoryPostProcessor sería un poco diferente ahora, ya que fue escrito en el marco temporal de Spring 1.x. Pero espero que le dé una idea. Tengo una versión actualizada que Probablemente podría poner a disposición.

Rob H
fuente
44
>> El proxy es casi invisible en tiempo de ejecución ¡Oh! Tengo curiosidad por verlos :) Descanso ... su respuesta fue muy completa. Esta es la segunda vez que me estás ayudando ... Gracias por toda la ayuda.
peakit
17
No hay problema. Puede ver el código de proxy si avanza con un depurador. Esa es probablemente la forma más fácil. No hay magia; son solo clases dentro de los paquetes de Spring.
Rob H
Y si el método que tiene la anotación @Transaction es implementar una interfaz, Spring utilizará la API de proxy dinámico para inyectar la transaccionalización y no usar proxies. Prefiero que mis clases transaccionales implementen interfaces en cualquier caso.
Michael Wiles
1
También encontré el esquema "yo" (usando un cableado explícito para hacerlo como me parece), pero creo que si lo haces de esa manera, probablemente sea mejor refactorizar para que no Tiene que. Pero sí, ¡eso a veces puede ser muy incómodo!
Donal Fellows
2
2019: como esta respuesta se está volviendo vieja, la publicación del foro referida ya no está disponible, lo que describiría el caso cuando tiene que hacer una llamada interna dentro del objeto sin omitir el proxy, usandoBeanFactoryPostProcessor . Sin embargo, hay un método (en mi opinión) muy similar descrito en esta respuesta: stackoverflow.com/a/11277899/3667003 ... y otras soluciones en todo el hilo también.
Z3d4s
196

Cuando Spring cargue sus definiciones de bean y se haya configurado para buscar @Transactionalanotaciones, creará estos objetos proxy alrededor de su bean real . Estos objetos proxy son instancias de clases que se generan automáticamente en tiempo de ejecución. El comportamiento predeterminado de estos objetos proxy cuando se invoca un método es simplemente invocar el mismo método en el bean "objetivo" (es decir, su bean).

Sin embargo, los proxies también se pueden suministrar con interceptores, y cuando estén presentes, el proxy invocará estos interceptores antes de que invoque el método de su bean de destino. Para los beans de destino anotados con @Transactional, Spring creará un TransactionInterceptory lo pasará al objeto proxy generado. Entonces, cuando llama al método desde el código del cliente, está llamando al método en el objeto proxy, que primero invoca el TransactionInterceptor(que comienza una transacción), que a su vez invoca el método en su bean de destino. Cuando finaliza la invocación, los TransactionInterceptorcommits / retrotraen la transacción. Es transparente para el código del cliente.

En cuanto a la cuestión del "método externo", si su bean invoca uno de sus propios métodos, entonces no lo hará a través del proxy. Recuerde, Spring envuelve su bean en el proxy, su bean no tiene conocimiento de ello. Solo las llamadas desde "fuera" de su bean pasan por el proxy.

¿Eso ayuda?

skaffman
fuente
36
> Recuerde, Spring envuelve su bean en el proxy, su bean no tiene conocimiento de esto. Esto lo dijo todo. Que gran respuesta. Gracias por ayudar.
peakit
Gran explicación, para el proxy y los interceptores. Ahora entiendo que spring implemente un objeto proxy para interceptar llamadas a un bean de destino. ¡Gracias!
dharag
Creo que está tratando de describir esta imagen de la documentación de Spring y ver esta imagen me ayuda mucho: docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
WesternGun
44

Como persona visual, me gusta considerar un diagrama de secuencia del patrón proxy. Si no sabes cómo leer las flechas, leí la primera de esta manera: se Clientejecuta Proxy.method().

  1. El cliente llama a un método en el objetivo desde su perspectiva y es interceptado silenciosamente por el proxy
  2. Si se define un aspecto anterior, el proxy lo ejecutará
  3. Luego, se ejecuta el método real (objetivo)
  4. Después de regresar y después de lanzar son aspectos opcionales que se ejecutan después de que el método regresa y / o si el método arroja una excepción
  5. Después de eso, el proxy ejecuta el aspecto posterior (si está definido)
  6. Finalmente el proxy regresa al cliente que llama

Diagrama de secuencia de patrón de proxy (Se me permitió publicar la foto con la condición de que mencionara sus orígenes. Autor: Noel Vaes, sitio web: www.noelvaes.eu)

progonkpa
fuente
27

La respuesta más simple es:

En cualquier método que declare, @Transactionalel límite de la transacción comienza y el límite finaliza cuando se completa el método.

Si está utilizando una llamada JPA, todas las confirmaciones están dentro de este límite de transacción .

Digamos que está guardando entidad1, entidad2 y entidad3. Ahora, mientras se guarda la entidad3 , se produce una excepción y , a medida que enitiy1 y entity2 entran en la misma transacción, la entidad1 y la entidad2 se revertirán con la entidad3.

Transacción:

  1. entity1.save
  2. entity2.save
  3. entity3.save

Cualquier excepción dará como resultado la reversión de todas las transacciones JPA con DB. Spring utiliza internamente las transacciones JPA.

RoshanKumar Mutha
fuente
2
"La excepción A̶n̶y̶ dará como resultado la reversión de todas las transacciones JPA con DB". Nota Solo RuntimeException da como resultado la reversión. Excepciones marcadas, no resultará en reversión.
Arjun
2

Puede ser tarde, pero me encontré con algo que explica su preocupación relacionada con el proxy (solo se interceptarán las llamadas de método 'externas' que ingresen a través del proxy).

Por ejemplo, tienes una clase que se ve así

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

y tienes un aspecto que se ve así:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

Cuando lo ejecutas así:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

Resultados de llamar a kickOff arriba del código dado arriba.

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

pero cuando cambias tu código a

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

Verá, el método llama internamente a otro método para que no se intercepte y la salida se vería así:

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

Puedes evitar esto haciendo eso

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

Fragmentos de código tomados de: https://www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/

Danyal Sandeelo
fuente
1

Todas las respuestas existentes son correctas, pero creo que no puedo dar solo este tema complejo.

Para obtener una explicación completa y práctica, puede echar un vistazo a esta guía Spring @Transactional In-Depth , que hace todo lo posible para cubrir la gestión de transacciones en ~ 4000 palabras simples, con muchos ejemplos de código.

Marco Behler
fuente
Fue genial ...
Alpit Anand hace