-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 
Author Message
 Post subject: ClassBridge - set() called multiple times
PostPosted: Wed Aug 04, 2010 5:14 pm 
Newbie

Joined: Tue May 12, 2009 10:25 am
Posts: 3
Hello,

We implemented a @ClassBridge(impl=OrderIndexer.class) to add some additional custom fields to an indexed object, Order.class. There are no fields annotated with @Field, nor are there any @IndexEmbedded fields on Order.class. This class does have some @OneToMany related entity collections that are marked Cascade.ALL.

When we save an Order for the first time, we notice via a breakpoint in OrderIndexer.set (the implementation of FieldBridge.set) that we re-enter this method several times. The number of times the set() method is called corresponds to the number of fields annotated with @OneToMany when the collections contain modified entities. If the OneToMany collections contain no modified entities, the ClassBridge is only called once.

Why would the presence of @OneToMany collections affect how many times the ClassBridge is called?

Note, each time set(String name, Object value, Document doc, LuceneOptions options) is called, the value (Order.class), has the same hashcode, but the Document is a new one.

Hibernate Versions:
hibernate-core-3.5.4-Final.jar
hibernate-search-3.2.1.Final.jar

Example code, shortened to relevant sections:

Order.class:

Code:
@Entity
@Indexed
@ClassBridge(impl=OrderIndexer.class)
public class Order implements Serializable {
    @Id
    @GeneratedValue
    private Integer orderid;

    @OneToMany(mappedBy="order", fetch=FetchType.LAZY, cascade={CascadeType.ALL})
    @Cascade({org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
    private List<OrderLedger> orderLedgers = new ArrayList<OrderLedger>(0);
}


OrderIndexer.class:

Code:
public class OrderIndexer implements FieldBridge {
    public void set(String name, Object value, Document doc, LuceneOptions options) {
        // called several times...
    }
}



OrderLedger.class:


Code:
@Entity
public class OrderLedger implements Serializable {
    @Id
    @GeneratedValue
    private Integer itemid;

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="orderid")
    private Order order;
}


Top
 Profile  
 
 Post subject: Re: ClassBridge - set() called multiple times
PostPosted: Tue Aug 10, 2010 2:21 am 
Hibernate Team
Hibernate Team

Joined: Thu Apr 05, 2007 5:52 am
Posts: 1689
Location: Sweden
Hi,

can you also post the session code which you use to create/save the Order? Also do Order and OrderLedger contain equals and hashCode implementations and if yes how do they look like?

--Hardy


Top
 Profile  
 
 Post subject: Re: ClassBridge - set() called multiple times
PostPosted: Tue Aug 10, 2010 9:34 am 
Newbie

Joined: Tue May 12, 2009 10:25 am
Posts: 3
The Hibernate Session code where an Order is saved is as follows. The EntityManagerFactory (factory) is injected via dependency injection. Order is saved via merge() since it is an object graph built up over several screens in a web application:

Code:
EntityManager em = factory.createEntityManager();
try {
    EntityTransaction transaction = em.getTransaction();
    boolean ok = false;
    try {
        transaction.begin();
        for (OrderLedger ol : (List<OrderLedger>)order.getOrderLedgers()) {
            ol.setStatus(order.getStatus());
            ol.setCreatedate(order.getOrderdate());
   }
        order = em.merge(order);
        transaction.commit();
        ok = true;
    } finally {
        if (!ok && transaction.isActive()) {
            transaction.rollback();
        }
    }
} finally {
    em.close();
}


We began generating equals and hashcode using Eclipse's "Generate Hashcode and Equals" on the primary key (GeneratedValue).

OrderLedger equals and hashcode:
Code:
   @Override
   public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((itemid == null) ? 0 : itemid.hashCode());
      return result;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (!(obj instanceof OrderLedger))
         return false;
      OrderLedger other = (OrderLedger) obj;
      if (itemid == null) {
         if (other.itemid != null)
            return false;
      } else if (!itemid.equals(other.itemid))
         return false;
      return true;
   }


Order does not implement equals and hashcode currently. It used to be implemented as follows but we obviously ran into problems with it (ie., in a Set) when Order has not yet been persisted:

Code:
@Override
   public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((orderid == null) ? 0 : orderid.hashCode());
      return result;
   }

   @Override
   public boolean equals(Object obj) {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (!(obj instanceof Order))
         return false;
      Order other = (Order) obj;
      if (orderid == null) {
         if (other.orderid != null)
            return false;
      } else if (!orderid.equals(other.orderid))
         return false;
      return true;
   }


Top
 Profile  
 
 Post subject: Re: ClassBridge - set() called multiple times
PostPosted: Wed Aug 11, 2010 6:44 am 
Hibernate Team
Hibernate Team

Joined: Thu Apr 05, 2007 5:52 am
Posts: 1689
Location: Sweden
Hi,

I think your problem lies in the fact of a missing equals/hashCode and the way you are using merge(). Are you not able to create some sort of business/suggorate key for Order? Also since you create a new entity manager every time you are mixing entities in different states (persistent vs detached). In the case of "several screens" I recommend to use the conversation (extended persistent context) pattern. In this case the same persistent context gets used over multiple screens avoiding some of the issues to the session per request pattern. How to create an extended persistent context depends on your framework stack.

--Hardy


Top
 Profile  
 
 Post subject: Re: ClassBridge - set() called multiple times
PostPosted: Wed Aug 11, 2010 2:26 pm 
Newbie

Joined: Tue May 12, 2009 10:25 am
Posts: 3
Thank you for your recommendations. We may try to refactor using the extended persistence context pattern but we have to think about it. We are not using an EJB container and prefer to stick to the JPA interface using EntityManager rather than HibernateSession.

To further test this issue, we implemented hashcode and equals as a surrogate key using some non-database ID fields such as Date createdate and String paymenttype. Further, we tried using EntityManager.persist to simplify the test case and when we put a breakpoint in the ClassBridge's set(String name, Object value, Document doc, LuceneOptions options) method we still see the thread of execution calling this several times on one persist().

For now, we will try to develop a simple standalone test case, test reproducibility and if so, re-post here.

Thanks again,
Stephen


Top
 Profile  
 
 Post subject: Re: ClassBridge - set() called multiple times
PostPosted: Thu Aug 12, 2010 3:12 am 
Hibernate Team
Hibernate Team

Joined: Thu Apr 05, 2007 5:52 am
Posts: 1689
Location: Sweden
Quote:
Thank you for your recommendations. We may try to refactor using the extended persistence context pattern but we have to think about it. We are not using an EJB container and prefer to stick to the JPA interface using EntityManager rather than HibernateSession.

There is no either or here. You don't have to switch to using the Hibernate Session directly to use an extended context. The same is possible with JPA and EntityManager. The key point is just to reuse the same EntityManager.

Quote:
To further test this issue, we implemented hashcode and equals as a surrogate key using some non-database ID fields such as Date createdate and String paymenttype. Further, we tried using EntityManager.persist to simplify the test case and when we put a breakpoint in the ClassBridge's set(String name, Object value, Document doc, LuceneOptions options) method we still see the thread of execution calling this several times on one persist().

For now, we will try to develop a simple standalone test case, test reproducibility and if so, re-post here.

That would be the best. If you can create a standalone test we can have a closer look at it. Instead of posting here you can also create a Jira issue here.

--Hardy


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.