9 Replies Latest reply on Oct 27, 2009 3:58 PM by adamw

    ejb 3 + lazy loading + detached entities

    svetok

      Hi

      We use EJB 3 together with Envers. And we also have standalone client. So we often have to work with detached entities.

      In everyday tasks when we need lazy related collection to be loaded before the entity will be detached, we just do any call to that collection.

      But with Envers I have the problem. I get the revision of object with the help of AuditReader#find . I do this in method of stateless session bean that is called from client-app over RMI. Inside that method I also call the size method of collection to force it's loading. After that I can see that collection is loaded.
      But when I get this entity on the client and try to call methods on the collection I get error:

      java.lang.NullPointerException
       at org.hibernate.envers.entities.mapper.relation.lazy.proxy.CollectionProxy.checkInit(CollectionProxy.java:47)
       at org.hibernate.envers.entities.mapper.relation.lazy.proxy.CollectionProxy.size(CollectionProxy.java:52)


      It seems like Envers don't understand that entity was detached or don't understand that collection was already loaded.

      I can avoid this exception by cloning entity before returning from session bean. I think it's the variant of "manual detach". But it's really bad variant cause I have big quantity of entity classes, some of them are really huge and complicated.

      hibernate version: 3.3.1.GA
      envers version: 1.2.1-hibernate-3.3
      jboss version: 4.2.3.GA

      Thanks

        • 1. Re: ejb 3 + lazy loading + detached entities
          svetok

          May be the problem is that we use javax.persistence.EntityManager and not org.hibernate.Session ?

          • 2. Re: ejb 3 + lazy loading + detached entities
            adamw

            Hello,

            not, that shouldn't be a problem. Please report a bug in JIRA, with a test case, if possible. (Even better, with a fix patch ;) )

            Adam

            • 3. Re: ejb 3 + lazy loading + detached entities
              svetok

              Adam,

              I spent almost a day to investigate where the problem is.

              For some mysterious reason when the object of type ListProxy is being recieved via RMI to the client-side, it 'losts' it's delegate field (our wrapped collection). Just before the return from server it was not null, and on client it's get null. So, method ListProxy#checkInit() try to load this collection from database and of course he can't.

              From one side it seems to be not the problem of Envers... But from the other side I can't undesrtand how during serializing/deserializing object could 'lost' part of field values and no exception is given by RMI.

              Can you advice me anything?

              Some parts of my code:

              Person.class

              @Entity
              @Table(name="People",schema="X")
              @Audited
              @AuditTable(value = "People", schema="history")
              public class Person extends CISEntity{
              
               private Integer id;
               private String name = "";
               private List<Phone> phones = new ArrayList<Phone>();
              
               @Id @GeneratedValue
               public Integer getId() {
               return id;
               }
              
               @SuppressWarnings("unused")
               private void setId(Integer id) {
               this.id = id;
               }
              
               public String getName() {
               return name;
               }
              
               public void setName(String name) {
               this.name = name;
               }
              
               @OneToMany(cascade={CascadeType.ALL})
               @Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
               @JoinColumn(name="pnt_object")
               @AuditJoinTable(name="People_Phones", schema="history")
               public List<Phone> getPhones() {
               return phones;
               }
              
               private void setPhones( List<Phone> phones ) {
               this.phones = phones;
               }
              
              }


              Phone.class
              @Entity
              @Table (name="Phones",schema="X")
              @Audited
              @AuditTable(value = "Phones", schema="history")
              public class Phone implements Serializable{
              
               private int id;
               private int pnt_object;
               private String phone = "";
              
               @Id @GeneratedValue
               public int getId() {
               return id;
               }
              
               @SuppressWarnings("unused")
               private void setId(int id) {
               this.id = id;
               }
              
               public int getPnt_object() {
               return pnt_object;
               }
              
               public void setPnt_object(int pnt_object) {
               this.pnt_object = pnt_object;
               }
              
              
               public String getPhone() {
               return phone;
               }
              
               public void setPhone(String phone) {
               this.phone = phone;
               }
              }


              Session bean
              @Stateless
              @RemoteBinding(jndiBinding="RemoteServices.HISTORY_MANAGER_JNDI_NAME")
              public class HistoryManagerTestBean implements HistoryTestManager{
              
               @PersistenceContext(unitName = "cis2")
               private EntityManager em;
              
               public Person getFullPerson(Integer id, int revision_number){
               AuditReader reader = AuditReaderFactory.get(em);
               Person p=reader.find(Person.class, id, revision_number);
               int i =p.getPhones().size();
               return p;
               }
              }


              Thanks

              p.s. I'm not sure about bug report in JIRA, because possibly it's not the problem of Envers.

              • 4. small fix
                svetok

                Sorry, method checkInit is in class CollectionProxy.

                • 5. Re: ejb 3 + lazy loading + detached entities
                  svetok

                  Can it be the effect of generics? I mean... this field delegate is of type T. It becomes Collection after compilation. And Collection does not implement Serializable interface...

                  I just don't know to think about all this problem... :(

                  • 6. Re: ejb 3 + lazy loading + detached entities
                    svetok

                    Hi, Adam!

                    I'm almost sure that the problem lies in the direction of generics - inheritance - serialization. But I haven't succeeded to find out the exact point.

                    BUT I've found a workaround. I know it's a "bad style", but for now it works and at least give me possibility to postpone the decision of this problem.

                    So I had to get rid of inheritance CollectionProxy <- ListProxy. I've moved everything from CollectionProxy to ListProxy. And also I've made the field "initializor" transient.

                    With this variant of ListProxy all mapped collections (that are Lists) work just fine.

                    I can post here my ListProxy class, may be it will help to find the real cause of problem.

                    public class ListProxy<U> implements List<U>, Serializable {
                    
                     private transient org.hibernate.envers.entities.mapper.relation.lazy.initializor.Initializor<List<U>> initializor;
                     protected List<U> delegate;
                    
                     public ListProxy() {
                     }
                    
                     public ListProxy(org.hibernate.envers.entities.mapper.relation.lazy.initializor.Initializor<List<U>> initializor) {
                     this.initializor = initializor;
                     }
                    
                     protected void checkInit() {
                     if (delegate == null) {
                     delegate = initializor.initialize();
                     }
                     }
                    
                     public boolean addAll(int index, Collection<? extends U> c) {
                     checkInit();
                     return delegate.addAll(index, c);
                     }
                    
                     public U get(int index) {
                     checkInit();
                     return delegate.get(index);
                     }
                    
                     public U set(int index, U element) {
                     checkInit();
                     return delegate.set(index, element);
                     }
                    
                     public void add(int index, U element) {
                     checkInit();
                     delegate.add(index, element);
                     }
                    
                     public U remove(int index) {
                     checkInit();
                     return delegate.remove(index);
                     }
                    
                     public int indexOf(Object o) {
                     checkInit();
                     return delegate.indexOf(o);
                     }
                    
                     public int lastIndexOf(Object o) {
                     checkInit();
                     return delegate.lastIndexOf(o);
                     }
                    
                     public ListIterator<U> listIterator() {
                     checkInit();
                     return delegate.listIterator();
                     }
                    
                     public ListIterator<U> listIterator(int index) {
                     checkInit();
                     return delegate.listIterator(index);
                     }
                    
                     public List<U> subList(int fromIndex, int toIndex) {
                     checkInit();
                     return delegate.subList(fromIndex, toIndex);
                     }
                    
                    
                     public int size() {
                     checkInit();
                     return delegate.size();
                     }
                    
                     public boolean isEmpty() {
                     checkInit();
                     return delegate.isEmpty();
                     }
                    
                     public boolean contains(Object o) {
                     checkInit();
                     return delegate.contains(o);
                     }
                    
                     public Iterator<U> iterator() {
                     checkInit();
                     return delegate.iterator();
                     }
                    
                     public Object[] toArray() {
                     checkInit();
                     return delegate.toArray();
                     }
                    
                     public <V> V[] toArray(V[] a) {
                     checkInit();
                     return delegate.toArray(a);
                     }
                    
                     public boolean add(U o) {
                     checkInit();
                     return delegate.add(o);
                     }
                    
                     public boolean remove(Object o) {
                     checkInit();
                     return delegate.remove(o);
                     }
                    
                     public boolean containsAll(Collection<?> c) {
                     checkInit();
                     return delegate.containsAll(c);
                     }
                    
                     public boolean addAll(Collection<? extends U> c) {
                     checkInit();
                     return delegate.addAll(c);
                     }
                    
                     public boolean removeAll(Collection<?> c) {
                     checkInit();
                     return delegate.removeAll(c);
                     }
                    
                     public boolean retainAll(Collection<?> c) {
                     checkInit();
                     return delegate.retainAll(c);
                     }
                    
                     public void clear() {
                     checkInit();
                     delegate.clear();
                     }
                    
                     @Override
                     public String toString() {
                     checkInit();
                     return delegate.toString();
                     }
                    
                     @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"})
                     @Override
                     public boolean equals(Object obj) {
                     checkInit();
                     return delegate.equals(obj);
                     }
                    
                     @Override
                     public int hashCode() {
                     checkInit();
                     return delegate.hashCode();
                     }
                    }


                    What's your opinion on all this?

                    Thanks

                    • 7. Re: ejb 3 + lazy loading + detached entities
                      adamw

                      Hello,

                      well it looks very weird, but you maybe be right that if CollectionProxy doesn't implement Serializable, and ListProxy does, then the field may be null after sending it. Especially if it works after moving everything to ListProxy.

                      Anyway, as CP and LP are Envers classes, it's probably a bug in Envers. Then a JIRA would be appropriate :)

                      Adam

                      • 8. Re: ejb 3 + lazy loading + detached entities
                        svetok

                        hi Adam!

                        You were right. I've fixed the problem in more appropriate way by moving "implements Serializable" to CollectionProxy.

                        I've made a patch and posted it on JIRA - http://opensource.atlassian.com/projects/hibernate/browse/HHH-4488 , though I've never done patches before :) and it can be not correct in some ways.

                        Thanks a lot for your project, it's really helpful.

                        Svetlana

                        • 9. Re: ejb 3 + lazy loading + detached entities
                          adamw

                          Hello,

                          very good patch, although without a test case ;)

                          Adam