Jpa 存储库查询 - java.lang.Object;不能投射到模型

Jpa Repository query - java.lang.Object; cannot be cast to model

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
}

我想要 return idnamereleaseDatecharacterName 在响应中,像这样:

{
    "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; cannot be cast to com.movies.mmdb.model.Movie"

有一个解决方案可以在电影中使用我需要的参数创建构造函数 return 并进行如下查询:

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

但是由于 characterName 在不同的模型中,我无法创建这样的构造函数。

带有 NEW 的构造函数表达式只能与 DTO(数据传输对象)一起使用。

但解决方案要简单得多。简单地 return 电影就像:

@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 必须有一个构造函数,该构造函数从具有匹配类型的查询中获取所有参数。

为 characterName 创建一个瞬态 getter 方法,如下所示:

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

}
}

然后使用您的构造函数解决方案。

本质上,问题是关于如何从 JPA 查询到具有嵌套值的 return 类型的投影。这是目前 JPA 查询中并不存在的东西。

除了 DTO,Spring JPA 中还有投影接口,它们实际上可以处理一些嵌套(参见 Spring Docs)。这些将是一个相当简单的选项,但您仍然不能轻易地将其强制转换为 Movie.

目前主要的其他选项是 ResultTransformer 回到 Hibernate。例如,这可以通过使用命名的 JPA 查询然后在 运行 之前返回到 Hibernate 查询 API 来访问。

这是已声明的命名查询(相对于问题示例中可用的 类 略有简化):

@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();

通常,对于单级查询,您可以使用 Hibernate 自带的 AliasToBeanResultTransformer,但这不会按 Movie.

合并或分组结果

这是一个示例结果转换器,首先映射 return 列(在 'tuple' 中,这是一个结果字段列表),然后通过 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());
    }
}

值得注意的是 ResultTransformerdeprecated 与 Hibernate 5.2,源代码中的精彩评论:@todo develop a new approach to result transformers.

很明显,JPA 中的投影区域仍然有点不完整。对 Hibernate 6 的建议是他们将切换到函数式接口和 lambda 样式 API,这将是一个很大的改进 - 很高兴看到类似的东西进入 JPA。