Quantcast
Channel: Java Team at Kentor » Frameworks
Viewing all articles
Browse latest Browse all 26

Hibernate and JPA guidelines [part 3]

$
0
0

This is the last post of three with Hibernate/JPA guidelines. This post consists more of some tips and trix rather then general guidelines. As I wrote in previous posts each topic is divided into three parts; problem, suggestion and value. All topics evolves from a identified problem and then makes a suggestion for dealing with the problem and a value for what benefits you get from using the suggestion.

7. Solving LAZY FetchType for OneToOne relations

Problem: OneToOne relations in Hibernate can be tricky when using fetch type lazy. If you try to make the not owning side lazy, i.e. the side that specifies mappedBy, hibernate will actually interpret it as eager and always do an extra query to populate it. You can find a describing example of the problem here; http://justonjava.blogspot.se/2010/09/lazy-one-to-one-and-one-to-many.html. Note though that sometimes when it comes to OneToOne relations you always want to populate the relation. Then consider making an exception from the general suggestion of always using lazy relations and make it eager.

Suggestion: A workaround for this is to make the relation a OneToMany relation instead of a OneToOne even if the collection always contains only one or zero items. You can then make a transient helper function to retrieve and set only that item. Here is an example of how it may look like:

...
@OneToMany(mappedBy="order", fetch=FetchType.LAZY)
public List<OrderDetail> getOrderDetails() {
    return this.orderDetails;
}

public void setOrderDetails(List<OrderDetail> orderDetails) {
    this.orderDetails = orderDetails;
}

@Transient
public OrderDetail getOrderDetail() {
    if (orderDetails != null && !orderDetails.isEmpty()) {
        return orderDetails.get(0);
    }
    return null;
}

@Transient
public void setOrderDetail(OrderDetail orderDetail) {
    if (orderDetails == null)
        orderDetails = new ArrayList<OrderDetail>();
    orderDetails.clear();
    orderDetails.add(orderDetail);
}
...

Value: Lazy OneToOne relations reduces the number of queries to the database.

8. Beware of OpenSessionInViewFilter

Problem: A very common filter to use in web applications, which uses Hibernate in the backend, is the OpenSessionInViewFilter. This filter makes sure that Hibernate keeps a Hibernate session open during a whole request cycle. This way Hibernate may automatically fetch lazy relations which haven´t been fetched earlier when you try to access them from some code. This might be good in some cases but when it comes to performance critical parts of an application, it is better to initialize the relations in earlier queries using join fetch. With OpenSessionInViewFilter enabled it is easy to miss these spots where a join fetch could improve performance.

Suggestion: During development consider turning off the OpenSessionInViewFilter from time to time when testing/developing performance sensitive parts of the application. This way a LazyInitializationException will be thrown if the code tries to access a lazy unfetched relation outside a transaction or open Hibernate session. Then you will get a clue where a join fetch should be added in a previous query.

Value: Spotting more places where a join fetch is appropriate reduces the number of queries to the database.

9. Use FlushMode.MANUAL and read-only flag when needed

Problem: When handling a lot of entities in batch operations it might be very CPU consuming. Often this is because Hibernate checks if an entity has changed every time it runs a new query. This is called dirty checks. If the operation is of the type where you never change the entities this is an unnecessary overhead. Actually, you may never want Hibernate to touch the object once you fetched them; you only want to read them. In some cases, like batch operations, the cost may be huge. Here is an example of a regular query which fetches entities batch-wise to traverse.

public void batchOperation() {
	int pageSize = 50;
	int offset = 0;
	List<Order> orderList = fetchOrders(offset, pageSize);
	while (orderList.size() < pageSize) {
		offset += 50;
		for (Order order : orderList) {
			System.out.println(order.getName());
		}
		orderList = fetchOrders(offset, pageSize);
	}
}

private List<Order> fetchOrders(int offset, int pageSize) {
	TypedQuery<Order> query = getEntityManager().createQuery(
			"SELECT o FROM Order o", Order.class).setFirstResult(offset).setMaxResults(pageSize);
	return query.getResultList();
}

It traverses Order entities, 50 at a time, and prints their names. Running this would result in heavy CPU consumption due to Hibernates dirty checks.

Suggestion: Use Hibernates flags FlushMode.MANUAL and read-only for the query that fetches entities you only want to read. Setting this instructs hibernate to not care about dirty checking the entities. FlushMode.MANUAL tells hibernate to never flush the objects unless the code explicitly tells you to. Read-only tells hibernate it should not keep snapshots of the objects in the session to perform dirty checks on. This way you save the overhead of Hibernates dirty checks and flushes. Below is a corrected version of the example above.

public void batchOperation() {
	int pageSize = 50;
	int offset = 0;
	List<Order> orderList = fetchOrders(offset, pageSize);
	while (orderList.size() < pageSize) {
		offset += 50;
		for (Order order : orderList) {
			System.out.println(order.getName());
		}
		orderList = fetchOrders(offset, pageSize);
	}
}

private List<Order> fetchOrders(int offset, int pageSize) {
	TypedQuery<Order> query = getEntityManager().createQuery(
			"SELECT o FROM Order o", Order.class).setFirstResult(offset).setMaxResults(pageSize);
	query.setReadOnly(true);
	query.setFlushMode(FlushMode.MANUAL);
	return query.getResultList();
}

Value: Disabling unnecessary dirty checks for entities you only read reduce CPU consumption. This is especially important in batch operations where you do a lot of queries.



Viewing all articles
Browse latest Browse all 26

Trending Articles