JPA:一对一的关系,提取类型,关系方向和查询优化
#java #hibernate #springboot #jpa

在上一篇文章中,我们只有一个名为“学生”的实体。让我们创建另一个名为库卡的实体。库卡实体将在数据库中具有自己的主ID列。

@Entity
@Table(name = "library_card")
public class LibraryCard {
    @Id
    @GeneratedValue
    private int id;
    @Temporal(TemporalType.DATE)
    private Date issuedDate;
    private boolean isActive;
}

就像学生实体一样,我们已经用<​​strong>@entity 注释及其成员变量带有适当注释的库卡。

我们可以在数据库中创建和坚持图书馆卡实体,就像我们坚持学生实体一样。我们想在这里做的是让每个学生拥有一张图书馆卡。

为了做一个简单的方法,我们可能会在学生实体中添加库卡成员变量,并在其中存储相应库卡的主要键值。使用这些库卡的这些主要钥匙值,我们可以参考库卡表本身。

@Entity
@Table(name = "student_table")
public class Student{
    .
    .
    private int library_card;
    .
    .
}

看起来很简单,但是这样做只会在将来引起数据库不一致的情况。

除此之外,我们确实没有在数据库中的两个表或Java侧的两个表之间建立任何关系。学生表中的库卡列仅包含一个应该是库表的主要键但不能保证的值。

解决此问题的解决方案是JPA提供的关系。我们可以定义的不同关系是一对一的,一对多的,多对多的,多对多的。

使用适当的表格需要我们了解数据本身。在我们的示例中,每个学生只能拥有一张库卡。因此,这里使用的适当关系是一对一的关系。

一对一关系

在一对一的关系中,一个实体与另一个实体完全关联。就数据库表而言,表中的每个记录都与另一个表格中的一个记录相关联。在我们的示例中,一个学生只能拥有一张图书馆卡,反之亦然。

在我们的学生实体中,而不是原始类型的成员变量库卡,让我们用类型库卡的变量卡替换为实体本身。

@Entity
@Table(name = "student_table")
public class Student{
    .
    .
    private LibraryCard card;
    .
    .
}

如果我们运行了应用程序,我们会出现错误,这是因为Hibernate不知道如何持续非重要类型卡成员。

我们正在寻找的注释是 @OnetoOne 。通过将此注释贴在我们的卡变量之上,我们告诉JPA我们想在图书馆卡实体和学生实体之间建立关系。

@OneToOne
private LibraryCard card;

现在,让我们创建几个图书馆卡并将它们持续到数据库中,就像我们坚持的学生实体一样。

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("myPersistenceUnit");
        EntityManager entityManager = entityManagerFactory.createEntityManager();

        Student student1 = new Student();
        student1.setName("Sohail Shah");
        student1.setAge(18);
        student1.setAdmission_no("1234");
        student1.setDob(new Date());
        student1.setStudentType(StudentType.SCHOLARSHIP);

        Student student2 = new Student();
        student2.setName("Ali");
        student2.setAge(20);
        student2.setAdmission_no("1235");
        student2.setDob(new Date());
        student2.setStudentType(StudentType.NON_SCHOLARSHIP);

        LibraryCard card1 = new LibraryCard();
        card1.setActive(true);
        card1.setIssuedDate(new Date());

        LibraryCard card2 = new LibraryCard();
        card2.setActive(false);
        card2.setIssuedDate(new Date());

        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        entityManager.persist(student1);
        entityManager.persist(student2);
        entityManager.persist(card1);
        entityManager.persist(card2);
        transaction.commit();
        entityManager.close();
        entityManagerFactory.close();
    }
}

Image description
Image description

这就是数据库当前外观的样子。 card_id列设置为空,因为我们已经设置了表之间的关系,但没有为其设置值。让我们现在就这样做。

在坚持实体之前添加这些行。

student1.setCard(card1);
student2.setCard(card2);

如果我们现在查看数据库,我们可以看到库卡的主要键都存在于学生表的“卡ID”列中

Image description

这可能与我们试图在没有 @onetoone 注释的情况下只需仅存储主键而尝试做的。但是在这里,我们实际上定义了两个表之间的实际关系。

card_id列包含称为外键的键。外键在数据库中很重要,因为它有助于在表之间建立关系。此外键列包含不同表的主要键。有不同类型的密钥,例如复合键,备用密钥,超键和其他几个密钥。这些密钥用于数据库中的不同目的。

既然我们之间的实体之间有关系,那么与他们合作变得更加容易。这种关系为我们的数据库带来了参考完整性。它确保插入正确的值并管理我们的数据一致性。

提取

让我们从数据库中获取学生,看看它与库卡表有关系的情况。

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("myPersistenceUnit");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Student student1 = entityManager.find(Student.class, 1);
System.out.println(student1)

向实体添加适当的ToString()方法,以供它们正确打印

Image description

这是提取的结果。请注意,我们只获取了学生的详细信息,但它还提供了图书馆的详细信息。由于两个实体之间定义了一对一的关系,因此,冬眠的获取也会生成一个加入查询以获取库卡详细信息。

Image description

这种行为在某些情况下可以适合,而在其他情况下也不适合。例如,假设我们的学生实体与不同实体有多种其他关系。因此,当我们获取学生的详细信息时,也获取了所有其他关系中的所有实体。但是我们可能不希望所有实体的数据与学生实体关系。

JPA提供了一种改变这种行为的方法。我们可以通过配置一对一注释来改变此行为。有两种提取类型。急切的提取和懒惰的提取。

急切的获取

默认情况下,将一对一注释设置为急切的提取类型。当我们设置了获取类型以渴望与所有者实体数据一起获取关系数据时。

@OneToOne(fetch = FetchType.EAGER)
private LibraryCard card;

懒惰

@OneToOne(fetch = FetchType.LAZY)
private LibraryCard card;

当获取类型设置为懒惰时,不会立即获取关系数据。它只有在需要时才提取。

Image description

如果我们使用Fetch Type Lazy运行该应用程序,则在生成的查询中,您可以看到没有JOIN子句。需要在需要时将库卡数据作为单独查询获取。

关系方向

关系的方向可以是单向或双向的。

在单向关系中,一个实体充当另一个实体的所有者。从逻辑上讲,在我们的情况下,学生必须拥有图书馆卡,而不是相反。因此,学生实体拥有图书馆卡实体。学生实体可以访问库卡实体的数据。但是,如果没有明确编写查询的情况,图书馆卡实体将无法访问学生实体的数据。

Image description

在双向关系中,两个实体都可以访问彼此的数据而无需明确编写查询。

Image description

要在学生和图书馆的实体之间建立双向关系,我们必须在图书馆卡实体中以一对一的映射添加学生作为会员变量。

@Entity
@Table(name = "library_card")
public class LibraryCard {
    @Id
    @GeneratedValue
    private int id;
    @Temporal(TemporalType.DATE)
    private Date issuedDate;
    private boolean isActive;

    @OneToOne
    private Student owner;
}

在坚持学生和图书馆卡实体之前,添加以下行并运行应用程序。我们的数据库应该看起来像这样。学生表和卡表存储彼此的信息。

card1.setOwner(student1);
card2.setOwner(student2);

Image description
Image description

这样,我们可以使用图书馆卡访问学生实体数据。

LibraryCard libraryCard = entityManager.find(LibraryCard.class, 3);
System.out.println(libraryCard.getOwner());

Image description

我们能够获取学生的详细信息。但是,让我们看生成的查询。

Image description

库卡详细信息正在多次访问,因为学生实体中也存在库卡,即使我们已经有了卡详细信息。 Hibernate不认识到同一张图书馆卡再次被查询,即使它们处于双向关系。这效率低下,可能导致问题。

我们想要的是通知Hibernate学生实体是主要实体,图书馆卡实体是其镜子。因此,当我们查询学生时,我们还希望检索库卡详细信息。但是,当我们从图书馆卡实体中查询学生的详细信息时,该实体还包含同一张库卡,我们不想执行其他查询。这可以通过在OneToOne注释中使用“ mappedby” 参数来实现。

@OneToOne(mappedBy = "card")
private Student owner;

现在,当我们再次运行应用程序时,库卡仅查询一次。

Image description

在这篇文章中,我们不仅涵盖了一对一的关系,还涵盖了确定何时获取数据,关系方向和优化查询的提取类型。

如果您喜欢这篇文章,请留下反应。


关注我:TwitterLinkedInGitHubLinktree