Java Spring引导-搜索类别树

Java Spring引导-搜索类别树,java,spring,postgresql,spring-boot,repository,Java,Spring,Postgresql,Spring Boot,Repository,我正在编写SpringBoot应用程序,它基本上是广告服务。在数据库中(我使用PostgreSQL方言),我存储类别树。每个类别都包含对其直接父类的引用。每个广告都指定了其类别,为了解决此问题,您只能将广告指定给叶类别 下面的图像捕捉到了结构 现在我需要从数据库中检索特定类别的广告。但假设我想检索E类的所有广告,这意味着我想检索E类的子类广告,但不一定是直接子类广告。在本例中,我想检索类别G、H、I和J的广告,因为它们都是Leaf,它们都是类别E的子类 如何通过Spring存储库实现它?下面是

我正在编写SpringBoot应用程序,它基本上是广告服务。在数据库中(我使用PostgreSQL方言),我存储类别树。每个类别都包含对其直接父类的引用。每个广告都指定了其类别,为了解决此问题,您只能将广告指定给叶类别

下面的图像捕捉到了结构

现在我需要从数据库中检索特定类别的广告。但假设我想检索E类的所有广告,这意味着我想检索E类的子类广告,但不一定是直接子类广告。在本例中,我想检索类别G、H、I和J的广告,因为它们都是Leaf,它们都是类别E的子类

如何通过Spring存储库实现它?下面是本例的简化实体类

类别

@Entity
@Table(name = "categories")
public class Category {

    @Id
    @Column(name = "cat_id")
    private int id;

    @Column(name = "cat_name", length = 50, nullable = false, unique = true)
    private String name;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "cat_parent", referencedColumnName = "cat_id", nullable = false)
    private Category parent;

    ...
}
。。。和广告

@Entity
@Table(name = "advertisements")
public class Advertisement {

    @Id
    @Column(name = "adv_id")
    private long id;

    @Column(name = "adv_title", length = 100, nullable = false)
    private String title;

    @Column(name = "adv_description", length = 5000, nullable = false)
    private String description;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "adv_category", referencedColumnName = "cat_id", nullable = false)
    private Category category;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "adv_owner", referencedColumnName = "user_id", nullable = false)
    private User owner;

    ...
}

这可能不是最优雅的方法,但您可以先收集类别包,然后使用in子句查询广告存储库,该子句将结果限制为仅在这些类别中的广告

首先将父/子关联的另一端添加到Category对象(如果还没有),然后创建一个方法,返回一个包含该类别和所有子对象(不仅仅是直接子对象)的集合,如下所示:

@JoinColumn(name = "cat_parent")
@OneToMany
Set<Category> children;
... 
public Set<Category> collectLeafChildren() { 
   Set<Category> results = new HashSet<>(); 
   if (children.isEmpty()) { 
      results.add(this); 
   } else { 
      children.forEach(child -> { 
        results.addAll(child.collectLeafChildren()); 
      }); 
   } 
   return results; 
}
并使用以下各项:

Set<Category> categories = category.collectLeafChildren();
List<Advertisement> advertisementRepository.findByCategoryIn(categories);
Set categories=category.collectLeafChildren();
列出advertisementRepository.findByCategoryIn(类别);

您可以使用Postgres公共表表达式(CTE)编写本机查询。看这本书。@manish我从来没用过,我会查一查。我想这与那些递归查询有关吧?这看起来并不容易。有两种方法可以创建分层数据结构。当结构中的对象持有对其直接父对象的引用时,该结构称为
邻接列表
。另一个选项是将结构建模为
嵌套集
。在嵌套集中查找叶节点非常简单,需要一个非常简单的SQL查询。然而,嵌套集的构造非常复杂,只有当层次结构树基本上是静态的并且不需要经常更改时,嵌套集才实际有用。请仔细阅读这两个模型,看看嵌套集是否能解决您的问题。@manish好吧,一旦我创建了类别树,我可能不会更改它。这只是我硕士论文的一个项目,不需要太完美。所以
nestedset
听起来像是可行的解决方案。不过,
邻接树
似乎要优雅得多。我找到了一个本机SQL查询来从根目录检索所有叶,但我未能修改它,使其返回任何子树的叶节点。这似乎是最简单的解决方案,一点也不坏。这个项目不需要最好的。问题是,我已经映射了关联的另一端(父级)。我听说两头都要交往是不好的。我可以解除父母的职务,但我不确定将来是否需要……很高兴知道,我不确定为什么持有协会的两端会成为一个问题?我已经做过几次了,而且侥幸逃脱了;)出于性能考虑,默认情况下,父关系(ManyToOne)是急切加载的,而子关系(OneToMany)是惰性的。在queryI dunno中,您可以使用实体图或连接抓取来进一步控制这些关系的映射方式,我只是想了解JPA的内容,但我的老师曾经告诉我,我们应该避免保持关联的两端,一个就够了。顺便说一下,我需要稍微改变一下你的方法-我只需要给一个孩子的叶子类别,我不需要介于两者之间的类别
public Set collectLeafChildren(){Set results=new HashSet();if(children.isEmpty()){results.add(this);}else{children.forEach(child->{results.addAll(child.collectLeafChildren();});}返回结果;}
很抱歉格式化。我想维护两端会有点繁琐。例如,如果要将新的子项添加到父项,则应确保该子项引用父项,并确保该子项位于子项的父项包中。使用新的代码示例(仅leafs)@Patryk更新了答案-您是否最终使用了此解决方案或其他解决方案来解决您的问题?如果对你有帮助,请接受答案,谢谢!
Set<Category> categories = category.collectLeafChildren();
List<Advertisement> advertisementRepository.findByCategoryIn(categories);