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

Hibernate Java Discussion :

Spring/Hibernate/Postgresql : objets détachés, comment faire ?


Sujet :

Hibernate Java

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    14
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2006
    Messages : 14
    Par défaut Spring/Hibernate/Postgresql : objets détachés, comment faire ?
    Bonjour,

    J'ai fait de nombreuses recherches ces derniers jours, lu beaucoup de documentation et de forums, et j'avoue être un peu perdu.

    Je travaille sur la partie serveur d'un projet de suivi de collaborateurs (pour commencer, une centaine d'utilisateurs qui pourrait s'étendre à plusieurs milliers).
    La question des performances est donc importante.

    J'utilise Spring, hibernate et postgresql côté serveur avec JSE 1.5. Le tout est déployé dans le tomcat de liferay. Pas de serveurs d'applications.

    La partie serveur expose des services à la partie web. La démarcation des transactions est basée sur les méthodes exposées dans ces services. Chaque service utilise un ou plusieurs DAO (qui se trouvent être des beans au sens spring comme les services). Chaque Dao partage un entityManagerFactory.

    Exemple de configuration (pour simplifier je n'ai mis que le dao et le service des collaborateurs mais le principe est le même pour les autres entity):

    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
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    <!-- Data source -->
    	<bean id="gfinetDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    		destroy-method="close" lazy-init="true">
    		<property name="driverClass">
    			<value>${jdbc.driver}</value>
    		</property>
    		<property name="jdbcUrl">
    			<value>${jdbc.url}</value>
    		</property>
    		<property name="user">
    			<value>${jdbc.user}</value>
    		</property>
    		<property name="password">
    			<value>${jdbc.password}</value>
    		</property>
    	</bean>
     
    	<!-- Entity manager factory -->
    	<bean id="gfinetEntityManagerFactory"
    		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    		<property name="dataSource" ref="gfinetDataSource" />
    		<property name="persistenceUnitName" value="gfinet${emf.persistence-unit}" />
    	</bean>
     
    	<!-- dao -->
    	<bean id="gfinetCollaboratorDao" class="fr.gfi.gfinet.server.dao.CollaboratorDao">
    		<property name="entityManagerFactory">
    			<ref bean="gfinetEntityManagerFactory" />
    		</property>
    	</bean>
     
    	<!-- service -->
    	<bean id="fr.gfi.gfinet.server.CollaboratorService" class="fr.gfi.gfinet.server.service.CollaboratorServiceImpl">
    		<property name="collaboratorDao">
    			<ref bean="gfinetCollaboratorDao" />
    		</property>
    		<property name="competenceDao">
    			<ref bean="gfinetCompetenceDao" />
    		</property>
    		<property name="competenceGroupDao">
    			<ref bean="gfinetCompetenceGroupDao" />
    		</property>
    		<property name="expertizeDao">
    			<ref bean="gfinetExpertizeDao" />
    		</property>
    	</bean>
     
    	<!-- Transaction manager -->
    	<bean id="gfinetTxManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    		<property name="entityManagerFactory" ref="gfinetEntityManagerFactory" />
    	</bean>
     
    	<!-- demarcation des transactions -->
    	<aop:config>
    		<aop:pointcut id="collaboratorServiceMethods"
    			expression="execution(* fr.gfi.gfinet.server.CollaboratorService.*(..))" />
    		<aop:advisor advice-ref="collaboratorServiceTxAdvice"
    			pointcut-ref="collaboratorServiceMethods" />
    	</aop:config>	
    	<tx:advice id="collaboratorServiceTxAdvice" transaction-manager="gfinetTxManager">
    		<tx:attributes>
    			<tx:method name="get*" propagation="SUPPORTS" rollback-for="fr.gfi.gfinet.common.exception.ServiceException" />
    			<tx:method name="*" propagation="REQUIRED" rollback-for="fr.gfi.gfinet.common.exception.ServiceException" />
    		</tx:attributes>
    	</tx:advice>
    Dans ce contexte, je cherche à tester mes services. Le problème que j'ai rencontré semble ultra-connu mais malheureusement je ne m'en sors pas.

    Je suis parti d'un LazyInitializationException. Je me suis alors rendu compte que le chargement tardif ne marchait plus en dehors des transactions,
    c'est à dire une fois que le service à retourné l'objet, paradoxalement, au moment ou on en aurait besoin.

    Pour illustrer mon problème voici deux exemples bêtes qui lèvent une exception :


    Exemple 1 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    // Imaginons qu'un collaborateur possède des missions (lazy loading activée):
     
    col1 = collaboratorService.getCollaborator(10);
    col.getMissions();		
     
    // Le dernier appel lève une exception LazyInitializationException si la 
    // collection n'a pas été initialisé dans le dao et donc dans la transaction 
    // lorsqu'on avait une session.
    Exemple 2 :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    Collaborator col = new Collaborator ();
    collaboratorService.saveCollaborator(col);		
    collaboratorService.deleteCollaborator(col);
     
    // Le delete lève une autre exception java.lang.IllegalArgumentException:
    // Removing a detached instance.
    Ces deux exceptions sont du même type. De manière générale les objets détachés ne sont plus "manipulables" en dehors d'une session hibernate.

    Pour résoudre ce problème je ne vois que deux solutions :
    • étendre la transaction et donc la session à la partie web avec le pattern OpenSessionInViewFilter ou OpenSessionInViewInterceptor (ce qui ne me semble pas très propre vu que ces préoccupations incombent il me semble plutôt à la partie serveur et limite un peu l'indépendance de chaque partie). OU ...

    • rattacher des objets détachés à une session.


    Je ne peux pour l'instant pas utilise le pattern OSIV car je n'ai absolument pas la main sur la partie web (intégré dans liferay).
    Je travaille dans des fichiers de test avec testng pour tester mes services. Je me dis donc que pour l'instant seule la deuxième solution peut me sauver.

    Ce qui amène enfin (pardon pour la longueur) mes questions.

    • Comment rattacher des objets quand on a pas de sessions puisque c'est spring qui crée les transactions puis les sessions ?
      Tout ce que j'ai c'est un entityManagerFactory de type org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean. Comment obtenir la session qui à été utilisé par la transaction précédente ?


    • Ou alors comment faut-il procéder pour ne plus avoir ce problème de session. D'ailleurs, je ne comprends pas pourquoi chaque transaction doit-avoir sa session et donc pourquoi un simple delete ne peut-il pas être effectué meme s'il est effectué depuis une transaction différente.


    Je m'excuse pour la longueur des explications. En espérant que les survivants pourront éclairer ma lanterne.

    Merci beaucoup.

  2. #2
    Membre chevronné
    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    365
    Détails du profil
    Informations personnelles :
    Localisation : Maroc

    Informations forums :
    Inscription : Janvier 2006
    Messages : 365
    Par défaut
    Salut,
    Etant donné que les transactions, je devine, ont été déclarées au niveau des méthodes de beans services, tout est donc géré par Spring (début et fin de transaction, ouverture et fermeture de session hibernate). Ce qui explique qu'à chaque appel de méthode de service, une session est ouverte et refermée une fois l'objet retourné. Je pense que c'est mieux ainsi que d'utiliser le pattern OpenSessionInView qui pourrait poser d'autres problèmes dans ton cas. A mon avis, pour résoudre les problèmes que tu rencontres, il faudrait juste redéfinir l'implémentation de tes méthodes de service, en faisant en sorte qu'un appel de méthode soit canonique. Par exemple, pour la suppression d'un objet collaborateur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    public class CollaboratorService {
        private CollaborateurDAO dao;
     
        public void deleteCollaborator(Long collaboratorId) {
             Collaborator col = dao.findById(collaboratorId);
             dao.delete(col);
        }
    }
    Le but c'est de faire en sorte que la méthode de service appelée réalise tout le travail nécessaire durant la transaction en cours avant de retourner. Et donc aussi faire en sorte de charger la collection dont tu as besoin dans la méthode appelée avant la fin de la transaction au retour de la méthode.
    Voilà, j'espère que cela te mette sur la voie.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    14
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2006
    Messages : 14
    Par défaut
    Merci pour ta réponse.

    Sur le principe je suis tout à fait d'accord. C'est ce que je fais dans les services.

    Mais dans les fichiers de tests (comme côté web), je peux avoir besoin, d'enchainer plusieurs appels de méthodes appartenant à des services différents et donc non regroupable dans une méthode de service. (Le pattern Commande pourrait aider mais ça complique drôlement seulement pour pouvoir faire deux appels consécutifs côté serveur).

    Plus simplement, je peux vouloir enchainer plusieurs méthodes du meme service qu'il ne serait pas nécessaire de regrouper dans une méthode de service car non nécessaire à la partie web puisque seulement utile pour tester le comportement et la robustesse des méthodes exposées (exemple un save et un update en suivant - je suis obligé de passer par l'objet dans ce cas).

    Dans tous les cas, ça ne résolve pas le problème du LazyInitializationException.

    A bien y réfléchir, le rattachement de session ne résout rien car la session créée dans la transaction n'existe plus. Et créer une nouvelle session ne changera rien non plus. La seule solution serait peut-être d'encapsuler toutes les méthodes d'un même service dans un énième bean (ma classe de test) géré par spring afin de profiter de la propagation de la session entre les couches et donc d'avoir la même session que celle utilisée par le service et le dao dans la classe de test. Néanmoins c'est vraiment pas propre et ça oblige à faire deux fichiers de conf pour spring (un pour les tests, un autre pour la mise en production).

    De plus testng se lançant avant spring, il n'est plus utilisable (et ses annotations avec) puisque les beans ne seront pas encore créés au lancement des méthodes de test.

    Si quelqu'un à un retour d'expérience à ce sujet, toute aide sera la bienvenue.

    Ca fait 1 semaine que je suis dessus, après ça j'ai plus d'idées.

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    14
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2006
    Messages : 14
    Par défaut
    J'ai résolu mon problème depuis mon premier thread. Pour ceux qui auront le même problème, j'ai opéré de la manière suivante.

    Partie web :
    ------------
    Implémentation du pattern OpenSessionInView via la class OpenEntityManagerInView de Spring.

    Partie serveur (pour les tests) :
    ------------------------------
    Encapsulation des services offerts à la partie web (beans au sens spring) dans d'autres beans crées spécialement pour les tests et donc définis dans un fichier de conf. utilisé seulement pour les tests. Du coup, au lieu d'avoir une seule classe de test CollaboratorServiceImplTestCase par exemple, j'ai deux classes :
    • CollaboratorServiceImplTestBean (bean dans spring)
    • CollaboratorServiceImplTestCase (classe de test banale avec @Test etc...) appelant simplement les méthodes de CollaboratorServiceImplTestBean.


    Ainsi mes classes de test bénéficies des transactions utilisées par mes services, et je n'ai plus d'objets détachés et donc de LazyInitializationException.

    C'est de la bidouille mais pour tester, je n'ai trouver que ça.

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

Discussions similaires

  1. Réponses: 10
    Dernier message: 16/08/2013, 21h03
  2. [Objet immuable] Comment faire ?
    Par smori dans le forum Débuter avec Java
    Réponses: 3
    Dernier message: 18/03/2013, 17h32
  3. Deploiement application j2ee + spring + hibernate + postgresql
    Par Thelo dans le forum Tomcat et TomEE
    Réponses: 1
    Dernier message: 06/02/2012, 15h12
  4. Logiciels 64 bits et Postgresql 8.3, comment faire?
    Par Sunsawe dans le forum PostgreSQL
    Réponses: 2
    Dernier message: 13/08/2010, 08h48
  5. Collection d'objets polymorphes: Comment faire ?
    Par sapin dans le forum Débuter
    Réponses: 25
    Dernier message: 19/06/2008, 13h32

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