The JPA Criteria API strives to provide all SQL capacities in an object-oriented way. It is the typed alternative to JPA query language, to be used with ORMs like Hibernate and EclipseLink.
This article is about how to build a database-query with the Criteria API. Please refer to my previous articles about JPA to find Java sources that may not be listed here.
Steps
Simple
Here is a very simple query, just selecting all records from database-table Team:
CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Team> query = builder.createQuery(Team.class); query.from(Team.class); TypedQuery<Team> typedQuery = entityManager.createQuery(query); return typedQuery.getResultList();
Minimal steps are:
- get a
CriteriaBuilderfrom JPAEntityManager - get a
CriteriaQueryfromCriteriaBuilder - define a
Rootby callingfrom()on theCriteriaQuery - wrap a
TypedQueryfromEntityManageraround theCriteriaQuery - fetch the result using one of the
TypedQuerymethods
Compared to the according JPQL-query
SELECT t from Team t
this is quite elaborate.
But it was just the start!-)
Complex
Following query joins three tables, sets up a WHERE-condition using wildcards, and builds explicit return-objects (DTOs).
-
Using the
EntityManager, create a criteria-builder:EntityManager entityManager = ....; CriteriaBuilder builder = entityManager.getCriteriaBuilder();
-
Using the
builder, create a criteria-query, targeting a result type (here it is an explicit data-transfer-object, not an entity-type):CriteriaQuery<ViewDto> query = builder.createQuery(ViewDto.class);
-
Using the
query, create a from-root, targeting the main entity class, and add all needed joins:Root<Team> teamRoot = query.from(Team.class); Join<Team,Responsibility> responsibilityJoin = teamRoot.joinSet("responsibilities"); Join<Responsibility,Person> personJoin = responsibilityJoin.join("person");
-
Using the
builder, create a SELECT clause, containing all needed attributes, or just an entity object (here it is the construction of the DTO)CompoundSelection<ViewDto> selectionConstructor = builder.construct( ViewDto.class, teamRoot, responsibilityJoin.get("name"), personJoin.get("name")); query.select(selectionConstructor);
-
Using the
builder, create WHERE conditions ("%"is the wildcard for LIKE):Predicate whereCondition = builder.and( builder.like(responsibilityJoin.get("name"), "%"+responsibilityName+"%"), builder.like(personJoin.get("name"), "%"+personName+"%")); query.where(whereCondition);
-
Using the
EntityManager, wrap a typed query around the built criteria-query:TypedQuery<ViewDto> typedQuery = entityManager.createQuery(query);
-
Call a result-method on the typed query, e.g.
getResultList():return typedQuery.getResultList();
This resembles following untyped JPQL query:
SELECT new fri.jpa.example.TeamDao$ViewDto(t, r.name, p.name) FROM Team t JOIN t.responsibilities r JOIN r.person p WHERE r.name like :responsibilityName and p.name like :personName
The ViewDto is a static inner class of fri.jpa.example.TeamDao,
the '$' replaces the '.' inside the fully qualified class name in SELECT clause.
The :responsibilityName and :personName
in WHERE clause are named parameter placeholders.
Example Code
DAO
Here is the DAO containing the complex query:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | package fri.jpa.example; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.*; public class TeamDao { private final EntityManager em; public TeamDao(EntityManager em) { this.em = em; } public static class ViewDto { public final Team team; public final String responsibilityName; public final String personName; public ViewDto(Team team, String responsibilityName, String personName) { this.team = team; this.responsibilityName = responsibilityName; this.personName = personName; } } public List<ViewDto> findByResponsibilityAndPersonViaWildcard( String responsibilityName, String personName) { final CriteriaBuilder builder = em.getCriteriaBuilder(); final CriteriaQuery<ViewDto> query = builder.createQuery(ViewDto.class); final Root<Team> teamRoot = query.from(Team.class); final Join<Team,Responsibility> responsibilityJoin = teamRoot.joinSet("responsibilities"); final Join<Responsibility,Person> personJoin = responsibilityJoin.join("person"); final CompoundSelection<ViewDto> selectionConstructor = builder.construct( ViewDto.class, teamRoot, responsibilityJoin.get("name"), personJoin.get("name")); query.select(selectionConstructor); final Predicate whereCondition = builder.and( builder.like(responsibilityJoin.get("name"), "%"+responsibilityName+"%"), builder.like(personJoin.get("name"), "%"+personName+"%")); query.where(whereCondition); final TypedQuery<ViewDto> typedQuery = em.createQuery(query); return typedQuery.getResultList(); } } |
Unit Test
Following is the unit test for the query.
Please refer to
my previous articles about JPA
to find out how you can use this super-class to test both Hibernate and EclipseLink.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | package fri.jpa.example; import static org.junit.Assert.*; import java.util.*; import java.util.function.Consumer; import javax.persistence.*; import org.junit.*; import fri.jpa.example.TeamDao.ViewDto; public abstract class JpaTest { private EntityManager em; @Test public void findTeamsByResponsibilityAndPersonViaWildcard() { final Person peter = transactional(em::persist, newPerson("Peter")); final Person paul = transactional(em::persist, newPerson("Paul")); final Person mary = transactional(em::persist, newPerson("Mary")); final Team developmentTeam = new Team(); final Responsibility developer = newResponsibility("Developer", peter); developmentTeam.getResponsibilities().add(developer); final Responsibility operator = newResponsibility("Operator", paul); developmentTeam.getResponsibilities().add(operator); final Responsibility devops = newResponsibility("DevOps", mary); developmentTeam.getResponsibilities().add(devops); transactional(em::persist, developmentTeam); final Team managerTeam = new Team(); final Responsibility developmentManager = newResponsibility("DevelopmentManager", mary); managerTeam.getResponsibilities().add(developmentManager); final Responsibility roomManager = newResponsibility("RoomManager", paul); managerTeam.getResponsibilities().add(roomManager); transactional(em::persist, managerTeam); final TeamDao teamDao = new TeamDao(em); final List<ViewDto> result = teamDao.findByResponsibilityAndPersonViaWildcard("Dev", "r"); // "Dev" is in "Developer" and "DevOps", "r" is in "Peter" and "Mary" assertEquals(3, result.size()); final ViewDto developerView = result.stream() .filter(v -> v.responsibilityName.equals(developer.getName())) .findFirst() .get(); assertNotNull(developerView); assertEquals(developmentTeam, developerView.team); assertEquals(peter.getName(), developerView.personName); final ViewDto devopsView = result.stream() .filter(v -> v.responsibilityName.equals(devops.getName())) .findFirst() .get(); assertNotNull(devopsView); assertEquals(developmentTeam, devopsView.team); assertEquals(mary.getName(), devopsView.personName); final ViewDto managerView = result.stream() .filter(v -> v.responsibilityName.equals(developmentManager.getName())) .findFirst() .get(); assertNotNull(managerView); assertEquals(managerTeam, managerView.team); assertEquals(mary.getName(), managerView.personName); } private Person newPerson(String name) { final Person person = new Person(); person.setName(name); return person; } private Responsibility newResponsibility(String name, Person person) { final Responsibility responsibility = new Responsibility(); responsibility.setName(name); responsibility.setPerson(person); return responsibility; } private <P> P transactional(Consumer<P> entityManagerFunction, P parameter) { final EntityTransaction transaction = em.getTransaction(); try { transaction.begin(); entityManagerFunction.accept(parameter); transaction.commit(); return parameter; } catch (Throwable th) { th.printStackTrace(); transaction.rollback(); throw th; } } /** @return the name of the persistence-unit to use for all tests. */ protected abstract String getPersistenceUnitName(); @Before public void setUp() { em = Persistence.createEntityManagerFactory(getPersistenceUnitName()).createEntityManager(); } @After public void tearDown() { for (Team team : findTeams()) transactional(em::remove, team); for (Responsibility responsibility : findResponsibilities()) transactional(em::remove, responsibility); for (Person person : findPersons()) transactional(em::remove, person); } } |
Conclusion
The JPA Criteria API is complex. But it is strongly typed,
and that's a big advantage over String queries, even when these are shorter.
Mind that there are also
CriteriaUpdate and CriteriaDelete classes
beside CriteriaQuery,
to be used for UPDATE and DELETE statements.


Keine Kommentare:
Kommentar veröffentlichen