【问题标题】:In which case do you use the JPA @JoinTable annotation?在哪种情况下使用 JPA @JoinTable 注释?
【发布时间】:2011-07-25 14:24:41
【问题描述】:

在什么情况下你使用JPA @JoinTable注解?

【问题讨论】:

    标签: java hibernate jpa many-to-many hibernate-mapping


    【解决方案1】:

    这是映射多对多关联的唯一解决方案:您需要两个实体表之间的连接表来映射关联。

    当您不想在多方表中添加外键并因此使其独立于一方时,它也用于 OneToMany(通常是单向)关联。

    hibernate documentation 中搜索@JoinTable 以获得解释和示例。

    【讨论】:

    • 嗨,这不是多对多关联的唯一解决方案。您可以创建具有两个双向@OneToMany 关联的加入实体
    【解决方案2】:

    EDIT 2017-04-29:正如一些评论者所指出的,JoinTable 示例不需要mappedBy 注释属性。事实上,最新版本的 Hibernate 通过打印以下错误来拒绝启动:

    org.hibernate.AnnotationException: 
       Associations marked as mappedBy must not define database mappings 
       like @JoinTable or @JoinColumn
    

    假设您有一个名为Project 的实体和另一个名为Task 的实体,每个项目可以有许多任务。

    您可以通过两种方式为此场景设计数据库架构。

    第一种解决方案是创建一个名为Project的表和另一个名为Task的表,并在名为project_id的任务表中添加一个外键列:

    Project      Task
    -------      ----
    id           id
    name         name
                 project_id
    

    这样,就可以确定任务表中每一行的项目。如果您使用这种方法,在您的实体类中您将不需要连接表:

    @Entity
    public class Project {
    
       @OneToMany(mappedBy = "project")
       private Collection<Task> tasks;
    
    }
    
    @Entity
    public class Task {
    
       @ManyToOne
       private Project project;
    
    }
    

    另一种解决方案是使用第三个表,例如Project_Tasks,并将项目和任务之间的关系存储在该表中:

    Project      Task      Project_Tasks
    -------      ----      -------------
    id           id        project_id
    name         name      task_id
    

    Project_Tasks 表称为“联接表”。要在 JPA 中实现第二个解决方案,您需要使用 @JoinTable 注释。例如,为了实现单向的一对多关联,我们可以这样定义我们的实体:

    Project实体:

    @Entity
    public class Project {
    
        @Id
        @GeneratedValue
        private Long pid;
    
        private String name;
    
        @JoinTable
        @OneToMany
        private List<Task> tasks;
    
        public Long getPid() {
            return pid;
        }
    
        public void setPid(Long pid) {
            this.pid = pid;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public List<Task> getTasks() {
            return tasks;
        }
    
        public void setTasks(List<Task> tasks) {
            this.tasks = tasks;
        }
    }
    

    Task实体:

    @Entity
    public class Task {
    
        @Id
        @GeneratedValue
        private Long tid;
    
        private String name;
    
        public Long getTid() {
            return tid;
        }
    
        public void setTid(Long tid) {
            this.tid = tid;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    

    这将创建以下数据库结构:

    @JoinTable 注释还允许您自定义连接表的各个方面。例如,我们是否像这样注释tasks 属性:

    @JoinTable(
            name = "MY_JT",
            joinColumns = @JoinColumn(
                    name = "PROJ_ID",
                    referencedColumnName = "PID"
            ),
            inverseJoinColumns = @JoinColumn(
                    name = "TASK_ID",
                    referencedColumnName = "TID"
            )
    )
    @OneToMany
    private List<Task> tasks;
    

    生成的数据库将变为:

    最后,如果您想为多对多关联创建架构,使用连接表是唯一可用的解决方案。

    【讨论】:

    • 使用第一种方法,我的项目填充了我的任务,每个任务在合并之前填充了父项目,但我的所有条目都根据我的任务数量重复。具有两个任务的项目在我的数据库中保存了两次。为什么?
    • UPDATE 我的数据库中没有重复的条目,休眠选择左外连接,我不知道为什么..
    • 我相信@JoinTable/@JoinColumn 可以与mappedBy 在同一字段上进行注释。所以正确的例子应该是将mappedBy 保留在Project 中,并将@JoinColumn 移动到Task.project(反之亦然)
    • 不错!但我还有一个问题:如果连接表Project_Tasks也需要Taskname,变成三列:project_id,task_id,task_name,如何实现?跨度>
    • 我认为您不应该在第二个用法示例中使用 mappedBy 来防止出现此错误Caused by: org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn:
    【解决方案3】:

    当实体可能是具有不同类型父母的多个父/子关系中的子时,使用@JoinTable 也更简洁。继续 Behrang 的示例,假设 Task 可以是 Project、Person、Department、Study 和 Process 的子级。

    task 表应该有 5 个nullable 外键字段吗?我觉得不会……

    【讨论】:

      【解决方案4】:

      它可以让您处理多对多关系。示例:

      Table 1: post
      
      post has following columns
      ____________________
      |  ID     |  DATE   |
      |_________|_________|
      |         |         |
      |_________|_________|
      
      Table 2: user
      
      user has the following columns:
      
      ____________________
      |     ID  |NAME     |
      |_________|_________|
      |         |         |
      |_________|_________|
      

      Join Table 允许您使用以下方法创建映射:

      @JoinTable(
        name="USER_POST",
        joinColumns=@JoinColumn(name="USER_ID", referencedColumnName="ID"),
        inverseJoinColumns=@JoinColumn(name="POST_ID", referencedColumnName="ID"))
      

      将创建一个表:

      ____________________
      |  USER_ID| POST_ID |
      |_________|_________|
      |         |         |
      |_________|_________|
      

      【讨论】:

      • 问题:如果我已经有了这个额外的表怎么办? JoinTable 不会覆盖existign 吗?
      • @TheWandererr 您找到问题的答案了吗?我已经有一个连接表
      • 在我的例子中,它在拥有的边表中创建了一个冗余列。例如。 POST 中的 POST_ID。你能说明为什么会这样吗?
      【解决方案5】:

      @ManyToMany协会

      大多数情况下,您需要使用@JoinTable 注解来指定多对多表关系的映射:

      • 链接表的名称和
      • 两个外键列

      所以,假设您有以下数据库表:

      Post 实体中,您将映射此关系,如下所示:

      @ManyToMany(cascade = {
          CascadeType.PERSIST,
          CascadeType.MERGE
      })
      @JoinTable(
          name = "post_tag",
          joinColumns = @JoinColumn(name = "post_id"),
          inverseJoinColumns = @JoinColumn(name = "tag_id")
      )
      private List<Tag> tags = new ArrayList<>();
      

      @JoinTable 注解用于通过name 属性指定表名,以及引用post 表的外键列(例如joinColumns)和表中的外键列通过inverseJoinColumns 属性引用Tag 实体的post_tag 链接表。

      注意@ManyToMany 注释的级联属性设置为PERSISTMERGE 只是因为级联REMOVE 是一个坏主意,因为我们将为另一个父记录@987654343 发出DELETE 语句@ 在我们的例子中,而不是 post_tag 记录。

      单向@OneToMany 关联

      缺少@JoinColumn 映射的单向@OneToMany 关联的行为类似于多对多表关系,而不是一对多。

      所以,假设您有以下实体映射:

      @Entity(name = "Post")
      @Table(name = "post")
      public class Post {
       
          @Id
          @GeneratedValue
          private Long id;
       
          private String title;
       
          @OneToMany(
              cascade = CascadeType.ALL,
              orphanRemoval = true
          )
          private List<PostComment> comments = new ArrayList<>();
       
          //Constructors, getters and setters removed for brevity
      }
       
      @Entity(name = "PostComment")
      @Table(name = "post_comment")
      public class PostComment {
       
          @Id
          @GeneratedValue
          private Long id;
       
          private String review;
       
          //Constructors, getters and setters removed for brevity
      }
      

      Hibernate 将为上述实体映射假定以下数据库架构:

      如前所述,单向 @OneToMany JPA 映射的行为类似于多对多关联。

      要自定义链接表,也可以使用@JoinTable注解:

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

      现在,链接表将被称为post_comment_ref,外键列将是post_id,用于post 表,post_comment_id,用于post_comment 表。

      单向@OneToMany 关联效率不高,因此最好使用双向@OneToMany 关联或仅使用@ManyToOne 端。

      【讨论】:

      • 嗨@Vlad,如果连接表没有额外的列,使用@JoinTable而不是连接实体更好吗? @JoinTable 与连接实体相比有什么优势? (反之亦然)
      • 查看 thisthis 文章以获得您问题的详细答案。
      • 我以前看过这些文章;他们都是伟大的。但我的错,我错过了second article结论部分。那部分是我的答案。谢谢@Vlad。
      • 如有疑问,请前往 Vlad Mihalcea Dot Com。这就是答案所在。
      猜你喜欢
      • 1970-01-01
      • 2011-10-18
      • 1970-01-01
      • 1970-01-01
      • 2012-07-17
      • 2016-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多