I access a EJB dao in an ejb container from a backing sessionscoped cdi bean. The dao execute a JQL query with join fetch and retrieve a entity with a @OneToMany reference. The @OneToMany collections are filled and I can use them inside EJB, but in the CDI backing bean, the collections are empty and cleared. My entities look like this:
@Entity
@NamedQuery(name = "order.with.items",
query = "select o from Order o inner join fetch o.item i where o.id=:orderNo")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Version
@Column(name = "version")
private int version;
@Column
private String name;
@OneToMany(mappedBy = "order")
private Set<Item> item = new HashSet<>();;
... getters setters
}
and the referenced item's:
@Entity
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Version
@Column(name = "version")
private int version;
@Column
private String name;
@ManyToOne
@JoinColumn(name = "order_id", referencedColumnName = "id")
private Order order;
//... getters setters
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()) // UPDATE: don't do it
return false; // with getClass - use instanceof
Item other = (Item) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (order == null) {
if (other.order != null)
return false;
} else if (!order.equals(other.order))
return false;
return true;
}
}
and the dao:
@Stateless
@LocalBean
public class OrderDao {
@PersistenceContext(unitName = "jpa-persistence-unit")
protected EntityManager entityManager;
public Order getOrderWhithItems(Long orderId) {
Order order = entityManager.createNamedQuery("order.with.items",Order.class).setParameter("orderId", orderId).getSingleResult();
// Here is the size greater than zero
System.out.println("# of items: " + order.getItem().size());
return order;
}
}
and the backing bean:
@Named
@SessionScoped
public class BackingBean {
@EJB
private OrderDao orderDao;
public BackingBean() {
Order order = orderDao.getOrderWhithItems( 4L);
Set<Item> items = order.getItem();
// This will ouputs 0
System.out.println("# of items " + items.size());
}
}
The problem is, that the order
contain all items in the method of the dao, but when the backing bean receive the order, the items are ripped and the set is empty. I wrote also an arquillian junit test to test the dao in it and it work perfectly and the order contain the items in the test. But not in the CDI session bean. When I extract the set in the dao like a DTO (data transfer object) I can receive the items in the backing bean.
The dao bean is in an ejb jar, in an ear enterprise archive. The CDI backing bean is in a war archive in the same ear. I simplified our problem case to the order item example. I could not find any resources of this stupid behavior. I use a wildfly 13 appserver and use the hibernate jpa orm.
The cause of that problem is a overwritten equals method. Hibernate us a big caching and proxiing mechanism and every Entity Class is handled by a proxy. The problem are the following test in the equals method:
if (getClass() != obj.getClass()) // This wouldn't work in JPA!!!
return false;
In the case of an entity these lines always return false, because obj
has the type of the proxy class and not of the entity. The obj
is stored in a set and the set is accessed by the proxy, while caching and so on.
Use never getClass
in a equals method of an entity, use always the instanceof
operator. In my case, this wrong implementation causes unpredictable behaviour while the instance of the entity travels from the dao to the backing bean.
The equals method should looks like:
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Item)) {
return false;
}
Item other = (Item) obj;
if (id != null) {
if (!id.equals(other.id)) {
return false;
}
}
return true;
}