5 Replies Latest reply on Apr 26, 2007 6:33 AM by quilian

    failed to lazily initialize a collection

      Hello again!

      I managed to get my other problems working by using a SMPC (thanks pete).
      however i now stumble accross the 1:n relationship of my entities Benutzer and Gruppe.

      I declared the Benutzer as following:

      @Entity
      @Name("benutzer")
      @Table(name = "benutzer")
      public class BenutzerImpl implements Serializable, Benutzer {
      
       private static final long serialVersionUID = -2113540391643036310L;
       private Long id;
       private Integer version;
       private String name;
      
       GruppeImpl gruppe;
      
       @Logger
       Log log;
      
       @Override
       public boolean equals(Object object) {
       if (object instanceof BenutzerImpl) {
       BenutzerImpl other = (BenutzerImpl) object;
      
       return (this.getId()==other.getId());
       }
       return false;
       }
      
       @Id
       @GeneratedValue
       public Long getId()
       public void setId(Long id)..
       @Version
       public Integer getVersion()...
       @SuppressWarnings("unused")
       private void setVersion(Integer version)..
      
      
       @ManyToOne(optional = true)
       @JoinColumn(name = "gruppe", nullable = true)
       public GruppeImpl getGruppe() {
       return gruppe;
       }
      
       @Transactional
       public void setGruppe(GruppeImpl aGruppe) {
      
       if (gruppe != null)
       {
      
       gruppe.removeBenutzer(this);
      
       gruppe = aGruppe;
      
       if (gruppe != null) {
      
       gruppe.addBenutzer(this);
       }
       }
       else
       {
       gruppe = aGruppe;
       }
       }
      }
      



      and the Gruppe like that:
      
      @Entity
      @Name("gruppeImpl")
      @Table(name = "gruppen")
      public class GruppeImpl implements Serializable, Gruppe {
       @Logger
       Log log;
      
       private static final long serialVersionUID = 2106790568007992821L;
      
       private Long id;
       private Integer version;
       private String name;
       private Boolean status;
      
       private List<BenutzerImpl> someBenutzer;
      
      
       @Override
       @Transient
       public boolean equals(Object object) {
       if (object instanceof GruppeImpl) {
       GruppeImpl other = (GruppeImpl) object;
      
       return (this.getId()==other.getId());
       }
       return false;
       }
      
      
       @OneToMany(mappedBy = "gruppe")
       @Transactional
       public List<BenutzerImpl> getBenutzer() {
       return someBenutzer;
       }
      
       public void setBenutzer(List<BenutzerImpl> newBenutzerList) {
      
       this.someBenutzer = newBenutzerList;
       }
      
       @Transactional
       public void addBenutzer(BenutzerImpl benutzer) {
       if (this.someBenutzer == null) {
       this.someBenutzer = new ArrayList<BenutzerImpl>();
       }
       if (!this.someBenutzer.contains(benutzer))
       this.someBenutzer.add(benutzer);
       }
      
       @Transactional
       public void removeBenutzer(BenutzerImpl benutzer) {
       if (this.someBenutzer == null)
       {
       return;
       }
       this.someBenutzer.remove(benutzer);
       }
      ...
      }
      



      The whole stuff in english:
      If the Benutzer gets a new Gruppe he must update the corresponding List Gruppe.getBenutzer(). Else the java object gruppe doesnt know that something happened to him. Therefore i extended the simple setter Benutzer.setGruppe() to remove/add from old/new Gruppe.Im not sure if hibernate or javax or anything else could possibly do that for me, knowing that i want a 1:n relationship. i only do the update if the property gruppe is already set, because hibernate itself calls the getter on initialization of the Benutzer.

      I also annotated the functions setGruppe, addBenutzer and removeBenutzer as @Transactional, because i want the operation to be in one transaction, hence rolled back completely if an error occurs. is that correct?

      If i choose another Gruppe and submit the form i get:
      17:35:52,468 ERROR [LazyInitializationException] failed to lazily initialize a collection of role: com.mobilanten.mowita.werkbank.GruppeImpl.benutzer, no session or session was closed
      org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.mobilanten.mowita.werkbank.GruppeImpl.benutzer, no session or session was closed
       at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
       at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
      ...
      



      I debugged and found out that both the new and the old Grupp have a session attribute with two different persistance contexts that referer to the same hibernate.session. By the time of execution of Benutzer.setGruppe() the session of the old Gruppe is marked as closed.

      Why are there 2 PCs ? i thought there'd only be this one (components.xml)
       <core:managed-persistence-context name="mowitaEntityManager"
       auto-create="true"
       persistence-unit-jndi-name="java:/mowitaWerkbankEntityManagerFactory"/>
      


      injected like that (GruppenManager and BenutzerManager):
       @In
       private EntityManager mowitaEntityManager;
      


      I thought the SMPC would be application global....

      Thanks for any help!
      Tobias Kilian

        • 1. Re: failed to lazily initialize a collection
          sammy8306

          As far as I know, SMPC is scoped over the conversation. That would be the implicit temporary conversation if no conversation is started manually.
          You have two options: either make sure the edits happen in the same conversation (and therefore the same EM) or merge the offending object into the second EM instance (using em.merge()).

          • 2. Re: failed to lazily initialize a collection
            msduk

            You need to use an extended persisten context otherwise is is closed on forwarding to the view.

            @PersistenceContext(type=EXTENDED) EntityManager em;

            • 3. Re: failed to lazily initialize a collection
              pmuir

              Yes, use a SMPC, not an extended PC. You don't want the @Transactional on those methods (it's an entity bean), and you probably want to let hibernate take care up updating the relationship for you - call entityManager.refresh(foo) to get hibernate to do this.

              • 4. Re: failed to lazily initialize a collection

                 

                "Sammy8306" wrote:
                As far as I know, SMPC is scoped over the conversation. That would be the implicit temporary conversation if no conversation is started manually.
                You have two options: either make sure the edits happen in the same conversation (and therefore the same EM) or merge the offending object into the second EM instance (using em.merge()).


                Hmm. I thought to have done that. The selectBenutzer() method which is called on selection of a Benutzer in the list calls beginConversation(), which
                is marked with @Begin(flushMode = FlushModeType.MANUAL)

                then when the save butten is pressed commit() is called which is marked with @End(beforeRedirect=true) and calls em.flush().

                Would'nt that mean i span a conversation around the whole edit process ?

                "petemuir" wrote:
                Yes, use a SMPC, not an extended PC. You don't want the @Transactional on those methods (it's an entity bean)


                Can you please explain why thats not needed? Do Entity beans always have an implicit transactional behavior??

                "petemuir" wrote:
                and you probably want to let hibernate take care up updating the relationship for you - call entityManager.refresh(foo) to get hibernate to do this.


                cool. So i dont need the add/remove on the list? But where should i do that call? I suppose i cant do it in the entity itself, because that would be ugly despite the fact that the entity doesnt know the PC.

                So i tried to do it in BenutzerManager.save() with no luck like that:
                 @DataModelSelection
                 @Out(required = false)
                 private BenutzerImpl selectedBenutzer;
                
                 private GruppeImpl selectedGruppe;
                
                 public void selectBenutzer()
                 {
                 selectedGruppe = mowitaEntityManager.merge(selectedBenutzer.getGruppe());
                 beginConversation();
                 // log.info("BenutzerManager selectBenutzer(#0)\n", selectedBenutzer);
                 // log.info("BenutzerManager editedBenutzer(#0)\n", editedBenutzer);
                
                // benutzer= selectedBenutzer;
                 }
                
                 @End(beforeRedirect=true)
                 public void commit()
                 {
                 log.info("Merging selectedBenutzer: #0", selectedBenutzer);
                 mowitaEntityManager.merge(selectedBenutzer);
                
                 log.info("refreshing selectedGruppe: #0", selectedGruppe);
                 mowitaEntityManager.refresh(selectedGruppe);
                
                 log.info("flushing...");
                 mowitaEntityManager.flush();
                
                 mowitaEntityManager.refresh(selectedBenutzer.getGruppe());
                 mowitaEntityManager.flush();
                
                 }
                


                I get an java.lang.IllegalArgumentException: Entity not managed by the call to
                mowitaEntityManager.refresh(selectedGruppe);


                The question is, how to get the gruppe that was selected before the user choose a new one, because if i call selectedBenutzer.getGruppe() in the save() method i already get the new Gruppe. Refreshing that newly selected Gruppe seems to work although i gat a lazy initialisation exception afterwards, when trying ot show the list of Gruppen.

                Thanks for your help,
                Tobias


                • 5. Re: failed to lazily initialize a collection

                   

                  "petemuir" wrote:
                  Yes, use a SMPC, not an extended PC. You don't want the @Transactional on those methods (it's an entity bean), and you probably want to let hibernate take care up updating the relationship for you - call entityManager.refresh(foo) to get hibernate to do this.


                  I now tried to do the refreshing but had no luck. I stumpled acrross several Forum posts taht cite the following from the EJB 3.0 specs:

                  Note that it is the application that bears responsibility for maintaining the consistency of runtime
                  relationships?for example, for insuring that the ?one? and the ?many? sides of a bidirectional
                  relationship are consistent with one another when the application updates the
                  relationship at runtime.


                  Doesnt that mean i should do those gruppe.add/remove calls in Benutzer.setGruppe() ???

                  Greetings,
                  Tobias