Hospedagem Profissional

Hospedagem Profissional
Clique aqui e ganhe US$ 10,00 para testar durante 1 mês a melhor hospedagem: Digital Ocean!

sábado, 11 de julho de 2009

Dicas e truques para HIBERNATE JPA

Seguinte galera, as dicas retiradas do site http://shrubbery.mynetgear.net a seguir são muito importantes, e pode evitar muita dor de cabeça na hora de usar o hibernate para implementar a especificação jpa em seu projeto.

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.





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;
}
...
}




Typical Mappings




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 @Columnannotations 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;
}
}



Collection of Primitives


Hibernate can map collections of primitives (Strings, Dates, even enums) to Java collections directly. This can be very convenient when an entity would be a very 'heavy' solution. This requires:




  1. A table to store the collection elements, with a foriegn key back to the parent.


  2. @JoinTable to map the Java collection to the appropriate rows in the table.


  3. Hibernate's proprietary @CollectionOfElements annotation.



For example, here is a User object with a sorted set of roles (enum):



@Entity
@Table(name = "user")
public class User {
...

private SortedSet<Role> roles = new TreeSet<Role>();

...

/**
* The roles for this user.
*
* @return the roles for the user
*/
@CollectionOfElements(fetch = FetchType.LAZY)
@Enumerated(EnumType.STRING)
@Sort(type = SortType.NATURAL)
@JoinTable(name = "userroles",
joinColumns = {@JoinColumn(name = "userid")})
@Column(name = "role", nullable = false)
public SortedSet<Role> getRoles()
{
return roles;
}

public void setRoles(SortedSet<Role> roles)
{
this.roles = roles;
}

...
}



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;
}
...
}



Splitting an Entity using Embedded


When an entity has many properties, Java programmers usually want a fine grained domain model while the DBA may want a more coarse grained database schema. For this situation, the groups of related columns can be bundled using an embedded mapping.



@Entity
@Table(name = "user")
public class User {
private Long id;
private UserSettings settings;
private ContactInfo info;

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

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

@Embedded
public UserSettings getSettings()
{
return settings;
}

public void setSettings(UserSettings settings)
{
this.settings = settings;
}

@Embedded
public ContactInfo getContactInfo()
{
return contactInfo;
}

public void setContactInfo(ContactInfo info)
{
this.contactInfo = info;
}
}


The embeddable objects:



@Embeddable
public class UserSettings implements Serializable
{
private boolean hasCheezburger;

@Column(name = "cheezburger", nullable = false)
public boolean isHasCheezburger()
{
return hasCheezburger;
}

public void setHasCheezburger(boolean cheebaga)
{
this.hasCheezburger = cheebaga;
}
}

@Embeddable
public class ContactInfo implements Serializable
{
private String jabberId;

@Column(name = "jabber", nullable = false)
public String getJabberId()
{
return jabberId;
}

public void setJabberId(String jabberId)
{
this.jabberId = jabberId;
}
}


In this case the "user" table has "id", "cheezburger" and "jabber" columns, but the columns are mapped to three different Java objects.





Null values


NOTE: When all of the values in an @Embedded object are null, Hibernate will set the field in the parent object to null. This can lead to NullPointerExceptions if not handled correctly. There are two ways to handle this:




  1. Make sure code that calls the @Embedded getter handles null return values.


  2. Add a non-nullable field to the embedded object, thus avoiding the behavior entirely.




Splitting an Entity using OneToOne


Sometimes a more fine grained model for both the db schema and the Java object model is more appropriate. In this case the two tables have the same primary key.



@Entity
@Table(name = "user")
public class User {
private Long id;
private UserSettings settings;
private ContactInfo info;

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

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

@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
public UserSettings getSettings()
{
return settings;
}

public void setSettings(UserSettings settings)
{
this.settings = settings;
}

public UserSettings addSettings()
{
UserSettings s = getSettings();
if (s == null)
{
s = new UserSettings();
s.setId(id);
setSettings(s);
}
return s;
}

}


The one-to-one object:



@Entity
@Table("user_settings")
public class UserSettings implements Serializable
{
private Long id;
private boolean hasCheezburger;

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

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

@Column(name = "cheezburger", nullable = false)
public boolean isHasCheezburger()
{
return hasCheezburger;
}

public void setHasCheezburger(boolean cheebaga)
{
this.hasCheezburger = cheebaga;
}
}



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.




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.




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




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.