IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JPA Java Discussion :

JPA - Utiliser une transaction unique à travers plusieurs appels à un service d'intégration


Sujet :

JPA Java

  1. #1
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Par défaut JPA - Utiliser une transaction unique à travers plusieurs appels à un service d'intégration
    Bonjour à tous,

    Une petite question par rapport à JPA. Je travaille avec EJB3, Java6, JBoss 5.1.0 et un Eclipse EE pour Web Developers en version Helios. Nous utilisons des transactions CMT pour le moment.

    Les différents couches de mon application n'ont rien d'innovant et sont :
    - business (logique applicative travaillant sur des DTO)
    - model (validation des valeurs des DTO)
    - integration (persistence, transforme les DTO en entités pour des fonctions find(), insert(), update(), delete(), ...)
    - integration.entity (objets entités)

    Depuis ma couche applicative, après une logique métier complexe, je me retrouve à vouloir enregistrer différentes listes d'objets DTO (généralement en update() plutôt qu'en insert(), admettons qu'il n'y a que des mises à jour). Je dois le réaliser au sein d'une et une seule transaction.

    Auriez-vous des directives à me conseiller pour réaliser ça ?

    Je ne comprends pas comment, depuis ma couche applicative, initialiser une transaction que je passe ensuite à chaque appel à ma couche d'intégration. Je n'ai pas d'objet EntityManager au niveau de mon service applicatif, en tout cas pour le moment.

    La fin de mon code applicatif ressemble à ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    for(PersonDTO person: persons){
    	personIntegrationService.save(person);
    }
    for(VehiculeDTO vehicule: vehicules){
    	vehiculeIntegrationService.save(vehicule);
    }
    ... autres appels de persistence
    Comment faire pour que "save()" ne fasse pas de commit lorsque la fonction se termine ?
    Y a-t-il moyen de faire ça en restant en CMT ?
    Ou dois-je obligatoirement changer les transactions de l'application pour passer de CMT à BMT et réaliser le begin() et commit() dans la couche applicative ?
    Et dans ce cas, j'aurais besoin d'obtenir un objet UserTransaction dans ma couche applicative, ne serait-ce donc pas contraire aux bonnes pratiques ?

    Je cherche avant-tout une façon propre et maintenable, sinon je peux toujours réaliser un service d'intégration particulier pour lequel j'utilise des transaction BMT et qui prend autant de listes de DTO en paramètres que j'ai d'objets DTO différents et qui persiste le tout entre un begin() et un commit(). C'est pour le moment la seule façon que je vois, mais il y a probablement nettement mieux.

    Merci d'avance pour vos suggestions.

  2. #2
    Rédacteur/Modérateur
    Avatar de Logan Mauzaize
    Homme Profil pro
    Architecte technique
    Inscrit en
    Août 2005
    Messages
    2 894
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Architecte technique
    Secteur : Transports

    Informations forums :
    Inscription : Août 2005
    Messages : 2 894
    Par défaut
    Je connais rien à JPA mais la solution me semble être:
    Avoir un ConnectionManager et un TransactionManager.
    Le TransactionManager est un attribut du ConnectionManager.
    Et tu partages ton ConnectionManager entre tes IntegrationService.

    En tout cas c'est le design adopté pour la modèle transactionnel
    Java : Cours et tutoriels - FAQ - Java SE 8 API - Programmation concurrente
    Ceylon : Installation - Concepts de base - Typage - Appels et arguments

    ECM = Exemple(reproduit le problème) Complet (code compilable) Minimal (ne postez pas votre application !)
    Une solution vous convient ? N'oubliez pas le tag
    Signature par pitipoisson

  3. #3
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Par défaut
    Je ne suis pas sûr que la réponse convienne pour JPA. Avec une approche CMT, les transactions ne sont normalement pas manuellement traitées par le programmeur. Le conteneur applicatif se charge d'ouvrir et de fermer les transactions quand il le faut. Il est possible naturellement d'altérer le comportement du conteneur applicatif, ce que je cherche justement à faire.

    Dans l'approche BMT, il est vrai que le programmeur se charge lui-même d'ouvrir et de fermer les transactions. Dans le cadre de JPA, cela reviendrait je suppose à injecter un EntityManager au niveau de mon service applicatif et d'y ouvrir une transaction par getTransaction().begin(), puis de passer cet EntityManager pour chaque appel à mes services d'intégration, et de finalement appeler getTransaction.commit() après le dernier appel. Actuellement tout est réalisé avec des CMT, j'aurais donc préféré éviter cette solution qui impliquerait de changer la stratégie des entités pour passer en BMT (et donc de gérer les transactions manuellement pour l'application entière, ce qui induit un coût non négligeable en développement).

    J'ai vu dans ce topic du forum que'une annotation devrait permettre de réutiliser une transaction ouverte par l'appelant... je suppose donc que l'exemple est en stratégie CMT puisque la configuration est faite à l'aide d'annotations et non par programmation... Je n'arrive pourtant pas à voir exactement d'ou vient la transaction du parent, comment l'ouvrir, etc... puisqu'on ne s'en occupe généralement jamais en stratégie CMT.

  4. #4
    Rédacteur/Modérateur
    Avatar de Logan Mauzaize
    Homme Profil pro
    Architecte technique
    Inscrit en
    Août 2005
    Messages
    2 894
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Architecte technique
    Secteur : Transports

    Informations forums :
    Inscription : Août 2005
    Messages : 2 894
    Par défaut
    Ca me fait penser aux aspects de transaction que fournie Spring.
    Le principe tu as une classe DAO avant chaque méthode du DAO Spring ouvre une connection et une transaction, s'il n'y a pas d'exception il commit la transaction sinon rollback, puis ferme la transaction et la connection.

    Dans un contexte comme celui-ci, tu peux avoir une méthode DAO qui appelles d'autres méthodes du DAO, Spring utilise la même connection et la même transaction.

    De toutes façons il y a bien quelqu'un qui doit fournir la connection à tes IntegrationService ?

    Pour info, c'est quoi BMT et CMT ?
    Java : Cours et tutoriels - FAQ - Java SE 8 API - Programmation concurrente
    Ceylon : Installation - Concepts de base - Typage - Appels et arguments

    ECM = Exemple(reproduit le problème) Complet (code compilable) Minimal (ne postez pas votre application !)
    Une solution vous convient ? N'oubliez pas le tag
    Signature par pitipoisson

  5. #5
    Membre Expert
    Profil pro
    Inscrit en
    Août 2006
    Messages
    3 278
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 3 278
    Par défaut
    C'est ton session bean (ejb 3) qui est porteur de la transaction.
    Pour chaque méthode tu peux définir si celle-ci crée une nouvelle transaction, utilise la transaction courante, n'en utilise pas, etc ... grâce aux attributs :
    Required
    RequiresNew
    Mandatory
    NotSupported
    Supports
    Never

    Ta couche inférieure utilisera la transaction définie au niveau de ton EJB, tu n'auras pas à la gérer à ce niveau.

  6. #6
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Par défaut
    Citation Envoyé par Nemek Voir le message
    Pour info, c'est quoi BMT et CMT ?
    Bean Managed Transaction et Container Managed Transaction respectivement. Dans le premier cas, tu manipules tes transactions manuellement, faisant des begin(), des rollback() et des commit(). Dans le second cas, le container du serveur applicatif gère pour toi la transaction. Il sait quand l'ouvrir, quand effectuer le commit, quand effectuer le rollback. C'est pratique pour la majorité des applications. C'est complexe d'altérer le comportement exactement de la façon voulue quand on s'écarte d'un cas d'utilisation standard (je pense d'ailleurs que mon cas ne s'écarte pas du cas standard, je ne sais juste pas le faire, et c'est ce que je cherche à éclaircir).

    Citation Envoyé par fr1man Voir le message
    C'est ton session bean (ejb 3) qui est porteur de la transaction.
    Voilà une information intéressante. Prenons un cas concret : mon service applicatif est un Stateless Session Bean EJB3 et possède une fonction execute(). A la fin de cette fonction, j'appelle N fois la fonction save() de mon service d'intégration pour l'objet Person et M fois la fonction save() de mon service d'intégration pour l'objet Vehicule.

    En code, je placerais donc
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
    juste avant la fonction execute() et
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    juste avant la fonction save() de chaque service d'intégration. De cette manière, je m'assure qu'une transaction est ouverte pour l'exécution de la fonction execute(). Elle devrait de plus être réutilisée lors de l'appel aux différents save().

    J'ai tout juste ? Je n'ai malheureusement pas le temps de tester ça maintenatn, je suis passé sur autre chose, mais ce sera fait en début de semaine. Merci de m'indiquer si la suggestion a été bien interprétée.

  7. #7
    Membre Expert
    Profil pro
    Inscrit en
    Août 2006
    Messages
    3 278
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 3 278
    Par défaut
    Ce que tu appelles tes services d'intégration, ce sont aussi des méthodes de ton EJB ?

    Au niveau des paramètres, tu peux laisser en required, qui permet d'en créer une s'il n'en existe pas, et d'utiliser la transaction courante, si elle existe.

  8. #8
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Par défaut
    Oui, mon service d'intégration est un EJB Stateless également, tout comme ma couche applicative. J'y injecte un EntityManager afin de gérer les entités, ce qui n'est pas le cas au niveau de la couche applicative.

  9. #9
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Par défaut
    Bonjour à tous,

    Je me permet de revenir vers vous pour détailler encore un peu plus mon problème, car après quelques tests, je n'ai pas réussi à placer les différents appels au sein d'une même transaction. Voici mon code (enfin les parties importantes, la totalité prenant plusieurs milliers de lignes) :


    ApplicationServiceBean.java

    Mon code applicatif. Les dernières lignes de la fonction execute() doivent être exécutées au sein d'une transaction.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Stateless
    public class ApplicationServiceBean implements ApplicationService {
    	@Override
    	@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    	public void execute() throws ForeignObjectNotSetException, ForeignObjectNotFoundException, ValidationException {
    		List<PersonDTO> persons = personApplicationService.findList(null, null, null);
    		applicationService.transform(persons); // modifie des valeurs sur les objets de la liste "persons"
     
    		// à protéger au sein d'une transaction
    		for(PersonDTO person: persons){
    			personApplicationService.save(person);
    		}
    	}
    }
    PersonApplicationServiceBean.java

    Un service de validation métier des objets, contient les méthodes de vérification des valeurs des PersonDTO. Ce service fait également le lien avec la couche d'intégration chargée de la persistence.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Stateless
    public class PersonApplicationServiceBean implements PersonApplicationService {
    	@Override
    	public PersonDTO find(Integer personId) {
    		return personIntegrationService.find(personId);
    	}
    	// autres fonctions comme findList() qui retourne une liste d'objets
     
    	@Override
    	@TransactionAttribute(TransactionAttributeType.REQUIRED)
    	public void save(PersonDTO person) throws ForeignObjectNotSetException, ForeignObjectNotFoundException, ValidationException {
    		// effectuer la validation des valeurs de l'objet, si une erreur est détectée un message est ajouté à la liste "messages"
    		List<Message> messages = new ArrayList<Message>();
    		boolean valid = true;
    		if(person.getPersonId() != null) valid = validateForUpdate(person, messages);
    		else valid = validateForInsert(person, messages);
     
    		if(!valid){
    			ValidationException e = new ValidationException();
    			e.getMessages().addAll(messages);
    			throw e;
    		}
     
    		if(person.getPersonId() != null) personIntegrationService.update(person);
    		else personIntegrationService.insert(person);
    	}
    PersonIntegrationServiceBean.java

    Le service chargé de la persistence de mes entités.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Stateless
    public class PersonIntegrationServiceBean implements PersonIntegrationService {
    	@PersistenceContext(unitName = "defaultDS")
    	private EntityManager entityManager;
     
    	@Override
    	@TransactionAttribute(TransactionAttributeType.REQUIRED)
    	public void update(PersonDTO dto) throws ForeignObjectNotFoundException, ForeignObjectNotSetException {
    		// l'objet personne étant lié à un objet agenda, on vérifie la liaison
    		AgendaDTO agendadto = dto.getAgenda();
    		if(agendadto == null || agendadto.getAgendaId() == null) throw new ForeignObjectNotSetException("Agenda referenced in Person object not set or without proper identifier");
    		AgendaEntity agenda = entityManager.find(AgendaEntity.class, agendadto.getAgendaId());
    		if(agenda == null) throw new ForeignObjectNotFoundException("Agenda referenced in Person object not found");
     
    		PersonEntity person = entityManager.find(PersonEntity.class, dto.getPersonId());
    		person.init(dto); // copie les valeurs du DTO vers l'entité
    		person.setAgenda(agenda);
    		entityManager.merge(person);
    	}
     
    	// fonction insert() à peu de chose près pareil, sauf que entityManager.persist() est appelé au lieu de entityManager.merge().
    	// autres fonctions nécessitant le entityManager comme find, findList, count, remove, ...
    }
    PersonEntity.java

    L'entité pour la personne, tout à fait standard.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Entity
    @Table(name = "tbl_personnes")
    public class PersonEntity{
    	private Integer personId;
    	private AgendaEntity agenda;
    	// une quantité importante d'autres champs
     
    	@Id
    	@Column(name = "id")
    	@GeneratedValue(strategy = GenerationType.AUTO)
    	public Integer getPersonId() {
    		return personId;
    	}
     
    	@JoinColumn(name = "agenda_id", referencedColumnName="id", nullable=false)
    	@ManyToOne(cascade=CascadeType.ALL,optional=false,targetEntity=AgendaEntity.class)
    	public AgendaEntity getAgenda() {
    		return agenda;
    	}
     
    	// de nombreux autres getters et setters sur plusieurs centaines de lignes
    	// init(PersonDTO dto), toString(), equals, constructeurs et autres méthodes utilitaires
    }
    Mon test est le suivant. La méthode findList() me récupère la totalité des objets de la base avec les paramètres null, null, null (il y en a 20 pour les tests). Je me suis arrangé pour que la validation d'un objet situé vers la fin de la liste lève une exception de type ValidationException lorsqu'on tente de le persister, ceci afin de vérifier qu'un rollback de la transaction est effectivement réalisé.

    Il me suffit alors de modifier le premier objet retourné par ma liste directement dans la base de données. J'appelle la fonction execute(), qui effectue un certain nombre de save() fonctionnant parfaitement avant de tomber sur la ValidationException. En regardant ensuite dans la base de données les valeurs, je vois que les changements sur le premier objet de la liste ont effectivement été réalisés, et donc que le rollback n'a malheureusement pas eu lieu...

    Je travaille avec JBoss 5.1.0 et la base de donnée HSQLDB intégrée au serveur, je ne sais pas si cette information peut être importante.

    Il me semblait avoir compris que le REQUIRE_NEW comme paramètre transactionnel de la fonction execute() assurerait qu'une transaction est ouverte au moment d'appeler save(), et que cette transaction serait réutilisée par l'option REQUIRED placé sur save(), insert() et update() de mes services de validation et d'intégration. Ca n'a toutefois par l'air de fonctionner comme je l'ai réalisé.

    Help, je comprends vraiment pas ce qui se passe

    (il risque de manquer à un ou deux endroits des fermetures d'accolades, de parenthèses ou autre, le code ayant été copié par bribes pour simplification, la totalité étant pas postable )

    Toute suggestion sera particulièrement appréciée.

    Merci d'avance !

  10. #10
    Membre Expert
    Profil pro
    Inscrit en
    Août 2006
    Messages
    3 278
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 3 278
    Par défaut
    Alors, déjà, je pense que ton REQUIRES_NEW n'est pas nécessaire.
    Un simple required suffira, il ouvrira une transaction s'il en existe pas.

    Ensuite pour ton rollback, je te conseille d'aller lire la page suivante:
    http://java.sun.com/j2ee/tutorial/1_...nsaction3.html
    Et puis particulièrement :
    There are two ways to roll back a container-managed transaction. First, if a system exception is thrown, the container will automatically roll back the transaction. Second, by invoking the setRollbackOnly method of the EJBContext interface, the bean method instructs the container to roll back the transaction. If the bean throws an application exception, the rollback is not automatic, but may be initiated by a call to setRollbackOnly. For a description of system and application exceptions, see Handling Exceptions.

  11. #11
    Membre confirmé Avatar de Lordsephiroth
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2006
    Messages
    199
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2006
    Messages : 199
    Par défaut
    C'était si simple...

    Effectivement, je n'avais pas percuté la distinction entre exceptions métier et exceptions système.

    Tout à l'air de fonctionner parfaitement maintenant. J'espère juste que le serveur tiendra une transaction avec plusieurs centaines de millier d'éléments, mais ceci fera partie de ma prochaine question

    Un énorme merci fr1man pour ta réponse éclairée

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 4
    Dernier message: 07/04/2010, 14h40
  2. Réponses: 10
    Dernier message: 08/02/2007, 13h18
  3. utiliser un menu unique pour plusieurs pages...
    Par brouette dans le forum ActionScript 1 & ActionScript 2
    Réponses: 8
    Dernier message: 17/12/2006, 01h29
  4. [VBA-E]une macro unique pour plusieurs fichiers excel
    Par fanchic29 dans le forum Macros et VBA Excel
    Réponses: 8
    Dernier message: 21/04/2006, 16h20
  5. utiliser une transaction avec le composant DBExpress
    Par vbcasimir dans le forum Bases de données
    Réponses: 1
    Dernier message: 09/06/2005, 14h10

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo