JPA criteria query: type safety and object orientation

Preamble

Since I started working, in addition to hibernate, which used to compare traffic, I have been using ORM to standardize JPA. In the past few days, I have studied the standard query of JPA, named: JPA criteria query. Compared with JPQL, its advantage is type safety , more object-oriented.

Using standard queries, developers can check whether the query is correct or not at compile time. It has only been heard in Hibernate before. The specifics are unknown and have not been used.

JPA metamodel concept and usage

In JPA, standard queries are based on the concept of a metamodel. A metamodel is defined for managed entities of a concrete persistence unit. These entities can be entity classes, embedded classes or mapped parent classes. Managed entities are provided The meta-information class is the meta-model class.

Static metamodel classes that describe the state of managed classes and the relationships between them can

  • 1. Generated from the annotation processor
  • 2. Generated from the program
  • 3. Access with EntityManager.

The following code, a simple entity class package com.demo.entities; next, the entity class Employee, assuming that the entity has basic properties such as id, name and age, and a OneToMany association with the class Address:

@Entity
@Table
public class Employee{  
	private int id;   
	private String name;
	private int age;
	@OneToMany
	private List<Address> addresses;
	// Other code…
}

 

The standard metamodel class name for the Employee class (defined in the com.demo.entities package) will be Employee_ annotated with javax.persistence.StaticMetamodel. The properties of the metamodel class are all static and public. Each property of Employee is mapped in the corresponding metamodel class using the following rules described in the JPA2 specification:

 

  • Non-collection types such as id, name, and age define the static attribute SingularAttribute<A, B> b, where b is an object of type B defined in class A.
  • For a collection type such as Addess, a static attribute ListAttribute<A, B> b is defined, where List object b is an object of type B defined in class A. Other collection types can be of type SetAttribute, MapAttribute or CollectionAttribute.

 The following is the metamodel class package com.demo.entities; generated by the annotation processor; under:

import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcesso")
@StaticMetamodel(Employee.class)
public class Employee_ {     
	public static volatile SingularAttribute<Employee, Integer> id;   
	public static volatile SingularAttribute<Employee, Integer> age;   
	public static volatile SingularAttribute<Employee, String> name;    
	public static volatile ListAttribute<Employee, Address> addresses;
}

 

As its name suggests, annotation processors process annotations to help generate source code. Annotation processing can be activated at compile time. Metamodel classes are created following the rules described in the JPA 2.0 specification for defining standard metamodel classes.

 

The biggest advantage of using metamodel classes is that they can access persistent properties of entities at compile time by virtue of their instantiation. This feature makes criteria queries more type-safe.

The metamodel API is closely related to the standard reflection API in Java. The main difference is that the compiler cannot verify its correctness using the standard reflection API. For example: the following code will pass the compilation test:

Class myClass = Class.forName("com.demo.Test");
Field myField = myClass.getField("myName");


Using the criteria query compiler assumes that the property myName is defined in com.demo.Test, once the class does not define the property myName, the compiler will throw a runtime exception.

The metamodel API forces the compiler to check that appropriate values ​​are assigned to persistent properties of entity classes. For example: consider the age property of the Employee class, which is an Integer variable. If the property is assigned a value of type String, the compiler will throw an error. The implementation is not required to support non-standard features. Programmer-written metamodel classes are often referred to as non-standard metamodel classes. When the EntityManagerFactory is created, the persistence provider initializes the properties of the metamodel class.

To better understand the criteria query, consider the Dept entity that has a collection of Employee instances. The code for the metamodel classes for Employee and Dept is as follows:

//All Necessary Imports
@StaticMetamodel(Dept.class)
public class Dept_ {    
	public static volatile SingularAttribute<Dept, Integer> id;   
	public static volatile ListAttribute<Dept, Employee> employeeCollection;    
	public static volatile SingularAttribute<Dept, String> name;
}
//All Necessary Imports
@StaticMetamodel(Employee.class)
public class Employee_ {     
	public static volatile SingularAttribute<Employee, Integer> id;    
	public static volatile SingularAttribute<Employee, Integer> age;    
	public static volatile SingularAttribute<Employee, String> name;    
	public static volatile SingularAttribute<Employee, Dept> deptId;
}


The code snippet below QueryRoot shows a criteria query to get all employees older than 24 years old:

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
criteriaQuery.where(condition);
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();


AbstractQuery is the parent class of the CriteriaQuery interface. It provides methods to get the query root. Corresponding SQL: SELECT * FROM employee WHERE age > 24

Build CriteriaQuery instance API description

CroteriaQuery

CriteriaQuery objects must function on Criteria queries on entity types or embedded types.
It is obtained by calling CriteriaBuilder, createQuery or CriteriaBuilder.createTupleQuery.
CriteriaBuilder is like a factory for CriteriaQuery.
The CriteriaBuilder factory class is obtained by calling EntityManager.getCriteriaBuilder or EntityManagerFactory.getCriteriaBuilder. 
The CriteriaQuery object for the Employee entity is created in the following way:

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);

The query root of a Criteria query defines the entity type that can be used to obtain the desired results for future navigation, and is similar to the FROM clause in an SQL query. 
Root instances are also typed and define the types that can appear in the FROM clause of a query. 
The query root instance can be obtained by passing an entity type to the AbstractQuery.from method. 
Criteria query, can have multiple query roots. 
The query root object for the Employee entity can be obtained using the following syntax: 

Root<Employee> employee = criteriaQuery.from(Employee.class);

Filter Queries

Filter conditions are applied to the FROM clause of the SQL statement. 
In a criterion query, the query condition is applied to the CriteriaQuery object through a Predicate or Expression instance. 
These conditions are applied to the CriteriaQuery object using the CriteriaQuery .where method. 
CriteriaBuilder is also a factory for Predicate instances. Predicate objects are created by calling CriteriaBuilder's conditional methods (equal, notEqual, gt, ge, lt, le, between, like, etc.). 
Predicate instances can also be obtained using the isNull, isNotNull and in methods of Expression instances, and compound Predicate statements can be constructed using the and, or andnot methods of CriteriaBuilder. 
The following code snippet shows the Predicate instance checking for employee instances whose age is greater than 24:

Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);
criteriaQuery.where(condition);


Through the age attribute of the Employee_ metamodel class, it is called a path expression. The compiler will throw an error if the age property is compared to a String literal, which is not possible in JPQL.

Execute queries and get metamodel instances

An executable query instance is created when the EntityManager.createQuery(CriteriaQuery) method is called, which returns a TypedQuery object specifying the actual type returned from the criteria query.

The TypedQuery interface is a subtype of javax.persistence.Queryinterface. In this snippet, the type information specified in TypedQuery is Employee, and when getResultList is called, the query will be executed 
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);
List<Employee> result = typedQuery.getResultList();
metamodel The instance is obtained by calling the EntityManager.getMetamodel method, and the metamodel instance of EntityType<Employee> is obtained by calling Metamodel.entity(Employee.class), which is passed into CriteriaQuery.from to obtain the query root.

Metamodel metamodel = em.getMetamodel();EntityType<Employee>
Employee_ = metamodel.entity(Employee.class);
Root<Employee> empRoot = criteriaQuery.from(Employee_);

It is also possible to call the Root.getModel method to obtain metamodel information. Instance Dept_ and name attributes of type EntityType<Dept> can be obtained by calling the getSingularAttribute method, which is compared with the String text:

CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
Root<Dept> dept = criteriaQuery.from(Dept.class);
EntityType<Dept> Dept_ = dept.getModel();
Predicate testCondition = criteriaBuilder.equal(dept.get(Dept_.getSingularAttribute("name", String.class)), "Ecomm");


The Expression object is used in the select, where and having clauses of the query statement. The interface has isNull, isNotNull and in methods. The following code snippet shows the usage of Expression.in. The employee's age is checked at 20 or 24. Expression

CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
criteriaQuery.where(employee.get(Employee_.age).in(20, 24));
em.createQuery(criteriaQuery).getResultList();

Corresponding SQL: SELECT * FROM employee WHERE age in (20, 24)

compound predicate

Criteria Query also allows developers to write compound predicates that check two conditions for the query below the multi-condition test. First, whether the name attribute starts with M, and second, whether the employee's age attribute is 25. The logical operator and executes to obtain the result record.

criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.like(employee.get(Employee_.name), "M%"), criteriaBuilder.equal(employee.get(Employee_.age), 25)));
em.createQuery(criteriaQuery).getResultList();

connection query

In SQL, joins span multiple tables to get query results. Similar entity joins are performed by calling From.join. Joins help navigate from one entity to another to get query results. 
The join method of Root returns a Join<Dept, Employee> type (or SetJoin, ListJoin, MapJoin or CollectionJoin type).

 

By default, join operations use inner joins, while outer joins can be implemented by specifying the JoinType parameter as LEFT or RIGHT in the join method.

CriteriaQuery<Dept> cqDept = criteriaBuilder.createQuery(Dept.class);
Root<Dept> deptRoot = cqDept.from(Dept.class);
Join<Dept, Employee> employeeJoin = deptRoot.join(Dept_.employeeCollection);
cqDept.where(criteriaBuilder.equal(employeeJoin.get(Employee_.deptId).get(Dept_.id), 1));
TypedQuery<Dept> resultDept = em.createQuery(cqDept);

grab connection

When it comes to collection properties, fetching connections can be very helpful for optimizing data access. This is achieved by prefetching associated objects and reducing lazy loading overhead. 
Using the criteria query, the fetch method is used to specify the associated property 
. The semantics of the Fetch connection are the same as the Join, because the Fetch operation does not return a Path object, so it cannot be referenced in the query in the future. 
In the following example, the employeeCollection object is loaded when querying the Dept object, there will be no second query to the database because of lazy loading.

CriteriaQuery<Dept> d = cb.createQuery(Dept.class);
Root<Dept> deptRoot = d.from(Dept.class);
deptRoot.fetch("employeeCollection", JoinType.LEFT);
d.select(deptRoot);
List<Dept> dList = em.createQuery(d).getResultList();


对应SQL: SELECT * FROM dept d, employee e  WHERE d.id = e.deptId

path expression

A Root instance, a Join instance, or an object obtained from the get method of another Path object. The Path object can be obtained using the get method. Path expressions are necessary when a query needs to navigate to an entity's properties. 
The parameters received by the Get method are properties specified in the entity metamodel class. 
The Path object is generally used in the select or where method of the Criteria query object. Examples are as follows:

CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
Root<Dept> root = criteriaQuery.from(Dept.class);
criteriaQuery.select(root.get(Dept_.name));&nbsp;

 

parameterized expression

     In JPQL, query parameters are passed at runtime by using the named parameter syntax (colon plus variable, such as :age). In a Criteria query, query parameters are created at runtime by creating a ParameterExpression object and passed in for calling the TypeQuery, setParameter method before the query. The following code snippet shows a ParameterExpression age of type Integer, which is set to 24:

ParameterExpression<Integer> age = criteriaBuilder.parameter(Integer.class);
Predicate condition = criteriaBuilder.gt(testEmp.get(Employee_.age), age);
criteriaQuery.where(condition);
TypedQuery<Employee> testQuery = em.createQuery(criteriaQuery);
List<Employee> result = testQuery.setParameter(age, 24).getResultList();
Corresponding SQL: SELECT * FROM Employee WHERE age = 24;

 

Sort results

     The results of a Criteria query can be sorted by calling the CriteriaQuery.orderBy method, which receives an Order object as a parameter. Order objects can be created by calling CriteriaBuilder.asc or CriteriaBuilder.Desc. In the following code snippet, Employee instances are sorted in ascending order based on age.  

CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
criteriaQuery.orderBy(criteriaBuilder.asc(employee.get(Employee_.age)));
em.createQuery(criteriaQuery).getResultList();

  对应  SQL: SELECT * FROM Employee ORDER BY age ASC

grouping

The groupBy method of a CriteriaQuery instance is used to group results based on Expression. The query calls the having method later by setting additional expressions. In the following code snippet, the query is grouped by the name property of the Employee class, and the results start with the letter N: 
CriteriaQuery<Tuple> cq = criteriaBuilder.createQuery(Tuple.class); 

Root<Employee> employee = cq.from(Employee.class);
cq.groupBy(employee.get(Employee_.name));
cq.having(criteriaBuilder.like(employee.get(Employee_.name), "N%"));
cq.select(criteriaBuilder.tuple(employee.get(Employee_.name),criteriaBuilder.count(employee)));
TypedQuery<Tuple> q = em.createQuery(cq);
List<Tuple> result = q.getResultList();

对应  SQL:    SELECT name, COUNT(*) FROM employeeGROUP BY name HAVING name like 'N%'

query projection

The result of the Criteria query is as specified in the Criteria query creation. Results can also be specified explicitly by passing the query root into CriteriaQuery.select. Criteria queries also give developers the ability to project various results. 

use construct()

Using this method, query results can consist of non-entity types. In the following code snippet, a Criteria query object is created for the EmployeeDetail class, which is not an entity type. 

CriteriaQuery<EmployeeDetails> criteriaQuery = criteriaBuilder.createQuery(EmployeeDetails.class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.construct(EmployeeDetails.class, employee.get(Employee_.name), employee.get(Employee_.age)));
em.createQuery(criteriaQuery).getResultList();
Corresponding SQL: SELECT name, age FROM employee<span style="white-space: normal;">&nbsp;</span>


Criteria queries can also return Object[] results by setting values ​​to the CriteriaBuilder.array method. In the code snippet below, the size of the array is 2 (consisting of String and Integer). Query that returns Object[]

CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);
Root<Employee> employee = criteriaQuery.from(Employee.class);
criteriaQuery.select(criteriaBuilder.array(employee.get(Employee_.name), employee.get(Employee_.age)));
em.createQuery(criteriaQuery).getResultList();

对应  SQL: SELECT name, age FROM employee

Queries that return tuples

A row of data or a single record in a database is often called a tuple. Queries can be used on tuples by calling the CriteriaBuilder.createTupleQuery() method. The CriteriaQuery.multiselect method takes in parameters, which must be returned in the query. 

CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Employee> employee = criteriaQuery.from(Employee.class);
criteriaQuery.multiselect(employee.get(Employee_.name).alias("name"), employee.get(Employee_.age).alias("age"));
em.createQuery(criteriaQuery).getResultList();

对应 SQL: SELECT name, age FROM employee 

in conclusion

     Criteria queries are a way to query a database in a more object-oriented way. In this article, I discussed type-safe Criteria queries in JPA2, and the concept of a meta-model that is important for understanding Criteria queries. Various APIs in Criteria queries are also discussed.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325062357&siteId=291194637
Recommended