Hibernate JPA Tips

From Shrubbery

Jump to: navigation, search


Tips for using the Hibernate JPA implementation

Contents

[edit] Fetch strategies and types

In native Hibernate, many-to-one association are lazy by default. This means that selecting a list of objects with an HQL query will not initialize the objects at the other end of the many-to-one association. In JPA the default fetch type is eager (i.e. non-lazy), with separate selects. This means that any JPAQL query for an entity that has many-to-one associations in it will result in an N+1 SELECTS problem even if those associations are not used. To eliminate the unnecessary queries, simply set the fetch type to LAZY using:

@ManyToOne(fetch=FetchType.LAZY)

Then, in any JPA query the association can be eagerly fetched using left join fetch.

[edit] Optimistic Locking

When optimistic locking is enabled for the parent and child entities in a typical parent-child relationship the parent will be optimistically locked whenever a child is added or removed from the parent's collection of children. That is, when a child is added the @Version attribute of the parent is incremented. In a highly concurrent system this may lead to some unnecessary contention and therefore lots of optimistic locking exceptions.

To disable the propagation of optimistic locking from child to parent, use:

@OptimisticLock(excluded = true)

Note that this can control the optimistic lock propagation on either association: parent->child or child->parent. When using this annotation it is important to think about what the behavior should be for your application.

Here is an example of disabling optimistic lock propagation in both directions:

public class Parent
{
    ...
    private Set<Child> children = new HashSet<Child>();

    @OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
    @OptimisticLock(excluded = true)
    public Set<Child> getChildren()
    {
        return children;
    }
    ...
}

public class Child
{
    ...

    @ManyToOne
    @OptimisticLock(excluded = true)
    public Parent getParent()
    {
        return parent;
    }
    ...
}


[edit] Typical Mappings

[edit] Basic Entity with a Surrogate Key

Most entities will have a Surrogate Key, and some other attributes. Basic entities have the @Entity and @Table annotations on the class and the @Id, @GeneratedValue, and @Column annotations on the ID property.

@Entity
@Table(name = "thing")
public class Thing {
    private Long id;

    @Id
    @GeneratedValue
    @Column(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

[edit] Parent->child using a Set

To make a bidirectional relationship where the 'many' side is fully dependent on the 'one' side, use:

  • @OneToMany in the parent, with the mappedBy attribute set to the name of the property in the child class that refers back to the parent.
  • Use cascade = {CascadeType.ALL} on the @OneToMany to automatically insert children if they are added to the parent's set of children (no need to call entityManager.persist() on the children).
  • Use @ManyToOne on the child's backpointer.
  • You can name the foriegn key column and the foreign key constraint in the child class using @JoinColumn and @ForeignKey respectively. NOTE: Make sure your foriegn key constraint name is unique in the schema. Most databases won't allow you to have two constraints with the same name (duh).
  • DON'T FORGET! The Child class needs to define hashCode() and equals() in a way that does not use the surrogate key!
public class Parent
{
    ...
    private Set<Child> children = new HashSet<Child>();

    @OneToMany(mappedBy = "parent", cascade = {CascadeType.ALL})
    public Set<Child> getChildren()
    {
        return children;
    }
    ...
}

public class Child
{
    ...

    @ManyToOne
    public Parent getParent()
    {
        return parent;
    }
    ...
}

[edit] Optimizations

  • Don't optimize simple code that is "good enough".
  • Measure performance before optimizing - Use a profiler. If something doesn't show up in the profiler, leave it alone.

[edit] N+1 SELECTs

Usually this happens when the UI displays a table of entities, where the entities have lazy many-to-one associations that are navigated in Java code. By default, there will be one select for the main query, and one select for each entity returned for each many-to-one association. If there are many entities, this will bombard the database with queries.

  1. Use left join fetch - Fetch the entities up front in the main query.
  2. Use batch size - Set an appropriate batch size for collections and entities. When one entity in an association is lazily loaded, Hibernate will pre-fetch other instances of that same entity that have proxies in the Persistence Context (Session).
  3. Use side-effect queries - Execute JPAQL queries to initialize the entities that will be referenced, discarding the results.

[edit] Over fetching

In a typical, 'real world' application there will be lots of entities and lots of @ManyToOne associations. By default, JPA interprets @ManyToOne as an eagerly fetched relationship, so when any business logic fetches that entity, all the @ManyToOne entities are fetched as well. This may cause unexpected performance problems when updating entities rapidly or displaying data in a table.

Solutions:

  1. Use lazy loading

[edit] Partial updates

In some cases, the business logic needs to update only a few properties of an entity. The usual sequence results is something like this:

  1. Thing x = em.find(Thing.class,id); Find by id. Hibernate issues a SELECT<tt> for the entity (and any non-lazy many-to-one entities), and remembers the original values.
  2. <tt>x.setSomeProperty(value); Set the property.
  3. Transaction commits, triggering em.flush(). Hibernate compares new values to the old values. Issues an UPDATE for all persistent properties of x.

Most of the time the business logic will update lots of properties of many different entities, but when only one property is being updated there is some unnecessary overhead. The overhead can be almost completely eliminated by using batch update statements (even though we aren't updating a batch of entities). The previous example could be re-coded as:

  Query q = em.createQuery("update from Thing t set t.someProperty = :propVal where t.id = :id")
    .setProperty("propVal",value)
    .setProperty("id",id);
  q.executeUpdate();

This has the following advantages:

  • No SELECTs. No memory used to store the original values.
  • The UPDATE is for only one column.
  • No comparison with the original values.

However, it is important to remember the following:

  • If there is other code that works with the entity, even in the same PersistenceContext / Session, it will not see the change.
  • Check the return value of executeUpdate() to make sure the expected number of rows were updated.
Personal tools