JPA — запрос с критерием. Использование API-интерфейса критериев JPA 2
Создание запроса к базе данных с критерием в JPA. Использование API-интерфейса критериев JPA 2
Используемые технологии
- Spring 4.1.5.RELEASE
- Hibernate 5.0.1.Final
- JPA 2.1
- MySQL 5.6.25
- IntelliJ IDEA 14
- Maven 3.2.5
1. Структура проекта
Настройки взяты из JPA – пример приложения Hello World, используемые здесь сущности взяты в измененной версии JPA – операции INSERT, UPDATE, DELETE.
2. Создание метамодели с использованием JPA-интерфейса критериев
Описание и генерацию метамодели для нашего примера вынес в отдельную статью — Создание метамодели для сущностного класса в JPA 2. Приведу полученный результат:
ContactEntity_:
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 |
package ru.javastudy.entities; import ru.javastudy.entities.ContactEntity; import ru.javastudy.entities.ContactTelDetailEntity; import ru.javastudy.entities.HobbyEntity; import java.util.Date; import javax.annotation.Generated; import javax.persistence.metamodel.SetAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(ContactEntity.class) public abstract class ContactEntity_ { public static volatile SingularAttribute<ContactEntity, String> firstName; public static volatile SingularAttribute<ContactEntity, String> lastName; public static volatile SetAttribute<ContactEntity, HobbyEntity> hobbies; public static volatile SingularAttribute<ContactEntity, Integer> id; public static volatile SingularAttribute<ContactEntity, Date> birthDate; public static volatile SingularAttribute<ContactEntity, Integer> version; public static volatile SetAttribute<ContactEntity, ContactTelDetailEntity> contactTelDetails; } |
Как видите в сгенерированной метамодели используется аннотация @StaticMetamodel(ContactEntity.class) с указанием сущностного класса. Обратите внимание, что класс метамодели ContactEntity_.class лежит в том же пакете, что и сущность ContactEntity.class.
ContactHobbyDetailEntity_:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package ru.javastudy.entities; import ru.javastudy.entities.ContactEntity; import ru.javastudy.entities.ContactHobbyDetailEntity; import javax.annotation.Generated; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(ContactHobbyDetailEntity.class) public abstract class ContactHobbyDetailEntity_ { public static volatile SingularAttribute<ContactHobbyDetailEntity, Integer> contactId; public static volatile SingularAttribute<ContactHobbyDetailEntity, String> hobbyId; public static volatile SingularAttribute<ContactHobbyDetailEntity, ContactEntity> contactByContactId; } |
ContactTelDetailEntity_:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package ru.javastudy.entities; import ru.javastudy.entities.ContactEntity; import ru.javastudy.entities.ContactTelDetailEntity; import javax.annotation.Generated; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(ContactTelDetailEntity.class) public abstract class ContactTelDetailEntity_ { public static volatile SingularAttribute<ContactTelDetailEntity, String> telType; public static volatile SingularAttribute<ContactTelDetailEntity, String> telNumber; public static volatile SingularAttribute<ContactTelDetailEntity, ContactEntity> contact; public static volatile SingularAttribute<ContactTelDetailEntity, Integer> id; public static volatile SingularAttribute<ContactTelDetailEntity, Integer> version; } |
HobbyEntity_:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package ru.javastudy.entities; import ru.javastudy.entities.ContactEntity; import ru.javastudy.entities.HobbyEntity; import javax.annotation.Generated; import javax.persistence.metamodel.SetAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.persistence.metamodel.StaticMetamodel; @Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor") @StaticMetamodel(HobbyEntity.class) public abstract class HobbyEntity_ { public static volatile SingularAttribute<HobbyEntity, String> hobbyId; public static volatile SetAttribute<HobbyEntity, ContactEntity> contacts; } |
После создания метамодели появляется возможность создания запроса с критерием, который будет проверяться на этапе компиляции, а не во время выполнения, т.к. он строго типизирован.
3. Создание запроса с критерием
Создадим один метод для запроса с критерием JPA 2:
interface ContactService:
1 2 3 4 5 |
public interface ContactService { //Запрос с критерием List<ContactEntity> findByCriteriaQuery(String firstName, String lastName); } |
Реализация ContactServiceImpl:
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 |
package ru.javastudy.impl; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.javastudy.entities.ContactEntity; import ru.javastudy.intf.ContactService; import ru.javastudy.metamodel.ContactEntity_; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.JoinType; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.List; @Repository @Service("jpaContactService") @Transactional public class ContactServiceImpl implements ContactService { @PersistenceContext private EntityManager em; //остальной код не показан public List<ContactEntity> findAllByNativeQuery2() { return em.createNativeQuery(ALL_CONTACT_NATIVE_QUERY, "nativeSqlResult").getResultList(); } @Transactional(readOnly = true) public List<ContactEntity> findByCriteriaQuery(String firstName, String lastName) { CriteriaBuilder cb = em.getCriteriaBuilder(); javax.persistence.criteria.CriteriaQuery<ContactEntity> criteriaQuery = cb.createQuery(ContactEntity.class); Root<ContactEntity> contactEntityRoot = criteriaQuery.from(ContactEntity.class); contactEntityRoot.fetch(ContactEntity_.contactTelDetails, JoinType.LEFT); contactEntityRoot.fetch(ContactEntity_.hobbies, JoinType.LEFT); criteriaQuery.select(contactEntityRoot).distinct(true); Predicate criteria = cb.conjunction(); //firstName if (firstName != null) { Predicate p = cb.equal(contactEntityRoot.get(ContactEntity_.firstName), firstName); criteria = cb.and(criteria, p); } //lastName if (lastName != null) { Predicate p = cb.equal(contactEntityRoot.get(ContactEntity_.lastName), lastName); criteria = cb.and(criteria, p); } criteriaQuery.where(criteria); List<ContactEntity> result = em.createQuery(criteriaQuery).getResultList(); return result; } } |
Рассмотрим код подробнее.
- Создаем экземпляр CriteriaBuilder с помощью вызова EntityManager.getCriteriaBuilder()
- Создаем типизированный запрос с помощью билдера, передавая параметром результирующий тип. Используется CriteriaQuery из пакета javax.persistence.
- Формируем основу для путевых выражений внутри запроса. Для этого вызывается CriteriaQuery.from() с передачей сущностного класса в качестве параметра. Результатом станет объект корня запроса Root<ContactEntity>.
- Root.fetch() обеспечивает незамедлительную выборку (по умолчанию в Hibernate (поставщик постоянства для JPA) — отложенная выборка). JoinType.LEFT — задает внешнее соединение. *Если вы читали предыдущие статьи, то выражение эквивалентно left join fetch в JPQL.
- Далее вызов CriteriaQuery.select() вместе с параметром корня запроса и методом distinct(true). Последний метод устранит дублированные записи.
- Экземпляр Predicate — ограничение, которое указывает критерий выборки, определенный выражением. Может быть простым или сложным. Получаем с помощью вызова CriteriaBuilder.conjuction().
- equal() — ожидаемо означает равенство. Внутри него вызывается Root.get() в котором передается атрибут метамодели сущностного класса, к которому будет применяться ограничение. CriteriaBuilder.and() объединяет предикат с существующим предикатом из переменной criteria.
- Полученный предикат со всеми ограничениями передается запросу с помощью CriteriaQuery.where().
- Создаем и вызываем запрос.
4. Тестирование запроса с критерием
Простенький метод для вызова запроса с критерием:
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 |
public class Main { public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:spring-config.xml"); ctx.refresh(); criteriaExample(ctx); } private static void criteriaExample(GenericXmlApplicationContext ctx) { ContactService service = ctx.getBean("jpaContactService", ContactService.class); List<ContactEntity> criteriaResult = service.findByCriteriaQuery("Name0", "LastName0"); for (ContactEntity contact : criteriaResult) { System.out.println(contact); if (contact.getContactTelDetails() != null) { for (ContactTelDetailEntity tel : contact.getContactTelDetails()) { System.out.println(tel); } } if (contact.getHobbies() != null) { for (HobbyEntity hobby : contact.getHobbies()) { System.out.println(hobby); } } } } |
Вывод:
ContactEntity{id=3, Name0′, LastName0′, 2015-07-08, 0}
ContactTelDetailEntity{version=0, telNumber=’555-44-33′, telType=’Рабочий’, id=3}
ContactTelDetailEntity{version=0, telNumber=’8-800-200-600′, telType=’Домашний’, id=1}
Исходные коды
JPA insert delete SQL — база данных использованная в цикле статей
JPA Tutorial — criteria — src
5