По сути, вопрос заключается в том, как выполнить проекцию из запроса JPA на тип возврата с вложенными значениями.Это то, чего на самом деле нет в JPA-запросах в настоящее время.
Помимо DTO, в Spring JPA есть проекционные интерфейсы, которые на самом деле могут обрабатывать немного вложенности (см. Spring Docs ).Это было бы достаточно простым вариантом, но вы все еще не можете легко привести его к Movie
.
. На данный момент основным другим вариантом является ResultTransformer
назад в Hibernate.Например, к этому можно обратиться, используя именованный запрос JPA, а затем вернуться к API запроса Hibernate перед его выполнением.
Это объявленный именованный запрос (слегка упрощенный по сравнению с доступными классами в примерах в вопросе):
@Entity
@NamedQuery(name = "Movie.byCelebrity",
query = "SELECT m.id, m.name, m.releaseDate, mc.characterName FROM Movie m JOIN m.movieCelebrities mc " +
"WHERE mc.role = :role")
public class Movie {
Затем его можно вызвать с помощью преобразователя результатов, например:
List<Movie> movies = entityManager
.createNamedQuery("Movie.byCelebrity")
.setParameter("role", role)
.unwrap(org.hibernate.query.Query.class)
.setResultTransformer(new MovieResultTransformer())
.getResultList();
Обычно для запроса на одном уровне можно использовать AliasToBeanResultTransformer
, который поставляется сHibernate, но это не будет объединять или группировать результаты по Movie
.
Вот пример преобразователя результатов для первого сопоставления возвращаемых столбцов (в «кортеже», который представляет собой список полей результатов), изатем объедините их с помощью Movie
:
public class MovieResultTransformer
implements ResultTransformer {
@Override
public Object transformTuple(Object[] tuple,
String[] aliases) {
Movie movie = new Movie();
movie.setId((Long) tuple[0]);
movie.setName((String) tuple[1]);
movie.setReleaseDate((Date) tuple[2]);
MovieCelebrity movieCelebrity = new MovieCelebrity();
movieCelebrity.setCharacterName((String) tuple[3]);
movie.getMovieCelebrities().add(movieCelebrity);
return movie;
}
@Override
public List transformList(List collection) {
Map<Long, Movie> movies = new LinkedHashMap<>();
for (Object item : collection) {
Movie movie = (Movie) item;
Long id = movie.getId();
Movie existingMovie = movies.get(id);
if (existingMovie == null)
movies.put(id, movie);
else
existingMovie.getMovieCelebrities()
.addAll(movie.getMovieCelebrities());
}
return new ArrayList<>(movies.values());
}
}
Стоит отметить, что ResultTransformer
был устарел с Hibernate 5.2, с замечательным комментарием в источнике: @todo develop a new approach to result transformers
.
Очевидно, что область проекций в JPA все еще немного неполна.Предложение для Hibernate 6 заключается в том, что они перейдут на функциональный интерфейс и API в стиле лямбда, что было бы большим улучшением - было бы хорошо увидеть что-то подобное в JPA.