Hibernate version:
Hibernate 3.2CR1, Hibernate Annotations 3.1 beta 9, Hibernate Entity Manager 3.1 beta7
Name and version of the database you are using:
MySQL 5.0.20
Problem description
When you try to merge a non-modified entity the @PreUpdate method of the assigned entity listener is not called.
I would expect that the PreUpdate method is always called no matter if the entity was modified by the application code or not, since the PreUpdate method might alter the entity which then requires persisting the changes (e.g. auditing, setting lastModified fields, performing summary calculations based on other referenced items).
TestCase
Code:
package test;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Persistence;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import junit.framework.TestCase;
import org.hibernate.annotations.GenericGenerator;
public class EntityListenerTest extends TestCase
{
@Entity
@EntityListeners(OrderListener.class)
static class Order
{
@Basic
String customerName;
@OneToMany(mappedBy = "order", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
@org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
@org.hibernate.annotations.OnDelete(action = org.hibernate.annotations.OnDeleteAction.CASCADE)
Set<OrderItem> items = new HashSet<OrderItem>();
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
String id;
@Temporal
Date lastModified;
}
@Entity
static class OrderItem
{
@Basic
int amount;
@ManyToOne(optional = false, fetch = FetchType.LAZY)
Order order;
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
String id;
}
public static class OrderListener
{
static boolean onPreUpdateCalled = false;
@PreUpdate
public void onPreUpdate(Order theOrder)
{
theOrder.lastModified = new Date();
onPreUpdateCalled = true;
}
}
private final static Logger LOG = Logger.getLogger(EntityListenerTest.class.getName());
private final EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");
public Order createAnOrder()
{
// create and persist an order with one item
Order theOrder = new Order();
theOrder.customerName = "acme";
OrderItem anItem = new OrderItem();
anItem.order = theOrder;
anItem.amount = 1;
theOrder.items.add(anItem);
persist(theOrder);
return theOrder;
}
private void merge(Object e) throws RuntimeException
{
EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try
{
tx = em.getTransaction();
tx.begin();
em.merge(e);
tx.commit();
}
catch (RuntimeException ex)
{
if (tx != null && tx.isActive()) tx.rollback();
throw ex;
}
finally
{
em.close();
}
}
private void persist(Object e) throws RuntimeException
{
EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try
{
tx = em.getTransaction();
tx.begin();
em.persist(e);
tx.commit();
}
catch (RuntimeException ex)
{
if (tx != null && tx.isActive()) tx.rollback();
throw ex;
}
finally
{
em.close();
}
}
public void testMergeOrderUmodified()
{
Order theOrder = createAnOrder();
OrderListener.onPreUpdateCalled = false;
// merge the unmodified order
merge(theOrder);
if (OrderListener.onPreUpdateCalled == false)
{
LOG.warning("OrderListener.onPreUpdate was not called.");
fail();
}
else
{
LOG.info("OrderListener.onPreUpdate was called.");
}
}
public void testMergeOrderWithModifiedField()
{
Order theOrder = createAnOrder();
OrderListener.onPreUpdateCalled = false;
// modify the order and merge
theOrder.customerName = "john doe";
merge(theOrder);
assertTrue(OrderListener.onPreUpdateCalled);
}
public void testMergeOrderWithModifiedItem()
{
Order theOrder = createAnOrder();
OrderListener.onPreUpdateCalled = false;
// modify an item and merge the order
OrderItem theItem = theOrder.items.iterator().next();
theItem.amount = 10;
merge(theOrder);
if (OrderListener.onPreUpdateCalled == false)
{
LOG.warning("OrderListener.onPreUpdate was not called.");
fail();
}
else
{
LOG.info("OrderListener.onPreUpdate was called.");
}
}
}
Any feedback is appreciated.
Regards,
Sebastian