简体   繁体   English

将使用ManyToMany关系的SQL查询转换为JPQL查询

[英]Convert SQL query that use ManyToMany relationship to JPQL query

I'm trying to convert the following SQL query into JPQL query: 我正在尝试将以下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

The problem is that i don't have a model class to represent movie_genre table because it's auto generated table from ManyToMany relationship. 问题是我没有模型类来表示movie_genre表,因为它是从ManyToMany关系自动生成的表。

so is there any way to convert that query into JPQL 所以有什么办法可以将该查询转换为JPQL

In the moment I'm using a native query instead: 目前,我正在使用本机查询:

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

Edit : here's the models: 编辑:这是模型:
Movie 电影

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

Genre 类型

@Entity
public class Genre {

    private Long id;
    private String name;
    @ManyToMany(mappedBy = "genres", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    private Set<Movie> movies = new HashSet<>();
}

You do not need a model movie_genre in order to write a JPQL statement. 您不需要模型movie_genre即可编写JPQL语句。 Hibernate knows about it implicitly when doing a JPQL statement. Hibernate在执行JPQL语句时隐式知道它。

Example: 例:

SELECT m from Movie m left join m.genres g where g.name = 'Action'

The bidirectional "m.genres g" works with all bidirectional JPQL statements, including many-to-many where the association model is implicit and not actually present. 双向“ m.genres g”可用于所有双向JPQL语句,包括多对多的关联模型是隐式的而实际上不存在的。

Example Code: 示例代码:

@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;
}
}

Working JPQL Example 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());


}

Despite the SQL query you provided, I re-thought your requirement and translated that to a JPQL. 尽管您提供了SQL查询,但我还是重新考虑了您的要求,并将其转换为JPQL。 So far what I understood you were finding related movies to a movie having common genres . 到目前为止,据我了解,您正在寻找与具有共同体裁的电影相关的电影 If this is correct, you can achieve this by 如果正确,则可以通过以下方法实现

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

This will generate the SQL as below 这将生成如下的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

Querying Collection Association in JPQL JPQL中的查询集合关联

When you are using JPQL, you are in the Object Relationship world. 使用JPQL时,您位于“对象关系”世界中。 So when you are querying for an association that is a collection, you access them through that collection field. 因此,当您查询作为集合的关联时,可以通过该集合字段进行访问。 And when it is a ManyToMany association, so you are not having a join table that does have a mapped entity, you need to Join with the collection field. 当它是ManyToMany关联时,因此您没有具有映射实体的联接表,则需要使用集合字段进行联接。 And JPA vendor automatically translates that to join with the join table. JPA供应商会自动将其转换为与联接表联接。

Like if you are querying movies of certain genres , this will go 就像您要查询某些类型的电影一样,

 SELECT m FROM Movie m JOIN m.genres mg WHERE mg.id = :genreId 

Performance Concern 性能关注

As you notice in the generated SQL, there are many levels of join to fetch and filter the data. 正如您在生成的SQL中所注意到的那样,有许多级别的联接来获取和过滤数据。 This leads to a performance bottleneck. 这导致性能瓶颈。 To overcome this, you can have entity for the movie_genre table. 为了克服这个问题,您可以为movie_genre表提供实体。

This scenario is fairly discussed here 这种情况在这里进行了公平讨论

The best way to map a many-to-many association with extra columns when using JPA and Hibernate 使用JPA和Hibernate映射带有额外列的多对多关联的最佳方法

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM