in Criteria, Java, Java EE, JPA, JPQL

[JPA 2] Criteria API and MetaModel

I want to mention the Criteria API, a very cool API which in my opinion is not used as much as it should be. The developers who implement the specification (JSR 317: Java Persistence 2.0) do an impressive work. Thanks to them we have an API that makes it possible to write type safe queries in an object oriented way.
Usually Java developers write queries using JPQL or they write native queries. The fact is that the API Criteria has a (small) learning curve : you have to explore the API and study some basic queries examples before writing your own queries. The first time you use it, it does not seem as intuitive as JPQL.

The Criteria API is particularly convenient when writing queries which do not have a static where clause. For instance in the case of a web page where the user can make a research based on optional criterias. Then the generated query is not always the same.

The Criteria API has its advantages and its disadvantages : it produces typesafe queries that can be checked at compile time but on the other hand queries can be a bit hard to read (apparently unlike QueryDSL or EasyCriteria).
Another advantage is that it can help to avoid SQL injection since the user input is validated or escaped by the JDBC driver (which is not the case with native queries).

To create typesafe queries, one uses the canonical metamodel class associated to an entity (an idea originally proposed by Gavin King, as far as i know). A possible definition of a metamodel class could be that one : it is a class that provides meta information about a managed entity. By default, it has the same name as the entity plus an underscore. For instance if an entity is called Employee then the metamodel class is called Employee_. It is annotated with the annotation javax.persistence.StaticMetamodel.
Fortunately you do not have to write them, you can generate them using an annotation processor, through Eclipse or a Maven plugin for instance.
I chose to generate the metamodel classes with the help of the Hibernate Metamodel Generator (an annotation processor) and the maven-processor-plugin maven plugin, at each build so that they are updated whenever the entities are modified. It is a good way to keep the metamodel classes up to date.

<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.0.5</version>
<dependencies>
<!-- Annotation processor to generate the JPA 2.0 metamodel classes for typesafe criteria queries -->
  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jpamodelgen</artifactId>
    <version>1.2.0.Final</version>
  </dependency>
</dependencies>
<executions>
  <execution>
    <id>process</id>
    <goals>
      <goal>process</goal>
    </goals>
    <phase>generate-sources</phase>
    <configuration>
      <outputDirectory>${project.basedir}/src/main/.metamodel/</outputDirectory>
      <processors>
        <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
      </processors>
    </configuration>
  </execution>
</executions>
</plugin>

<!--  add sources (classes generated inside the .metamodel folder) to the build -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.7</version>
<executions>
  <execution>
    <id>add-source</id>
    <phase>generate-sources</phase>
    <goals>
      <goal>add-source</goal>
    </goals>
    <configuration>
      <sources>
        <source>${basedir}/src/main/.metamodel</source>
      </sources>
    </configuration>
  </execution>
</executions>
</plugin>

And here is the kind of dynamic query one can create :



import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.CriteriaBuilder.In;
...

/**
	 * 
	 * SQL query generated if all criterias are selected  :
	 * select
	 * place_a.*
	 * from place_a,
	 * place_b,
	 * place_c,
	 * place_d,
	 * place_e,
	 * place_f
	 * where place_c.id = place_b.id_place_c
	 * and place_d.id = place_b.id_place_d
	 * and place_d.id = place_a.id_place_d
	 * and place_e.street = place_f.street_name
	 * and place_a.id = place_f.id_place_a
	 * and place_c.id = 945874
	 * and place_a.kind in ('FAST', 'SLOW')
	 * and place_f.street_name in ('HUGO', 'BORIS', 'JOSE')
	 * and place_b.dateconstruction between to_date('1900-10-12', 'YYYY-MM-DD') and to_date('2012-06-12', 'YYYY-MM-DD')
	 * and place_a.reason = 'RACE'
	 * order by place_a.id asc
	 * 
	 * @param criterias
	 *            the criterias
	 * @param reason
	 *            the reason
	 * @return a list of PlaceA
	 */
	@Override
	public List<PlaceA> findAllPlacesA(Map<String, Object> criterias, String reason) {

		// ******************************************************************
		// 0. Query creation
		// ******************************************************************
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<PlaceA> query = cb.createQuery(PlaceA.class);

		// Query roots : defines the entities on which the query is based
		// FROM clause
		Root<PlaceA> PlaceARoot = query.from(PlaceA.class);

		// Joins (inner joins by default)
		Join<PlaceA, PlaceD> placeDs = PlaceARoot.join(PlaceA_.PlaceD);
		Join<PlaceD, PlaceB> placeBs = PlaceDs.join(PlaceD_.PlaceB);
		Join<PlaceB, PlaceC> placeCs = PlaceBs.join(PlaceB_.placeC);
		Join<PlaceA, PlaceF> placeFs = PlaceARoot.join(PlaceF_.kinds);
		Join<PlaceF, PlaceE> placeEs = placeFs.join("street");


		// SELECT clause
		query.select(PlaceARoot);

		// ******************************************************************
		// 1. Get the criterias that the user typed in
		// ******************************************************************
		 ...

		// ******************************************************************
		// 2. Optional criterias
		// ******************************************************************
		List<Predicate> predicateList = new ArrayList<Predicate>();

		ParameterExpression<String> paramReason = cb.parameter(String.class, "theReason");
		Predicate reasonPredicate = cb.equal(PlaceARoot.get(PlaceA_.reason).get(Reason_.detail), paramReason);
		predicateList.add(reasonPredicate);

		Predicate constructionPeriodPredicate = cb.between(PlaceBs.<Date> get(PlaceB_.constructionDate),
				beginUserCriteria, endUserCriteria);
		predicateList.add(constructionPeriodPredicate);

		if ((null != userCriteriaPlaceC) && (!(userCriteriaPlaceC.isEmpty()))) {
			Integer userCriteriaPlaceCInt = Integer.valueOf(userCriteriaPlaceC);
			Predicate placeCPredicate = cb.equal(placeCs.<Integer> get("id"), userCriteriaPlaceCInt);
			predicateList.add(placeCPredicate);
		}

		if ((null != userCriteriaPlaceF) && (userCriteriaPlaceF.size() != 0)) {
			In<PlaceFenum> in = cb.in(PlaceFs.<PlaceFenum> get(PlaceA_.street_name));
			for (PlaceFenum conditionColumnValue : userCriteriaPlaceFListeEnum) {
				in.value(conditionColumnValue);
			}
			predicateList.add(in);
		}

		if ((null != userCriteriaKindList) && (userCriteriaKindList.size() != 0)) {
			In<KindPlaceAEnum> in = cb.in(PlaceARoot
					.<KindPlaceAEnum> get(PlaceA_.kind));
			for (KindPlaceAEnum conditionColumnValue : userCriteriaKindList) {
				in.value(conditionColumnValue);
			}
			predicateList.add(in);
		}

		Predicate[] predicates = new Predicate[predicateList.size()];
		predicateList.toArray(predicates);

		// WHERE clause
		query.where(predicates);
		query.orderBy(cb.asc(PlaceARoot.get(PlaceA_.id)));

		// For debug only
		TypedQuery<PlaceA> findAllPlaceAs = em.createQuery(query);
		// unwrap requires hibernate-core library
		log.debug("findAllPlaceAs : " + findAllPlaceAs.unwrap(org.hibernate.Query.class).getQueryString());

		findAllPlaceAsExporter.setParameter("theReason", reason);

		// ******************************************************************
		// 3. Query execution
		// ******************************************************************
		List<PlaceA> listPlaceAs = findAllPlaceAs.getResultList();
		return listPlaceAs;
	}

The list of predicates is built dynamically : a predicate is added to the list if the user selected the corresponding criteria.