使用 Hibernate Criteria Builder 查询多个连接的实体
Using Hibernate Criteria Builder to query across several joined entities
为了一个学校团体项目,我正在建立一个旨在联合音乐家的小型社交媒体网站。我们的网站后端是使用 Spring 使用 Hibernate JPA 引导构建的。
我们的网站由用户、Posts、标签和 SkillLevels 组成。每个用户都与 Post 处于双向 OneToMany 关系,映射在 Post 端。对于每个 Post,任何单独的标签都可以与该 Post 关联一次,并为该应用程序分配一个 SkillLevel。本质上,Posts 和 Tags 处于多对多关系中,每个关系都应用了 SkillLevel。在使用中,一个例子是 Post 应用了标签“violin”并且 SkillLevel“初学者”与该应用程序相关联。
Posts、标签和 SkillLevels 之间的这种关系是使用名为 AppliedSkillLevel 的联接 table 完成的,并按照 this article 中的示例 Post 进行设置对应Student,Tag对应Course,AppliedSkillLevel对应Rating。一个主要区别是,虽然它们直接在连接 table 中存储评级分数,但我将 SkillLevels 作为一个单独的实体,通过映射在 AppliedSkillLevel 端的额外 ManyToOne 关系连接到 AppliedSkillLevel。这使我能够将姓名与技能水平相关联,并轻松重构关系。
既然我已经完成了所有这些设置,我想为网站构建一个强大的搜索功能。我希望能够进行搜索,例如“使用标签 'violin' 查找 Post,其中技能级别 'intermediate' 应用于 Post 和标签之间的关系" 或 "找到 Posts with contentType(Post 的字段) == video 和 (标签 'violin' 其中技能水平 'intermediate' 应用于Post 和标记或应用技能水平 'master' 的标记 'guitar'”,或者使用本地字段和 Post 与条件类型的某种组合来查询用户已经描述过了。
为此,我认为使用 Hibernate Criteria API 并构建一个结构来构建查询将是我最好的方法。但是,我无法弄清楚如何以确保获得正确结果的方式进行标准构建。参考了 Join 和 Fetch 用于交叉 tables,我的第一个想法是 join/fetch 将整个实体图(或至少给定搜索所需的所有部分)合并为一个组合从对象。但是,我发现 this Stack Overflow Post 这表明这对于 Fetch 是不可能的,您只能获取相邻关系,或者最多只能获取两个关系,我不清楚。我假设相同的规则适用于连接。
所以问题就变成了,我怎样才能以获得正确结果的方式进行这些查询?例如,如果我想获得带有标签 'violin' 的 Posts 和应用于该 post 标签组合的技能水平 'advanced',我该怎么做?我可以从 Root<Post>
开始并加入 AppliedSkillLevel,然后从那里分别加入 Tag 和 SkillLevel,但是当我查询它们时,我怎么知道我有一个 Post 和一个(小提琴,高级)对,而不是 post 与(小提琴,中级)和(吉他,高级)。同样,如果我在寻找一个 Post 适合某个配置文件的用户,我怎么知道我找到了一个,而不是找到一个 Post 适合部分配置文件和另一个的用户Post 适合个人资料的其余部分?
先谢谢你。
你总是可以像这样转换一个获取连接:
Join <Table1, Table2> joinTable = (Join <Table1, Table2>) root.fetch(Table1_.table2s);
什么可以让您执行 fecth,并将其视为 Join,
尽管我建议使用所有连接(在 Lazy 等实体之间建立关系)和 return Pojo,而不是使用类似于以下方式的具有必要信息的实体:
// assuming these variables as input variables of the method
String tagName = "violin";
String skillName = "advanced"
Long idSkill = null;
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PostPojo> cq = cb.createQuery(PostPojo.class);
Root<Post> rootPost = cq.from(Post.class);
Join<Post,AppliedSkillLevel> joinAppliedSkillLevel = rootPost.join(Post_.appliedSkillLevels,JoinType.LEFT);
Join<AppliedSkillLevel,Tag> joinTag = joinAppliedSkillLevel.join(AppliedSkillLevel_.tags,JoinType.LEFT);
Join<AppliedStillLevel,SkillLevel> joinSkillLevel = = joinAppliedSkillLevel.join(AppliedSkillLevel_.skillLevels,JoinType.LEFT);
List<Predicate> listPredicates = new ArrayList<>();
listPredicates.add(cb.equal(joinTag.get(Tag_.name),tagName));
listPredicates.add(cb.equal(joinSkillLevel.get(SkillLevel_.name),skillName));
// the predicates you want, you could set filters programatically like this:
/*
* if(idSkill != null){
* listPredicates.add(cb.equal(joinSkillLevel.get(SkillLevel_.name),idSkill));
* }
*/
cq.where(listPredicates .toArray(new Predicate[listPredicates .size()]));
cq.multiselect(
rootPost.get(Post_.id),
rootPost.get(Post_.title),
rootPost.get(Post_.content),
[...] // other fields you want
joinTag.get(Tag_.id),
joinTag.get(Tag_.name),
[...] // other fields you want
joinSkillLevel.get(SkillLevel_.id),
joinSkillLevel.get(SkillLevel_.name)
[...] // other fields you want
);
List<PostPojo> results = entityManager.createQuery(cq).getResultList();
在你的 Pojo class PostPojo
并且在您的 PostPojo 中有一个构造函数,该构造函数按顺序和类型与多选子句的每个参数匹配(假设类型是因为您没有放置实体)。
public PostPojo(Long idPost, String titlePost, String contentPost, Long idTag,
String nameTag, Long idSkill, String nameSkill){
this.idPost = idPost
[...]
}
通过这种方式,您将获得包含所需信息和过滤器的 PostPojo 对象列表,并且无需使用 fetch 并将实体之间的关系建立为 Lazy,您将优化内存使用。
为了一个学校团体项目,我正在建立一个旨在联合音乐家的小型社交媒体网站。我们的网站后端是使用 Spring 使用 Hibernate JPA 引导构建的。
我们的网站由用户、Posts、标签和 SkillLevels 组成。每个用户都与 Post 处于双向 OneToMany 关系,映射在 Post 端。对于每个 Post,任何单独的标签都可以与该 Post 关联一次,并为该应用程序分配一个 SkillLevel。本质上,Posts 和 Tags 处于多对多关系中,每个关系都应用了 SkillLevel。在使用中,一个例子是 Post 应用了标签“violin”并且 SkillLevel“初学者”与该应用程序相关联。
Posts、标签和 SkillLevels 之间的这种关系是使用名为 AppliedSkillLevel 的联接 table 完成的,并按照 this article 中的示例 Post 进行设置对应Student,Tag对应Course,AppliedSkillLevel对应Rating。一个主要区别是,虽然它们直接在连接 table 中存储评级分数,但我将 SkillLevels 作为一个单独的实体,通过映射在 AppliedSkillLevel 端的额外 ManyToOne 关系连接到 AppliedSkillLevel。这使我能够将姓名与技能水平相关联,并轻松重构关系。
既然我已经完成了所有这些设置,我想为网站构建一个强大的搜索功能。我希望能够进行搜索,例如“使用标签 'violin' 查找 Post,其中技能级别 'intermediate' 应用于 Post 和标签之间的关系" 或 "找到 Posts with contentType(Post 的字段) == video 和 (标签 'violin' 其中技能水平 'intermediate' 应用于Post 和标记或应用技能水平 'master' 的标记 'guitar'”,或者使用本地字段和 Post 与条件类型的某种组合来查询用户已经描述过了。
为此,我认为使用 Hibernate Criteria API 并构建一个结构来构建查询将是我最好的方法。但是,我无法弄清楚如何以确保获得正确结果的方式进行标准构建。参考了 Join 和 Fetch 用于交叉 tables,我的第一个想法是 join/fetch 将整个实体图(或至少给定搜索所需的所有部分)合并为一个组合从对象。但是,我发现 this Stack Overflow Post 这表明这对于 Fetch 是不可能的,您只能获取相邻关系,或者最多只能获取两个关系,我不清楚。我假设相同的规则适用于连接。
所以问题就变成了,我怎样才能以获得正确结果的方式进行这些查询?例如,如果我想获得带有标签 'violin' 的 Posts 和应用于该 post 标签组合的技能水平 'advanced',我该怎么做?我可以从 Root<Post>
开始并加入 AppliedSkillLevel,然后从那里分别加入 Tag 和 SkillLevel,但是当我查询它们时,我怎么知道我有一个 Post 和一个(小提琴,高级)对,而不是 post 与(小提琴,中级)和(吉他,高级)。同样,如果我在寻找一个 Post 适合某个配置文件的用户,我怎么知道我找到了一个,而不是找到一个 Post 适合部分配置文件和另一个的用户Post 适合个人资料的其余部分?
先谢谢你。
你总是可以像这样转换一个获取连接:
Join <Table1, Table2> joinTable = (Join <Table1, Table2>) root.fetch(Table1_.table2s);
什么可以让您执行 fecth,并将其视为 Join,
尽管我建议使用所有连接(在 Lazy 等实体之间建立关系)和 return Pojo,而不是使用类似于以下方式的具有必要信息的实体:
// assuming these variables as input variables of the method
String tagName = "violin";
String skillName = "advanced"
Long idSkill = null;
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PostPojo> cq = cb.createQuery(PostPojo.class);
Root<Post> rootPost = cq.from(Post.class);
Join<Post,AppliedSkillLevel> joinAppliedSkillLevel = rootPost.join(Post_.appliedSkillLevels,JoinType.LEFT);
Join<AppliedSkillLevel,Tag> joinTag = joinAppliedSkillLevel.join(AppliedSkillLevel_.tags,JoinType.LEFT);
Join<AppliedStillLevel,SkillLevel> joinSkillLevel = = joinAppliedSkillLevel.join(AppliedSkillLevel_.skillLevels,JoinType.LEFT);
List<Predicate> listPredicates = new ArrayList<>();
listPredicates.add(cb.equal(joinTag.get(Tag_.name),tagName));
listPredicates.add(cb.equal(joinSkillLevel.get(SkillLevel_.name),skillName));
// the predicates you want, you could set filters programatically like this:
/*
* if(idSkill != null){
* listPredicates.add(cb.equal(joinSkillLevel.get(SkillLevel_.name),idSkill));
* }
*/
cq.where(listPredicates .toArray(new Predicate[listPredicates .size()]));
cq.multiselect(
rootPost.get(Post_.id),
rootPost.get(Post_.title),
rootPost.get(Post_.content),
[...] // other fields you want
joinTag.get(Tag_.id),
joinTag.get(Tag_.name),
[...] // other fields you want
joinSkillLevel.get(SkillLevel_.id),
joinSkillLevel.get(SkillLevel_.name)
[...] // other fields you want
);
List<PostPojo> results = entityManager.createQuery(cq).getResultList();
在你的 Pojo class PostPojo
并且在您的 PostPojo 中有一个构造函数,该构造函数按顺序和类型与多选子句的每个参数匹配(假设类型是因为您没有放置实体)。
public PostPojo(Long idPost, String titlePost, String contentPost, Long idTag,
String nameTag, Long idSkill, String nameSkill){
this.idPost = idPost
[...]
}
通过这种方式,您将获得包含所需信息和过滤器的 PostPojo 对象列表,并且无需使用 fetch 并将实体之间的关系建立为 Lazy,您将优化内存使用。