В JPA можно загружать сущности из базы по их id или по их типу. В первом случае загружается какая-то конкретная сущность, во втором — все сущности указанного типа.
В принципе с этим уже можно работать — загружаешь все сущности в память да обрабатываешь их с помощью Stream api. Разумеется в реальности никто так делать не будет: памяти на всех не хватит, загружать все сущности долго, обрабатываться они будут медленно и вообще моветон. Было бы гораздо лучше, если бы можно было загружать только нужные сущности и желательно бы это делать в sql стиле, описывая декларативно, что надо загрузить.
Java Persistence Query Language
Аналогом SQL в мире JPA является JPQL — Java Persistence Query Language. Фактически это как SQL, только запросы делаются не к таблицам, а к классам. Самый простой пример использования JPQL, это загрузка всех сущностей определённого типа, как я уже показывал раньше:
1
2
3
|
em.createQuery("from Person")
.getResultList()
.forEach(System.out::println);
|
1
|
Person{firstName='Test', lastName='Testoff', dob=2016-05-13, passport=Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}, primaryAddress=Address{city='Kickapoo', street='Main street', building='1', tenants=Test}, workingPlaces=[Company{name='Acme Ltd', workers=Test}]}
|
"from Person" буквально означает «дай-ка мне сущностей класса Person без каких-либо ограничений». Если же хочется, можно и уточнить, что нужна не Person сущность, а её поля:
1
2
3
|
em.createQuery("select passport from Person ", Passport.class)
.getResultList()
.forEach(System.out::println);
|
1
|
Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}
|
В данном примере я говорю JPA «А дай-ка мне все поля passport из сущностей класса Person без каких-либо ограничений» и в ответ мне возвращаются именно сущности Passport, а не Person, даже не смотря на "from Person".
Методу createQuery() можно передавать опциональный тип возвращаемого объекта. Независимо от того, передали тип объектаили нет, JPA в любом случае самостоятельно определяет, что фактически возвращается из базы, а передаваемый тип используется только для строгой типизации во время компиляции. Например, если я в предудыщем примере заменю Passport.class на Person.class, компиляция пройдёт успешно, коллекция, возвращаемая getResultList(), будет иметь тип List<Person>, но при исполнении всё сломается:
1
2
3
|
em.createQuery("select passport from Person ", Person.class)
.getResultList()
.forEach(System.out::println);
|
1
|
java.lang.IllegalArgumentException: Type specified for TypedQuery [ru.easyjava.data.jpa.hibernate.entity.Person] is incompatible with query return type [class ru.easyjava.data.jpa.hibernate.entity.Passport]
|
Условия
Запросы вида «выбери мне свойство» прикольные, но бесполезные. Полезнее было бы по этим свойствам фильтровать:
1
2
3
|
em.createQuery("from Passport as p where p.owner.lastName='Testoff'")
.getResultList()
.forEach(System.out::println);
|
1
|
Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}
|
Этот запрос означает «найди мне все сущности класса Passport, в поле owner которых хранятся сущности, поле lastName которых равно Testoff». Практически как обычный SQL, только в условиях пишутся не имена столбцов в таблицах, а имена полей в классах.
Можно использовать и обычные SQL выражения, такие как is null, between, like и так далее:
1
2
3
4
|
em.createQuery("from Passport as p where p.owner.lastName like :name")
.setParameter("name", "Te%")
.getResultList()
.forEach(System.out::println);
|
1
|
Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}
|
Параметры в запросах поддерживаются автоматически и не требуют особого обращения, в отличие от jdbc. Достаточно написать имя параметра, предварённое двоеточием, и задать его значение методом setParameter(). Помимо именованных параметров поддерживаются и позиционные:
1
2
3
4
|
em.createQuery("Select p from Person as p, IN(p.workingPlaces) as wp where wp.name = ?1")
.setParameter(1, "Acme Ltd")
.getResultList()
.forEach(System.out::println);
|
1
|
Person{firstName='Test', lastName='Testoff', dob=2016-05-13, passport=Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}, primaryAddress=Address{city='Kickapoo', street='Main street', building='1', tenants=Test}, workingPlaces=[Company{name='Acme Ltd', workers=Test}]}
|
В запросе выше показывается, как обращаться с связанным сущностями, используя оператор IN, представляющий хранящуюся в сущности коллекцию как элемент, к которому можно обратиться.
Именованные запросы
В JPA, помимо параметризованных запросов предусмотрели и статические именованные запросы, которые задаются отдельно, обычно поближе к сущности:
1
2
|
@NamedQuery(name = "findCompaniesWithWorkerPassport",
query = "Select c from Company as c, IN(c.workers) as w where w.passport.series = :series")
|
1
2
3
4
|
em.createNamedQuery("findCompaniesWithWorkerPassport")
.setParameter("series", "AS")
.getResultList()
.forEach(System.out::println);
|
1
|
Company{name='Acme Ltd', workers=Test}
|
Именованные запросы позволяют повторно использовать один и тот же запрос в разных местах кода, тем самым уменьшая количество повторений в программе и упрощая его редактирование в будущем. Кроме того, отделение кода запроса от места его исполнения позволяет последнему не заботиться вовсе о том, какой запрос фактически исполняется, уменьшая тем самым связность кода.
Код примера доступен на github.