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 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.
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.
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.
3.140.197.10