Статьи

Как получить строку JPQL / SQL из запроса CriteriaQuery в JPA?

Это полно сложных вещей, которые должны (а иногда и могут) быть простыми. Получение представления JQPL / SQL String для JPA 2.0 CriteriaQuery является одним из них.

К настоящему времени вы все знаете API критериев JPA 2.0 : безопасный для типов способ написания запроса JQPL. Этот API является умным в том смысле, что вы не используете Strings для построения вашего запроса, но довольно многословен… и иногда вы теряетесь в десятках строк Java-кода, просто чтобы написать простой запрос. Вы заблудились в своем CriteriaQuery , вы не знаете, почему ваш запрос не работает, и вы хотели бы отладить его . Но как вы отлаживаете это? Ну, одним из способов было бы просто отобразить представление JPQL и / или SQL. Просто, не правда ли? Да, но JPA 2.0 javax.persistence.Query не имеет API для этого. Затем вам нужно полагаться на реализацию … то есть код будет другим, если вы используете EclipseLink , Hibernateили OpenJPA .

CriteriaQuery, который мы хотим отладить

Допустим, у вас есть простой объект Book, и вы хотите получить все книги, отсортированные по их идентификатору. Что-то вроде SELECT b FROM Book b ORDER BY b.id DESC . Как бы вы написали это с помощью CriteriaQuery? Что-то вроде этих 5 строк кода Java:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> q = cb.createQuery(Book.class);
Root<Book> b = q.from(Book.class);
q.select(b).orderBy(cb.desc(b.get("id")));
TypedQuery<Book> findAllBooks = em.createQuery(q);

Так что представьте, когда у вас есть более сложные. Иногда вы просто заблудились, он глючит, и вы хотели бы иметь представление JPQL и / или SQL String, чтобы узнать, что происходит. Затем вы можете даже протестировать его.

Получение строковых представлений JPQL / SQL для запроса критериев

Итак, давайте использовать API для получения представлений CriteriaQuery в формате JPQL / SQL String (точнее, TypedQuery, созданный из CriteriaQuery ). Плохая новость заключается в том, что для этого не существует стандартного API JPA 2.0. Вам нужно использовать API реализации, надеясь, что реализация позволяет это (слава богу, что это (почти) имеет место для 3 основных платформ ORM JPA). Хорошая новость заключается в том , что запрос интерфейс (и , следовательно , TypedQuery ) имеет UnWrap метод. Этот метод возвращает реализацию API запроса провайдера. Давайте посмотрим, как вы можете использовать его с EclipseLink , Hibernate и OpenJPA .

EclipseLink

EclipseLink представление запроса «s является org.eclipse.persistence.jpa.JpaQuery интерфейс и org.eclipse.persistence.internal.jpa.EJBQueryImpl реализации. Этот интерфейс предоставляет вам встроенный собственный запрос ( org.eclipse.persistence.queries.DatabaseQuery ) с двумя очень удобными методами: getJPQLString () и getSQLString (). К сожалению, метод getJPQLString () не будет переводить CriteriaQuery в JPQL, он работает только для запросов, изначально написанных на JPQL (динамический или именованный запрос). Метод getSQLString () полагается на то, что запрос «подготовлен», что означает, что вы должны выполнить запрос один раз, прежде чем получите представление SQL String.

findAllBooks.unwrap(JpaQuery.class).getDatabaseQuery().getJPQLString(); // doesn't work for CriteriaQuery
findAllBooks.unwrap(JpaQuery.class).getDatabaseQuery().getSQLString();

Hibernate

Представлением запроса Hibernate является org.hibernate.Query . Этот интерфейс имеет несколько реализаций и очень полезный метод, который возвращает строку SQL-запроса: getQueryString (). Я не смог найти метод, который возвращает представление JPQL, если я что-то пропустил, пожалуйста, дайте мне знать.

findAllBooks.unwrap(org.hibernate.Query.class).getQueryString()

OpenJPA

Представление OpenJPA Query — org.apache.openjpa.persistence.QueryImpl, а также имеет метод getQueryString (), который возвращает SQL (а не JPQL). Он делегирует вызов внутреннему интерфейсу org.apache.openjpa.kernel.Query . Я не смог найти метод, который возвращает представление JPQL, если я что-то пропустил, пожалуйста, дайте мне знать.

findAllBooks.unwrap(org.apache.openjpa.persistence.QueryImpl.class).getQueryString()

Модульное тестирование

После того как вы получили SQL-строку, почему бы не проверить ее модульно? Эй, но я не хочу проверять свой ORM, зачем мне это делать? Что ж, бывает, что я обнаружил, но в новых выпусках OpenJPA путем модульного тестирования запроса … так что для этого есть вариант использования. Во всяком случае, вот как вы могли бы сделать это:

assertEquals("SELECT b FROM Book b ORDER BY b.id DESC", 
findAllBooksCriteriaQuery.unwrap(org.apache.openjpa.persistence.QueryImpl.class).getQueryString());

Вывод

Как видите, получить представление String для TypedQuery не так просто. Вот дайджест трех основных ORM:

ORM Framework Реализация запроса Как получить строку JPQL Как получить строку SPQL
EclipseLink JpaQuery getDatabaseQuery (). getJPQLString () * getDatabaseQuery (). getSQLString () **
Hibernate запрос N / A getQueryString ()
OpenJPA QueryImpl getQueryString () N / A

(*) Возможно только для динамического или именованного запроса. Невозможно в CriteriaQuery
(**). Сначала нужно выполнить запрос, если нет, значение равно нулю.

Чтобы проиллюстрировать все это, я написал простые тестовые примеры с использованием EclipseLink , Hibernate и OpenJPA, которые вы можете загрузить с GitHub . Попробуйте и дайте мне знать.

А как насчет API в JPA 2.1?

Для точки зрения разработчиков было бы здорово иметь два метода в интерфейсе javax.persistence.Query (и, следовательно, javax.persistence.TypedQuery ), которые могли бы легко возвращать представления JPQL и SQL String, например: Query. getJPQLString () и Query.getSQLString (). Эй, это было бы идеальное время, чтобы иметь его в JPA 2.1, который будет отправлен менее чем через год. Теперь, как разработчик, это может быть сложно сделать, я хотел бы услышать вашу точку зрения по этому поводу.

В любом случае, я собираюсь отправить электронное письмо в экспертную группу JPA 2.1 … на всякий случай, если это будет в следующей версии JPA; o)

Рекомендации