【问题标题】:JPA JoinColumn vs mappedByJPA JoinColumn 与 mappedBy
【发布时间】:2012-08-09 21:58:18
【问题描述】:

有什么区别:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, 
    mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}

【问题讨论】:

标签: java hibernate jpa orm


【解决方案1】:

注解@JoinColumn表示该实体是关系的所有者(即:对应的表有一个列,该列具有指向被引用表的外键),而属性@987654322 @表示这一侧的实体是关系的逆,所有者驻留在“其他”实体中。这也意味着您可以从使用“mappedBy”(完全双向关系)注释的类中访问另一个表。

特别是,对于问题中的代码,正确的注释应如下所示:

@Entity
public class Company {
    @OneToMany(mappedBy = "company",
               orphanRemoval = true,
               fetch = FetchType.LAZY,
               cascade = CascadeType.ALL)
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}

【讨论】:

  • 在这两种情况下,Branch 都有带有公司 ID 的字段。
  • Company 表没有包含引用表的外键的列 - Branch 引用了 Company.. 为什么说“对应表的列具有引用表的外键桌子” ?请您再解释一下。
  • @MykhayloAdamovych 我用示例代码更新了我的答案。注意在Company中使用@JoinColumn是错误的
  • @MykhayloAdamovych:不,这实际上不太正确。如果Branch 没有引用Company 的属性,但基础表有一个引用@JoinTable 的列,那么您可以使用@JoinTable 对其进行映射。这是一种不寻常的情况,因为您通常会映射对象中与其表对应的列,但它可能会发生,而且是完全合法的。
  • 这是另一个不喜欢 ORM 的原因。文档通常过于狡猾,在我的书中,这是在太多魔法领域徘徊。我一直在努力解决这个问题,当一个字一个字地跟随 @OneToOne 时,子行在引用父级的 FKey 列中更新为 null
【解决方案2】:

@JoinColumn 可以用于关系的双方。 问题是关于在@OneToMany 方面使用@JoinColumn(罕见情况)。这里的重点在于物理信息重复(列名)以及未优化的 SQL 查询,会产生一些额外的UPDATE 语句

根据documentation

由于 多对一(几乎)总是 JPA 规范中双向关系的所有者方,因此一对多关联由@OneToMany(mappedBy=...)注释/p>

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

TroopSoldier 通过部队属性具有双向的一对多关系。您不必(不得)在mappedBy 端定义任何物理映射。

要映射一个双向的一对多,一对多的一方作为拥有方,你必须删除mappedBy元素并将多对一的@JoinColumn设置为insertableupdatable 为假。此解决方案未优化,会产生一些额外的UPDATE 语句。

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

【讨论】:

  • 我无法弄清楚如何在您的第二个 sn-p 中拥有 Troop,Soldier 仍然是拥有者,因为它包含引用 Troop 的外键。 (我正在使用mysql,我检查了你的方法)。
  • 在您的示例中,注释mappedBy="troop" 指的是哪个字段?
  • @Fractaliste 注解mappedBy="troop" 指的是士兵类中的属性部队。在上面的代码中,该属性是不可见的,因为这里 Mykhaylo 省略了它,但您可以通过 getter getTroop() 推断它的存在。查看Óscar López的答案,很清楚,你就明白了。
  • 这个例子是对 JPA 2 规范的滥用。如果作者的目标是创建双向关系,那么它应该在父端使用 mappedBy,在子端使用 JoinColumn(如果需要)。通过这里介绍的方法,我们得到了 2 个单向关系:OneToMany 和 ManyToOne,它们是独立的,但只是靠运气(更多是由于误用),这两个关系是使用相同的外键定义的
  • 如果您使用的是 JPA 2.x,我在下面的回答会更简洁一些。虽然我建议尝试这两种路由并查看 Hibernate 在生成表时做了什么。如果您正在进行一个新项目,请选择您认为适合您需求的任何一代。如果您使用的是旧数据库并且不想更改结构,请选择与您的架构匹配的数据库。
【解决方案3】:

单向一对多关联

如果您将@OneToMany 注释与@JoinColumn 一起使用,那么您就有一个单向关联,就像下图中的父Post 实体和子PostComment 之间的关联:

当使用单向一对多关联时,只有父方映射关联。

在此示例中,只有 Post 实体将定义与子 PostComment 实体的 @OneToMany 关联:

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();

双向一对多关联

如果将@OneToManymappedBy 属性集一起使用,则具有双向关联。在我们的例子中,Post 实体都有一个PostComment 子实体的集合,而子PostComment 实体有一个对父Post 实体的引用,如下图所示:

PostComment实体中,post实体属性映射如下:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

我们将fetch 属性显式设置为FetchType.LAZY 的原因是,默认情况下,所有@ManyToOne@OneToOne 关联都会被急切地获取,这可能会导致N+1 查询问题。

Post实体中,comments关联映射如下:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

@OneToMany 注释的mappedBy 属性引用子PostComment 实体中的post 属性,这样,Hibernate 就知道双向关联是由@ManyToOne 方控制的,即负责管理此表关系所基于的外键列值。

对于双向关联,您还需要有两个实用方法,例如addChildremoveChild

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

这两种方法确保双向关联的双方是同步的。如果不同步两端,Hibernate 不保证关联状态更改会传播到数据库。

选择哪一个?

单向@OneToMany 关联的性能不是很好,所以你应该避免它。

你最好使用双向@OneToMany,效率更高。

【讨论】:

    【解决方案4】:

    注解ma​​ppedBy理想情况下应该一直用在双向关系的Parent端(Company类),在这种情况下应该在Company类中指向成员变量'company' Child 类(Branch 类)

    注解@JoinColumn 用于指定用于加入实体关联的映射列,此注解可用于任何类(Parent 或 Child),但理想情况下应仅在一侧使用(在父类或子类中都不是)在这种情况下,我在双向关系的子端(分支类)中使用它,指示分支类中的外键。

    下面是工作示例:

    父类、公司

    @Entity
    public class Company {
    
    
        private int companyId;
        private String companyName;
        private List<Branch> branches;
    
        @Id
        @GeneratedValue
        @Column(name="COMPANY_ID")
        public int getCompanyId() {
            return companyId;
        }
    
        public void setCompanyId(int companyId) {
            this.companyId = companyId;
        }
    
        @Column(name="COMPANY_NAME")
        public String getCompanyName() {
            return companyName;
        }
    
        public void setCompanyName(String companyName) {
            this.companyName = companyName;
        }
    
        @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
        public List<Branch> getBranches() {
            return branches;
        }
    
        public void setBranches(List<Branch> branches) {
            this.branches = branches;
        }
    
    
    }
    

    子类,分支

    @Entity
    public class Branch {
    
        private int branchId;
        private String branchName;
        private Company company;
    
        @Id
        @GeneratedValue
        @Column(name="BRANCH_ID")
        public int getBranchId() {
            return branchId;
        }
    
        public void setBranchId(int branchId) {
            this.branchId = branchId;
        }
    
        @Column(name="BRANCH_NAME")
        public String getBranchName() {
            return branchName;
        }
    
        public void setBranchName(String branchName) {
            this.branchName = branchName;
        }
    
        @ManyToOne(fetch=FetchType.LAZY)
        @JoinColumn(name="COMPANY_ID")
        public Company getCompany() {
            return company;
        }
    
        public void setCompany(Company company) {
            this.company = company;
        }
    
    
    }
    

    【讨论】:

      【解决方案5】:

      我不同意 Óscar López 接受的答案。这个答案不准确!

      不是@JoinColumn,表示此实体是关系的所有者。相反,它是 @ManyToOne 注释执行此操作(在他的示例中)。

      @ManyToOne@OneToMany@ManyToMany 等关系注释告诉 JPA/Hibernate创建映射。默认情况下,这是通过单独的 Join Table 完成的。


      @JoinColumn

      @JoinColumn 的目的是创建一个 join 列 尚不存在。如果是这样,那么这个注解可以用来 命名连接列。


      MappedBy

      MappedBy 参数的目的是指示 JPA:不要 创建另一个连接表,因为关系已经被映射 由这种关系的相反实体。



      请记住:MappedBy 是关系注释的属性,其目的是生成一种机制来关联两个实体,默认情况下它们通过创建连接表来实现。 MappedBy 在一个方向上停止该进程。

      不使用MappedBy 的实体被称为关系的所有者,因为映射的机制是在其类中通过使用针对外部的三个映射注释之一来规定的关键领域。这不仅指定了映射的性质,还指示了连接表的创建。此外,还可以通过在外键上应用 @JoinColumn 注释来抑制连接表,从而将其保留在所有者实体的表中。

      总而言之:@JoinColumn 要么创建一个新的连接列,要么重命名一个现有的;而MappedBy 参数与另一个(子)类的关系注释协同工作,以便通过连接表或通过在所有者实体的关联表中创建外键列来创建映射。

      为了说明MapppedBy 的工作原理,请考虑以下代码。如果要删除MappedBy 参数,那么Hibernate 实际上会创建两个连接表!为什么?因为在多对多关系中存在对称性,并且 Hibernate 没有理由选择一个方向而不是另一个方向。

      因此,我们使用MappedBy 告诉 Hibernate,我们选择了另一个实体来指示两个实体之间关系的映射。

      @Entity
      public class Driver {
          @ManyToMany(mappedBy = "drivers")
          private List<Cars> cars;
      }
      
      @Entity
      public class Cars {
          @ManyToMany
          private List<Drivers> drivers;
      }
      

      在所有者类中添加@JoinColumn(name = "driverID")(见下文),将阻止创建连接表,而是在 Cars 表中创建 driverID 外键列以构造映射:

      @Entity
      public class Driver {
          @ManyToMany(mappedBy = "drivers")
          private List<Cars> cars;
      }
      
      @Entity
      public class Cars {
          @ManyToMany
          @JoinColumn(name = "driverID")
          private List<Drivers> drivers;
      }
      

      【讨论】:

      • 很好的答案,恕我直言,比公认的要好。我只是想知道为什么我仍然有外键列并且没有连接表,即使我从未使用过@JoinColumn
      • 好的,似乎不需要@JoinColumn 来避免连接表。用注释声明两边 + 用 mappedBy 声明两边也会引入这种行为。
      • 最好的解释之一,谢谢!
      【解决方案6】:

      我想补充一点,@JoinColumn 并不总是与this 答案所暗示的物理信息位置 相关。即使父表没有指向子表的表数据,您也可以将@JoinColumn@OneToMany 组合使用。

      How to define unidirectional OneToMany relationship in JPA

      Unidirectional OneToMany, No Inverse ManyToOne, No Join Table

      它似乎只在JPA 2.x+ 中可用。当您希望子类只包含父类的 ID,而不是完整的参考时,这很有用。

      【讨论】:

      • 你是对的,JPA2中引入了对单向OneToMany无连接表的支持
      【解决方案7】:

      让我把它简单化。
      无论映射如何,您都可以在任一侧使用 @JoinColumn

      让我们把它分成三种情况。
      1) 从分公司到公司的单向映射。
      2) 从公司到分公司的双向映射。
      3) 只有从公司到分公司的单向映射。

      因此,任何用例都属于这三个类别。那么让我来解释一下如何使用 @JoinColumnma​​ppedBy
      1) 从分公司到公司的单向映射。
      在 Branch 表中使用 JoinColumn
      2) 从公司到分公司的双向映射。
      如@Mykhaylo Adamovych 的回答所述,在 Company 表中使用 ma​​ppedBy
      3)公司到分公司的单向映射。
      只需在 Company 表中使用 @JoinColumn

      @Entity
      public class Company {
      
      @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
      @JoinColumn(name="courseId")
      private List<Branch> branches;
      ...
      }
      

      这表示基于分支表中的外键“courseId”映射,获取所有分支的列表。注意:在这种情况下,您无法从分支机构获取公司,只有从公司到分支机构的单向映射存在。

      【讨论】:

        【解决方案8】:

        JPA 是一个分层的 API,不同的层次有自己的注解。最高级别是 (1) 描述持久类的实体级别,然后是 (2) 关系数据库级别,假设实体映射到关系数据库和 (3) java 模型。

        1 级注释:@Entity@Id@OneToOne@OneToMany@ManyToOne@ManyToMany。 您可以单独使用这些高级注释在应用程序中引入持久性。但是你必须根据 JPA 所做的假设来创建你的数据库。这些注释指定实体/关系模型。

        2 级注释:@Table@Column@JoinColumn、... 如果您对 JPA 的默认设置不满意,或者如果您需要映射到现有数据库,则会影响从实体/属性到关系数据库表/列的映射。这些注解可以看作是实现注解,它们指定应该如何进行映射。

        在我看来,最好尽可能坚持高级注释,然后根据需要引入较低级别的注释。

        回答问题:@OneToMany/mappedBy 最好,因为它只使用实体域中的注释。 @oneToMany/@JoinColumn 也很好,但它使用了一个实现注释,这不是绝对必要的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-12-03
          • 1970-01-01
          • 1970-01-01
          • 2019-11-14
          • 1970-01-01
          • 2015-09-12
          相关资源
          最近更新 更多