¿Dónde debo poner la anotación @Transactional: en una definición de interfaz o en una clase de implementación?

91

La pregunta del título en código:

@Transactional (readonly = true)
public interface FooService {
   void doSmth ();
}


public class FooServiceImpl implements FooService {
   ...
}

vs

public interface FooService {
   void doSmth ();
}

@Transactional (readonly = true)
public class FooServiceImpl implements FooService {
   ...
}
romano
fuente

Respuestas:

121

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

La recomendación del equipo de Spring es que solo anote clases concretas con la @Transactionalanotación, en lugar de anotar interfaces. Ciertamente puede colocar la @Transactionalanotación en una interfaz (o un método de interfaz), pero esto solo funcionará como lo esperaría si está utilizando proxies basados ​​en interfaz. El hecho de que las anotaciones no se hereden significa que si está utilizando proxies basados ​​en clases, la infraestructura de proxys basados ​​en clases no reconocerá la configuración de la transacción y el objeto no se incluirá en un proxy transaccional (lo que sería definitivamente malo ) . Así que, por favor, siga el consejo del equipo de Spring y solo anote las clases concretas (y los métodos de las clases concretas) con la @Transactionalanotación.

Nota: Dado que este mecanismo se basa en proxies, solo se interceptarán las llamadas a métodos "externos" que ingresen a través del proxy. Esto significa que la 'auto-invocación', es decir, un método dentro del objeto de destino que llama a 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!

(Énfasis agregado a la primera oración, otro énfasis del original).

Romain Hippeau
fuente
Big +1 Por cierto, me tomé la libertad de hacer de la cita una cita, para que la gente no se confunda, y agregué las cursivas y demás del original.
TJ Crowder
He leído esta declaración antes en Spring docu, pero todavía no puedo entender por qué la configuración de transacciones no será reconocida por la infraestructura de proxy basada en clases . Personalmente, creo que esto es solo una limitación de la implementación de Spring, no el problema de la infraestructura de proxy subyacente.
dma_k
Mirando las fuentes de Spring, uno puede descubrir que JdkDynamicAopProxypasa por todos los asesores de beans en cada invocación de método (ver también DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()), que en caso de configuración de transacción declarativa, incluye BeanFactoryTransactionAttributeSourceAdvisor. A su vez TransactionAttributeSourcePointcut#matches()se invoca, que debe recoger la información relativa a la transacción. Se pasa a la clase de destino y siempre puede atravesar todas las interfaces que implementa esta clase. Ahora: ¿por qué esto no puede funcionar de manera confiable?
dma_k
2
@dma_k: el tiempo de ejecución de Java solo brinda acceso directo a anotaciones de clases heredadas de superclases o anotaciones de métodos sin herencia alguna. Entonces Pointcut.matches () tendría que atravesar recursivamente todas las interfaces, encontrar el método anulado "real" con reflexión, manejar métodos puente, sobrecarga, varargs, genéricos, proxies y, por supuesto, hacerlo rápido . Entonces tienes que lidiar con la herencia de diamantes, ¿cuál de las muchas @Transactionalanotaciones heredadas se aplicaría? Entonces, aunque todo es posible , no culpo a Spring. jira.springsource.org/browse/SPR-975
Peter Davis
9

Puede ponerlos en la interfaz, pero tenga en cuenta que es posible que las transacciones no terminen ocurriendo en algunos casos. Vea el segundo consejo en la Sección 10.5.6 de los documentos de Spring:

Spring recomienda que solo anote clases concretas (y métodos de clases concretas) con la anotación @Transactional, en lugar de anotar interfaces. Sin duda, puede colocar la anotación @Transactional en una interfaz (o un método de interfaz), pero esto solo funciona como lo esperaría si está utilizando proxies basados ​​en interfaz. El hecho de que las anotaciones de Java no se hereden de las interfaces significa que si está utilizando proxies basados ​​en clases (proxy-target-class = "true") o el aspecto basado en tejido (mode = "aspectj"), la configuración de la transacción es no reconocido por la infraestructura de proxy y tejido, y el objeto no se incluirá en un proxy transaccional, lo que sería definitivamente malo.

Recomendaría ponerlos en la implementación por este motivo.

Además, para mí, las transacciones parecen un detalle de implementación, por lo que deberían estar en la clase de implementación. Imagínese tener implementaciones de contenedor para el registro o implementaciones de prueba (simulacros) que no necesitan ser transaccionales.

Payaso enojado
fuente
9

La recomendación de Spring es que anote las implementaciones concretas en lugar de una interfaz. No es incorrecto usar la anotación en una interfaz, simplemente es posible hacer un mal uso de esa función y omitir inadvertidamente su declaración de @Transaction.

Si ha marcado algo transaccional en una interfaz y luego se refiere a una de sus clases de implementación en otro lugar en la primavera, no es muy obvio que el objeto que crea Spring no respetará la anotación @Transactional.

En la práctica, se parece a esto:

public class MyClass implements MyInterface { 

    private int x;

    public void doSomethingNonTx() {}

    @Transactional
    public void toSomethingTx() {}

}
zmf
fuente
1

Apoyando a @Transactional en las clases concretas:

Prefiero diseñar una solución en 3 secciones generalmente: una API, una Implementación y una Web (si es necesario). Hago todo lo posible para mantener la API lo más ligera / simple / POJO posible minimizando las dependencias. Es especialmente importante si lo juegas en un entorno distribuido / integrado donde tienes que compartir mucho las API.

Poner @Transactional requiere bibliotecas Spring en la sección API, que en mi humilde opinión no es efectivo. Entonces prefiero agregarlo en la Implementación donde se está ejecutando la transacción.

Firdous Amir
fuente
0

Ponerlo en la interfaz está bien siempre que todos los implementadores previsibles de su IFC se preocupen por los datos TX (las transacciones no son problemas que solo tratan las bases de datos). Si al método no le importa TX (pero necesita ponerlo allí para Hibernate o lo que sea), colóquelo en el impl.

Además, podría ser un poco mejor colocar @Transactionallos métodos en la interfaz:

public interface FooService {
    @Transactional(readOnly = true)
    void doSmth();
}
engfer
fuente