在哪种情况下使用 JPA @JoinTable
注释?
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;
}
}
这将创建以下数据库结构:
https://i.stack.imgur.com/TDcoI.png
@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;
生成的数据库将变为:
https://i.stack.imgur.com/WhC82.png
最后,如果您想为多对多关联创建模式,则使用连接表是唯一可用的解决方案。
这是映射多对多关联的唯一解决方案:您需要两个实体表之间的连接表来映射关联。
当您不想在多方表中添加外键并因此使其独立于一方时,它也用于 OneToMany(通常是单向)关联。
在 hibernate documentation 中搜索 @JoinTable 以获取解释和示例。
@OneToMany
关联的 join entity。
@ManyToMany 关联
大多数情况下,您需要使用 @JoinTable
注释来指定多对多表关系的映射:
链接表的名称和
两个外键列
因此,假设您有以下数据库表:
https://i.stack.imgur.com/PsEDG.png
在 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
)和 post_tag
中的外键列通过 inverseJoinColumns
属性引用 Tag
实体的链接表。
请注意,@ManyToMany 注释的级联属性设置为 PERSIST 和 MERGE 只是因为级联 REMOVE 是一个坏主意,因为我们将为另一个父记录发出 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 将为上述实体映射假定以下数据库模式:
https://i.stack.imgur.com/u52Ur.png
如前所述,单向 @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
表,外键列将是 post_id
,对于 post_comment
表,外键列将是 post_comment_id
。
单向 @OneToMany 关联效率不高,因此最好使用双向 @OneToMany 关联或仅使用 @ManyToOne 端。
@JoinTable
而不是连接实体会更好吗? @JoinTable
与连接实体相比有什么优势? (或相反亦然)
当一个实体可能是与不同类型的父级的多个父/子关系中的子级时,使用 @JoinTable
也更简洁。继续 Behrang 的示例,假设 Task 可以是 Project、Person、Department、Study 和 Process 的子项。
task
表是否应该有 5 个 nullable
外键字段?我想不是...
它可以让您处理多对多关系。例子:
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/@JoinColumn
可以在与mappedBy
相同的字段上进行注释。因此,正确的示例应该是将mappedBy
保留在Project
中,并将@JoinColumn
移动到Task.project
(反之亦然)Project_Tasks
也需要Task
的name
,变成三列:project_id
,task_id
,task_name
,如何实现呢?Caused by: org.hibernate.AnnotationException: Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn: