使用Hibernate Criteria Builder在多个加入的实体中进行查询

问题描述

对于一个学校团体项目,我正在建立一个小型社交媒体网站,目的是让音乐家们加入其中。我们的网站后端是通过Spring Boot和Hibernate JPA构建的。

我们的网站包括用户,帖子,标签和技能级别。每个用户与Post处于双向OnetoMany关系中,映射在Post端。对于每个帖子,任何单个标签都可以与该帖子关联一次,然后将SkillLevel分配给该应用程序。本质上,帖子和标签是在ManyToMany关系中,并且将SkillLevel应用于每个关系。在使用中,示例将是贴有标签“ violin”的帖子和与该应用程序相关联的SkillLevel“初学者”。

职位,标签和技能水平之间的这种关系是通过称为AppliedSkillLevel的联接表完成的,并按照this article中的示例进行设置,其中职位与学生对应,职位与课程对应,应用技能水平与评分对应。一个主要区别是,尽管它们直接在联接表中存储评分分数,但我将SkillLevels作为单独的实体,通过附加的ManyToOne关系连接到AppliedSkillLevel,并映射到AppliedSkillLevel端。这使我可以将姓名与技能水平相关联,并可以轻松地重构关系。

现在我已完成所有设置,我想为该网站构建强大的搜索功能。我希望能够进行诸如“使用标记“小提琴”查找帖子,其中技能水平为“中级”应用于该帖子和标签间的关系”之类的搜索,或“使用contentType(帖子的字段)查找帖子” ==视频,((将技能级别为“中级”的标记“小提琴”应用于该职位与该标记间的关系,或者将技能级别为“主”的标记“吉他”应用于该职位)或对用户进行查询本地字段和帖子以及已描述的条件类型的组合。

为此,我认为使用Hibernate Criteria API并构建结构进行查询构建将是我最好的方法。但是,我在确定如何建立标准以确保获得正确结果方面遇到困难。引用Join和Fetch用于交叉表后,我的第一个想法是将整个实体图(或至少是给定搜索所需的所有部分)连接/提取一个组合的From对象中。但是,我发现this Stack Overflow Post表示这对于Fetch来说是不可能的,并且您只能提取两个相邻的关系,或者最多只能提取两个关系,这对我来说尚不清楚。我假设对“联接”也适用相同的规则。

问题就变成了,如何以正确的方式进行这些查询?例如,如果我想获得带有标签“小提琴”的帖子,并将技能水平“高级”应用于该帖子标签组合,我该怎么做?我可以从Root<Post>开始,然后加入AppliedSkillLevel,然后再分别加入Tag和SkillLevel,但是当我对它们进行查询时,我如何知道我有一对(小提琴,高级)对,而不是带有(小提琴,中级)和(吉他,高级)的职位。同样,如果我追寻的职位适合某个个人资料的用户,我怎么知道我找到了一个,而不是找到一个用户适合某个个人资料的一部分,而另一个帖子适合其他个人资料的用户呢?

谢谢你。

解决方法

您总是可以像这样强制获取连接:

Join <Table1,Table2>  joinTable = (Join <Table1,Table2>) root.fetch(Table1_.table2s);

什么可以让您做fecth,并将其视为Join,

尽管我建议使用所有联接(在诸如Lazy之类的实体之间建立关系)并以类似于以下方式的方式返回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类PostPojo中

在您的PostPojo中,构造器将按顺序和类型与multiselect子句的每个参数匹配(假定类型,因为您没有放置实体)。

public PostPojo(Long idPost,String titlePost,String contentPost,Long idTag,String nameTag,Long idSkill,String nameSkill){
     this.idPost = idPost
     [...]
}

通过这种方式,您将获得具有所需信息和过滤器的PostPojo对象列表,并且无需使用获取和建立实体之间的关系为Lazy的方式,就可以优化内存使用。