JPA предоставляет несколько альтернатив для запроса данных. Такие альтернативы могут быть классифицированы с учетом множества критериев, например, используемого языка (SQL против JPQL) или того, являются ли запросы статическими (время компиляции) или динамическими (время исполнения).
Статические запросы определяются с использованием аннотаций @NamedQuery ( javax.persistence.NamedQuery ) и @NamedQueries ( javax.persistence.NamedQueries ) в самом определении класса @Entity:
1
2
3
4
|
@NamedQuery ( name= "findAllCustomersWithName" , query= "SELECT c FROM Customer c WHERE c.name LIKE :custName" ) |
С другой стороны, EntityManager предоставляет методы createQuery (…) и createNativeQuery (…), которые принимают JPQL или SQL-запрос соответственно.
Таким образом, запросы могут быть определены как во время компиляции, так и во время выполнения.
( Примечание . Рекомендуется всегда использовать параметризованные запросы, используя методы setParameter (…) из Query, чтобы избежать уязвимостей SQL-инъекций.)
Критерии API
Однако JPA предоставляет альтернативный подход к объектам запросов: Criteria API . Действительно, одним из мотивов перехода на JPA является работа с объектами, а не с диалектами SQL, не так ли?
Давайте посмотрим пример кода.
Определение сущности:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Entity public class User { @Id private Integer userId; @Basic @Column (length= 15 , nullable= false ) private String name; @Basic @Column (length= 64 , nullable= false ) private String userDigestedPasswd; @Basic @Column (length= 50 , nullable= true ) private String email; @Basic @Column (nullable= false ) public Integer privilegeLevel; @Basic @Column (nullable= false ) private Boolean active; } |
Давайте запросим БД и проверим результаты (используя JUnit):
01
02
03
04
05
06
07
08
09
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
|
public class UserTest { @Test public void testUserCriteria(){ EntityManagerFactory emf = null ; EntityManager em = null ; try { emf = Persistence.createEntityManagerFactory( "criteria" ); em = emf.createEntityManager(); final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery<User> q = cb.createQuery(User. class ); final Root<User> users = q.from(User. class ); final Predicate condition = cb.equal(users.get( "privilegeLevel" ), 5 ); q.select(users).where(condition).orderBy(cb.asc(users.get( "userId" ))); em.getTransaction().begin(); List<User> result = em.createQuery(q).getResultList(); em.getTransaction().commit(); assertNotNull(result); assertEquals( 2 , result.size()); assertEquals( 1 , ( int )result.get( 0 ).getUserId()); assertEquals( "Pepe" , result.get( 0 ).getName()); assertEquals( 3 , ( int )result.get( 1 ).getUserId()); assertEquals( "Dolores" , result.get( 1 ).getName());} catch (Exception e) { fail( "Unexpected Exception " + e.getMessage()); } finally { if (em != null ) em.close(); if (emf != null ) emf.close(); } } } |
Следующие строки показывают создание запроса:
1
2
3
4
5
|
final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery<User> q = cb.createQuery(User. class ); final Root<User> users = q.from(User. class ); final Predicate condition = cb.equal(users.get("privilegeLevel); q.select(users).where(condition).orderBy(cb.asc(users.get("userId |
Прежде всего, получите CriteriaBuilder от EntityManager . Затем получите экземпляр CriteriaQuery , установив класс для хранения результатов. В нашем случае User.class :
1
2
|
final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery<User> q = cb.createQuery(User. class ); |
После этого должен быть задан объект для выполнения запроса:
1
|
final Root<User> users = q.from(User. class ); |
Теперь пришло время установить условия соответствия запросов. В примере кода условием является просто атрибут privilegeLevel, равный 5:
1
|
final Predicate condition = cb.equal(users.get( "privilegeLevel" ), 5 ); |
Наконец, запрос строится с добавлением условий на Root. Также могут быть установлены параметры группировки и сортировки (т. Е. Сортировка по возрастанию установлена в userId ):
1
|
q.select(users).where(condition).orderBy(cb.asc(users.get(“userId”))); |
Пожалуйста, посмотрите на CriteriaBuilder для различных вариантов. Параметры группировки и сортировки можно найти в CriteriaQuery .
Использование метамодели для проверки во время компиляции
Обратите внимание, что только что созданный запрос требует отслеживания имен атрибутов объекта. Например, для построения запроса используется имя атрибута privilegeLevel . Однако, если имя атрибута было изменено позже, код скомпилируется и завершится с ошибкой только во время выполнения:
1
2
3
4
|
final CriteriaQuery<User> q = cb.createQuery(User. class ); final Root<User> users = q.from(User. class ); final Predicate condition = cb.equal(users.get( "privilegeLevel" ), 5 ); q.select(users).where(condition).orderBy(cb.asc(users.get( "userId" ))); |
Это не хорошо.
К счастью, используя метамодель, мы сможем создавать проверенные запросы во время компиляции. Краткое введение можно найти в руководстве по Java EE6 .
Используя метамодель, код будет ссылаться на SingularAttribute объекта, а не на строку, содержащую имя атрибута объекта. Таким образом, если атрибут объекта был изменен позже, компилятор пометит его для нас.
Прежде всего, необходимо создать соответствующий класс метамодели ( EntityType ). Хотя это может быть достигнуто несколькими способами, вероятно, самый простой для реализации openJPA — это добавить флаг сборки openJPA : -Aopenjpa.metamodel = true .
Итак, мы создали класс User_ , который является соответствующим классом метамодели для User :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
* Generated by OpenJPA MetaModel Generator Tool. **/ package com.wordpress.tododev.criteria.entities; import javax.persistence.metamodel.SingularAttribute; @javax .persistence.metamodel.StaticMetamodel (value=com.wordpress.tododev.criteria.entities.User. class ) @javax .annotation.Generated (value= "org.apache.openjpa.persistence.meta.AnnotationProcessor6" ,date= "Mon Mar 04 16:47:46 CET 2013" ) public class User_ { public static volatile SingularAttribute<User,Boolean> active; public static volatile SingularAttribute<User,String> email; public static volatile SingularAttribute<User,String> name; public static volatile SingularAttribute<User,Integer> privilegeLevel; public static volatile SingularAttribute<User,String> userDigestedPasswd; public static volatile SingularAttribute<User,Integer> userId; } |
Если бы такой класс был добавлен в репозиторий, любое последующее изменение класса User останется незамеченным. Более того, не стоит добавлять автоматически сгенерированные элементы в системы управления версиями кода.
Используя ant , maven или аналогичные инструменты, можно добавить цель для создания классов метамодели. Такая цель должна выполняться после любого изменения объектов JPA.
Также возможно использовать IDE для этого. Например, для тех, кто использует Eclipse, просто нужно добавить уже упомянутый флаг компиляции в Свойства-> Компилятор Java-> Процессор аннотаций и lib (jar), содержащий Процессор аннотаций для выбранной реализации JPA, в раздел Factory Path внутри Процессора аннотаций (может приводить к проблемам компиляции в автоматическом режиме, при условии, что класс метамодели должен быть скомпилирован перед использованием кода).
Давайте добавим еще один тест в комплект. Этот не предоставит строку, содержащую имя атрибута, но вместо этого использует класс метамодели:
01
02
03
04
05
06
07
08
09
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
|
@Test public void testUserCriteriaMetaModel(){ EntityManagerFactory emf = null ; EntityManager em = null ; try { emf = Persistence.createEntityManagerFactory( "criteria" ); em = emf.createEntityManager(); final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery<User> q = cb.createQuery(User. class ); final Metamodel m = em.getMetamodel(); final Root<User> user = q.from(m.entity(User. class )); final Predicate condition = cb.equal(user.get(User_.privilegeLevel), 5 ); q.select(user).where(condition).orderBy(cb.asc(user.get(User_.userId))); em.getTransaction().begin(); List<User> result = em.createQuery(q).getResultList(); em.getTransaction().commit(); assertNotNull(result); assertEquals( 2 , result.size()); assertEquals( 1 , ( int )result.get( 0 ).getUserId()); assertEquals( "Pepe" , result.get( 0 ).getName()); assertEquals( 3 , ( int )result.get( 1 ).getUserId()); assertEquals( "Dolores" , result.get( 1 ).getName()); } catch (Exception e) { fail( "Unexpected Exception " + e.getMessage()); } finally { if (em != null ) em.close(); if (emf != null ) emf.close(); } } |
Более важные изменения: user.get (User_.privilegeLevel) вместо users.get («privilegeLevel») и user.get (User_.userId) вместо users.get ( «UserID»).
- Загрузите исходный код с GitHub .
Ссылка: | Проверка во время компиляции запросов JPA от нашего партнера JCG Серхио Молина в блоге TODOdev . |