À quoi appartient l'annotation @Transactional?

528

Devriez-vous placer le @Transactionaldans les DAOclasses et / ou leurs méthodes ou est-il préférable d'annoter les classes Service qui appellent à l'aide des objets DAO? Ou est-il judicieux d'annoter les deux "calques"?

Thomas Einwaller
la source

Réponses:

537

Je pense que les transactions appartiennent à la couche Service. C'est celui qui connaît les unités de travail et les cas d'utilisation. C'est la bonne réponse si vous avez plusieurs DAO injectés dans un service qui doivent travailler ensemble en une seule transaction.

duffymo
la source
Je suis d'accord avec ça. Parfois, cela n'a pas d'importance, mais parfois vous pouvez en bénéficier, par exemple, la session Hibernate est étendue pour la transaction while, donc tous les objets chargés sont dans le cache de 1er niveau et vous n'avez pas besoin de rattacher les objets à la session, plus chargés paresseusement les propriétés fonctionnent sans fuzz.
dma_k
5
Une transaction globale peut-elle comprendre plusieurs de ces @Transactional (propagation = Propagation.REQUIRED)? Ou @Transactional est toujours une limite pour une transaction? Je ne suis pas sûr de l'avoir obtenu de la documentation, mais il semble un peu que vous puissiez même créer des transactions qui se composent de méthodes @Transactional et de tout ce qui s'exécute à l'intérieur
lisak
1
Oui, je pense que le @Transactional le plus à l'extérieur est celui qui devient la limite de la transaction si Propagation.REQUIRED est activé.
duffymo
309

En général, je suis d'accord avec les autres affirmant que les transactions sont généralement démarrées au niveau du service (en fonction de la granularité dont vous avez besoin bien sûr).

Cependant, entre-temps, j'ai également commencé à ajouter @Transactional(propagation = Propagation.MANDATORY)à ma couche DAO (et à d'autres couches qui ne sont pas autorisées à démarrer des transactions mais qui en ont besoin), car il est beaucoup plus facile de détecter les erreurs lorsque vous avez oublié de démarrer une transaction dans l'appelant ( par exemple le service). Si votre DAO est annoté avec une propagation obligatoire, vous obtiendrez une exception indiquant qu'il n'y a pas de transaction active lorsque la méthode est invoquée.

J'ai également un test d'intégration où je vérifie tous les beans (post-processeur de bean) pour cette annotation et échoue s'il y a une @Transactionalannotation avec propagation autre que obligatoire dans un bean qui n'appartient pas à la couche services. De cette façon, je m'assure que nous ne démarrons pas de transactions sur la mauvaise couche.

mnp
la source
3
Si cela se fait dans la couche dao (interfaces), dans l'impl dao. couche ou les deux?
Johan
3
Je ne sais pas si cela s'inscrit dans cette discussion, mais une autre astuce pourrait être d'ajouter @Transactional (readOnly = true) dans l'impl de dao dans les opérations de non-écriture.
maxivis
10
@Johan Spring conseille de mettre les annotations de transaction sur les classes d'implémentation au lieu des interfaces.
Mathias G.
J'aime vraiment cette idée, en essayant cela pour mes projets aussi
Thomas Einwaller
1
Laisse-moi voir si je comprends ... tu dis que je devrais mettre @Transactionalla classe d'implémentation de service et que je devrais mettre l'implémentation de classe @Transactional(propagation = MANDATORY)DAO (référentiel)?
John Henckel
93

Les annotations transactionnelles doivent être placées autour de toutes les opérations indissociables.

Par exemple, votre appel est "changer le mot de passe". Cela consiste en deux opérations

  1. Modifiez le mot de passe.
  2. Auditez le changement.
  3. Envoyez un e-mail au client pour lui indiquer que le mot de passe a changé.

Donc, dans ce qui précède, si l'audit échoue, le changement de mot de passe doit-il également échouer? Si tel est le cas, la transaction devrait se situer autour de 1 et 2 (donc au niveau de la couche service). Si l'e-mail échoue (devrait probablement avoir une sorte de sécurité intégrée afin qu'il n'échoue pas), doit-il restaurer le mot de passe de modification et l'audit?

Voilà le genre de questions que vous devez vous poser lorsque vous décidez où placer le @Transactional.

Michael Wiles
la source
41

La bonne réponse pour les architectures Spring traditionnelles est de placer la sémantique transactionnelle sur les classes de service, pour les raisons que d'autres ont déjà décrites.

Une tendance émergente au printemps est la conception basée sur le domaine (DDD). Spring Roo illustre bien la tendance. L'idée est de rendre les objets POJO du domaine beaucoup plus riches qu'ils ne le sont sur les architectures Spring typiques (généralement anémiques ), et en particulier de mettre la sémantique de transaction et de persistance sur les objets du domaine eux-mêmes. Dans les cas où tout ce qui est nécessaire est de simples opérations CRUD, les contrôleurs Web opèrent directement sur les POJO de l'objet de domaine (ils fonctionnent comme des entités dans ce contexte), et il n'y a pas de niveau de service. Dans les cas où une sorte de coordination est nécessaire entre les objets de domaine, vous pouvez avoir un handle de bean de service qui, avec@Transactionselon la tradition. Vous pouvez définir la propagation des transactions sur les objets de domaine sur quelque chose comme REQUIREDpour que les objets de domaine utilisent toutes les transactions existantes, telles que les transactions qui ont été démarrées au niveau du bean de service.

Techniquement, cette technique utilise AspectJ et <context:spring-configured />. Roo utilise des définitions inter-types AspectJ pour séparer la sémantique d'entité (transactions et persistance) de l'objet de domaine (essentiellement les champs et les méthodes métier).

naXa
la source
38

Le cas normal serait d'annoter au niveau de la couche de service, mais cela dépend vraiment de vos besoins.

L'annotation sur une couche de service entraînera des transactions plus longues que l'annotation au niveau DAO. En fonction du niveau d'isolement des transactions, vous pouvez rencontrer des problèmes, car les transactions simultanées ne verront pas mutuellement les changements, par exemple. LECTURE RÉPÉTABLE.

L'annotation sur les DAO gardera les transactions aussi courtes que possible, avec l'inconvénient que la fonctionnalité que votre couche de service expose ne se fera pas en une seule transaction (annulable).

Il n'est pas logique d'annoter les deux couches si le mode de propagation est défini par défaut.

Hammarback
la source
31

Je place le @Transactionalsur la @Servicecouche et définit rollbackFortoute exception et readOnlypour optimiser davantage la transaction.

Par défaut, @Transactionalil ne recherchera que RuntimeException(Exceptions non vérifiées), en définissant la restauration sur Exception.class(Exceptions vérifiées), il restaurera toute exception.

@Transactional(readOnly = false, rollbackFor = Exception.class)

Voir Exceptions vérifiées et non vérifiées .

user2601995
la source
17

Ou est-il judicieux d'annoter les deux "calques"? - n'est-il pas logique d'annoter à la fois la couche service et la couche dao - si l'on veut s'assurer que la méthode DAO est toujours appelée (propagée) à partir d'une couche service avec une propagation "obligatoire" dans DAO. Cela fournirait une certaine restriction pour les méthodes DAO d'être appelées à partir de la couche UI (ou des contrôleurs). De plus, lors des tests unitaires de la couche DAO en particulier, le fait d'annoter DAO garantira également qu'il est testé pour la fonctionnalité transactionnelle.

tweekran
la source
Cela ne résulterait-il pas en des transactions imbriquées? Et tous les problèmes subtils qui vont avec?
HDave
11
Non, il n'y a pas de transactions imbriquées dans JPA. Les mettre dans les deux serait parfaitement bien - si vous êtes déjà dans une transaction lorsque vous frappez le DAO, cette transaction serait poursuivie.
fool4jesus
4
Une transaction imbriquée peut se produire si vous utilisez propagation=Propagation.REQUIRES_NEW. Dans le cas contraire, pour la plupart des cas, y compris propogation = obligatoire, le DAO ne fera que participer à la transaction existante lancée par la couche service.
dan carter
15

Pour les transactions au niveau de la base de données

principalement utilisé @Transactionaldans les DAO juste au niveau de la méthode, donc la configuration peut être spécifiquement pour une méthode / en utilisant par défaut (obligatoire)

  1. La méthode de DAO qui récupère les données (sélectionnez ..) - n'en avoir pas besoin @Transactional peut entraîner des frais supplémentaires en raison de l'intercepteur de transaction / et du proxy AOP qui doivent également être exécutés.

  2. Les méthodes de DAO qui insèrent / mettent à jour obtiendront @Transactional

très bon blog sur transctionnel

Pour le niveau application -
j'utilise la logique transactionnelle pour la logique métier, je voudrais pouvoir effectuer un retour en cas d'erreur inattendue

@Transactional(rollbackFor={MyApplicationException.class})
public void myMethod(){

    try {    
        //service logic here     
    } catch(Throwable e) {

        log.error(e)
        throw new MyApplicationException(..);
    }
}
lukass77
la source
6
+1 très bel article sur TransactionalinJava
oak
11

Habituellement, il faut placer une transaction dans la couche service.

Mais comme indiqué précédemment, l'atomicité d'une opération est ce qui nous dit où une annotation est nécessaire. Ainsi, si vous utilisez des frameworks comme Hibernate, où une seule opération "enregistrer / mettre à jour / supprimer / ... modification" sur un objet a le potentiel de modifier plusieurs lignes dans plusieurs tables (à cause de la cascade à travers le graphe d'objet), de Bien sûr, il devrait également y avoir une gestion des transactions sur cette méthode DAO spécifique.

yannick555
la source
10

@TransactionalDes annotations doivent être placées autour de toutes les opérations inséparables. L'utilisation de la @Transactionalpropagation des transactions est gérée automatiquement. Dans ce cas, si une autre méthode est appelée par la méthode actuelle, cette méthode aura la possibilité de rejoindre la transaction en cours.

Prenons donc l'exemple:

Nous avons 2 modèles c'est à dire Country et City. La cartographie relationnelle Countryet le Citymodèle sont comme on Countrypeut avoir plusieurs villes, donc la cartographie est comme,

@OneToMany(fetch = FetchType.LAZY, mappedBy="country")
private Set<City> cities;

Ici, le pays est mappé sur plusieurs villes en les récupérant Lazily. Alors, voici le rôle @Transactinallorsque nous récupérons l'objet Country de la base de données, nous obtiendrons toutes les données de l'objet Country mais n'obtiendrons pas Ensemble de villes car nous récupérons les villes LAZILY.

//Without @Transactional
public Country getCountry(){
   Country country = countryRepository.getCountry();
   //After getting Country Object connection between countryRepository and database is Closed 
}

Lorsque nous voulons accéder à Ensemble de villes à partir de l'objet pays, nous obtiendrons des valeurs nulles dans cet ensemble car l'objet de l'ensemble créé uniquement cet ensemble n'est pas initialisé avec ses données pour obtenir les valeurs de l'ensemble que nous utilisons, @Transactionalc'est-à-dire

//with @Transactional
@Transactional
public Country getCountry(){
   Country country = countryRepository.getCountry();
   //below when we initialize cities using object country so that directly communicate with database and retrieve all cities from database this happens just because of @Transactinal
   Object object = country.getCities().size();   
}

Donc, fondamentalement, le @Transactionalservice peut effectuer plusieurs appels en une seule transaction sans fermer la connexion avec le point final.

Harshal Patil
la source
2
très instructif, merci! Juste ce que je cherchais, une explication de ce @Transactionalqui est vraiment
CybeX
6

Il vaut mieux l'avoir dans la couche service! Ceci est clairement expliqué sur l'un des articles que j'ai croisé hier! Voici le lien que vous pouvez consulter!

cadran solaire
la source
5

entrez la description de l'image ici

Le @Transactionaldoit être utilisé sur la couche service car il contient la logique métier. La couche DAO n'a généralement que des opérations CRUD de base de données.

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

Doc de printemps: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html

Alin
la source
5

La couche de service est le meilleur endroit pour ajouter @Transactional annotations car la plupart de la logique métier présente ici, elle contient un comportement de cas d'utilisation au niveau de détail.

Supposons que nous l'ajoutions à DAO et à partir du service, nous appelons 2 classes DAO, l'une échouée et l'autre réussie, dans ce cas si @Transactional n'est pas sur le service, une base de données sera validée et l'autre sera annulée.

Par conséquent, ma recommandation est d'utiliser cette annotation à bon escient et d'utiliser uniquement la couche Service.

Projet Github - Java-Algos

VishalTambe
la source
Des exceptions telles que ObjectOptimisticLockingFailureException se produisent uniquement une fois la transaction terminée. Si vous avez des threads séparés pour d'autres opérations comme le service de messagerie, cette conception échoue totalement. Nous souffrons en ce moment. La seule solution restante serait l'AOP.
Nabin Kumar Khatiwada
4

Tout d'abord, définissons où nous devons utiliser la transaction ?

Je pense que la bonne réponse est - lorsque nous devons nous assurer que la séquence d'actions sera terminée ensemble en une seule opération atomique ou qu'aucune modification ne sera apportée même si l'une des actions échoue.

Il est bien connu de mettre la logique métier dans les services. Les méthodes de service peuvent donc contenir différentes actions qui doivent être exécutées comme une seule unité logique de travail. Si tel est le cas, cette méthode doit être marquée comme transactionnelle . Bien sûr, toutes les méthodes ne nécessitent pas une telle limitation, vous n'avez donc pas besoin de marquer l'ensemble du service comme transactionnel .

Et encore plus - n'oubliez pas de prendre en compte que @Transactional peut évidemment réduire les performances de la méthode. Pour avoir une vue d'ensemble, vous devez connaître les niveaux d'isolement des transactions. Savoir que cela pourrait vous aider à éviter d'utiliser @Transactional là où il n'est pas nécessairement nécessaire.

smaiakov
la source
3

Il est préférable de conserver @Transactional dans une couche intermédiaire distincte entre DAO et Service Layer. Étant donné que la restauration est très importante, vous pouvez placer toutes vos manipulations de base de données dans la couche intermédiaire et écrire la logique métier dans la couche Service. La couche intermédiaire interagira avec vos couches DAO.

Cela vous aidera dans de nombreuses situations comme ObjectOptimisticLockingFailureException - Cette exception se produit uniquement après la fin de votre transaction. Donc, vous ne pouvez pas l'attraper dans la couche intermédiaire, mais vous pouvez maintenant l'attraper dans votre couche de service. Cela ne serait pas possible si vous avez @Transactional dans la couche Service. Bien que vous puissiez attraper le contrôleur, le contrôleur doit être aussi propre que possible.

Si vous envoyez du courrier ou des sms dans un fil séparé après avoir terminé toutes les options d'enregistrement, de suppression et de mise à jour, vous pouvez le faire en service une fois la transaction terminée dans votre couche intermédiaire. Encore une fois, si vous mentionnez @Transactional dans la couche de service, votre courrier sera envoyé même si votre transaction échoue.

Donc, avoir une couche @Transaction intermédiaire aidera à rendre votre code meilleur et facile à manipuler. Sinon, si vous utilisez dans la couche DAO, vous ne pourrez peut-être pas restaurer toutes les opérations. Si vous utilisez dans la couche Service, vous devrez peut-être utiliser AOP (Aspect Oriented Programming) dans certains cas.

Nabin Kumar Khatiwada
la source
2

Idéalement, la couche Service (Manager) représente votre logique métier et, par conséquent, elle doit être annotée avec @Transactional.La couche Service peut appeler différents DAO pour effectuer des opérations de base de données. Supposons une situation où vous avez N nombre d'opérations DAO dans une méthode de service. Si votre première opération DAO a échoué, d'autres peuvent encore être réussies et vous finirez par un état de base de données incohérent. La couche Service d'annotation peut vous éviter de telles situations.

salsinga
la source
1

Je préfère utiliser @Transactionalsur la couche services au niveau de la méthode.

Spark-Débutant
la source
1

@Transactionalutilise dans la couche service qui est appelée en utilisant la couche contrôleur ( @Controller) et l'appel de la couche service à la couche DAO ( @Repository), c'est-à-dire l'opération liée à la base de données.

Sam
la source