I have problems setting existing but detached entities into a ManyToOne relation, Hibernate throws a PersistentObjectException saying: ‘detached entity passed to persist: model.persons.Customer’.
My use case is as follows:
I open a JSF2 view which lets me create an order through a Named bean and a call to a stateless OrderDAO EJB. On that view i can select a customer from a drop down list. The drop down list is populated using another stateless CustomerDAO EJB which fetches a list of customers from the database. I select the customer and the customer is set to the order. When i save the order using the stateless OrderDAO EJB the above mentioned exception is thrown.
My entities looks as follows:
@Entity
public class Order extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -8061887078955032972L;
@ManyToOne(cascade={CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST}, optional = false)
private Customer customer = null;
...
}
@Entity
@DiscriminatorValue("customer")
public class Customer extends Person implements Serializable {
private static final long serialVersionUID = 2788107135840578322L;
@OneToMany(cascade=CascadeType.ALL, mappedBy="customer")
private List<Order> orders = null;
...
}
And the code involved looks as follows:
@Named
@ConversationScoped
public class OrderController implements Serializable {
private static final long serialVersionUID = -4868506512979135651L;
@EJB
private OrderEJB orderBean;
private Order order;
...
public Order getOrder() {
if (order == null) {
if (id == null) {
order = orderBean.create();
} else {
order = orderBean.findById(id);
}
}
return order;
}
public String saveOrder() {
order = orderBean.save(order);
return "savedOrder";
}
}
@Stateless
public class OrderEJB extends GenericDAO<Order> {
}
public class GenericDAO<T extends AbstractEntity> {
public T create() {
try {
return getClassType().newInstance();
} catch (Exception e) {
logger.log(Level.SEVERE, "Error creating new instance of "+getClassType(), e);
}
return null;
}
public T save(T entity) {
if (entity.getId() == null) {
saveNew(entity);
} else {
entity = update(entity);
}
return entity;
}
private void saveNew(T entity) {
em.persist(entity);
}
private T update(T entity) {
return em.merge(entity);
}
}
Is the fact that i use two different EJBs, one for the order and one for the customer a problem?
The problem is that you save (persist) a new entity with a reference to a detached entity with cascade enabled for persisting (via cascade all) on that relation.
This is an unsupported combination.
JPA wants the entire chain in this relation to be new objects. If one already has a persistent identity (attached or detached), it won’t work.
There are two solutions:
Remove the cascade on the relation from Customer to Order. Customer will still get a reference to the existing Order, but JPA will not attempt to persist the Order itself. You’ll loose the ability to persist new Customers with new Orders in one go, but if this never happens, then this could be a good solution.
The second solution is a bit counter intuitive, but does work; call em.merge() instead of em.persist() for the new Customer with existing Order.
Merge actually has “saveOrUpdate” semantics, and thus does a save without caring if the object is new or not.