【问题标题】:How to model a tree with Hibernate?如何用 Hibernate 建模一棵树?
【发布时间】:2011-10-27 05:45:32
【问题描述】:

我有一个名为“域”的类。每个域可以有多个子域(相同类型)。

我需要能够确定子域和根域。子域本身可以有子域。这可能有好几个层次。

例子:

Rootdomain  
|- Subdomain 1  
|   |- Subdomain 2  
|   |
|   |- Subdomain 3
|
|- Subdomain 4
|   |- Subdomain 5

如何使用 Hibernate 注释为这样的 Java 类建模?

【问题讨论】:

    标签: java hibernate spring


    【解决方案1】:

    建模会很简单:

    @Entity
    class Domain {
      @ManyToOne //add column definitions as needed
      private Domain parent;      //each Domain with parent==null is a root domain, all others are subdomains
    
      @OneToMany //add column definitions as needed
      private List<Domain> subdomains;
    }
    

    请注意,parent 是负责数据库条目的属性,即您需要为要存储的关系的子域设置 parent

    查询并不重要,因为 SQL(以及 HQL 和 JPQL)不容易支持树查询。 Hibernate 可以通过延迟加载下一个级别来做到这一点,但是如果您想在一个查询中加载一堆级别,那就很难了。

    【讨论】:

    • 我不建议使用列表。 Hibernate 不能同时获取多个包(列表)。如果您尝试在同一查询中加载此树的 3 个级别,则需要使用 Set 代替。
    • @Dherik 你是对的。不过,使用索引应该可以使用列表。
    【解决方案2】:

    有多种可能性,具体取决于您需要进行哪种操作。

    最直接的方法是简单地建立一个父子一对多关联。根据您需要执行的操作,选择适当的类型:单向一对多、单向多对一或双向。

    让树的所有节点与根节点具有多对一关系通常很有用。这允许在单个查询中非常轻松地加载整个树。

    【讨论】:

      【解决方案3】:

      我建议您首先正确使用纯 Java OO 模型,然后再考虑使用哪些 Hibernate 注释。如果您发现您需要合法地更改您的模型以适应 Hibernate,您可以随时这样做。

      最后,这种类型的问题通常通过Composite pattern 的一些变体来解决(不要过多关注模式的东西,只关注它背后的结构和想法。)

      使用关系数据库术语(以一种相当轻松的方式):

      A.如果您的域(根域和子域)是关系(表中的 n 元组集合,没有重复项且具有可识别的主键)

      B.您的域和子域具有相似的结构那么

      C.您可以通过定义 "parent" 外键将它们全部存储在同一个物理表中,以便一个元组的父 FK 映射到另一个元组的主键。

      最重要的是,这种递归关系必须是非循环的。如何在结构上处理它取决于您的问题域(您是否有一个根域,或者您可以有多个不相关的根域?)根域可以通过具有 NULL 父外键或条件来表示其中根域元组的父外键等于它自己的主键。任何一个都有优点和缺点(这是典型的愚蠢的火焰战争的主题。)

      【讨论】:

      • 我强烈不同意复合模式的使用(有关详细信息,请参阅我的答案)。但如果你用“一些变化”解决了这个问题,那么你应该在一些细节上解释这个变化。
      • 我所说的变体是让所有叶子和复合材料都具有相同的具体类型(有关详细信息,请参见 Thomas 的回答。)当然,对“复合模式”的严格(因此是幼稚的)解释" 意味着会导致很多问题(与图片中的hibernate无关)。现实情况是,所有“kosher”,按书本实施的模式也会遇到问题。 “复合”这个名称不应该引起对叶类型和复合类型严格不同的想象,而是应该由 things 组成,后者也可以由其他可能与类型等效的 things 组成。跨度>
      【解决方案4】:

      如果您想使用 Hibernate/JPA 延迟初始化(这是正常情况),那么您应该不要使用复合模式

      Composite Pattern 与 Hibernate 相关的问题是:在 Composite 中,您有一个具有引用其子组件的 Composite。 但是Component只是一个抽象类或者接口,所以每个Component都是一个Leaf或者一个Composite。 如果您现在对 Composite 的 cild 集使用延迟初始化,但随后需要将具体的子代转换为 Leaf 或 Composite,您将得到一个 Cast Exception,因为 hibernate 使用无法转换为 Leaf 或的组件的代理复合。

      组合模式的第二个缺点是,每个类在其整个生命周期内都是叶子或组合。 如果你的结构永远不会改变,那很好。但是如果因为有人想添加子节点/叶子而必须将叶子变成组合,它就行不通了。


      因此,如果您有一些动态结构,我推荐一个在父节点和子节点之间具有双向关系的类 Node。 如果您经常需要在代码中导航到父母或孩子,这种关系应该是双向的。 维持这种关系有点棘手,所以我决定发布更多代码。

      @Entity
      public class Domain {
      
          @Id
          private long id;
      
           /** The parent domain, can be null if this is the root domain. */
          @ManyToOne
          private Domain parent;
      
          /**
           * The children domain of this domain.
           * 
           * This is the inverse side of the parent relation.
           * 
           * <strong>It is the children responsibility to manage there parents children set!</strong>
           */
          @NotNull
          @OneToMany(mappedBy = "parent")
          private Set<Domain> children = new HashSet<Domain>();
          /**
           * Do not use this Constructor!
           * Used only by Hibernate.
           */
          Domain() {
          }
      
          /**
           * Instantiates a new domain.
           * The domain will be of the same state like the parent domain.
           *
           * @param parent the parent domain
           * @see Domain#createRoot()
           */
          public Domain(final Domain parent) {
              if(parent==null) throw new IllegalArgumentException("parent required");
      
              this.parent = parent;
              registerInParentsChilds();
          }
      
          /** Register this domain in the child list of its parent. */
          private void registerInParentsChilds() {
              this.parent.children.add(this);
          }
      
          /**
           * Return the <strong>unmodifiable</strong> children of this domain.
           * 
           * @return the child nodes.
           */
          public Set<Domain> getChildren() {
              return Collections.unmodifiableSet(this.children);
          }        
      
          /**
           * Move this domain to an new parent domain.
           *
           * @param newParent the new parent
           */
          public void move(final Domain newParent)  {
              Check.notNullArgument(newParent, "newParent");
      
              if (!isProperMoveTarget(newParent) /* detect circles... */ ) { 
                  throw new IllegalArgumentException("move", "not a proper new parent", this);
              }
      
              this.parent.children.remove(this);
              this.parent = newParent;
              registerInParentsChilds();
          }
      
          /**
           * Creates the root.
           *
           * @param bid the bid
           * @return the domain
           */
          public static Domain createRoot() {
              return new Domain();
          }
      }
      

      【讨论】:

      • 当我删除一个节点时,会同时删除所有子节点吗? inverse 属性和 cascade 允许一次性保存一棵树呢?
      • @Aaron Digulla:我的回答目前不涉及这个主题。因此,如果您删除其父级,目前它不会删除子级。但是您应该能够通过级联删除来实现这一点。我不相信您需要 inverse 属性来删除。
      • 我看到了至少 2 个版本,但我想知道哪个更好:您也可以添加 @JoinColumn Annotation(name= "parent_id"),但正如我上面提到的,我不知道有什么好处。
      • @Zarathustra:哪两个版本? - 它是一个问题吗? -- 并且 @JoinColumn 注释不是必需的,如果缺少它,则 JPA 应用默认值。 (docs.jboss.org/hibernate/annotations/3.5/reference/en/html/…)
      猜你喜欢
      • 1970-01-01
      • 2014-07-24
      • 2017-10-27
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 2016-05-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多