Рубрики
Без рубрики

Лучший способ использовать функции SQL в запросах JPQL или API критериев с помощью JPA и Hibernate

Узнайте, как зарегистрировать и вызвать любую функцию SQL в запросах API критериев JPQL, HQL или JPA с помощью JPA и Hibernate.

Автор оригинала: Vlad Mihalcea.

Вступление

При выполнении запроса сущности (например, JPQL, HQL или API критериев) вы можете использовать любую функцию SQL без необходимости ее регистрации, если функция передается непосредственно в предложение WHERE базового оператора SQL.

Однако, если функция SQL используется в предложении SELECT, и Hibernate не зарегистрировал функцию SQL (будь то функция, зависящая от базы данных или определяемая пользователем), вам придется зарегистрировать функцию перед ее использованием в запросе сущности.

В этой статье вы узнаете о различных способах регистрации функций SQL с помощью JPA и гибернации.

Проекция DTO с помощью GROUP_CONCAT

Как уже объяснялось в этой статье , прогнозы идеально подходят для отчетов и аналитики, и по этой причине мы используем следующее Опубликуйте резюме D В bean для хранения каждой записи нашего отчета о публикации резюме:

Теперь, поскольку наше приложение работает на MySQL, мы можем использовать GROUP_CONCAT для объединения нескольких строковых значений, принадлежащих одной и той же группе.

Запрос JPQL, который создает проекцию DTO, выглядит следующим образом:

List postSummaries = entityManager
.createQuery("""
    select
       p.id as id,
       p.title as title,
       group_concat(t.name) as tags
    from Post p
    left join p.tags t
    group by p.id, p.title
	""")
.unwrap(Query.class)
.setResultTransformer(
    Transformers.aliasToBean(PostSummaryDTO.class)
)
.getResultList();

Или вместо этого мы могли бы использовать API критериев JPA:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

CriteriaQuery cq = cb.createQuery(
    PostSummaryDTO.class
);

Root post = cq.from(Post.class);
Join tags = post.join("tags", JoinType.LEFT);
cq.groupBy(post.get("id"), post.get("title"));

cq.select(
    cb
    .construct(
        PostSummaryDTO.class,
        post.get("id"),
        post.get("title"),
        cb.function(
            "group_concat", 
            String.class, 
            tags.get("name")
        )
    )
);

List postSummaries = entityManager
.createQuery(cq)
.getResultList();

Написание запросов API критериев JPA не очень просто. Плагин Codota IDE может помочь вам в написании таких запросов, что повысит вашу производительность.

Для получения более подробной информации о том, как вы можете использовать Codota для ускорения процесса написания запросов API критериев, ознакомьтесь с этой статьей .

Однако, если мы попытаемся выполнить следующий запрос JPQL или API критериев, по умолчанию Hibernate выдаст следующее Исключение QueryException при анализе запроса JPQL:

java.lang.IllegalArgumentException: org.hibernate.QueryException: No data type for node: org.hibernate.hql.internal.ast.tree.MethodNode 
 \-[METHOD_CALL] MethodNode: '('
    +-[METHOD_NAME] IdentNode: 'group_concat' {originalText=group_concat}
    \-[EXPR_LIST] SqlNode: 'exprList'
       \-[DOT] DotNode: 'groupconca2_.name' {propertyName=name,dereferenceType=PRIMITIVE,getPropertyPath=name,path=t.name,tableAlias=groupconca2_,className=com.vladmihalcea.book.hpjp.hibernate.query.function.GroupConcatFunctionTest$Tag,classAlias=t}
          +-[ALIAS_REF] IdentNode: 'groupconca2_.id' {alias=t, className=com.vladmihalcea.book.hpjp.hibernate.query.function.GroupConcatFunctionTest$Tag, tableAlias=groupconca2_}
          \-[IDENT] IdentNode: 'name' {originalText=name}
 [select    p.id as id,    p.title as title,    group_concat(t.name) as tags from com.vladmihalcea.book.hpjp.hibernate.query.function.GroupConcatFunctionTest$Post p left join p.tags t group by p.id, p.title]

Проблема в том, что Hibernate не распознает функцию group_concat SQL, поэтому запрос JPQL не может быть проанализирован.

Теперь существует несколько способов регистрации такой функции SQL, и мы рассмотрим их все следующим образом.

В Hibernate 5 API критериев анализируется в JPQL, поэтому все, что относится к анализу запросов JPQL, также применимо к запросам API критериев.

Регистрация функции SQL с помощью диалекта Hibernate

Наиболее известный способ регистрации функции SQL-это пользовательский режим гибернации Диалект .

public class CustomMySQLDialect 
        extends MySQL57Dialect {
    public CustomMySQLDialect() {
        super();

        registerFunction(
            "group_concat",
            new StandardSQLFunction(
                "group_concat", 
                StandardBasicTypes.STRING
            )
        );
    }
}

И использование Пользовательского MySQLDialect при загрузке в спящий режим:


    name="hibernate.dialect" 
    value="com.vladmihalcea.book.hpjp.hibernate.query.function.CustomMySQLDialect"

Однако у этого метода есть большой недостаток. Каждый раз , когда нам нужно обновить Диалект , мы должны помнить, что нужно изменить Пользовательский класс MySQLDialect , чтобы расширить новый Диалект . Таким образом, было бы гораздо удобнее, если бы мы могли просто зарегистрировать функцию SQL без необходимости переопределять классы гибернации.

Регистрация функции SQL с помощью JPA и конструктора метаданных.

Начиная с Hibernate 5.2.18, вы можете использовать утилиту Конструктор метаданных для настройки Конструктор метаданных , даже если вы загружаетесь через JPA.

Интерфейс Конструктор метаданных может быть реализован следующим образом:

public class SqlFunctionsMetadataBuilderContributor 
        implements MetadataBuilderContributor {
		
    @Override
    public void contribute(MetadataBuilder metadataBuilder) {
        metadataBuilder.applySqlFunction(
            "group_concat",
            new StandardSQLFunction(
                "group_concat", 
                StandardBasicTypes.STRING
            )
        );
    }
}

И мы можем предоставить Конструктор метаданных функций Sql через hibernate.metadata_builder_contributor свойство конфигурации:


    name="hibernate.metadata_builder_contributor" 
    value="com.vladmihalcea.book.hpjp.hibernate.query.function.SqlFunctionsMetadataBuilderContributor"

Вывод

Хотя вы всегда можете запустить собственный SQL-запрос, если хотите воспользоваться функциями, специфичными для базы данных, которые недоступны в JPQL, если вы создаете JPQL динамически с помощью API критериев, вы можете вызвать любую функцию SQL, если Hibernate знает об этом.