【问题标题】:Jpa Repository query - java.lang.Object; cannot be cast to modelJpa 存储库查询 - java.lang.Object;不能转换为模型
【发布时间】:2018-09-29 13:14:17
【问题描述】:

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
}

我想在响应中返回 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; 不能转换为 com.movi​​es.mmdb.model.Movie"

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

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

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

【问题讨论】:

    标签: java spring-boot spring-data-jpa spring-rest


    【解决方案1】:

    本质上,问题是关于如何从 JPA 查询到具有嵌套值的返回类型的投影。这是目前 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 合并或分组结果。

    这是一个示例结果转换器,它首先映射返回列(在“元组”中,这是一个结果字段列表),然后通过 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 中的 Projections 领域仍然有些不完整。对 Hibernate 6 的建议是,他们将切换到函数式接口和 lambda 样式 API,这将是一个很大的改进 - 很高兴看到 JPA 中出现类似的涟漪。

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 这个查询将创建一个有点复杂的响应,它会在响应中创建一个对象 MovieCelebrity,并带有我需要的 characterName。我想要的是没有 MovieCelebrity 对象的简单响应,只有 characterName 在根响应中。请查看问题中的回复,不知道我想要什么,也许我没有多大意义。
      • 在这种情况下,创建一个 DTO 类,该类具有一个接受这些参数的构造函数。我更新了我的答案
      • MovieDTO 无法投射到电影。
      • 我更新了答案。返回值必须是 MovieDTO 而不是 Movie
      • @Ayoubk,实体之间存在一对多关系,因此您可能拥有多个字符。在这种特殊情况下,最好的做法是拥有 DTO。如果您想稍后对结果列表进行操作以在数据库中进行更改 - 请使用 Omid P 提供的方法。
      【解决方案3】:

      为 characterName 创建一个临时 getter 方法,如下所示:

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

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

      【讨论】:

        猜你喜欢
        • 2020-07-11
        • 1970-01-01
        • 2018-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-16
        • 1970-01-01
        相关资源
        最近更新 更多