我正在尝试将以下SQL查询转换为JPQL查询:
SELECT *
FROM movie m INNER JOIN movie_genre mg ON m.id = mg.movie_id
WHERE mg.genre_id = (SELECT mg2.genre_id FROM movie_genre mg2 WHERE mg2.movie_id = ?1 AND mg2.movie_id <> mg.movie_id AND mg.genre_id = mg2.genre_id)
GROUP BY mg.movie_id ORDER BY count(*) DESC
问题是我没有模型类来表示movie_genre
表,因为它是根据ManyToMany
关系自动生成的表。
有什么办法可以将该查询转换为JPQL
此刻,我正在使用本机查询:
@Query(value = "SELECT * FROM movie m INNER JOIN movie_genre mg ON m.id = mg.movie_id " +
"WHERE mg.genre_id = (SELECT mg2.genre_id FROM movie_genre mg2 WHERE mg2.movie_id = ?1 AND mg2.movie_id <> mg.movie_id AND mg.genre_id = mg2.genre_id) " +
"GROUP BY mg.movie_id ORDER BY count(*) DESC", nativeQuery = true)
Page<Movie> findRelatedMoviesToAMovieById(@Param("id") int id, Pageable pageable);
编辑:
这是模型:
电影
@Entity
public class Movie extends DateAudit {
private Long id;
private String name;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(name = "movie_genre",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = @JoinColumn(name = "genre_id")
)
private List<Genre> genres = new ArrayList<>();
}
类型
@Entity
public class Genre {
private Long id;
private String name;
@ManyToMany(mappedBy = "genres", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Movie> movies = new HashSet<>();
}
答案 0 :(得分:1)
您不需要模型movie_genre即可编写JPQL语句。 Hibernate在执行JPQL语句时暗中了解它。
示例:
SELECT m from Movie m left join m.genres g where g.name = 'Action'
双向“ m.genres g”可用于所有双向JPQL语句,包括多对多的关联模型是隐式的而实际上不存在的。
示例代码:
@Entity
@Table(name = "MOVIE")
public class Movie {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
@ManyToMany(cascade = {CascadeType.ALL})
@JoinTable(name = "movie_genre",
joinColumns = @JoinColumn(name = "movie_id"),
inverseJoinColumns = {@JoinColumn(name = "genre_id")}
)
private Set<Genre> genres = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Genre> getGenres() {
return genres;
}
public void setGenres(Set<Genre> genres) {
this.genres = genres;
}
}
@Entity
@Table(name = "GENRE")
public class Genre {
@Id
@GeneratedValue
private Long id;
@Column
private String name;
@ManyToMany(mappedBy = "genres", cascade = {CascadeType.ALL})
private Set<Movie> movies = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Movie> getMovies() {
return movies;
}
public void setMovies(Set<Movie> movies) {
this.movies = movies;
}
}
有效的JPQL示例
@PersistenceContext
EntityManager entityManager;
@Test
@Transactional
public void test() {
Set<Movie> actionMovies = new HashSet<>();
Set<Movie> dramaMovies = new HashSet<>();
Set<Genre> dramaGenres = new HashSet<>();
Set<Genre> actionGenres = new HashSet<>();
Set<Genre> generes = new HashSet<>();
Movie actionMovie = new Movie();
actionMovie.setName("Batman");
actionMovies.add(actionMovie);
Movie dramaMovie = new Movie();
dramaMovie.setName("Forest Gump");
dramaMovies.add(dramaMovie);
Genre actionGenre = new Genre();
actionGenre.setName("Action");
generes.add(actionGenre);
actionGenres.add(actionGenre);
Genre dramaGenre = new Genre();
dramaGenre.setName("Drama");
generes.add(dramaGenre);
dramaGenres.add(dramaGenre);
//Bidirectional sets
actionGenre.setMovies(actionMovies);
dramaGenre.setMovies(dramaMovies);
actionMovie.setGenres(actionGenres);
dramaMovie.setGenres(dramaGenres);
genreRepository.saveAll(generes);
//Example JPQL join through not present association model.
Query query = entityManager.createQuery("SELECT m from Movie m left join m.genres g where g.name = 'Action'");
List<Movie> resultList = query.getResultList();
assertEquals("Batman",resultList.get(0).getName());
}
答案 1 :(得分:1)
尽管您提供了SQL查询,但我还是重新考虑了您的要求,并将其转换为JPQL。到目前为止,据我了解,您正在查找具有相同类型的电影的相关电影。如果这是正确的,您可以通过
SELECT m FROM Movie m JOIN m.genres mg
WHERE mg.id IN
(SELECT g.id FROM Genre g JOIN g.movies gm WHERE gm.id = :movieId)
AND m.id <> :movieId
GROUP BY m ORDER BY count(*) DESC
这将生成如下的SQL
select distinct movie0_.id as id1_1_, movie0_.name as name2_1_
from movie movie0_ inner join movie_genre genres1_ on movie0_.id=genres1_.movie_id inner join genre genre2_ on genres1_.genre_id=genre2_.id
where (genre2_.id in
(select genre3_.id from genre genre3_ inner join movie_genre movies4_ on genre3_.id=movies4_.genre_id inner join movie movie5_
on movies4_.movie_id=movie5_.id where movie5_.id=?))
and movie0_.id <> ?
group by movie0_.id order by count(*) desc
使用JPQL时,您位于“对象关系”世界中。因此,当您查询作为集合的关联时,可以通过该集合字段进行访问。并且,当它是ManyToMany
关联时,因此您没有具有映射实体的联接表,则需要使用集合字段进行联接。而且JPA供应商会自动将其转换为与联接表联接。
就像您要查询某些类型的电影一样,
SELECT m FROM Movie m JOIN m.genres mg WHERE mg.id = :genreId
正如您在生成的SQL中注意到的那样,有许多级别的联接可用于获取和过滤数据。这导致性能瓶颈。为了克服这个问题,您可以为movie_genre
表提供实体。
这种情况在这里得到了公平的讨论
The best way to map a many-to-many association with extra columns when using JPA and Hibernate