Hibernate:多对多包含使用 Criteria 的特定 ID
Hibernate: Many to Many contains specific IDs using Criteria
我正在开发一个 JPA 应用程序,我遇到了这些实体之间多对多关联的问题:
电影
class Movie {
@Id
String id;
...
@ManyToMany
@JoinTable(
name = "movie_category",
joinColumn = @JoinColumn(name = "movie_id", referencedColumnName = "id")
)
Set<Category> categories = new HashSet<>();
}
类别
class Category {
@Id
String id;
...
}
我只想 select 具有所有类别的电影(例如 List<Category>
)。在 Criteria API 中,我可以这样做:
List<Category> requiredCategories = ... //from request
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<Movie> cq = b.createQuery(Movie.class);
Root<Movie> r = cq.from(Movie.class);
Expression<Set<Category>> categories = r.get(Movie_.categories);
Predicate predicate = b.and();
for(Category c : requiredCategories) {
predicate = b.and(predicate, b.isMember(c, categories));
}
工作正常,但问题是我必须先从 Category
table 获取 Category
对象,然后在 isMember
函数中使用它。我想以某种方式避免加入 Category
table.
例如:我想要所有具有类别 COMEDY
和 FANTASY
的电影。 (考虑这些关键字是 Category.id
),所以我需要通过 id 从 Category
table 两个实体中提取,然后在 isMember
函数中使用它们。我想避免这种情况,因为信息 category_id
已经存储在关联 table movie_category
中。
简而言之,我想要 isMember
之类的东西,但提供 Category.id
而不是实体。
这样的事情可能吗?
据我所知,使用 JPA 内置工具无法进行此类查询,因为无法直接访问连接 table。但这可以通过 FluentJPA:
public List<Movie> getMoviesByCategories(List<String> categoryIds) {
int matchTotal = categoryIds.size();
FluentQuery query = FluentJPA.SQL((Movie movie,
JoinTable<Movie, Category> movieCategory) -> {
discardSQL(movieCategory.join(movie, Movie::getCategories));
List<String> movieIds = subQuery(() -> {
String movieId = movieCategory.getJoined().getId();
String catId = movieCategory.getInverseJoined().getId();
SELECT(movieId);
FROM(movieCategory);
WHERE(categoryIds.contains(catId));
GROUP(BY(movieId));
HAVING(COUNT(movieId) == matchTotal); // COUNT(DISTINCT(movieId));
});
SELECT(movie);
FROM(movie);
WHERE(movieIds.contains(movie.getId()));
});
return query.createQuery(em, Movie.class).getResultList();
}
产生以下 SQL:
SELECT t0.*
FROM t0
WHERE (t0.id IN (SELECT t1.movie_id
FROM movie_category t1
WHERE (t1.CATEGORIES_id IN ?1 )
GROUP BY t1.movie_id
HAVING (COUNT(t1.movie_id) = ?2) ) )
子查询需要产生 and
语义。参见 this answer。
解释了 FluentJPA 中的多对多语义 here。
我正在开发一个 JPA 应用程序,我遇到了这些实体之间多对多关联的问题:
电影
class Movie {
@Id
String id;
...
@ManyToMany
@JoinTable(
name = "movie_category",
joinColumn = @JoinColumn(name = "movie_id", referencedColumnName = "id")
)
Set<Category> categories = new HashSet<>();
}
类别
class Category {
@Id
String id;
...
}
我只想 select 具有所有类别的电影(例如 List<Category>
)。在 Criteria API 中,我可以这样做:
List<Category> requiredCategories = ... //from request
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<Movie> cq = b.createQuery(Movie.class);
Root<Movie> r = cq.from(Movie.class);
Expression<Set<Category>> categories = r.get(Movie_.categories);
Predicate predicate = b.and();
for(Category c : requiredCategories) {
predicate = b.and(predicate, b.isMember(c, categories));
}
工作正常,但问题是我必须先从 Category
table 获取 Category
对象,然后在 isMember
函数中使用它。我想以某种方式避免加入 Category
table.
例如:我想要所有具有类别 COMEDY
和 FANTASY
的电影。 (考虑这些关键字是 Category.id
),所以我需要通过 id 从 Category
table 两个实体中提取,然后在 isMember
函数中使用它们。我想避免这种情况,因为信息 category_id
已经存储在关联 table movie_category
中。
简而言之,我想要 isMember
之类的东西,但提供 Category.id
而不是实体。
这样的事情可能吗?
据我所知,使用 JPA 内置工具无法进行此类查询,因为无法直接访问连接 table。但这可以通过 FluentJPA:
public List<Movie> getMoviesByCategories(List<String> categoryIds) {
int matchTotal = categoryIds.size();
FluentQuery query = FluentJPA.SQL((Movie movie,
JoinTable<Movie, Category> movieCategory) -> {
discardSQL(movieCategory.join(movie, Movie::getCategories));
List<String> movieIds = subQuery(() -> {
String movieId = movieCategory.getJoined().getId();
String catId = movieCategory.getInverseJoined().getId();
SELECT(movieId);
FROM(movieCategory);
WHERE(categoryIds.contains(catId));
GROUP(BY(movieId));
HAVING(COUNT(movieId) == matchTotal); // COUNT(DISTINCT(movieId));
});
SELECT(movie);
FROM(movie);
WHERE(movieIds.contains(movie.getId()));
});
return query.createQuery(em, Movie.class).getResultList();
}
产生以下 SQL:
SELECT t0.*
FROM t0
WHERE (t0.id IN (SELECT t1.movie_id
FROM movie_category t1
WHERE (t1.CATEGORIES_id IN ?1 )
GROUP BY t1.movie_id
HAVING (COUNT(t1.movie_id) = ?2) ) )
子查询需要产生 and
语义。参见 this answer。
解释了 FluentJPA 中的多对多语义 here。