2 Replies Latest reply on Apr 27, 2008 9:38 AM by juliaf

    LazyInitializationException in many-to-many relation that co

    juliaf

      Hello,

      I am not sure if I should be asking my question here or at the Beginners Corner. I am just starting with EJB3 and trying to build a simple test application.
      I am getting a LazyInitializationException: failed to lazily initialize a collection of role: eco.ejb3.entity.Subject.usersThatSubscribedToMe, no session or session was closed when my JUnit test tries to exercise a method of my EJB3 session bean.
      The structure is as follows:
      There are 2 entity beans: Subject and User mapped onto respective tables and there is a bi-directional many-to-many relationship between them mapped as follows:

      @Entity
      @Table(name="user")
      public class User implements Serializable{

      private Integer userID;
      ...
      Collection subjectsThisUserSubscribedTo;
      ...
      public User(String accountName, String email) {
      this.setAccountName(accountName);
      this.setEmail(email);
      this.subjectsThisUserSubscribedTo = new rrayList();
      }

      @Id
      @GeneratedValue
      @Column(name="UID", nullable = false)
      public Integer getUserID() { return userID; }
      public void setUserID(Integer uid) { userID = uid; }

      @ManyToMany
      @JoinTable(name="shared_subject",
      joinColumns={@JoinColumn(name="UID")},
      inverseJoinColumns={@JoinColumn(name="SID")})
      @Basic(fetch=FetchType.EAGER)
      public Collection getSubjectsThisUserSubscribedTo() {
      return subjectsThisUserSubscribedTo;
      }
      public void setSubjectsThisUserSubscribedTo(
      Collection subjectsThisUserSubscribedTo) {
      this.subjectsThisUserSubscribedTo = subjectsThisUserSubscribedTo;
      }

      ...
      }


      @Entity
      @Table(name = "subject")
      public class Subject implements Serializable{

      private Integer subjectID;
      ...
      Collection usersThatSubscribedToMe;
      ...
      public Subject(String subjectName, User owner) {
      this.subjectName = subjectName;
      this.owner = owner;
      Collection usersThatSubscribedToMe = new ArrayList();
      }

      @Id
      @GeneratedValue
      @Column(name = "SID")
      public Integer getSubjectID() { return subjectID; }
      public void setSubjectID(Integer sid) { subjectID = sid; }

      @ManyToMany(mappedBy="subjectsThisUserSubscribedTo")
      @Basic(fetch=FetchType.EAGER)
      public Collection getUsersThatSubscribedToMe() {
      return usersThatSubscribedToMe;
      }
      public void setUsersThatSubscribedToMe(Collection usersThatSubscribedToMe) {
      this.usersThatSubscribedToMe = usersThatSubscribedToMe;
      }
      ...
      }

      Then there is a message bean

      @Stateless
      public class SubjectBean implements SubjectManager {

      @PersistenceContext(unitName="myDS")
      private EntityManager entityManager;

      ...

      @TransactionAttribute(TransactionAttributeType.REQUIRED)
      public void addPublicSubject(Subject s, User u) {

      s.getUsersThatSubscribedToMe().add(u);
      entityManager.persist(s);

      }
      Etc...

      When this method is called by the client the Exception noted above is thrown.
      Does anyone have a suggestion what is the cause of the problem and how to fix it?

      I am running this example on JBoss 5. The server.log is a bit too verbose to paste it here. However, looking through the log I got an impression that JBoss converts statements into prepared queries, but they all seem to range over a single table, being either a subject or a user and there is no mention of the table shared_subject that binds this relationship at the level of my sql schema. I am not sure that I understand what is actually going on there. I also suspect that the problem may not be with evaluating the rationships but with something else. Maybe the cause is that the entity bean is detached from the EntityManager? If so, how to attach it back. What are the rules? It is not obvious to me that I am doing something wrong in my classes. Maybe I missed the point somewhere?

      Many thanks to those of you who doesn't mind taking a moment to help a programming novice in trouble.

        • 1. Re: LazyInitializationException in many-to-many relation tha
          jaikiran

           

          I am getting a LazyInitializationException: failed to lazily initialize a collection of role : eco.ejb3.entity.Subject.usersThatSubscribedToMe, no session or session was closed


          A LazyInitializationException occurs when the entity or its associated collection is being accessed when the session is no longer available. In your case, i guess, your JUnit test first called a method on the bean to retrieve the entity. When this is done, a session is opened to retrieve that persistent object. The object is then returned to the client (your JUnit test case). At the same time the session is closed (since the bean method transaction completed).

          Now at some later point in time, in your testcase you invoke the addPublicSubject method on the bean passing it the entity which was loaded as part of a session which no longer exists. Now when you try to access the entities collection, it throws this LazyInitializationException.

          To avoid this exception, you either have to access that collection when the session is open or use the merge method on the entitymanager to attach the entity to a session. Like this:

          @TransactionAttribute(TransactionAttributeType.REQUIRED)
          public void addPublicSubject(Subject s, User u) {
          
           //merge the subject. Remember that the merge operation, returns a merged object.
           // So use this returned object, henceforth in the code.
           Subject mergedSubject = entityManager.merge(s);
          
           //Let's also merge the User since it too was detached
           User mergedUser = entityManager.merge(u);
          
           mergedSubject.getUsersThatSubscribedToMe().add(mergedUser);
          
           entityManager.persist(mergedSubject);
          
          }


          This is just a pseudo-code and you might have to do some additional changes to get this working. If this doesn't work, then please post back with the exception logs and the code changes that you did.

          While posting the logs or code or xml content, remember to wrap it in a code block using the Code button in the message editor window and please hit the Preview button to make sure your post is correctly formatted

          • 2. Re: LazyInitializationException in many-to-many relation tha
            juliaf

            Hello, jaikiran

            Many thanks for posting the tip. I fixed the LazyInitialisationException problem in this instance. However, I think I am facing a much more general EJB design problem, which I will try to explain further down.
            With regards to the earlier posting, in addition to using merge() before persist(), as you helpfully suggested, I also had to add this:

            @TransactionAttribute(TransactionAttributeType.REQUIRED)
            public void addPublicSubject(Subject s, User u) {
            
             User u = entityManager.merge(user);
             Subject s = entityManager.merge(subject);
             
             u.getSubjectsThisUserSubscribedTo().add(s);
             s.getUsersThatSubscribedToMe().add(u);
            
             entityManager.persist(u);
             entityManager.persist(s);
            
            }
            

            Thus, it updates the relationship table (shared_subject in this case)

            In addition I modified findSubject(int subjectId) of session bean SubjectBean as follows
            @TransactionAttribute(TransactionAttributeType.REQUIRED)
            public Subject findSubject(int subjectId) {
             Subject s = (Subject)entityManager.find(Subject.class, subjectId);
             
             s.getOwner();
             s.getUpdater();
             Collection<Topic> temp_topics= s.getMyTopics();
             if(!temp_topics.isEmpty()) temp_topics.size();
             Set<User> temp_users = s.getUsersThatSubscribedToMe();
             if(!temp_users.isEmpty()) temp_users.size();
             return s;
            }
            

            because I read in one of the tutorials that all relationships must be managed explicitly in the transactional code.
            That is if we want the client to have access to the cmr fields of the Subject they must be pre-fetched within the transaction by executing seemingly useless statements like collectionXXX.size() or s.getOwner() even though we don™t assign the result of such statements to anything and don™t use it within the transaction.

            EJB design problem:
            So, to avoid LasyInitialisationException CMR Fields first have to be accessed in the transactional context of a session bean in order to be loaded into the Entity Bean
            Suppose, I have a bean User with the following CMR fields:

             Set<Subject> mySubjects;
             Set<Topic> topicsThisUserCreated;
             Set<Entry> entriesThisUserAuthored;
             Set<Topic> topicsThisUserUpdated;
             Set<Topic> entriesThisUserUpdated;
             Set<Subject> subjectsThisUserSubscribedTo;
            

            Respectively each object in each collection has CMR fields
            
            Subject:
             User owner;
             User updater;
             Collection<Topic> myTopics;
             Set<User> usersThatSubscribedToMe;
            Topic:
             User creator;
             User updater;
             Subject subject;
             Topic parentTopic;
             private Collection<Topic> nestedTopics;
             private Set<Entry> entries;
            
            Entry:
             private Topic topic;
             private User author;
             private User updater;
             Plus entry has a field:
             private String entryBody; which is @Lob
            


            When User is accessed in the detached context (somewhere in the client) technically speaking it is possible that the client will call something like:
            public void buildOutput(User user, Output out){
             User u = user;
             Set<Subject> ss = u.getSubjectsUserSubscribedTo();
             for(subject:ss) {
             Set<Topic> tt = subject.getMyTopics();
             for(topic:tt) {
             outputTopic(topic, u, out);
            
             }
            }
            public void outputTopic(Topic topic, User u, out) {
            
             if(topic.getParent()!=null) outputTopic(topic.getParent(), u, out);
             out.setElemet(ÂÂ�Topic.class.getName());
             out.add(topic.getName);
             out.add(topic.getCreator().getUserName());
             out.add(topic.getUpdater().getUserName();
             out.add(topic.getCreator().getTopicsThisUserUpdated().size());
             Set<Entry> ee = topic.getEntries();
             for(entry:ee){
             out.setElement(Entry.class.getName()ÂÂ�);
             out.add(entry.getName();
             out.add(entry.getAuthor().getUserName());
             out.add(entry.getBody());
             }
            }
            
            

            I don™t think it will work because LazyInitializationException can be thrown in the dozens of places in this snippet of code.
            This poses a design issue:
            The getters and setters of CMR fields must be public so that the EJB container could manage them.
            If they are public the client has access to them in the detached context, but to avoid Lazy Loading Exception they have to be set and accessed in the transactional context of a session bean.
            My understanding is that EJB3 disposed of the Data Transfer and Data Access objects in order to simplify the EJB model and instead introduced the notion of the detached context in which Data Access objects (User, Subject, Topic, Entry in this case) act as Data Transfer Objects as far as client is concerned. However, LazyInitializationException will always be on the way in this model.
            So, the question is how can we ensure that the client doesn™t access CMR fields outside of transactional context from the design point of view?