Reputation: 865
JBoss EAP 6
Hibernate 4
I have a J2EE application with a web browser client. ( Apache click ) Both the internal business logic and the client use the same entity objects.
I would like to have all relations in the entities set to lazy loading. This way I have good performance.
But when using the entities in the client ( that is the server side code of apache click ) I would need a lot of the relations to be eager loaded. The client code is accessing the back-end through a session bean.
So I have a couple of ways I can solve this:
Create 2 of each JPA entities, one with eager loading and one with lazy loading. And then use the one with eager loading in the client, and the one with lazy loading in the server. Most of the server logic will be in a transaction, so lazy loading is fine here.
Make all relations lazy loading. When accessing the entities from the client, make sure there is a transaction. ( @TransactionAttribute(TransactionAttributeType.REQUIRED) ) and code the access to the necessary fields so they are accessible after session bean call. But that means that I have to start a transaction when that is not required, i.e. if I am only getting some objects. And I have to maintain more code. And I have to know exactly what relations the client needs.
Create an inheritance hierarchy, where I have a super entity, and then 2 child, one with objects relations lazy loaded, and one with only values, no objects. i.e. :
Super
@MappedSuperclass
public class SuperOrder {
@Id
@Column(name = "id")
@GeneratedValue(.....)
private Long id;
@Column(name = "invoice", length = 100)
private String invoice;
Child 1
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "testorder")
@SequenceGenerator(....)
public class Order extends SuperOrder {
@ManyToOne(targetEntity = PrintCustomerEnt.class, fetch = FetchType.EAGER, optional = true)
@JoinColumn(name = "print_customer_id", nullable = true)
@ForeignKey(name = "fk_print_customer")
@Valid
private PrintCustomerEnt printCustomer;
public PrintCustomerEnt getPrintCustomer() {
return printCustomer;
}
public void setPrintCustomer(final PrintCustomerEnt printCustomer) {
this.printCustomer = printCustomer;
}
}
Child 2
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "testorder")
@SequenceGenerator(...)
public class LazyOrder extends SuperOrder {
@Transient
private String printCustomerName;
@Column(name = "print_customer_id", nullable = true)
private Long printCustomerId;
What is the best practice... or is there something other good way to do this.
Basically the problem is I want to use the same entities in different scenarios. Sometimes I need eager loading, and sometimes I need lazy loading.
Upvotes: 4
Views: 5737
Reputation: 211
I've been programing in JPA 2 for some a while now, and I can say there are couple of now written rules that I almost always apply:
This rules apply on 99% of my projects. I think these are best practices due to my personal experience and some research I've been doing.
Note: I must say I do not use JOIN FETCH on Lazy Inicialization, instead I write a Prefetch Method. Example:
@Entity
class Entity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
private Integer id;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child1> collection1;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "mappedName",
orphanRemoval = true)
private List<Child2> collection2; }
And then we have the Controller:
class EntityController{
public Entity findCompraFolioFull(Integer id) {
EntityManager em = getEntityManager();
try {
Entity entity = em.find(Entity.class, id);
//Initialize Collections inside Transaccion, this prevents
//LazyInizialization No Proxy Exception later in code when calling
//hollow collections
cp.getCollection().size();
cp.getCollection().size();
return cp;
} finally {
em.close();
}
}
}
I don't recomend FETCH JOIN
public Entity findEntityByJoinFetch(Integer id) {
EntityManager em = getEntityManager();
try {
TypedQuery<Entity> tq = em.createQuery(
"SELECT e FROM Entity e\n"
+ "LEFT JOIN FETCH e.collection1\n"
+ "LEFT JOIN FETCH e.collection2\n"
+ "WHERE e.id = :id", Entity.class);
tq.setParameter("id", id);
return tq.getSingleResult();
} finally {
em.close();
}
}
Reasons I don't recomend Fetch Join Appoach:
If your collections are java.util.List type then this getSingleResult() will fail in hibernate due to lack of capacity to fetch MultipleBags without indexing notations on your OneToMany Relation.
You can always change the type of your collections to java.util.set in order to multiple bags to be fetched but this brings new kind of situations to deal with, Sets aren't ordered and HashCode() method won't work correctly so you'll have to @Override it inside Children Classes, and if you are using JAVAFX TableView to bind model to Items you won't be able to bind collections Set Type to Item Property of TableView, not directly at least.
Upvotes: 2
Reputation: 385
I suggest that you create just one JPA entity with lazy relationships, and when you need to load eagerly some of them create a Service that uses JPQL(HQL) to do some FETCH trick. The idea is one JPA entity and many services.
Upvotes: 1