Understanding the criteria API

The Java Persistence Criteria API allows the developer to create dynamic JPQL queries in the Java programming language. In JPA 2.1, it is now possible to execute bulk criteria statements. The Criteria query API defines an abstract expression tree that is generally optimal and compatible with the JPA provider to turn it into a database query or execution update statement: data manipulation language. The abstract expression tree is designed to be similar to the JPQL in terms of semantics and operation. We can think of the Criteria API as a facility to build a meta-model of statements that consist of entities, mapped super classes, and embedded classes inside a persistence unit.

Criteria queries

Criteria queries rely on an annotation processor to generate the metadata for persistence-capable objects. It is possible to build the meta-model manually for relatively simple classes.

The javax.persistence.criteria package contains the main elements of the Criteria API including CriteriaBuilder, CriteriaQuery, Expression, From, Root, and Join.

The package javax.persistence.metamodel contains interfaces and one annotation that defines a meta-model for a persistence capable object. You will find interfaces such as Attribute, BasicType, and EntityType, and there is one annotation, StaticMetamodel. Most of the time you will use the JPA provider's annotation processor to build these meta-models. It is rare to write meta-models by hand.

Let's revisit another version of the Employee entity class. Here is the definition for it:

@Entity
public class Employee {
    @Id @Column(name = "EMP_ID") private int id;
    @Column(name = "FIRST_NAME") private String firstName;
    @Column(name = "LAST_NAME")  private String lastName;
    privateBigDecimal salary;
    @Column(name = "DAILY_RATE")
    privateBigDecimaldailyRate;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "TAX_CODE_ID")
    privateTaxCodetaxCode;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "REGION_ID")
    private Region region;
    // ... omitted
}

We added the following fields: the daily pay rate for contractors, and the annual salary for an employee, director, or other staff member.

The TaxCode and Region entities are very straightforward entities with just a primary key column with a nomination: TAX_CODE_ID or REGION_ID with NAME. A tax code has many employees and zero or more employees share a region too.

If we wanted to find all of the employees who earned greater than or equal to a certain high salary, we could write the following JPQL:

SELECT e FROM Employee e WHERE e.salary>= 50000

On the other hand we can write a criteria API statement to build the query dynamically in Java. Here is a code snippet of a test that highlights the task:

@RunWith(Arquillian.class)
public class EmployeeCriteriaQueryTest {
    /* ... omitted imports ... */
    @PersistenceContext em;

    @Test
    public void shouldExecuteCriteriaQuery() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Employee> c = 
            builder.createQuery(Employee.class);
        Root<Employee> p = c.from(Employee.class);
        Predicate condition = builder.ge(
            p.get(Employee_.salary), new BigDecimal("50000"));
        c.where(condition);
        TypedQuery<Employee> q = em.createQuery(c);
        List<Employee> result = q.getResultList();
        assertEquals(NUM_DIRECTORS, result.size());
    }
}

First, we retrieve a CriteriaBuilder from the entity manger. Next, we build a parameterized CriteriaQuery instance, which is the entry point in the meta-model of the query. Every criteria query requires a root object instance that represents the result set class, which is the meaning of from() call on the builder. At this juncture, we have semantically built the equivalent of the JPQL statement SELECT e FROM Employee e.

We then create a Predicate object to serve as a condition to the CriteriaQuery instance. The predicate is built from javax.persistence.Path references to either a single attribute or another compound path. These Path references are associated with a literal value, the BigDecimal, or combined together in either a unary or binary expression operation. See the CriteriaBuilder.ge() method, which is the greater than or equal to operator. Remember, we build the expression equivalent in Java.

It certainly helps to see the manually built static meta-model class Employee_. Yes, that is the class name with the appended underscore character.

@StaticMetamodel(Employee.class)
public class Employee_ {
    static SingularAttribute<Employee, Integer> employeeId;
    static SingularAttribute<Employee, String>  firstName;
    static SingularAttribute<Employee, String>  lastName;
    static SingularAttribute<Employee, BigDecimal> salary;
    static SingularAttribute<Employee, BigDecimal> dailyRate;
    static SingularAttribute<Employee, TaxCode> taxCode;
    static SingularAttribute<Employee, Region>  region;
}

We annotate the Employee_ entity with @StaticMetamodel in order to define the static meta-model for the corresponding Employee entity. We do this for the benefit of Criteria API queries. The doubly parameterized type SingularAttribute represents a single value field or properties of the entity, mapped super-class, or embedded class javax.persistence.metamodel package. The attribute is a Path element.

There are other classes (CollectionAttribute, ListAttribute, SetAttribute, and MapAttribute and associated Java collection instances) that stand for one-to-many and many-to-many relationships on fields and properties. These attributes are types of PluralAttribute also in the same package. It is now clear how an annotation processor handles most of this boiler code for us with tens and perhaps hundreds of Java Persistence entities!

The biggest benefit of the Criteria API is that we can dynamically build our filters in Java programming depending on the user input. We can add additional predicates or remove them. We can combine them in additional ways. JPA thus allows the application developer to write dynamic search operations in the natural language of the user. These operators hide the technical detail of piecing together a set of functions.

This final piece of code depicts the combinatorial search for the optional the first and last names.

public void advancedSearch(
        String firstName, String lastName )
{
    CriteriaBuilder builder = em.getCriteriaBuilder();
    CriteriaQuery<Employee> c =
        builder.createQuery(Employee.class);
    Root<Employee> p = c.from(Employee.class);
    List<Predicate> predicates = new ArrayList<>();
    if ( firstName != null ) {
        predicates.add(
        builder.like(p.get(Employee_.firstName),
                     firstName));
    }
    if ( lastName != null ) {
        predicates.add(
            builder.like(p.get(Employee_.lastName),
                        lastName));
    }
    c.where(predicates.toArray(new Predicate[]{}));
    TypedQuery<Employee> q = em.createQuery(c);
    List<Employee> result = q.getResultList();
        // Do some real work here!
}

In the advancedSearch() method, we create a list collection of predicate elements. Depending on the count of method parameters, it accept 0, 1, or 2 predicates that are the equivalent of WHERE firstNameLIKE :f or WHERE lastNAME LIKE :n. We bind these sub filters together with the where() method on the CriteriaQuery method, which effectively performs the same as the AND operator.

We've covered JPA 2.0, so and now we shall look at the new bulk update and delete features in JPA 2.1.

CriteriaUpdate

JPA now supports bulk updates in the Criteria API. There is a new interface, CriteriaUpdate, that is obtainable from the entity manager. This interface has a number of overloaded set() methods, which accept the SingularAttribute or Path instance. In other words, CriteriaUpdate is almost the same as a CriteriaQuery, but with the ability to update the entity, mapped supper-classes, or embedded classes; and it does so through the static meta-model.

With our employee record examples, suppose we want to change all the tax codes for all staff that earn greater than or equal to 50,000 dollars per year; we can write this JPQL statement:

UPDATE Employee e 
  SET e.taxCode = TaxCode( 504, 'Director' ) 
  WHERE e.salary>= 50000

With the new CriteriaUpdate instance, we can write a unit test with a method that performs the equivalent operation. Here is the test code:

public class EmployeeCriteriaUpdateTest
extends AbstractEmployeeCriteriaTest {
    /* ... */

    @Test
    public void shouldExecuteCriteriaUpdate() throws Exception {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaUpdate<Employee> c =
            builder.createCriteriaUpdate(Employee.class);
        Root<Employee> p = c.from(Employee.class);
        Predicate condition = builder.ge(
            p.get(Employee_.salary), new BigDecimal("50000"));
        c.where(condition);
        TaxCode taxCode = new TaxCode(504, "Director");
        c.set(p.get(Employee_.taxCode), taxCode);
        utx.begin();
        Query query = em.createQuery(c);
        int rowsAffected = query.executeUpdate();
        assertTrue(rowsAffected> 0 );
        utx.commit();
    }
}

Let's examine the Arquillian test EmployeeCriteriaDeleteTest in detail. In the method shouldExecuteCriteriaUpdate(), we build the expression graph as before with Predicate and then configure the filter of CriteriaUpdate by calling the where() method with the Predicate instance. We then configure the meta-model with the update expressions by invoking the CriteriaUpdate set() method. There can be more than one update expression and therefore we can change multiple properties in the entity at a single time.

As with all JPA updates that change the persistence unit, we retrieve the server UserTransaction instance and join an existing transaction boundary or start one. We create a JPA query from the CriteriaUpdate instance and then execute it. The affected row's return value tells us if the JPA update was successful or not; at least one row should have been updated for this test.

CriteriaDelete

Similarly to update entities in bulk, there are times when an application wants to remove a collection of entities that fulfill a set of conditions. This is the purpose of the CriteriaDelete interface, which is a new interface in JPA 2.1.

Incidentally, both CriteriaUpdate and CriteriaDelegate interfaces are direct subclasses of a new JPA 2.1 interface, CommonAbstractCriteria. Incidentally, the CriteriaQuery and Subquery interfaces have a common parent interface AbstractQuery.

Now let's suppose we want to remove all staff from the employee records, who earn 50,000 dollars per year or more. We can write a JPQL statement as follows:

DELETE FROM Employee e 
   WHERE e.salary>= 50000 
   AND e.taxCode = TaxCode( 504, 'Director' )

The CriteriaDelete interface only allows the target entity to be configured with from() methods and defines a set of Predicate instances with overloaded where() methods.

The equivalent code in Java to remove the entities is illustrated in the Arquillian test, EmployeeCriteriaDeleteTest. Here is the code:

@RunWith(Arquillian.class)
public class EmployeeCriteriaDeleteTest
extends AbstractEmployeeCriteriaTest {
    /* ... */

    @Test
    public void shouldExecuteCriteriaDelete() 
    throws Exception {
        assertNotNull(em);
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaDelete<Employee> c =
        builder.createCriteriaDelete(Employee.class);
        Root<Employee> p = c.from(Employee.class);
        Predicate condition1 = builder.ge(
            p.get(Employee_.salary),
            new BigDecimal("50000"));
        Predicate condition2 = builder.equal(
            p.get(Employee_.taxCode),
            new TaxCode(504, "Director"));
        c.where(condition1, condition2);
        utx.begin();
        Query query = em.createQuery(c);
        int rowsAffected = query.executeUpdate();
        assertTrue(rowsAffected> 0);
        utx.commit();
    }
}

As usual we obtain a CriteriaBuilder instance from the entity manager, but this time we ask for a CriteriaDelete instance. We define the target entity by calling from(). We create two conditions and apply these Predicate instances to the CriteriaDelete instance. The where() method accepts a variable length number of arguments, which is the AND operation in JPQL. This completes building the abstract expression tree for the delete statement.

Since removal of entities effectively changes rows inside target database, we must begin a transaction boundary before we execute the query using the entity manager. Once again, we read the update count from the executeUpdate() call. There should be at least one affected row in the test.

The Criteria API is a very useful part of Java Persistence for building dynamic queries, and now it is possible write bulk updates and removals too.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.197.10