Announcement Announcement Module
Collapse
No announcement yet.
OpenSessionInView ignores Hibernate collection insertions Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • OpenSessionInView ignores Hibernate collection insertions

    Using Spring 1.1.1 and Hibernate 2.1.6.
    I'm trying to use OpenSessionInView. If I set singleSession="false" (seems to be the recommended approach), and add an object to a persistent collection, the insert is ignored.
    If I set singleSession="true" the insert works fine.

    Relevent code:
    Spring configuration
    Code:
    <bean id="openSessionInViewInterceptor"  
       class="org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor">
        <property name="singleSession"><value>false</value></property>
        <property name="sessionFactory"><ref local="sessionFactory"/></property>
    </bean>
    Hibernate configuration
    Code:
    <class name="com.joliciel.aplikaterm.domain.ClientImpl" table="Client" 
    	dynamic-update="false" dynamic-insert="false">
    	<id name="id" column="idClient" type="long" >
    		<generator class="hilo" />
    	</id>
    	...
    	<set name="dictionnairesDB" table="Dictionnaire" lazy="true" 
    		inverse="true" cascade="all-delete-orphan" sort="unsorted" >
    		<key column="idClient" />
    		<one-to-many class="com.joliciel.aplikaterm.domain.DictionnaireImpl"/>
    	</set>
    </class>
    ClientImpl code segments
    Code:
    protected Set getDictionnairesDB&#40;&#41; &#123;
    	return this.dictionnaires;
    &#125;
    protected void setDictionnairesDB&#40;Set dictionnaires&#41; &#123; this.dictionnaires=dictionnaires;&#125;
    public void ajouterDictionnaire&#40;Dictionnaire dico&#41; &#123;
       LOG.debug&#40;"ajouterDictionnaire&#40;" + dico.getNom&#40;&#41; + "&#41;"&#41;;
       DictionnaireImpl leDico = &#40;DictionnaireImpl&#41; dico;
       leDico.setClient&#40;this&#41;;
       this.dictionnaires.add&#40;leDico&#41;;
       LOG.debug&#40;"end ajouterDictionnaire&#40;&#41;"&#41;;
     &#125;
    To run ajouterDictionnaire (add dictionary), the client is being retrieved as follows:
    Code:
    Client client = null;
    Iterator clients = getHibernateTemplate&#40;&#41;.iterate&#40;"FROM ClientImpl AS client"
    	+ " WHERE client.codeClient = ?"
    	+ " AND client.actif=?",
    	new Object&#91;&#93; &#123;codeClient, Boolean.TRUE&#125;,
    	new Type&#91;&#93; &#123;Hibernate.STRING, Hibernate.BOOLEAN&#125;&#41;;
    if &#40;clients.hasNext&#40;&#41;&#41;
    	client = &#40;Client&#41; clients.next&#40;&#41;;
    return client;
    I did a diff between log4j files created with singleSession=false and singleSession=true, and here are the results.

    Extracts from log4j before ajouterDictionnaire with singleSession=false (no insertion):

    DEBUG [net.sf.hibernate.impl.SessionImpl] - loading [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - attempting to resolve [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - object not resolved in any cache [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.persister.EntityPersister] - Materializing entity: [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.impl.BatcherImpl] - about to open: 1 open PreparedStatements, 1 open ResultSets
    DEBUG [net.sf.hibernate.SQL] - select clientimpl0_.idClient as idClient0_, clientimpl0_.typeDeClient as typeDeCl2_0_, clientimpl0_.version as version0_, clientimpl0_.codeClient as codeClient0_, clientimpl0_.nom as nom0_, clientimpl0_.courrielAdmin as courriel6_0_, clientimpl0_.fuseauHoraire as fuseauHo7_0_, clientimpl0_.siteWeb as siteWeb0_, clientimpl0_.texte_dAccueil as texte_dA9_0_, clientimpl0_.actif as actif0_, clientimpl0_.idLangueParDefaut as idLangu11_0_, clientimpl0_.fichierLogo as fichier12_0_, clientimpl0_.largeurLogo as largeur13_0_, clientimpl0_.hauteurLogo as hauteur14_0_ from Client clientimpl0_ where clientimpl0_.idClient=?
    DEBUG [net.sf.hibernate.loader.Loader] - total objects hydrated: 1
    DEBUG [net.sf.hibernate.impl.SessionImpl] - resolving associations for [com.joliciel.aplikaterm.domain.LangueImpl#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - done materializing entity [com.joliciel.aplikaterm.domain.LangueImpl#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - creating collection wrapper:[com.joliciel.aplikaterm.domain.ClientImpl.organisa tionsDB#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - creating collection wrapper:[com.joliciel.aplikaterm.domain.ClientImpl.dictionn airesDB#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - done materializing entity [com.joliciel.aplikaterm.domain.ClientCentreDeDocum entation#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - initializing non-lazy collections
    DEBUG [net.sf.hibernate.impl.IteratorImpl] - exhausted results
    DEBUG [net.sf.hibernate.impl.IteratorImpl] - closing iterator

    Extracts from log4j before ajouterDictionnaire with singleSession=true (insertion succeeds):

    DEBUG [net.sf.hibernate.impl.SessionImpl] - loading [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - attempting to resolve [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - resolved object in session cache [com.joliciel.aplikaterm.domain.ClientImpl#1]
    DEBUG [net.sf.hibernate.impl.IteratorImpl] - exhausted results
    DEBUG [net.sf.hibernate.impl.IteratorImpl] - closing iterator

    Actual call to ajouterDictionnaire is identical.

    Extracts from log4j after ajouterDictionnaire with singleSession=false (no insertion):

    DEBUG [net.sf.hibernate.impl.SessionImpl] - flushing session
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Flushing entities and processing referenced collections
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Processing unreferenced collections
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Scheduling collection removes/(re)creates/updates
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Flushed: 0 insertions, 0 updates, 0 deletions to 0 objects
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
    DEBUG [net.sf.hibernate.impl.SessionImpl] - executing flush
    DEBUG [net.sf.hibernate.impl.SessionImpl] - post flush
    DEBUG [net.sf.hibernate.impl.SessionImpl] - transaction completion

    Extracts from log4j after ajouterDictionnaire with singleSession=true (insertion succeeds):
    DEBUG [net.sf.hibernate.impl.SessionImpl] - flushing session
    DEBUG [net.sf.hibernate.engine.Cascades] - processing cascades for: com.joliciel.aplikaterm.domain.ClientCentreDeDocum entation
    DEBUG [net.sf.hibernate.engine.Cascades] - cascading to collection: com.joliciel.aplikaterm.domain.ClientImpl.organisa tionsDB
    DEBUG [net.sf.hibernate.engine.Cascades] - cascading to collection: com.joliciel.aplikaterm.domain.ClientImpl.dictionn airesDB
    DEBUG [net.sf.hibernate.engine.Cascades] - cascading to saveOrUpdate()
    DEBUG [net.sf.hibernate.impl.SessionImpl] - saveOrUpdate() persistent instance
    DEBUG [net.sf.hibernate.engine.Cascades] - cascading to saveOrUpdate()
    DEBUG [net.sf.hibernate.impl.SessionImpl] - saveOrUpdate() persistent instance
    DEBUG [net.sf.hibernate.engine.Cascades] - cascading to saveOrUpdate()
    DEBUG [net.sf.hibernate.impl.SessionImpl] - saveOrUpdate() unsaved instance
    DEBUG [net.sf.hibernate.id.TableHiLoGenerator] - new hi value: 2
    DEBUG [net.sf.hibernate.impl.SessionImpl] - generated identifier: 65537
    DEBUG [net.sf.hibernate.impl.SessionImpl] - saving [com.joliciel.aplikaterm.domain.DictionnaireImpl#65 537]
    ...
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Collection dirty: [com.joliciel.aplikaterm.domain.ClientImpl.dictionn airesDB#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Flushing entities and processing referenced collections
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Updating entity: [com.joliciel.aplikaterm.domain.ClientCentreDeDocum entation#1]
    ...
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Collection found: [com.joliciel.aplikaterm.domain.ClientImpl.dictionn airesDB#1], was: [com.joliciel.aplikaterm.domain.ClientImpl.dictionn airesDB#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Collection found: [com.joliciel.aplikaterm.domain.UtilisateurImpl.lan guesDeTravailDB#2], was: [com.joliciel.aplikaterm.domain.UtilisateurImpl.lan guesDeTravailDB#2]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Collection found: [com.joliciel.aplikaterm.domain.OrganisationImpl.ut ilisateursDB#1], was: [com.joliciel.aplikaterm.domain.OrganisationImpl.ut ilisateursDB#1]
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Processing unreferenced collections
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Scheduling collection removes/(re)creates/updates
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Flushed: 1 insertions, 1 updates, 0 deletions to 11 objects
    DEBUG [net.sf.hibernate.impl.SessionImpl] - Flushed: 0 (re)creations, 1 updates, 0 removals to 6 collections
    ...
    DEBUG [net.sf.hibernate.impl.SessionImpl] - executing flush
    DEBUG [net.sf.hibernate.persister.EntityPersister] - Inserting entity: [com.joliciel.aplikaterm.domain.DictionnaireImpl#65 537]
    ...
    DEBUG [net.sf.hibernate.impl.BatcherImpl] - done closing: 0 open PreparedStatements, 0 open ResultSets
    DEBUG [net.sf.hibernate.impl.BatcherImpl] - closing statement
    DEBUG [net.sf.hibernate.impl.SessionImpl] - post flush
    DEBUG [net.sf.hibernate.impl.SessionImpl] - transaction completion

    Any ideas?

    Best regards,
    Assaf

  • #2
    What is the transaction story here? Are you using HibernateTransactionManager or JTATransacitonManager, so that flushing actually happens when you get back out of the transaction?

    Comment


    • #3
      I'm using the HibernateTransactionManager.
      The transaction is wrapped around a service layer call:
      Code:
      public void ajouterDictionnaire&#40;Client client, Dictionnaire dico&#41; 
      	throws ExceptionAplikaterm
      &#123;
          // check if dictionary name already exists
          controllerNomDuDictionnaire&#40;client, dico&#41;;
          // add the dictionary
          client.ajouterDictionnaire&#40;dico&#41;;
       &#125;
      ... which checks if the dictionary name already exists and adds the dictionary it if it doesn't.
      Note that the client is retrieved prior to transaction start, and is passed into the above method.
      If I add a call to saveOrUpdate at the end of the above method, I get:
      net.sf.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
      Presumably, because the Client object is retrieved in one session, and the Dictionnaires collection loaded in another session.

      I managed to get the code working in both singleSession=true and false by passing client's unique code into the service layer, and loading it inside the transaction instead of outside of it:
      Code:
      public void ajouterDictionnaire&#40;String codeClient, Dictionnaire dico&#41; 
      	throws ExceptionAplikaterm
      &#123;
          // retrieve the client based on its code
          Client client = clientDao.rechercherClient&#40;codeClient&#41;;
          // check if dictionary name already exists
          controllerNomDuDictionnaire&#40;client, dico&#41;;
          // add the dictionary
          client.ajouterDictionnaire&#40;dico&#41;;
      &#125;
      This implies I can never pass parent domain objects into a transaction - I always have to load them internally, which seems quite restrictive.
      What if I want to modify a parent's properties and its collections in the same transaction? Does this imply I won't be able to change its properties outside of the service layer (e.g. automatically assigning them in the Spring SimpleFormController subclass)? Or should I be transactionalizing the SimpleFormController's handleRequest itself?

      Best regards,
      Assaf

      Comment


      • #4
        Definitely it's not kosher to mix and match objects from two live Sessions. Ultimately, what the OpenSessionInView filter (singleSession=false) is targetted at is to allow lazy loading to happen when the view layer gets back a result from calling down into the transactionally wrapped service layer. But calling down, doing some work on the returned data, and calling down again, which will try to use another session, is not going to make Hibernate very happy, but it's also not clean anyways, as you're mixing data from different transactions. Now on the other hand, using the OSiV filter with singleSession=true, while it allows mixing and matching of data from different transactions and will stop Hibernate from complaining, is still not any cleaner when it comes down to it; you are still mixing and matching data from different transactions...

        The OSiV filter exists because it's a mechanism that some people want (and it's mentioned as a mechanism even on the Hibernate site). But that doesn't mean that it doesn't have some serious negatives when used in either mode, along with the positives. Another negative, for example, is that even if you are out of the transaction, you can modify an object graph (in teh view layer) and when the session is closed, those changes will be written out. Now is that ever really appropriate?

        Comment


        • #5
          So what is the correct way?

          Hi,

          I am a little confused. What is the correct way in data access? How to avoid two open sessions in attempting to access a collection?

          Comment


          • #6
            I don't know about the "correct" method.

            I'm in the midst of another discussion about this http://forum.springframework.org/showthread.php?t=11282.

            I don't personally see a problem in getting a domain object in one transaction, binding to its properties and possibly collections in the UI, and then saving it to the DB in another transaction. But to do this, you'll have to get rid of OpenSessionInView entirely, which implies "touching" the required collections somehow in the service layer.

            Some people would suggest passing a separate UI object into the service layer, and performing the whole thing in one transaction in the service layer - see a relevent discussion, especially the second post by James Cook.

            Hope this helps.

            Best regards,
            Assaf
            Last edited by robyn; May 14th, 2006, 04:43 PM.

            Comment


            • #7
              Why can't view use the same transaction manager?

              Originally posted by Colin Sampaleanu
              Another negative, for example, is that even if you are out of the transaction, you can modify an object graph (in teh view layer) and when the session is closed, those changes will be written out. Now is that ever really appropriate?
              So are you saying that when the view works with an object graph outside of the business/database transaction, the Hibernate actually uses auto-commit mode for all subsequent interactions with the database, effectively turning each SQL statement into its own implicit transaction?

              If so, is there any relatively-easy way to re-use the same transaction manager (eg. HibernateTransactionManager) so that it can explicitly initiate and close a readonly transaction for the duration of view rendering? That way, we wouldn't have to worry about the view inadvertently making persistent changes to the data store. Any sample code or subclass for doing this programmtically (I don't expect this to be possible declaratively)?

              Comment

              Working...
              X