Автор оригинала: 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 с открытым исходным кодом.