Où dois-je mettre l'annotation @Transactional: dans une définition d'interface ou dans une classe d'implémentation?

91

La question du titre dans le code:

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


public class FooServiceImpl implements FooService {
   ...
}

contre

public interface FooService {
   void doSmth ();
}

@Transactional (readonly = true)
public class FooServiceImpl implements FooService {
   ...
}
romain
la source

Réponses:

121

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

La recommandation de l'équipe Spring est que vous n'annotiez que les classes concrètes avec l' @Transactionalannotation, par opposition aux interfaces d'annotation. Vous pouvez certainement placer l' @Transactionalannotation sur une interface (ou une méthode d'interface), mais cela ne fonctionnera que comme vous vous y attendez si vous utilisez des proxys basés sur l'interface. Le fait que les annotations ne soient pas héritées signifie que si vous utilisez des proxys basés sur les classes, les paramètres de transaction ne seront pas reconnus par l'infrastructure de proxy basée sur les classes et l'objet ne sera pas enveloppé dans un proxy transactionnel (ce qui serait décidément mauvais ) . Alors s'il vous plaît, suivez les conseils de l'équipe Spring et n'annotez que les classes concrètes (et les méthodes des classes concrètes) avec l' @Transactionalannotation.

Remarque: puisque ce mécanisme est basé sur des proxies, seuls les appels de méthode «externes» arrivant via le proxy seront interceptés. Cela signifie que «l'auto-invocation», c'est-à-dire une méthode dans l'objet cible appelant une autre méthode de l'objet cible, ne conduira pas à une transaction réelle à l'exécution même si la méthode invoquée est marquée @Transactional!

(Emphase ajoutée à la première phrase, autre emphase de l'original.)

Romain Hippeau
la source
Big +1 BTW, j'ai pris la liberté de faire de la citation une citation, pour que les gens ne soient pas confus, et j'ai ajouté les italiques et autres à partir de l'original.
TJ Crowder
J'ai déjà lu cette déclaration dans Spring docu, mais je ne comprends toujours pas pourquoi les paramètres de transaction ne seront pas reconnus par l'infrastructure de proxy basée sur les classes ? Je pense personnellement qu'il ne s'agit que d'une limitation de mise en œuvre de Spring, pas du problème de l'infrastructure proxy sous-jacente.
dma_k du
En regardant les sources Spring, on peut découvrir que JdkDynamicAopProxytous les conseillers de bean passent par chaque appel de méthode (voir aussi DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice()), ce qui inclut dans le cas d'une configuration de transaction déclarative BeanFactoryTransactionAttributeSourceAdvisor. A son tour TransactionAttributeSourcePointcut#matches()est invoqué, qui devrait collecter les informations relatives à la transaction. Il reçoit la classe cible et peut toujours traverser toutes les interfaces implémentées par cette classe. Maintenant: pourquoi cela ne peut pas fonctionner de manière fiable?
dma_k du
2
@dma_k - Le runtime Java donne uniquement un accès direct aux annotations de classe héritées des superclasses, ou aux annotations de méthode sans aucun héritage. Donc Pointcut.matches () devrait en fait traverser toutes les interfaces de manière récursive, trouver la «vraie» méthode surchargée avec réflexion, gérer les méthodes de pont, surcharger, varargs, génériques, proxies, et bien sûr le faire rapidement . Ensuite, vous devez traiter de l'héritage de diamants - laquelle des nombreuses @Transactionalannotations héritées s'appliquerait? Donc, bien que tout soit possible , je ne blâme pas Spring. jira.springsource.org/browse/SPR-975
Peter Davis
9

Vous pouvez les mettre sur l'interface, mais sachez que les transactions peuvent ne pas se produire dans certains cas. Voir le deuxième conseil dans la section 10.5.6 de la documentation Spring:

Spring vous recommande d'annoter uniquement les classes concrètes (et les méthodes des classes concrètes) avec l'annotation @Transactional, par opposition aux interfaces d'annotation. Vous pouvez certainement placer l'annotation @Transactional sur une interface (ou une méthode d'interface), mais cela ne fonctionne que comme vous vous y attendriez si vous utilisez des proxys basés sur l'interface. Le fait que les annotations Java ne soient pas héritées des interfaces signifie que si vous utilisez des proxys basés sur les classes (proxy-target-class = "true") ou l'aspect basé sur le tissage (mode = "aspectj"), les paramètres de transaction sont non reconnu par l'infrastructure de proxy et de tissage, et l'objet ne sera pas enveloppé dans un proxy transactionnel, ce qui serait décidément mauvais.

Je recommanderais de les mettre sur la mise en œuvre pour cette raison.

De plus, pour moi, les transactions semblent être un détail d'implémentation et doivent donc être dans la classe d'implémentation. Imaginez avoir des implémentations wrapper pour la journalisation ou des implémentations de test (mocks) qui n'ont pas besoin d'être transactionnelles.

ColèreClown
la source
9

La recommandation de Spring est d'annoter les implémentations concrètes au lieu d'une interface. Il n'est pas incorrect d'utiliser l'annotation sur une interface, il est simplement possible de mal utiliser cette fonctionnalité et de contourner par inadvertance votre déclaration @Transaction.

Si vous avez marqué quelque chose de transactionnel dans une interface et que vous vous référez ensuite à l'une de ses classes d'implémentation ailleurs au printemps, il n'est pas tout à fait évident que l'objet créé par spring ne respectera pas l'annotation @Transactional.

En pratique, cela ressemble à ceci:

public class MyClass implements MyInterface { 

    private int x;

    public void doSomethingNonTx() {}

    @Transactional
    public void toSomethingTx() {}

}
zmf
la source
1

Soutenir @Transactional sur les classes concrètes:

Je préfère concevoir une solution en 3 sections généralement: une API, une Implémentation et un Web (si besoin). Je fais de mon mieux pour garder l'API aussi légère / simple / POJO que possible en minimisant les dépendances. C'est particulièrement important si vous y jouez dans un environnement distribué / intégré où vous devez beaucoup partager les API.

Mettre @Transactional nécessite des bibliothèques Spring dans la section API, ce qui à mon humble avis n'est pas efficace. Je préfère donc l'ajouter dans l'implémentation où la transaction s'exécute.

Amir étrange
la source
0

Le mettre sur l'interface est très bien tant que tous les implémenteurs prévisibles de votre IFC se soucient des données TX (les transactions ne sont pas des problèmes que seules les bases de données traitent). Si la méthode ne se soucie pas de TX (mais que vous devez la mettre là pour Hibernate ou autre), mettez-la sur impl.

En outre, il pourrait être un peu préférable de placer @Transactionalsur les méthodes de l'interface:

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