了解JPA/Hibernate关联

本文探究了 JPA/Hibernate 中一对一,一对多,多对对关联关系的应用场景以及单向维护和双向维护的优缺点。

1, 概览

Java Persistence API(JPA)是Java应用程序的对象-关系映射(ORM)规范。此外,Hibernate是JPA规范的流行实现之一。

关联是ORM的一个基本概念,允许我们定义实体之间的关系。在本教程中,我们将讨论JPA/Hibernate中单向和双向关联的区别。

2,单向关联

单向关联在面向对象编程中常用于建立实体之间的关系。然而,需要注意的是,在单向关联中,只有一个实体持有对另一个实体的引用。

为了在Java中定义一个单向关联,我们可以使用注解,如 @ManyToOne@OneToMany@OneToOne@ManyToMany。通过使用这些注解,我们可以在代码中的两个实体之间创建一个清晰的、定义明确的关系。

2.1,一对多的关系

在一对多的关系中,一个实体对另一个实体的一个或多个实例有一个引用。

一个常见的例子是一个部门(Department)和其雇员(Employee)之间的关系。每个部门有许多雇员,但每个雇员只属于一个部门。

让我们看一下如何定义一对多的单向关联:

@Entity
public class Department {
 
    @Id
    private Long id;
 
    @OneToMany
    @JoinColumn(name = "department_id")
    private List<Employee> employees;
}

@Entity
public class Employee {
 
    @Id
    private Long id;
}

这里,Department 实体有一个对 Employee 实体列表的引用。@OneToMany 注解指定了这是一个一对多的关联。@JoinColumn 注解指定了 Employee 表中引用 Department 表的外键列。

2.2, 多对一的关系

在多对一关系中,一个实体的许多实例与另一个实体的一个实例相关。

例如,让我们考虑学生(Student)和学校(School)。每个学生只能在一个学校注册,但每个学校可以有多个学生。

让我们来看看如何定义一个多对一的单向关联:

@Entity
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "school_id")
    private School school;
}

@Entity
public class School {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

在这种情况下,我们在 StudentSchool 实体之间有一个多对一的单向关联。@ManyToOne 注解指定了每个学生只能在一所学校注册,而 @JoinColumn 注解指定了外键列名来连接 StudentSchool 实体。

2.3, 一对一的关系

在一对一的关系中,一个实体的实例只与另一个实体的一个实例相关。

一个常见的例子是一个雇员(Employee)和一个停车点(ParkingSpot)之间的关系。每个雇员都有一个ParkingSpot,而每个ParkingSpot都属于一个雇员。

让我们来看看如何定义一个一对一的单向关联:

@Entity
public class Employee {
 
    @Id
    private Long id;
 
    @OneToOne
    @JoinColumn(name = "parking_spot_id")
    private ParkingSpot parkingSpot;
 
}

@Entity
public class ParkingSpot {
 
    @Id
    private Long id;
 
}

这里,Employee 实体有一个对 ParkingSpot 实体的引用。@OneToOne 注解指定了这是一个一对一的关联。@JoinColumn 注解指定了 Employee 表中引用 ParkingSpot 表的外键列。

2.4,多对多的关系

在多对多的关系中,一个实体的许多实例与另一个实体的许多实例相关。

假设我们有两个实体–书(Book)和作者(Author)。每本书可以有多个作者,而每个作者可以写多本书。在JPA中,这种关系是用 @ManyToMany 注解来表示的。

让我们来看看如何定义一个多对多的单向关联:

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToMany
    @JoinTable(name = "book_author",
            joinColumns = @JoinColumn(name = "book_id"),
            inverseJoinColumns = @JoinColumn(name = "author_id"))
    private Set<Author> authors;

}

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
}

在这里,我们可以看 BookAuthor实体之间的多对多的单向关联。@ManyToMany 注解指定了每本书可以有多个作者,每个作者可以写多本书。@JoinTable 注解指定了连接表的名称和外键列来连接 BookAuthor 实体。

3,双向关联

双向关联是两个实体之间的关系,每个实体都有对另一个实体的引用。

为了定义双向关联,我们使用 @OneToMany@ManyToMany 注解中的 mappedBy 属性。然而,需要注意的是,仅仅依靠单向关联可能是不够的,因为双向关联提供了额外的好处。

3.1, 一对多的双向关联

在一对多的双向关联中,一个实体有一个对另一个实体的引用。此外,另一个实体有一个对第一个实体的引用集合。

例如,一个部门(Department)实体有一个雇员(Employee)实体的集合。同时,一个 Employee 实体有一个对其所属 Department 实体的引用。

让我们来看看如何创建一个一对多的双向关联:

@Entity
public class Department {
 
    @OneToMany(mappedBy = "department")
    private List<Employee> employees;
 
}
 
@Entity
public class Employee {
 
    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;
 
}

Department 实体中,我们使用 @OneToMany 注解来指定 Department 实体和 Employee 实体之间的关系。mappedBy 属性指定了 Employee 实体中拥有该关系的属性名称。在本例中,Department 实体并不拥有该关系,所以我们指定 mappedBy = "department"

Employee 实体中,我们使用 @ManyToOne 注解来指定 Employee 实体和 Department 实体之间的关系。@JoinColumn 注解指定了 Employee 表中引用 Department 表的外键列的名称。

3.2,多对多的双向关联

当处理多对多的双向关联时,重要的是要理解所涉及的每个实体将有一个对另一个实体的引用集合。

为了说明这个概念,让我们考虑这样一个例子:一个 Student 实体有一个 Course 实体的集合,一个 Course 实体又有一个 Student 实体的集合。通过建立这样的双向关联,我们使两个实体都能意识到对方,并使导航和管理它们的关系变得更容易。

下面是一个关于如何创建多对多双向关联的例子:

@Entity
public class Student {
 
    @ManyToMany(mappedBy = "students")
    private List<Course> courses;
 
}
 
@Entity
public class Course {
 
    @ManyToMany
    @JoinTable(name = "course_student",
        joinColumns = @JoinColumn(name = "course_id"),
        inverseJoinColumns = @JoinColumn(name = "student_id"))
    private List<Student> students;
 
}

Student 实体中,我们使用 @ManyToMany 注解来指定 Student 实体和 Course 实体之间的关系。mappedBy 属性指定了拥有该关系的 Course 实体中的属性名称。在本例中,Course 实体拥有该关系,所以我们指定 mappedBy = "students"

在课程实体中,我们使用 @ManyToMany 注解来指定课程实体和学生实体之间的关系
@JoinTable 注解指定了存储关系的连接表的名称。

4, 单向与双向关联

在面向对象的编程中,单向和双向的关联在两个类之间的关系方向上有所不同。

首先,单向关联只在一个方向上有关系,而双向关联则在两个方向上有关系。这种差异会影响软件系统的设计和功能。例如,双向关联可以使相关类之间的导航更容易,但它们也会引入更多的复杂性和潜在的错误。

另一方面,单向关联可能更简单,更不容易出错,但它们可能需要更多的变通方法来在相关类之间进行导航。

总的来说,理解单向关联和双向关联之间的区别对于在软件系统的设计和实现方面做出明智的决定至关重要。

下面是一个表格,总结了数据库中单向和双向关联的区别:

单向关联 双向关联
定义 两个表之间的关系,其中一个表有一个外键,引用另一个表的主键。 两个表之间的关系,其中两个表都有一个外键,引用另一个表的主键。
导航 只能在一个方向上导航 - 从子表到父表。 可在两个方向上导航–从任何一张表到另一张。
性能 一般来说,由于表结构更简单,约束条件更少,所以速度更快。 一般来说,由于额外的约束和表结构的复杂性,速度较慢。
数据一致性 通过子表的外键约束引用父表的主键来保证。 通过子表的外键约束引用父表的主键来保证。
灵活性 不太灵活,因为子表的变化可能需要对父表 schema 进行修改。 更加灵活,因为任何一个表的变化都可以独立进行而不影响另一个表。

值得注意的是,实施的具体细节会因使用的数据库管理系统而不同。然而,为了提供一个一般性的理解,上表概述了单向和双向关联之间的区别。

认识到这些变化很重要,因为它们会大大影响数据库系统的性能和功能。

5,总结

在这篇文章中,我们看到单向或双向关联的选择是如何依赖于软件的具体要求的。单向关联比较简单,可能对许多应用来说已经足够了,而双向关联提供了更多的灵活性,在更复杂的情况下可能会很有用。

然而,双向关联也会引入更多的复杂性和潜在的问题,如循环依赖和内存泄漏,因此应谨慎使用。仔细考虑权衡并为每种情况选择适当的关联类型是很重要的。


参考: Understanding JPA/Hibernate Associations | Baeldung