106 votes

Comment utiliser @Transactional avec Spring Data ?

Je viens de commencer à travailler sur un projet Spring-data, Hibernate, MySQL, JPA. J'ai opté pour Spring-data afin de ne pas avoir à me soucier de la création de requêtes à la main.

J'ai remarqué que l'utilisation de @Transactional n'est pas nécessaire lorsque vous utilisez spring-data puisque j'ai également essayé mes requêtes sans l'annotation.

Y a-t-il une raison spécifique pour laquelle je devrais/ne devrais pas utiliser l'option @Transactional annotation ?

Travaux :

@Transactional
public List listStudentsBySchool(long id) {
    return repository.findByClasses_School_Id(id);
}

Ça marche aussi :

public List listStudentsBySchool(long id) {
    return repository.findByClasses_School_Id(id);
}

Merci d'avance !

171voto

Oliver Gierke Points 11630

Sur quoi porte réellement votre question ? L'utilisation du @Repository ou l'annotation @Transactional .

@Repository n'est pas du tout nécessaire car l'interface que vous déclarez sera de toute façon soutenue par un proxy que l'infrastructure Spring Data crée et pour lequel elle active la traduction des exceptions. Ainsi, l'utilisation de cette annotation sur une interface de référentiel Spring Data n'a aucun effet.

@Transactional - pour le module JPA, nous avons cette annotation sur la classe d'implémentation qui soutient le proxy ( SimpleJpaRepository ). Ceci pour deux raisons : premièrement, la persistance et la suppression d'objets nécessitent une transaction dans JPA. Nous devons donc nous assurer qu'une transaction est en cours d'exécution, ce que nous faisons en annotant la méthode avec l'attribut @Transactional .

Les méthodes de lecture comme findAll() y findOne(…) utilisent @Transactional(readOnly = true) ce qui n'est pas strictement nécessaire mais déclenche quelques optimisations dans l'infrastructure de transaction (réglage de l'option FlushMode à MANUAL afin de permettre aux fournisseurs de persistance d'éviter les vérifications fastidieuses lors de la fermeture de l'interface utilisateur. EntityManager ). En outre, l'indicateur est également activé sur la connexion JDBC, ce qui entraîne des optimisations supplémentaires à ce niveau.

Selon la base de données que vous utilisez, il peut omettre les verrous de table ou même rejeter les opérations d'écriture que vous pourriez déclencher accidentellement. Nous recommandons donc d'utiliser @Transactional(readOnly = true) pour les méthodes de requête également, ce que vous pouvez facilement réaliser en ajoutant cette annotation à votre interface de référentiel. Assurez-vous d'ajouter un simple @Transactional aux méthodes de manipulation que vous avez pu déclarer ou redécorer dans cette interface.

4voto

dgiffone Points 91

Je pense que la question est un peu plus large et ne peut être réduite aux annotations sur la couche d'accès aux données. Nous devons prendre en compte l'ensemble de la pile de l'application, les stratégies de transaction que nous voulons appliquer, etc. Il y a un ensemble très complet d'articles sur ce sujet par Mark Richards sur le site IBM developerworks. Vous pouvez trouver le premier article ici : https://developer.ibm.com/articles/j-ts1/

Meilleures salutations

3voto

ses Points 2924

Dans vos exemples, cela dépend de si votre dépôt a @Transactional ou pas.

Si oui, alors le service, (tel qu'il est) dans votre cas - ne devrait pas être utilisé. @Transactional (puisqu'il n'y a aucun intérêt à l'utiliser). Vous pouvez ajouter @Transactional plus tard, si vous prévoyez d'ajouter plus de logique à votre service qui traite d'autres tables / référentiels - alors il sera utile de l'avoir.

Si non - alors votre service doit utiliser @Transactional si vous voulez vous assurer que vous n'avez pas de problèmes d'isolement, que vous ne lisez pas quelque chose qui n'est pas encore commuté par exemple.

--

Si l'on parle de référentiels en général (comme interface de collecte de crudités) :

  1. Je dirais : NON, vous ne devriez pas utiliser @Transactional

Pourquoi pas : si nous croire ce référentiel est en dehors du contexte de l'entreprise, et il ne devrait pas connaître la propagation ou l'isolation (niveau de verrouillage). Il ne peut pas deviner dans quel contexte de transaction il pourrait être impliqué.

les référentiels sont "sans activité" (si vous le pensez)

disons que vous avez un référentiel :

class MyRepository
   void add(entity) {...}
   void findByName(name) {...}

et il y a une logique d'entreprise, disons MyService

 class MyService() {

   @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.SERIALIZABLE)
   void doIt() {
      var entity = myRepository.findByName("some-name");
      if(record.field.equal("expected")) {
        ... 
        myRepository.add(newEntity)
      }
   }

 }

C'est-à-dire que dans ce cas : MyService décide dans quoi elle veut impliquer le dépôt.

Dans ce cas, avec propagation="Required", on s'assure que les DEUX méthodes de dépôt - findByName() y add() sera impliqué dans une transaction unique, et l'isolation="Serializable" permettra de s'assurer que personne ne peut interférer avec cela. Il gardera un verrou pour la ou les tables dans lesquelles get() et add() sont impliqués.

Mais un autre service peut vouloir utiliser MyRepository différemment, sans s'impliquer dans aucune transaction, en utilisant par exemple findByName() qui n'est pas intéressé par une quelconque restriction pour lire tout ce qu'il peut trouver à ce moment.

  1. Je dirais OUI, si vous traitez votre référentiel comme un référentiel qui renvoie toujours des entités valides (pas de lectures sales), etc, (ce qui évite aux utilisateurs de l'utiliser de manière incorrecte). C'est-à-dire que votre référentiel devrait prendre en charge le problème d'isolation (concurrence et cohérence des données), comme dans l'exemple :

nous voulons (dépôt) nous assurer que lorsque nous add(newEntity) il vérifie d'abord s'il existe déjà une entité portant le même nom, et si c'est le cas, il l'insère, le tout en une seule unité de travail verrouillée. (la même chose que ce que nous avons fait pour le niveau de service ci-dessus, mais nous ne déplaçons pas cette responsabilité vers le référentiel).

Disons qu'il ne peut pas y avoir 2 tâches avec le même nom "en cours" (règle de gestion).

 class TaskRepository
   @Transactional(propagation=Propagation.REQUIRED, 
   isolation=Isolation.SERIALIZABLE)
   void add(entity) {
      var name = entity.getName()
      var found = this.findFirstByName(name);
      if(found == null || found.getStatus().equal("in-progress")) 
      {
        .. do insert
      }
   }
   @Transactional
   void findFirstByName(name) {...}

La deuxième est plus proche du référentiel de style DDD.


Je suppose qu'il y a plus à couvrir si :

  class Service {
    @Transactional(isolation=.., propagation=...) // where .. are different from what is defined in taskRepository()
    void doStuff() {
      taskRepository.add(task);
    }
  }

0voto

Zozo Points 97

Nous utilisons également l'annotation @Transactional pour verrouiller l'enregistrement afin qu'un autre thread/demande ne modifie pas la lecture.

0voto

menoktaokan Points 148

Nous utilisons @Transactional lorsque nous créons/mettons à jour une autre entité en même temps. Si la méthode qui a @Transactional lève une exception, l'annotation permet d'annuler les insertions précédentes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X