In this tutorial, I want to show you a way of how to perform persistence operations with Criteria API in a more convenient form using Builder pattern and JPA Static Metamodel Generator.
The full source code is available over on Github.
In the Java world, the most popular way to work with relational databases is to use Spring Data JPA. It is quite a sophisticated framework that allows you to instantly get started working with the database. The most compelling feature in the Spring Data is the Repository. All you need to do to access the database table is to create a Java interface and extend it from the CrudRepository
with the defined domain class and with the id type of the domain class as type arguments:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
}
For each declared interface, Spring will create an appropriate bean, allowing you to easily use it with Dependency Injection. With the CrudRepository
, this interface will provide basic implementations for CRUD operations for the managed entity class. For example, the following read operations will allow you to retrieve all the rows and a specific row by id:
package org.springframework.data.repository;
public interface CrudRepository<T, ID> extends Repository<T, ID> {
…
Iterable<T> findAll();
Optional<T> findById(ID id);
…
}
If you want to filter the data with specific predicates, Spring Repository allows you to define your own methods with the query derivation mechanism using entity’s fields and keywords:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
List<Customer> findByZipCode(String zipCode);
List<Customer> findByLastNameAndZipCode(String lastName, String zipCode);
}
It looks very clear and convenient. But there are few cons and pitfalls you should be aware of:
Using query derivation mechanism language to compose a query, you can really minimize your code with supported keywords. Modern Intellij IDEA will help you to build such a query quite easily:
@Entity
class Customer {
@Id
private Long id;
private String zipCode;
}
interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findAllByZip(String zipCode);
}
If we construct a query using wrong or non-existing field names, like zip
instead of zipCode
, when we run this code we will probably see an exception, saying that no property zip
is found for the Customer
type:
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'zip' found for type 'Customer' Did you mean ''id''
But if we change the argument type of this field, for example to Integer
, we will not receive any exception during start up:
@Entity
class Customer {
@Id
private Long id;
private String zipCode;
}
interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findAllByZipCode(Integer zipCode);
}
You’ll receive an exception only when you manually invoke this method passing specific value:
java.lang.IllegalArgumentException: Parameter value [90001] did not match expected type [java.lang.String (n/a)]
There is no way to mock this part of code. Therefore, you should be very careful when you compose a query or when you refactor an entity’s field type.
Query derivation mechanism language is quite useful, but it becomes difficult to read if you deal with a complex query containing a deep nested join. Let’s say we want to get Order entities filtered by address attributes:
List<Order> findAllByCustomerAddressAddressLineLikeAndCustomerAddressCityNameAndCustomerAddressCityCountryName(String addressLine, String city, String country);
Sure, you can simplify this query with JPQL using the @Query
annotation over this method. But in this case, you will lose all the privileges of the query derivation mechanism with proper validation when Spring starts up:
public interface OrderRepository extends CrudRepository<Order, Long> {
@Query("SELECT o FROM Order o " +
" INNER JOIN o.customer cr" +
" INNER JOIN cr.address a" +
" INNER JOIN a.city ct" +
" INNER JOIN ct.country cn" +
" WHERE a.addressLine LIKE :address_line AND ct.name = :city AND cn.name = :country")
List<Order> filterByAddress(@Param("address_line") String addressLine, @Param("city") String city, @Param("country") String country);
}
The other scenario is to use the Criteria API by extending your Repository with JpaSpecificationExecutor
interface and allowing you to access the Root
, CriteriaQuery
and CriteriaBuilder
within the Specification
interface:
orderRepository.findAll((Specification<Order>) (root, query, cb) -> {
Path<Address> addressPath = root.get(Order_.customer).get(Customer_.address);
Path<City> cityPath = addressPath.get(Address_.city);
return cb.and(
cb.equal(addressPath.get(Address_.addressLine), "%" + addressLine + "%"),
cb.equal(cityPath.get(City_.name), city),
cb.equal(cityPath.get(City_.country).get(Country_.name), country)
);
});
Using Spring Data JPA, we have to create a repository for each table. Explicitness is always good, but it’s quite problematic if you have about 200 tables. In this case, you need to create 200 repositories accordingly, even if you need only one query per repository.
My idea is to eliminate all these issues by creating an extension for the Criteria API:
select()
.equal(Order_.status, OrderStatus.Shipped)
.like(Order_.customer, Customer_.address, Address_.addressLine, “11 Aleksandr Pushkin St”)
.equal(Order_.customer, Customer_.address, Address_.city, City_.name, “Tbilisi”)
.findAll();
equal(Order_.customer, Customer_.address, Address_.city, City_.country, Country_.name, “Georgia”)
select()
.equal(Order_.status, OrderStatus.Shipped)
.and(Order_.customer, Customer_.address,
like(Address_.addressLine, "11 Aleksandr Pushkin St"),
and(Address_.city,
equal(City_.name, "Tbilisi"),
equal(City_.country, Country_.name, “Georgia”)
)
)
.findAll();
select(Order.class)
.equal(Order_.status, OrderStatus.Shipped)
.and(Order_.customer, Customer_.address,
like(Address_.addressLine, "11 Aleksandr Pushkin St"),
and(Address_.city,
equal(City_.name, "Tbilisi"),
equal(City_.country, Country_.name, “Georgia”)
)
)
.findAll();
Now, let’s dive into the code and try to build a simple implementation. Our extension will provide the ability to select, update and delete rows.
First, we add the BaseQuery
interface that will contain basic operations for selecting, updating and deleting queries. This interface should have two generic types: R
as the Root
type and Q extends BaseQuery<R, Q>
as the BaseQuery
implementation itself, which we can use as a return type for Builder pattern methods:
public interface BaseQuery<R, Q extends BaseQuery<R, Q>> {
}
Next, we add the first method for the equal
operation. It will accept SingularAttribute
as the entity field that we want to compare and the comparing value itself:
public interface BaseQuery<R, Q extends BaseQuery<R, Q>> {
...
<V> Q equal(SingularAttribute<R, V> attribute, V value);
}
SingularAttribute
provides two generic types: the first one is the entity type, and the second is the type of the entity’s represented attribute. Therefore, we put R
as the first generic type, and we add an extra V
generic type as the second type, equal to the value type. Please note that if we want to provide type safety, the value should have the same data type as the represented attribute.
Next, we can add the second overloaded implementation of the equal
filter, that will contain a subsequent parent-child relation to allow you to compare a nested attribute. In this case, we need to add an extra generic type P
for the in-between relation:
public interface BaseQuery<R, Q extends BaseQuery<R, Q>> {
...
<P, V> Q equal(SingularAttribute<R, P> attribute1, SingularAttribute<P, V> attribute2, V value);
}
Lastly, we will add the third overloaded equal
method with an extra P1
and P2
generic types to allow you to reach an attribute available after two subsequent parent-child relations:
public interface BaseQuery<R, Q extends BaseQuery<R, Q>> {
...
<P1, P2, V> Q equal(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, V> attribute3, V value);
}
Now, we are ready to initialize BaseQuery
using the Criteria API. This class again should have two generic types: the Root
type and the BaseQueryImpl
implementation itself, which we can use as a return type for Builder pattern methods:
public class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> {
}
Before we initialize the equal implementations, we first need to define the collection of predicates. Our query will have nested criteria predicates with postponed initialization. Therefore, we introduce the QueryPredicate
interface with the following arguments: CommonAbstractCriteria
, CriteriaBuilder
and Path
to allow us to create the necessary predicate:
@FunctionalInterface
public interface QueryPredicate<R> {
Predicate apply(CommonAbstractCriteria criteria, CriteriaBuilder cb, Path<R> root);
}
Next, we introduce a collection of QueryPredicate
as BaseQueryImpl
field and a method that will convert this collection to a collection of javax.persistence.criteria.Predicate
:
public class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> {
protected final Collection<QueryPredicate<R>> predicates;
public BaseQueryImpl() {
this.predicates = new ArrayList<>();
}
protected <T> Predicate[] buildPredicates(CommonAbstractCriteria criteria, Collection<QueryPredicate<T>> predicates,
CriteriaBuilder cb, Path<T> root) {
return predicates
.stream()
.map(t -> t.apply(criteria, cb, root))
.toArray(Predicate[]::new);
}
}
In addition, we’ll introduce abstract self()
method that should return this
instance for each implementation:
public abstract class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> {
...
//subclasses must override this method to return "this"
protected abstract Q self();
}
Now, we are ready to create defined equal
implementations. Each implementation should retrieve each required Path
and accumulate an equal predicate in the predicates collection:
public abstract class BaseQueryImpl<R, Q extends BaseQueryImpl<R, Q>> implements BaseQuery<R, Q> {
...
@Override
public <V> Q equal(SingularAttribute<R, V> attribute, V value) {
predicates.add((criteria, cb, root) -> cb.equal(root.get(attribute), value));
return self();
}
@Override
public <P, V> Q equal(SingularAttribute<R, P> attribute1, SingularAttribute<P, V> attribute2, V value) {
predicates.add((criteria, cb, root) -> cb.equal(root.get(attribute1).get(attribute2), value));
return self();
}
@Override
public <P1, P2, V> Q equal(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2,
SingularAttribute<P2, V> attribute3, V value) {
predicates.add((criteria, cb, root) -> cb.equal(root.get(attribute1).get(attribute2).get(attribute3), value));
return self();
}
}
You can add additional equal
statements on your own accepting a chain of 3+ subsequent parent-child relations as well as predicates for other operators, like notEqual
, like
, greaterThan
, lessThan
, isNull
, etc.
To perform select statements, we will create the SelectQuery
interface extending the BaseQuery
:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
}
This interface will mostly contain the terminating methods that will return the final result from a database. The first terminating method will be findAll()
returning the list of R
(Root type):
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
List<R> findAll();
}
Next, we will provide two overloaded findAll()
methods to select nested join table:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
...
<P> List<P> findAll(SingularAttribute<R, P> attribute);
<P1, P2> List<P2> findAll(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
}
The second group of terminating interfaces we implement is getOne()
, which will return a single result or throw an exception if there is no result found:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
...
R getOne();
<P> P getOne(SingularAttribute<R, P> attribute);
<P1, P2> P2 getOne(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
}
The third terminating group we implement is findOne()
, which will return a single result wrapped in the Optional
:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
...
Optional<R> findOne();
<P> Optional<P> findOne(SingularAttribute<R, P> attribute);
<P1, P2> Optional<P2> findOne(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
}
The next terminating group we can provide is the count()
method that will return number of rows matching the query:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
...
long count();
<P> long count(SingularAttribute<R, P> attribute);
<P1, P2> long count(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
}
Next, let’s add non-terminating interfaces to order the rows. Methods without Order argument will provide the ascending order:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
...
<P> SelectQuery<R, Q> order(SingularAttribute<R, P> attribute);
<P> SelectQuery<R, Q> order(SingularAttribute<R, P> attribute, Order order);
<P1, P2> SelectQuery<R, Q> order(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
<P1, P2> SelectQuery<R, Q> order(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, Order order);
<P1, P2, P3> SelectQuery<R, Q> order(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3);
<P1, P2, P3> SelectQuery<R, Q> order(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3, Order order);
}
Lastly, if you want to initialize Lazy relations, you can add fetch()
methods:
public interface SelectQuery<R, Q extends SelectQuery<R, Q>> extends BaseQuery<R, Q> {
...
<P> SelectQuery<R, Q> fetch(SingularAttribute<R, P> attribute);
<P1, P2> SelectQuery<R, Q> fetch(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2);
<P1, P2, P3> SelectQuery<R, Q> fetch(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2, SingularAttribute<P2, P3> attribute3);
}
Now we have enough interfaces to perform select statements. You can add any other interfaces on your own including pagination, exists and so on. Let’s finish the implementation for the declared methods by creating the SelectQueryImpl
class:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
}
This class will contain the following fields: javax.persistence.EntityManager
, javax.persistence.criteria.CriteriaBuilder
, javax.persistence.criteria.CriteriaQuery
, javax.persistence.criteria.Root
and the List of javax.persistence.criteria.Order
:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
private final EntityManager em;
private final CriteriaBuilder cb;
private final CriteriaQuery query;
private final Root<R> root;
private final List<Order> orderList;
}
Next, let’s declare a constructor. To initialize Criteria fields, we need the EntityManager
and the root R
entity type:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
public SelectQueryImpl(EntityManager em, Class<R> type) {
this.em = em;
this.cb = em.getCriteriaBuilder();
this.query = cb.createQuery();
this.root = query.from(type);
this.orderList = new ArrayList<>();
}
@Override
protected SelectQueryImpl<R> self() {
return this;
}
}
Before implementing declared terminating and non-terminating interfaces, we will add a private method that will accept javax.persistence.criteria.Selection
argument and return built TypedQuery
:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
private <T> TypedQuery<T> buildQuery(Selection<T> selection) {
CriteriaQuery<T> resultQuery = query
.select(selection)
.orderBy(orderList);
if (!predicates.isEmpty()) {
resultQuery = resultQuery.where(buildPredicates(query, predicates, cb, root));
}
return em.createQuery(resultQuery);
}
}
This method will build our query by selecting the required table path, providing predicates and orderList.
Now we are ready to implement required interfaces. To implement the first group for the findAll()
interfaces, we just need to pass desired table path to the buildQuery()
method and call getResultList()
:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
@Override
public List<R> findAll() {
return buildQuery(root)
.getResultList();
}
@Override
public <P> List<P> findAll(SingularAttribute<R, P> attribute) {
return buildQuery(root.get(attribute))
.getResultList();
}
@Override
public <P1, P2> List<P2> findAll(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2) {
return buildQuery(root.get(attribute1).get(attribute2))
.getResultList();
}
}
The second implementation is for the getOne()
interface, which will act the same except for the fact that we should limit the result by one row and call the getSingleResult()
:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
@Override
public R getOne() {
return buildQuery(root)
.setMaxResults(1)
.getSingleResult();
}
@Override
public <P> P getOne(SingularAttribute<R, P> attribute) {
return buildQuery(root.get(attribute))
.setMaxResults(1)
.getSingleResult();
}
@Override
public <P1, P2> P2 getOne(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2) {
return buildQuery(root.get(attribute1).get(attribute2))
.setMaxResults(1)
.getSingleResult();
}
}
The next realization for the findOne()
can be implemented using getOne()
method with try/catch block:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
@Override
public Optional<R> findOne() {
try {
return Optional.of(getOne());
} catch (NoResultException e) {
return Optional.empty();
}
}
@Override
public <P> Optional<P> findOne(SingularAttribute<R, P> attribute) {
try {
return Optional.of(getOne(attribute));
} catch (NoResultException e) {
return Optional.empty();
}
}
@Override
public <P1, P2> Optional<P2> findOne(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2) {
try {
return Optional.of(getOne(attribute1, attribute2));
} catch (NoResultException e) {
return Optional.empty();
}
}
}
The last terminating group is count()
, which will return count of rows matching the query:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
@Override
public long count() {
return buildQuery(cb.count(root))
.getSingleResult();
}
@Override
public <P> long count(SingularAttribute<R, P> attribute) {
return buildQuery(cb.count(root.get(attribute)))
.getSingleResult();
}
@Override
public <P1, P2> long count(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2) {
return buildQuery(cb.count(root.get(attribute1).get(attribute2)))
.getSingleResult();
}
}
Lastly, we need to implement non-terminating interfaces for the order()
and fetch()
methods:
public class SelectQueryImpl<R> extends BaseQueryImpl<R, SelectQueryImpl<R>> implements SelectQuery<R, SelectQueryImpl<R>> {
...
@Override
public <P> SelectQueryImpl<R> order(SingularAttribute<R, P> attribute) {
return order(attribute, Order.ASC);
}
@Override
public <P> SelectQueryImpl<R> order(SingularAttribute<R, P> attribute, Order sort) {
javax.persistence.criteria.Order order = sort == Order.ASC ? cb.asc(root.get(attribute)) : cb.desc(root.get(attribute));
orderList.add(order);
return this;
}
@Override
public <P1, P2> SelectQueryImpl<R> order(SingularAttribute<R, P1> attribute1,
SingularAttribute<P1, P2> attribute2) {
return order(attribute1, attribute2, Order.ASC);
}
@Override
public <P1, P2> SelectQueryImpl<R> order(SingularAttribute<R, P1> attribute1,
SingularAttribute<P1, P2> attribute2, Order sort) {
Path<P2> path = root.get(attribute1).get(attribute2);
javax.persistence.criteria.Order order = sort == Order.ASC ? cb.asc(path) : cb.desc(path);
orderList.add(order);
return this;
}
@Override
public <P1, P2, P3> SelectQueryImpl<R> order(SingularAttribute<R, P1> attribute1,
SingularAttribute<P1, P2> attribute2,
SingularAttribute<P2, P3> attribute3) {
return order(attribute1, attribute2, attribute3, Order.ASC);
}
@Override
public <P1, P2, P3> SelectQueryImpl<R> order(SingularAttribute<R, P1> attribute1,
SingularAttribute<P1, P2> attribute2,
SingularAttribute<P2, P3> attribute3, Order sort) {
Path<P3> path = root.get(attribute1).get(attribute2).get(attribute3);
javax.persistence.criteria.Order order = sort == Order.ASC ? cb.asc(path) : cb.desc(path);
orderList.add(order);
return this;
}
@Override
public <P> SelectQueryImpl<R> fetch(SingularAttribute<R, P> attribute) {
root.fetch(attribute);
return this;
}
@Override
public <P1, P2> SelectQueryImpl<R> fetch(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2) {
root.fetch(attribute1).fetch(attribute2);
return this;
}
@Override
public <P1, P2, P3> SelectQueryImpl<R> fetch(SingularAttribute<R, P1> attribute1, SingularAttribute<P1, P2> attribute2,
SingularAttribute<P2, P3> attribute3) {
root.fetch(attribute1).fetch(attribute2).fetch(attribute3);
return this;
}
}
To perform update statements, we will create the UpdateQuery
interface extending the BaseQuery
:
public interface UpdateQuery<R, Q extends UpdateQuery<R, Q>> extends BaseQuery<R, Q> {
}
This query will contain only two methods: the first one to set the attributes we want to update and the second one is the terminating method that will return the number of updated rows:
public interface UpdateQuery<R, Q extends UpdateQuery<R, Q>> extends BaseQuery<R, Q> {
<V> UpdateQuery<R, Q> set(SingularAttribute<R, V> attribute, V value);
int execute();
}
Implementation for the UpdateQuery
should be quite straightforward except for the fact, now we should use CriteriaUpdate
instead of CriteriaQuery
:
public class UpdateQueryImpl<R> extends BaseQueryImpl<R, UpdateQueryImpl<R>> implements UpdateQuery<R, UpdateQueryImpl<R>> {
private final EntityManager em;
private final CriteriaBuilder cb;
private final CriteriaUpdate<R> query;
private final Root<R> root;
public UpdateQueryImpl(EntityManager em, Class<R> type) {
this.em = em;
this.cb = em.getCriteriaBuilder();
this.query = cb.createCriteriaUpdate(type);
this.root = query.from(type);
}
@Override
public <V> UpdateQueryImpl<R> set(SingularAttribute<R, V> attribute, V value) {
query.set(attribute, value);
return this;
}
@Override
public int execute() {
if (!predicates.isEmpty()) {
query.where(buildPredicates(query, predicates, cb, root));
}
return em.createQuery(query)
.executeUpdate();
}
@Override
protected UpdateQueryImpl<R> self() {
return this;
}
}
Lastly, to perform delete statements, we will create the DeleteQuery
interface extending the BaseQuery
:
public interface DeleteQuery<R, Q extends DeleteQuery<R, Q>> extends BaseQuery<R, Q> {
}
DeleteQuery
will have only one terminating method, returning the number of deleted rows:
public interface DeleteQuery<R, Q extends DeleteQuery<R, Q>> extends BaseQuery<R, Q> {
int execute();
}
Implementation for the DeleteQuery
with the CriteriaDelete
:
public class DeleteQueryImpl<R> extends BaseQueryImpl<R, DeleteQueryImpl<R>> implements DeleteQuery<R, DeleteQueryImpl<R>> {
private final EntityManager em;
private final CriteriaBuilder cb;
private final CriteriaDelete<R> query;
private final Root<R> root;
public DeleteQueryImpl(EntityManager em, Class<R> type) {
this.em = em;
this.cb = em.getCriteriaBuilder();
this.query = cb.createCriteriaDelete(type);
this.root = query.from(type);
}
@Override
protected DeleteQueryImpl<R> self() {
return this;
}
@Override
public int execute() {
if (!predicates.isEmpty()) {
query.where(buildPredicates(query, predicates, cb, root));
}
return em.createQuery(query)
.executeUpdate();
}
}
In order to create any of the BaseQuery
implementations, we should pass an instance of EntityManager
every time we want to perform a query. To simplify this injection process, we can declare an extra class that will hold the EntityManager
:
public class CriteriaApiHelper {
private final EntityManager em;
public CriteriaApiHelper(EntityManager em) {
this.em = em;
}
}
Now we can just initialize methods for BaseQuery
by creating a suitable implementation for each query type:
public class CriteriaApiHelper {
…
public <T> SelectQueryImpl<T> select(Class<T> type) {
return new SelectQueryImpl<>(em, type);
}
public <T> UpdateQueryImpl<T> update(Class<T> type) {
return new UpdateQueryImpl<>(em, type);
}
public <T> DeleteQueryImpl<T> delete(Class<T> type) {
return new DeleteQueryImpl<>(em, type);
}
}
Now you can perform select, update and delete queries using CriteriaApiHelper
:
List<Order> listOfOrders = criteriaApiHelper.select(Order.class)
.equal(Order_.status, OrderStatus.Shipped)
.like(Order_.customer, Customer_.address, Address_.addressLine, "11 Aleksandr Pushkin St")
.equal(Order_.customer, Customer_.address, Address_.city, City_.name, "Tbilisi")
.equal(Order_.customer, Customer_.address, Address_.city, City_.country, Country_.name, “Georgia”)
.findAll();
In order to see the entities with an underscore at the end, you should include the JPA Static Metamodel Generator library:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<scope>provided</scope>
</dependency>
After that, you need to rebuild your project and mark the target/generated-sources/annotations
package as the Generated Sources Root:
In the next chapters, we will implement nested expressions providing or/and
predicates and sub queries.