原文源自:https://stackabuse.com/a-guide-to-jpa-with-hibernate-relationship-mapping/
Java Persistence API(JPA)是Java生态系统的持久性标准。
它允许我们将我们的领域模型直接映射到数据库结构中,
然后让我们在代码中灵活地操作对象--而不是去搞那些繁琐的JDBC组件,如Connection、ResultSet等。
Our Example
在开始之前,让我们提醒一下我们在本系列的前一部分中使用的例子。
我们的想法是绘制一所学校的模型,让学生学习教师提供的课程。
首先,我们来定义一个关系。如果我们看一下我们的类图,我们可以看到一些关系:
教师和课程--学生和课程--课程和教材。
学生和地址之间也有联系,但它们并不被认为是关系。
这是因为地址不是一个实体(也就是说,它没有被映射到一个自己的表中)。所以,就JPA而言,它不是一个关系。
- One-to-Many
- Many-to-One
- One-to-One
- Many-to-Many
Let's tackle these relationships one by one.
One-to-Many/Many-to-One
We'll get started with the One-to-Many and Many-to-One relationships, which are closely related. You could go ahead and say that they're the opposite sides of the same coin.
What's a One-to-Many relationship?
As its name implies, it's a relationship that links one entity to many other entities.
In our example, this would be a Teacher
and their Courses
. A teacher can give multiple courses, but a course is given by only one teacher (that's the Many-to-One perspective - many courses to one teacher).
Another example could be on social media - a photo can have many comments, but each of those comments belongs to that one photo.
Before diving into the details of how to map this relationship, let's create our entities:
@Entity public class Teacher { private String firstName; private String lastName; } @Entity public class Course { private String title; }
Now, the fields of the Teacher
class should include a list of courses. Since we'd like to map this relationship in a database, which can't include a list of entities within another entity - we'll annotate it with a @OneToMany
annotation:
@OneToMany private List<Course> courses;
How does JPA reflect this relationship in the database? Generally, for this type of relationship, we must use a foreign key in a table.
JPA does this for us, given our input on how it should handle the relationship. This is done via the @JoinColumn
annotation:
@OneToMany @JoinColumn(name = "TEACHER_ID", referencedColumnName = "ID") private List<Course> courses;
Owning Side and Bidirectionality //只用放一边
In the previous example, the Teacher
class is called the owning side of the One-To-Many relationship. This is because it defines the join column between the two tables.
The Course
is called the referencing side in that relationship.
Eager vs Lazy Loading
Another thing worth noting is eager and lazy loading. With all our relationships mapped, it's wise to avoid impacting the software's memory by putting too many entities in it if unnecessary.
Imagine that Course
is a heavy object, and we load all Teacher
objects from the database for some operation. We don't need to retrieve or use the courses for this operation, but they're still being loaded alongside the Teacher
objects.
This can be devastating for the application's performance. Technically, this can be solved by using the Data Transfer Object Design Pattern and retrieving Teacher
information without the courses.
However, this can be a massive overkill if all we're gaining from the pattern is excluding the courses.
Thankfully, JPA thought ahead and made One-to-Many relationships load lazily by default.
这意味着关系不会被立即加载,而是在实际需要时才加载。
In our example, that would mean until we call on the Teacher#courses
method, the courses are not being fetched from the database.
By contrast, Many-to-One relationships are eager by default, meaning the relationship is loaded at the same time the entity is.
One-to-One
Now that we've set up the foundations of relationship mapping in JPA through One-to-Many/Many-to-One relationships and their settings, we can move on to One-to-One relationships.
This time, instead of having a relationship between one entity on one side and a bunch of entities on the other, we'll have a maximum of one entity on each side.
This is, for example, the relationship between a Course
and its CourseMaterial
. Let's first map CourseMaterial
, which we haven't done yet:
@Entity public class CourseMaterial { @Id private Long id; private String url; }
The annotation for mapping a single entity to a single other entity is, unshockingly, @OneToOne
.
Before setting it up in our model, let's remember that a relationship has an owning side - preferably the side which will hold the foreign key in the database.
In our example, that would be CourseMaterial
as it makes sense that it references a Course
(though we could go the other way around):
@OneToOne(optional = false) @JoinColumn(name = "COURSE_ID", referencedColumnName = "ID") private Course course;
There is no point in having material without a course to encompass it. That's why the relationship is not optional
in that direction.
Speaking of direction, let's make the relationship bidirectional, so we can access the material of a course if it has one. In the Course
class, let's add:
@OneToOne(mappedBy = "course") private CourseMaterial material;
Here, we're telling Hibernate that the material within a Course
is already mapped by the course
field of the CourseMaterial
entity.
Also, there's no optional
attribute here as it's true
by default, and we could imagine a course without material (from a very lazy teacher).
In addition to making the relationship bidirectional, we could also add cascading operations or make entities load eagerly or lazily.
Many-to-Many
现在,最后但并非最不重要的: 多对多关系。我们将这些关系保留到最后,因为它们比前面的关系需要更多的工作。
实际上,在数据库中,多对多关系涉及到一个中间表引用其他两个表。
幸运的是,JPA完成了大部分的工作,我们只需要抛出一些注解,它就会为我们处理剩下的工作。
因此,在我们的例子中,Many-to-Many关系将是Student和Course实例之间的关系,因为一个学生可以参加多个课程,一个课程可以被多个学生关注。
为了映射Many-to-Many关系,我们将使用@ManyToMany注解。然而,这次我们还将使用 @JoinTable 注解来设置表示该关系的表:
@ManyToMany @JoinTable( name = "STUDENTS_COURSES", joinColumns = @JoinColumn(name = "COURSE_ID", referencedColumnName = "ID"), inverseJoinColumns = @JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID") ) private List<Student> students;
Now, go over what's going on here. The annotation takes a few parameters. First of all, we must give the table a name. We've chosen it to be STUDENTS_COURSES
.
After that, we'll need to tell Hibernate which columns to join in order to populate STUDENTS_COURSES
. The first parameter, joinColumns
defines how to configure the join column (foreign key) of the owning side of the relationship in the table. In this case, the owning side is a Course
.