Когда вы используете JPA — иногда — JPQL не справится, и вам придется прибегнуть к нативному SQL. С самого начала ORM, такие как Hibernate , оставляли открытую «черную дверь» для этих случаев и предлагали API, аналогичный Spring JdbcTemplate , Apache DbUtils или jOOQ для простого SQL . Это полезно, поскольку вы можете продолжать использовать ORM в качестве единой точки входа для взаимодействия с базой данных.
Однако написание сложного динамического SQL с использованием конкатенации строк утомительно и подвержено ошибкам, а также открывает возможность для уязвимостей, связанных с инъекцией SQL . Использование API безопасного типа, такого как jOOQ, было бы очень полезно, но вам может быть трудно поддерживать две разные модели соединений, транзакций и сессий в одном приложении только для 10-15 собственных запросов.
Но правда в том, что
Вы используете JOOQ для ваших собственных запросов JPA!
Это правда! Есть несколько способов добиться этого.
Извлечение кортежей (т.е. Object [])
Самый простой способ — не использовать какие-либо из расширенных функций JPA, а просто извлекать кортежи в собственной форме Object[]
JPA. Предполагая этот простой служебный метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
public static List<Object[]> nativeQuery( EntityManager em, org.jooq.Query query ) { // Extract the SQL statement from the jOOQ query: Query result = em.createNativeQuery(query.getSQL()); // Extract the bind values from the jOOQ query: List<Object> values = query.getBindValues(); for ( int i = 0 ; i < values.size(); i++) { result.setParameter(i + 1 , values.get(i)); } return result.getResultList(); } |
Использование API
Это все, что вам нужно для объединения двух API в их простейшей форме для выполнения «сложных» запросов через EntityManager
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
List<Object[]> books = nativeQuery(em, DSL.using(configuration) .select( AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME, BOOK.TITLE ) .from(AUTHOR) .join(BOOK) .on(AUTHOR.ID.eq(BOOK.AUTHOR_ID)) .orderBy(BOOK.ID)); books.forEach((Object[] book) -> System.out.println(book[ 0 ] + " " + book[ 1 ] + " wrote " + book[ 2 ])); |
Согласитесь, в результатах нет большой безопасности типов — поскольку мы получаем только Object[]
. Мы с нетерпением ждем будущей Java, которая будет поддерживать типы кортежей (или даже записей), такие как Scala или Ceylon .
Поэтому лучшим решением может быть следующее:
Выборка объектов
Предположим, у вас есть следующие очень простые сущности:
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
|
@Entity @Table (name = "book" ) public class Book { @Id public int id; @Column (name = "title" ) public String title; @ManyToOne public Author author; } @Entity @Table (name = "author" ) public class Author { @Id public int id; @Column (name = "first_name" ) public String firstName; @Column (name = "last_name" ) public String lastName; @OneToMany (mappedBy = "author" ) public Set<Book> books; } |
Предположим, мы добавим дополнительный служебный метод, который также передает ссылку на Class
в EntityManager
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public static <E> List<E> nativeQuery( EntityManager em, org.jooq.Query query, Class<E> type ) { // Extract the SQL statement from the jOOQ query: Query result = em.createNativeQuery( query.getSQL(), type); // Extract the bind values from the jOOQ query: List<Object> values = query.getBindValues(); for ( int i = 0 ; i < values.size(); i++) { result.setParameter(i + 1 , values.get(i)); } // There's an unsafe cast here, but we can be sure // that we'll get the right type from JPA return result.getResultList(); } |
Использование API
Теперь это довольно удобно, просто поместите свой запрос jOOQ в этот API и получите от него сущности JPA — лучшее из обоих миров, поскольку вы можете легко добавлять / удалять вложенные коллекции из извлеченных сущностей, как если бы вы извлекали их через JPQL:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
List<Author> authors = nativeQuery(em, DSL.using(configuration) .select() .from(AUTHOR) .orderBy(AUTHOR.ID) , Author. class ); // This is our entity class here authors.forEach(author -> { System.out.println(author.firstName + " " + author.lastName + " wrote" ); books.forEach(book -> { System.out.println( " " + book.title); // Manipulate the entities here. Your // changes will be persistent! }); }); |
Выборка EntityResults
Если вы очень дерзкие и испытываете странные чувства к аннотациям или просто хотите пошутить над коллегами перед тем, как уйти в отпуск, вы также можете прибегнуть к помощи javax.persistence.SqlResultSetMapping
JPA. Представьте себе следующее объявление сопоставления:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
@SqlResultSetMapping ( name = "bookmapping" , entities = { @EntityResult ( entityClass = Book. class , fields = { @FieldResult (name = "id" , column = "b_id" ), @FieldResult (name = "title" , column = "b_title" ), @FieldResult (name = "author" , column = "b_author_id" ) } ), @EntityResult ( entityClass = Author. class , fields = { @FieldResult (name = "id" , column = "a_id" ), @FieldResult (name = "firstName" , column = "a_first_name" ), @FieldResult (name = "lastName" , column = "a_last_name" ) } ) } ) |
По сути, вышеприведенное объявление отображает столбцы базы данных ( @SqlResultSetMapping -> entities -> @EntityResult -> fields -> @FieldResult -> column
) на объекты и их соответствующие атрибуты. С помощью этого мощного метода вы можете генерировать результаты сущностей из любого вида результата SQL-запроса.
Опять же, мы создадим небольшой маленький вспомогательный метод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
public static <E> List<E> nativeQuery( EntityManager em, org.jooq.Query query, String resultSetMapping ) { // Extract the SQL statement from the jOOQ query: Query result = em.createNativeQuery( query.getSQL(), resultSetMapping); // Extract the bind values from the jOOQ query: List<Object> values = query.getBindValues(); for ( int i = 0 ; i < values.size(); i++) { result.setParameter(i + 1 , values.get(i)); } // This implicit cast is a lie, but let's risk it return result.getResultList(); } |
Обратите внимание, что в приведенном выше API-интерфейсе используется анти-шаблон , что в данном случае нормально, так как JPA, во-первых, не является API-интерфейсом, безопасным для типов.
Использование API
Теперь, опять же, вы можете передать ваш типобезопасный jOOQ-запрос в EntityManager
через вышеуказанный API, передавая имя SqlResultSetMapping
следующим образом:
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
|
List<Object[]> result = nativeQuery(em, DSL.using(configuration .select( AUTHOR.ID.as( "a_id" ), AUTHOR.FIRST_NAME.as( "a_first_name" ), AUTHOR.LAST_NAME.as( "a_last_name" ), BOOK.ID.as( "b_id" ), BOOK.AUTHOR_ID.as( "b_author_id" ), BOOK.TITLE.as( "b_title" ) ) .from(AUTHOR) .join(BOOK).on(BOOK.AUTHOR_ID.eq(AUTHOR.ID)) .orderBy(BOOK.ID)), "bookmapping" // The name of the SqlResultSetMapping ); result.forEach((Object[] entities) -> { JPAAuthor author = (JPAAuthor) entities[ 1 ]; JPABook book = (JPABook) entities[ 0 ]; System.out.println(author.firstName + " " + author.lastName + " wrote " + book.title); }); |
Результатом в этом случае снова является Object[]
, но на этот раз Object[]
не представляет кортеж с отдельными столбцами, но представляет объекты, как объявлено аннотацией SqlResultSetMapping
.
Этот подход интригует и, вероятно, его используют, когда вам нужно отобразить произвольные результаты запросов, но при этом все еще требуются управляемые объекты. Мы можем только порекомендовать интересную серию блогов Торбена Янссена об этих расширенных функциях JPA, если вы хотите узнать больше:
- Отображение набора результатов: основы
- Сопоставление набора результатов: сложные сопоставления
- Сопоставление набора результатов: сопоставления результатов конструктора
- Сопоставление результирующего набора: специфические функции Hibernate
Вывод
Выбор между ORM и SQL (или между Hibernate и jOOQ , в частности) не всегда прост.
- ORM блестят, когда дело доходит до применения постоянства графов объектов, т.е. когда у вас много сложных CRUD, включая сложные стратегии блокировки и транзакций.
- SQL лучше всего подходит для выполнения массового SQL, как для операций чтения, так и для записи, при выполнении аналитики и создания отчетов.
Когда вам «везет» (как, например, работа проста), ваше приложение находится только с одной стороны, и вы можете выбирать между ORM и SQL. Когда вам «повезет» (как в — оооо, это интересная проблема), вам придется использовать оба. ( См. Также интересную статью Майка Хэдлоу на эту тему )
Сообщение здесь: Вы можете! Используя собственный API запросов JPA, вы можете запускать сложные запросы, используя всю мощь своей РСУБД, и по-прежнему отображать результаты в объекты JPA. Вы не ограничены использованием JPQL.
Примечание
В то время как в прошлом мы критиковали некоторые аспекты JPA (подробнее см., Как JPA 2.1 стал новым EJB 2.0 ), наша критика была в основном сфокусирована на (ан-) использовании JPA аннотаций. Когда вы используете API безопасного типа, например jOOQ, вы можете предоставить компилятору всю необходимую информацию о типе, чтобы легко создавать результаты. Мы убеждены, что будущая версия JPA будет более активно использовать систему типов Java, что позволит более свободно интегрировать SQL, JPQL и сохранение сущностей.
Ссылка: | Напечатайте Безопасные Запросы для API собственных запросов JPA от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ . |