Автор оригинала: 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
методу:
ListpostCountByYearMap = (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}
С помощью Преобразователя результатов списка
мы можем переписать предыдущий Преобразователь результатов
пример следующим образом:
ListpostCountByYearMap = (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
с открытым исходным кодом.