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

Persistance des données Java Discussion :

Quelques questions sur l'utilisation d'Hibernate


Sujet :

Persistance des données Java

  1. #1
    Nouveau Candidat au Club
    Quelques questions sur l'utilisation d'Hibernate
    Bonjour,

    J'ai fais un petit projet test pour apprendre Hibernate et il y a un certain nombre de choses que je ne sais pas comment faire correctement (en gros ça marche mais ça à l'air de tenir plus de la chance que de la technique).

    J'ai une couche contrôleur, 1 couche service et 1 couche accès aux données. Dans ma couche accès aux données j'accède aux objets persistés via Hibernate (jusque là ça va). J'ai mis certaines relations en lazy loading (mettons une relation Classe vers Eleves). Là où ça se complique c'est que des fois j'ai besoin des données des élèves et des fois non, suivant les méthodes de ma couche contrôleur.

    1) Si je veux faire un service qui liste les classes, pas de problème la relation est en lazy loading et je ne récupère donc pas les données des élèves en trop tant que je n'en ai pas besoin. Par contre une Classe à une relation "parent" vers une Ecole (qui elle liste toutes les classes). Comme le client de mon service n'a pas besoin de ce lien, je le mets à null dans la couche contrôleur pour qu'il ne soit pas sérialisé. Ca fonctionne mais je ne comprends pas pourquoi ça n'a pas d'impact sur la base de données, alors que je n'ai pas détaché mon entité. J'ai de la chance et je ne peux pas compter que ça fonctionnera à tous les coups ou il y a une vrai raison et ça fonctionnera tout le temps?
    Avec les traces activées, je vois que les requêtes d'écriture ne sont pas déclanchées tout de suite (dès fois une lecture les délanche). Je suppose donc que le framework choisit quand exécuter les requêtes et j'ai juste eu de la chance qu'il n'ai pas choisit d'exécuter ma modif??? Ou alors c'est parceque lorsque je veux vraiment faire une modif c'est fait dans une méthode annotée @Transactional et qu'en dehors de ces méthodes aucune mise à jour n'est prise en compte?

    2) Pour éviter d'avoir des impacts sur la base de données j'ai essayé de détacher systématiquement mes entités dans la couche de persistence. Sauf que ça fonctionne très bien dans le cas où je n'ai pas besoin des données en lazy loading mais nettement moins bien quand j'essaye d'y accéder. Je comprends la raison, mais comment je devrais faire? ne jamais détacher? Ou détacher plus haut (couche service ou couche contrôleur)? Ca me gène un peu d'exposer l'EntityManager dans les couches supérieures donc je ne pense pas que ce soit la bonne solution... Du coup je ne détache pas, ça fonctionne mais je tombe dans le cas 1).

    Merci pour les éclaircissements que vous pourriez m'apporter.

  2. #2
    Expert éminent sénior
    Première question: pourquoi diable y-a-t-il sérialisation entre ton controlleur et ton dao? Ce n'est à priori pas nécessaire.

    En supposant ce problème retiré, ce que tu dois faire, c'est avoir une seul et unique transaction qui dure tant que ton controlleur a besoin de données. A priori ton controlleur travaille pour 1 demande de ta vue (donc 1 transaction) en effectuant un certain nombre d'opérations sur le dao.

    Pour ce qui est de l'exécution des requêtes d'hibernate, le moment où elle s'exécuteront dépendra de si tu utilise hibernate tout seul ou comme un provider JPA. En JPA toute modification est prise en compte et la transaction commit par défaut. En Hibernate, la transaction ne commit pas sauf si tu le demande. Dans tous les cas, hibernate modifiera la db dès qu'il estime que la requête que tu t'apprête à exécuter aura besoin de données présentes dans les pojos modifiés pour retourner un résultat correct.

    Exemple: tu change le nom d'un élève puis tu cherche tous les élève dont le nom commence par a. Les modifications seront flushées.
    David Delbecq Java developer chez HMS Industrial Networks AB.    LinkedIn | Google+

  3. #3
    Nouveau Candidat au Club
    J'ai du mal m'exprimer. La sérialisation n'est bien évidemment pas entre le controleur et le DAO mais entre le contrôleur et la vue. Je récupère des données dpuis mon DAO, elles remontent jusqu'à mon contrôleur et c'est là que j'essaye de retirer tout ce dont la vue n'a pas besoin. Le problème c'est que si mon objet est détaché au niveau du DAO je peux retirer les données dont je n'ai pas besoin (pour que la sérialisation soit plus légère) mais dans ce cas là je ne peux plus accéder aux données en lazy loading.
    Et si je ne détache pas je peux charger les données en lazy loading lorsque j'y accède dans mon contrôleur mais quand je retire des données (dans le contrôleur) avant la sérialisation je ne vois aucune requête dans la console mais j'ai peur qu'elle puisse être effectuée quand même au moment ou la transaction est flushée...

    Ex: une méthode du contrôleur qui récupère La liste des Eleves d'une classe n'a pas besoin de renvoyer à la vue la relation vers la Classe car on sait déjà dans quelle classe il est. Donc je fais un eleve.setClasse(null) dans le contrôleur. Même si je n'ai pas détaché l'objet Eleve je n'ai pas de requête qui efface la relation entre l'élève et la classe mais ça je ne comprends pas pourquoi...

  4. #4
    Expert éminent sénior
    Il faudrait que tu détaille un peu plus ton architecture. Pourquoi ce besoin de sérialisation? Ton DAO et ta vue sont sur des JVMs différentes? Si c'est le cas, en général on ne transfère pas directement les objets Business, on recours plutôt à des DAO intermédiaire qui collent plus aux méthodes du controlleur qu'à la structure DB. Ce qui permet par exemple d'avoir une méthode qui retourne des collections, d'autres juste des données sommaires.
    Il faudrait aussi que tu détaille comment tu démarque tes transactions et tes sessions. Est-ce que tu fais du JPA?
    David Delbecq Java developer chez HMS Industrial Networks AB.    LinkedIn | Google+

  5. #5
    Nouveau Candidat au Club
    J'utilise Spring + JPA. La sérialisation c'est de la sérialisation JSON pour la vue et elle est gérée automatiquement par le controleur Spring.

    Mon controleur est annoté avec l'annotation @RestController.

    Dedans j'ai 2 méthodes:

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Eleves[] getEleves(int classeId) {
      ... // récupération des données auprès de la couche service
      for (Eleves eleve : eleves) {
         eleve.setClasse(null);    //On efface la relation vers la classe pour alléger la sérialisation. Mauvaise idée???
      }
    }
     
    Classes[] getClasses() {
    ...  // récupération des données auprès de la couche service
    }


    Quand ma vue invoque getClasses() elle s'attend à avoir 1 tableau de Classe dont chacune possède une liste d'élèves.

    Quand ma vue invoque getEleves() elle s'attend à avoir 1 tableau d'élève mais pour alléger la sérialisation je voudrait mettre la backreference de chaque Eleve vers Classe à null (la vue sait déjà dans quelle classe on est puisqu'elle à passé le classeId).

    Que je mette ou pas l'annotation @Transactional ça fonctionne. Du coup je ne sais pas bien où l'objet est détaché (la transaction et la session sont ouvertes au niveau du contrôleur géré par Spring ???. Si c'est le cas pourquoi je ne vois pas passer de requête SQL quand je fais eleve.setClasse(null) puisque je suis encore dans le contrôleur?).

  6. #6
    Expert éminent sénior
    Si c'est du JPA, la session a la durée de vie de la transaction, géré par le conteneur. Je ne connais pas assez spring et sa gestion transactionnelle pour savoir à quel moment elle est démarquée par rapport au conteneur ou si ça a le moindre rapport.


    Comme tu commence à le constater c'est une mauvaise idée de mapper directement tes entités sur la couche rest:

    tu expose éventuellement des clés privées sur la couche rest, pire quand tu va recevoir des données, tu va être obligé de vérifier que l'utilisateur n'utilise pas par exemple ton élève pour le changer de classe en catimini voir créerait tout un structure dans ton dos. Il faut que tu sépare en ayant des objet spécifiques à ta vue et ton controlleur rest se charge de transmettre ce qu'il y a à transmettre. Exemple: tu veux stocker une nouvelle note pour un élève, ta méthode devrait jsute accepter l'id de l'élève et la note.
    David Delbecq Java developer chez HMS Industrial Networks AB.    LinkedIn | Google+

  7. #7
    Nouveau Candidat au Club
    D'accord. Donc ça veut dire créer obligatoirement des DTO? J'avais vu comprendre qu'avec Hibernate on pouvait justement se passer de DTO.

  8. #8
    Expert éminent sénior
    Tes pojos hibernate sont effectivement tes DTO pour ta partie base de données oui. Mais ils mappent ta structure de données, qui n'est pas nécessairement la même que la structure que tu veux montrer à ton client (ici ton client REST). D'ailleurs c'est tout le sujet de la question: "tu veux exposer quelque chose qui ne correspond pas à ta DB: pas d'arbre, pas de parent". Pour ce faire le plus simple reste d'avoir des DTOs spécifiques à ton REST.
    David Delbecq Java developer chez HMS Industrial Networks AB.    LinkedIn | Google+

  9. #9
    Membre régulier
    un peu tardive...
    en continuant, tu vas avoir des DTO pour chaque service , car chaque client de ton web service voudront telles ou telles datas et pas d'autres....

    une bonne liste de DTO dans tout ton projet !! table BD <-> entity java <-> DTO

  10. #10
    Membre expert
    Citation Envoyé par tchize_ Voir le message
    Si c'est du JPA, la session a la durée de vie de la transaction, géré par le conteneur. Je ne connais pas assez spring et sa gestion transactionnelle pour savoir à quel moment elle est démarquée par rapport au conteneur ou si ça a le moindre rapport.
    org.springframework.transaction.annotation.Transactional != javax.transaction.Transactional

    Côté serveur JEE, les transactions font partie de la spécification générale et non pas de JPA en particulier qui n'est qu'un des possibles consommateurs des transactions gérées par le container.
    Côté Spring, le concept des transactions est géré indépendamment de la spécification JEE ce qui permet à une application Spring de fonctionner de manière "identique" (sic) sous un container JEE que sous un simple application container.
    Mais sous un serveur JEE, Spring "sait" qu'il est dans ce contexte particulier (non seulement JEE, mais aussi en fonction de la version 6/7… et du type de container et sa version… ) et donc choisit les classes qui implémentent ses propres abstractions en fonction du contexte et il est parfaitement capable de s'apercevoir si un Tx a été démarrée par le container ou non, et faire en sorte de respecter la "volonté" du développeur exprimée dans les paramètres des annotations.

    Tout cela se complique quand on mélange les 2 : des @EJB appelant des @Component Spring (et recip.) , mais Spring essaie de faire en sorte de propager correctement le contexte transactionnel d'un espace à l'autre
    + le fait que Spring a pas mal de paramètres pour gérer le "comment" de l'aspect transactionnel (de son abstraction :pas de celle du serveur JEEE…): directement via AspectJ (@Transactional sera respecté lors des appels de méthode du même bean) ou via des proxy (CGLIB ou JVM)
    (@Transactional ne fonctionne que pour les appels inter-beans…, l'un voulant @Transactional sur les implémentations et l'autre sur les interfaces…)

    ce qui fait en théorie pas mal de combinaisons possibles…

    (et sans parler des nested Tx, des read-only, du support de l'isolation level…)

    Pour une application Spring simple:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
     
    // <tx:annotation-driven />
    @EnableTransactionManagement(proxyTargetClass =true)
    // <aop:aspectj-autoproxy />
    @EnableAspectJAutoProxy(proxyTargetClass = true)

    et
    les org.springframework.transaction.annotation.Transactional sur les implémentations des méthodes des bean @Repository
    sans oublier
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    @Bean
        public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
          return new PersistenceExceptionTranslationPostProcessor();
        }

    dans votre @Configuration (ou l'équivalent en XML) pour que la traduction des exceptions par le stéréotype @Repository fonctionne comme attendu.

    Les paramètres spécifiques à un environnement particulier sont alors généralement liés aux propriétés JPA utilisées lors de la création de l'entity manager (factory), exemple:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
     
    jpaProperties.put("javax.persistence.transactionType", "jta");
    jpaProperties.put("hibernate.transaction.jta.platform","org.hibernate.engine.transaction.jta.platform.internal.WeblogicJtaPlatform");

    +
    évidemment le JPA vendor adapter et la data source qui sont aussi spécifiques à votre environnement, exemples:

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
     
    	@Value("${connection.show_sql:false}")
    	private String				showSql;
     
    	@Value("${connection.datasource}")
    	private String				jndiConnectionName;
     
    	/**
             * 
             * @return the JPA vendor
             */
    	@Bean
    	public JpaVendorAdapter jpaVendorAdapter() {
    		HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
    		adapter.setDatabase(Database.ORACLE);
    		adapter.setGenerateDdl(false);
    		adapter.setShowSql(ConversionUtils.isStringTrue(showSql));
    		adapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
    		return adapter;
    	}
     
    	/**
             * <jee:jndi-lookup id="dataSource" jndi-name="${connection.datasource}" />
             * 
             * @return the data source
             * @throws NamingException
             */
    	@Bean(destroyMethod = "")
    	public DataSource dataSource() throws NamingException {
    		final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
    		return dsLookup.getDataSource(jndiConnectionName);
    	}
     
    	/**
             * equivalent to the <tx:jta-transaction-manager /> XML
             * 
             * @return the transaction manager
             */
    	@Bean
    	public PlatformTransactionManager transactionManager() {
    		JtaTransactionManager txManager = new JtaTransactionManager();
    		txManager.afterPropertiesSet();
    		return txManager;
    	}