Java Persistence Query Language

В JPA можно загружать сущности из базы по их id или по их типу. В первом случае загружается какая-то конкретная сущность, во втором — все сущности указанного типа.

В принципе с этим уже можно работать — загружаешь все сущности в память да обрабатываешь их с помощью Stream api. Разумеется в реальности никто так делать не будет: памяти на всех не хватит, загружать все сущности долго, обрабатываться они будут медленно и вообще моветон. Было бы гораздо лучше, если бы можно было загружать только нужные сущности и желательно бы это делать в sql стиле, описывая декларативно, что надо загрузить.

Java Persistence Query Language

Аналогом SQL в мире JPA является JPQL — Java Persistence Query Language. Фактически это как SQL, только запросы делаются не к таблицам, а к классам. Самый простой пример использования JPQL, это загрузка всех сущностей определённого типа, как я уже показывал раньше:

em.createQuery("from Person")
  .getResultList()
  .forEach(System.out::println);
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 сущность, а её поля:

em.createQuery("select passport from Person ", Passport.class)
  .getResultList()
  .forEach(System.out::println);
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>, но при исполнении всё сломается:

em.createQuery("select passport from Person ", Person.class)
  .getResultList()
  .forEach(System.out::println);
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]

Условия

Запросы вида «выбери мне свойство» прикольные, но бесполезные. Полезнее было бы по этим свойствам фильтровать:

em.createQuery("from Passport as p where p.owner.lastName='Testoff'")
  .getResultList()
  .forEach(System.out::println);
Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}

Этот запрос означает «найди мне все сущности класса Passport, в поле owner которых хранятся сущности, поле lastName которых равно Testoff». Практически как обычный SQL, только в условиях пишутся не имена столбцов в таблицах, а имена полей в классах.

Можно использовать и обычные SQL выражения, такие как is null, between, like и так далее:

em.createQuery("from Passport as p where p.owner.lastName like :name")
  .setParameter("name", "Te%")
  .getResultList()
  .forEach(System.out::println);
Passport{series='AS', no='123456', issueDate=2016-05-13, validity=P20Y, owner=Testoff}

Параметры в запросах поддерживаются автоматически и не требуют особого обращения, в отличие от jdbc. Достаточно написать имя параметра, предварённое двоеточием, и задать его значение методом setParameter(). Помимо именованных параметров поддерживаются и позиционные:

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);
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, помимо параметризованных запросов предусмотрели и статические именованные запросы, которые задаются отдельно, обычно поближе к сущности:

@NamedQuery(name = "findCompaniesWithWorkerPassport",
query = "Select c from Company as c, IN(c.workers) as w where w.passport.series = :series")
em.createNamedQuery("findCompaniesWithWorkerPassport")
  .setParameter("series", "AS")
  .getResultList()
  .forEach(System.out::println);
Company{name='Acme Ltd', workers=Test}

Именованные запросы позволяют повторно использовать один и тот же запрос в разных местах кода, тем самым уменьшая количество повторений в программе и упрощая его редактирование в будущем.  Кроме того, отделение кода запроса от места его исполнения позволяет последнему не заботиться вовсе о том, какой запрос фактически исполняется, уменьшая тем самым связность кода.

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *