miércoles, enero 05, 2011

Deep Inside, You Cry Cry Cry, Don’t Let Your Hopes, Die Die Die

JPA2 is present in major ORM vendors (EclipseLink, Hibernate, OpenJPA). Main features are present like multiple levels of embedded objects, ordered lists, support for validation, new criteria query API.

Now it time to talk (with an example) about new criteria query API. For this example I will use Hibernate and its Hibernate Metamodel Generator.

New Criteria API allows criteria queries to be constructed in a strongly-typed manner, using metamodel objects to provide type safety. As is mentioned in Hibernate site: "For developers it is important that the task of the metamodel generation can be automated".

And that's is what Hibernate Metamodel Generator does. This metamodel generator is an annotation processor that is run each time an annotated @Entity bean is compiled, and generates its metamodel class.

Let's see an example:

A class like:

@Entity
public class Pizza {

@Id
@GeneratedValue
private Integer id;
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}


@Column
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
@OneToMany(cascade= CascadeType.PERSIST, mappedBy="pizza")
private Set ingredients = new HashSet();
public Set getIngredients() {return ingredients;}
public void setIngredients(Set ingredients) {this.ingredients = ingredients;}

//Constructor, Transient Methods, ...
}

generated metamodel will look:

@StaticMetamodel(Pizza.class)
public abstract class Pizza_ {

public static volatile SetAttribute ingredients;
public static volatile SingularAttribute id;
public static volatile SingularAttribute name;


Easy to understand, each persistent attribute has its type and which entity belongs. For example field "name" belongs to Pizza and is a "String", and thats so important because precisely that will allow Criteria API to be typesafely.

Ok, now we know that new Criteria API is typesafely and this is acquired with metamodel programming. How about starting from the start?

First of all is configuring Eclipse for compiling using Annotation Processor (in Hibernate site is explained for using in other IDEs or Maven).


Only configure annotations processor output directory and which annotation processor is required, in our case hibernate-jpamodelgen and hibernate-jpa-2.0.

Next step is create our entity class:

@Entity
public class Pizza {

@Id
@GeneratedValue
private Integer id;

public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}

@Column
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
@OneToMany(cascade= CascadeType.PERSIST, mappedBy="pizza")
private Set ingredients = new HashSet();
public Set getIngredients() {return ingredients;}
public void setIngredients(Set ingredients) {this.ingredients = ingredients;}

public Pizza(String name) {
super();
this.name = name;
}

protected Pizza() {
}
}

Nothing different here. Same is applied for Ingredient entity (not showed here).

But wait go to target/metamodel, refresh the directory and what you see there? The metamodel classes. Ok if you have arrived successful here, then no problem you have done the complicated thing. Now let's write our first new Criteria query.



CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery pizzaQuery = criteriaBuilder.createQuery(Pizza.class);
SetJoin ingredientsNode = pizzaQuery.from(Pizza.class).join(Pizza_.ingredients);
pizzaQuery.distinct(true).where(criteriaBuilder.equal(ingredientsNode.get(Ingredient_.name), ingredient));
TypedQuery typedQuery = this.entityManager.createQuery(pizzaQuery);
return typedQuery.getResultList();


This method returns all pizzas that are produced with given ingredient. If you analyze line per line, first of all is created the query about pizza, then is created the join relationship ingredientsNode and "woop" metamodel class starts the game, we are making a join with Pizza_.ingredients, this is the representation of ingredients class. Also we are telling to filter ingredients where the name is the given one criteriaBuilder.equal(ingredientsNode.get(Ingredient_.name), ingredient), and you see that Ingredient_.name is used. This information is crucial because we are telling to Criteria API that the field is the one defined as Ingredient.name. If name attribute is deleted, metamodel class is modified too and our code does not compile.

Take a look the same criteria query but in Hibernate Criteria:


Criteria criteria = session.createCriteria(Pizza.class);
criteria.createAlias("ingredients", "i")
.add(Restrictions.eqProperty("id", "i.pizza"))
.add(Restrictions.eq("i.name", ingredient));
return (List)criteria.list();

For me it is easy to understand first one than second one, it is like more natural, and does not required any cast.

In Hibernate site more examples of new Criteria API can be found. I encourage you to start using JPA2, not only for new Criteria API but for using all new features.

0 comentarios: