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 id、name、releaseDate 和characterName 在响应中,像这样:
{
"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());
}
}
值得注意的是 ResultTransformer
是 deprecated 与 Hibernate 5.2,源代码中的精彩评论:@todo develop a new approach to result transformers
.
很明显,JPA 中的投影区域仍然有点不完整。对 Hibernate 6 的建议是他们将切换到函数式接口和 lambda 样式 API,这将是一个很大的改进 - 很高兴看到类似的东西进入 JPA。
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 id、name、releaseDate 和characterName 在响应中,像这样:
{
"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());
}
}
值得注意的是 ResultTransformer
是 deprecated 与 Hibernate 5.2,源代码中的精彩评论:@todo develop a new approach to result transformers
.
很明显,JPA 中的投影区域仍然有点不完整。对 Hibernate 6 的建议是他们将切换到函数式接口和 lambda 样式 API,这将是一个很大的改进 - 很高兴看到类似的东西进入 JPA。