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

Лучший способ использовать преобразователь результатов гибернации

Узнайте, как наилучшим образом настроить результирующий набор данного запроса JPA или Hibernate, будь то JPQL, API критериев или собственный SQL, с помощью ResultTransformer.

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

Вступление

В этой статье я собираюсь представить лучший способ использования преобразователя результатов гибернации, чтобы вы могли настроить набор результатов для данного JPA или запроса гибернации.

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

Модель предметной области

Давайте предположим, что у нас есть следующая Запись сущность:

Объект Post отображается следующим образом:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    private Long id;

    private String title;

    @Column(name = "created_on")
    private LocalDate createdOn;

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }

    public LocalDate getCreatedOn() {
        return createdOn;
    }

    public Post setCreatedOn(LocalDate createdOn) {
        this.createdOn = createdOn;
        return this;
    }
}

Обратите внимание, что атрибут CreatedOn имеет тип Локальные данные , который поддерживается JPA 2.2 и находится в спящем режиме с версии 5 .

Поскольку сущность Post использует API в стиле , намного проще создать сущность Post и передать ее непосредственно методу persist , как показано в следующем примере:

entityManager.persist(
    new Post()
        .setId(1L)
        .setTitle(
            "High-Performance Java Persistence " +
            "eBook has been released!")
        .setCreatedOn(LocalDate.of(2016, 8, 30))
);

entityManager.persist(
    new Post()
        .setId(2L)
        .setTitle(
            "High-Performance Java Persistence " +
            "paperback has been released!")
        .setCreatedOn(LocalDate.of(2016, 10, 12))
);

entityManager.persist(
    new Post()
        .setId(3L)
        .setTitle(
            "High-Performance Java Persistence " +
            "Mach 1 video course has been released!")
        .setCreatedOn(LocalDate.of(2018, 1, 30))
);

entityManager.persist(
    new Post()
        .setId(4L)
        .setTitle(
            "High-Performance Java Persistence " +
            "Mach 2 video course has been released!")
        .setCreatedOn(LocalDate.of(2018, 5, 8))
);

entityManager.persist(
    new Post()
        .setId(5L)
        .setTitle(
            "Hypersistence Optimizer has been released!")
        .setCreatedOn(LocalDate.of(2019, 3, 19))
);

Подсчет сообщений по годам

Теперь мы хотим подсчитать количество сообщений, публикуемых каждый год, чтобы мы могли использовать следующий запрос JPQL:

select 
   YEAR(p.createdOn) as year, 
   count(p) as postCount 
from Post p 
group by 
   YEAR(p.createdOn) 
order by 
   YEAR(p.createdOn)"

Однако, поскольку этот запрос возвращает проекцию, мы хотим инкапсулировать ее в DTO, как показано ниже Количество записей по годам класс:

public class PostCountByYear {

    private final int year;

    private final int postCount;

    public PostCountByYear(
            int year, 
            int postCount) {
        this.year = year;
        this.postCount = postCount;
    }

    public int getYear() {
        return year;
    }

    public int getPostCount() {
        return postCount;
    }
}

Один из вариантов заполнения Количества записей по годам – это результат конструктора JPA, как описано в этой статье .

Однако ResultTransformer является еще более гибким, поскольку позволяет нам агрегировать данные любым удобным для нас способом и даже выбирать возвращаемый тип.

Преобразователь результатов спящего режима

Чтобы использовать ResultTransformer , нам нужно развернуть JPA Запрос в режим гибернации org.hibernate.запрос.Запрос , который дает нам доступ к setResultTransformer методу:

List postCountByYearMap = (List) 
entityManager
.createQuery(
    "select " +
    "   YEAR(p.createdOn) as year, " +
    "   count(p) as postCount " +
    "from Post p " +
    "group by " +
    "   YEAR(p.createdOn) " +
    "order by " +
    "   YEAR(p.createdOn)")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(
    new ResultTransformer() {
        @Override
        public Object transformTuple(
                Object[] tuple, 
                String[] aliases) {
            return new PostCountByYear(
                ((Number) tuple[0]).intValue(),
                ((Number) tuple[1]).intValue()
            );
        }

        @Override
        public List transformList(List tuples) {
            return tuples;
        }
    }
)
.getResultList();

Не пугайтесь того факта, что setResultTransformer устарел в Hibernate 5.2. Теоретически, он не должен был устареть, так как альтернативы его использованию нет.

Причина, по которой он был признан устаревшим, заключается в том, что в Hibernate 6 будет предоставлена альтернатива @Function interface , но миграция, вероятно, будет простой, поэтому не отбрасывайте ResultTransformer только потому, что он устарел слишком рано.

Проблема с преобразователем ResultTransformer по умолчанию заключается в том, что мы не можем использовать лямбда-код Java для преобразования Объекта[] кортежа, представляющего запись в JDBC ResltSet .

Преобразователь результатов списка, гораздо лучший преобразователь результатов спящего режима

Интерфейс ResultTransformer должен был с самого начала определять только метод transformTuple . Список преобразования был добавлен только для размещения DistinctRootEntityResultTransformer .

Было бы намного лучше, если бы вместо добавления метода transformList в интерфейс ResultTransformer и большинства реализаций просто возвращал список неизмененных кортежей, был добавлен новый интерфейс ListResultTransformer для расширения ResultTransformer и определения метода transformList .

Мы можем легко решить эту проблему, определив интерфейс List ResultTransformer следующим образом:

@FunctionalInterface
public interface ListResultTransformer extends ResultTransformer {

    /**
     * Default implementation returning the tuples list as-is.
     *
     * @param tuples tuples list
     * @return tuples list
     */
    @Override
    default List transformList(List tuples) {
        return tuples;
    }
}

Вам даже не нужно его определять. Вы можете получить его из проекта hibernate-types с открытым исходным кодом .

Просто добавьте зависимость hibernate-types в соответствии с используемой версией Hibernate и начните ее использовать:


    com.vladmihalcea
    hibernate-types-55
    ${hibernate-types.version}

С помощью Преобразователя результатов списка мы можем переписать предыдущий Преобразователь результатов пример следующим образом:

List postCountByYearMap = (List) 
entityManager
.createQuery(
    "select " +
    "   YEAR(p.createdOn) as year, " +
    "   count(p) as postCount " +
    "from Post p " +
    "group by " +
    "   YEAR(p.createdOn) " +
    "order by " +
    "   YEAR(p.createdOn)")
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(
    (ListResultTransformer) 
    (tuple, aliases) -> new PostCountByYear(
        ((Number) tuple[0]).intValue(),
        ((Number) tuple[1]).intValue()
    )
)
.getResultList();

Намного лучше, правда?

Хотя в вышеупомянутом примере использовался запрос JPQL, ResultTransformer также может быть применен к API критериев или собственным SQL-запросам, поэтому он не ограничивается только запросами JPQL.

Вывод

ResultTransformer – это очень мощный механизм, позволяющий программно настраивать набор результатов JPA или гибернации запросов.

Даже если у ResultTransformer по умолчанию есть первоначальный недостаток в дизайне , который не позволяет использовать его в качестве функционального интерфейса , мы можем преодолеть это ограничение, используя ListResultTransformer , поставляемый проектом hibernate-types с открытым исходным кодом.