Запрос репозитория Jpa - java.lang.Object;нельзя привести к модели - PullRequest
0 голосов
/ 29 сентября 2018

Movie модель:

@Entity
public class Movie {

    private Long id;
    private String name;
    private Date releaseDate;
    private List<MovieCelebrity> movieCelebrities = new ArrayList<>();

    // getters & setters
}

MovieCelebrity модель:

@Entity
public class MovieCelebrity extends DateAudit {

    private Long id;
    private String characterName;
    private Movie movie;

    // getters & setters
}

Я хочу вернуть id , имя , releaseDate и имя_символа вместе в ответе, что-то вроде этого:

{
    "id": 1,
    "name": Scarface,
    "releaseDate": 1983,
    "characterName": "Tony Montana"
}

Итак, я сделалследующий запрос:

@Query("SELECT m.id, m.name, m.releaseDate, mc.characterName FROM Movie m JOIN m.movieCelebrities mc " +
       "WHERE mc.celebrity.id = :id AND mc.role = :role")
Page<Movie> findMoviesByCelebrity(@Param("id") Long id, @Param("role") CelebrityRole role, Pageable pageable);

Но я получаю следующую ошибку в ответе:

"java.base / [Ljava.lang.Object; невозможно привести кcom.movies.mmdb.model.Movie "

Существует решение для создания конструктора в фильме с параметром, который мне нужно вернуть, и сделать запрос, например:

@Query("SELECT new Movie(m.id, m.name, m.releaseDate, mc.characterName) FROM...)

Но так как characterName находится в другой модели, я не могу сделать такой конструктор.

Ответы [ 3 ]

0 голосов
/ 29 сентября 2018

создайте переходный метод получения для characterName следующим образом:

public class Movie {
private String name;
@Transient
public String getCharacterName(){
return getMovieCelebrities().iterator().next().getCharacterName();

}
}

, затем используйте ваше конструкторское решение.

0 голосов
/ 03 октября 2018

По сути, вопрос заключается в том, как выполнить проекцию из запроса 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.

0 голосов
/ 29 сентября 2018

Выражение конструктора с NEW может использоваться только с DTO (объектами передачи данных).

Но решение намного проще.Просто верните фильм как:

@Query("SELECT m FROM Movie m JOIN m.movieCelebrities mc " +
       "WHERE mc.celebrity.id = :id AND mc.role = :role")
Page<Movie> findMoviesByCelebrity(@Param("id") Long id, @Param("role") CelebrityRole role, Pageable pageable);

Или, если вы хотите использовать DTO:

@Query("SELECT NEW your.package.MovieDTO(m.id, m.name, m.releaseDate, mc.characterName) FROM Movie m JOIN m.movieCelebrities mc " +
       "WHERE mc.celebrity.id = :id AND mc.role = :role")
Page<MovieDTO> findMoviesByCelebrity(@Param("id") Long id, @Param("role") CelebrityRole role, Pageable pageable);

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

...