Overview of JPA/Hibernate Cascade Types – JPA/Hibernate级联类型概述

最后修改: 2019年 6月 20日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll discuss what cascading is in JPA/Hibernate. Then we’ll cover the various cascade types that are available, along with their semantics.

在本教程中,我们将讨论JPA/Hibernate中的级联是什么。然后,我们将介绍各种可用的级联类型,以及它们的语义。

2. What Is Cascading?

2.什么是级联?

Entity relationships often depend on the existence of another entity, for example the PersonAddress relationship. Without the Person, the Address entity doesn’t have any meaning of its own. When we delete the Person entity, our Address entity should also get deleted.

实体关系通常依赖于另一个实体的存在,例如,PersonAddress关系。如果没有PersonAddress实体就没有任何自己的意义。当我们删除Person实体时,我们的Address实体也应该被删除。

Cascading is the way to achieve this. When we perform some action on the target entity, the same action will be applied to the associated entity.

级联是实现这一目的的方法。当我们对目标实体执行某些操作时,同样的操作将应用于相关的实体。

2.1. JPA Cascade Type

2.1 JPA级联类型

All JPA-specific cascade operations are represented by the javax.persistence.CascadeType enum containing entries:

所有JPA特定的级联操作都由包含条目的javax.persistence.CascadeTypeenum表示。

  • ALL
  • PERSIST
  • MERGE
  • REMOVE
  • REFRESH
  • DETACH

2.2. Hibernate Cascade Type

2.2.休眠的级联类型

Hibernate supports three additional Cascade Types along with those specified by JPA. These Hibernate-specific Cascade Types are available in org.hibernate.annotations.CascadeType:

Hibernate支持三个额外的级联类型,以及JPA指定的那些级联类型。这些Hibernate特有的级联类型在org.hibernate.annotations.CascadeType中可用。

  • REPLICATE
  • SAVE_UPDATE
  • LOCK

3. Difference Between the Cascade Types

3.级联类型之间的差异

3.1. CascadeType.ALL

3.1. CascadeType.ALL

CascadeType.ALL propagates all operations — including Hibernate-specific ones — from a parent to a child entity.

CascadeType.ALL 将所有操作–包括Hibernate特定的操作–从父实体传播到子实体。

Let’s see it in an example:

让我们在一个例子中看到它。

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
    private List<Address> addresses;
}

Note that in OneToMany associations, we’ve mentioned cascade type in the annotation.

注意,在OneToMany关联中,我们已经在注释中提到了级联类型。

Now let’s see the associated entity Address:

现在让我们看看相关实体Address

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String street;
    private int houseNumber;
    private String city;
    private int zipCode;
    @ManyToOne(fetch = FetchType.LAZY)
    private Person person;
}

3.2. CascadeType.PERSIST

3.2. CascadeType.PERSIST

The persist operation makes a transient instance persistent. Cascade Type PERSIST propagates the persist operation from a parent to a child entity. When we save the person entity, the address entity will also get saved.

persist操作使一个暂存的实例持久化。级联类型PERSIST将持久化操作从父实体传播到子实体。当我们保存person实体时,address实体也将被保存。

Let’s see the test case for a persist operation:

让我们看看持久化操作的测试案例。

@Test
public void whenParentSavedThenChildSaved() {
    Person person = new Person();
    Address address = new Address();
    address.setPerson(person);
    person.setAddresses(Arrays.asList(address));
    session.persist(person);
    session.flush();
    session.clear();
}

When we run the above test case, we’ll see the following SQL:

当我们运行上述测试案例时,我们会看到以下SQL。

Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: insert into Address (
    city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

3.3. CascadeType.MERGE

3.3. CascadeType.MERGE

The merge operation copies the state of the given object onto the persistent object with the same identifier. CascadeType.MERGE propagates the merge operation from a parent to a child entity.

合并操作将给定对象的状态复制到具有相同标识符的持久化对象上。CascadeType.MERGE将合并操作从一个父实体传播到一个子实体。

Let’s test the merge operation:

让我们测试一下合并操作。

@Test
public void whenParentSavedThenMerged() {
    int addressId;
    Person person = buildPerson("devender");
    Address address = buildAddress(person);
    person.setAddresses(Arrays.asList(address));
    session.persist(person);
    session.flush();
    addressId = address.getId();
    session.clear();

    Address savedAddressEntity = session.find(Address.class, addressId);
    Person savedPersonEntity = savedAddressEntity.getPerson();
    savedPersonEntity.setName("devender kumar");
    savedAddressEntity.setHouseNumber(24);
    session.merge(savedPersonEntity);
    session.flush();
}

When we run the test case, the merge operation generates the following SQL:

当我们运行测试案例时,合并操作会产生以下SQL。

Hibernate: select address0_.id as id1_0_0_, address0_.city as city2_0_0_, address0_.houseNumber as houseNum3_0_0_, address0_.person_id as person_i6_0_0_, address0_.street as street4_0_0_, address0_.zipCode as zipCode5_0_0_ from Address address0_ where address0_.id=?
Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from Person person0_ where person0_.id=?
Hibernate: update Address set city=?, houseNumber=?, person_id=?, street=?, zipCode=? where id=?
Hibernate: update Person set name=? where id=?

Here, we can see that the merge operation first loads both address and person entities and then updates both as a result of CascadeType.MERGE.

在这里,我们可以看到,合并操作首先加载了addressperson实体,然后作为CascadeType.MERGE的结果更新这两个实体。

3.4. CascadeType.REMOVE

3.4.CascadeType.REMOVE

As the name suggests, the remove operation removes the row corresponding to the entity from the database and also from the persistent context.

顾名思义,删除操作将实体所对应的行从数据库中删除,同时也从持久化上下文中删除。

CascadeType.REMOVE propagates the remove operation from parent to child entity. Similar to JPA’s CascadeType.REMOVE, we have CascadeType.DELETE, which is specific to Hibernate. There is no difference between the two.

CascadeType.REMOVE将删除操作从父实体传播到子实体。 与JPA的CascadeType.REMOVE类似,我们有CascadeType.DELETE,这是Hibernate特有的。两者之间没有区别。

Now it’s time to test CascadeType.Remove:

现在是测试CascadeType.Remove的时候了。

@Test
public void whenParentRemovedThenChildRemoved() {
    int personId;
    Person person = buildPerson("devender");
    Address address = buildAddress(person);
    person.setAddresses(Arrays.asList(address));
    session.persist(person);
    session.flush();
    personId = person.getId();
    session.clear();

    Person savedPersonEntity = session.find(Person.class, personId);
    session.remove(savedPersonEntity);
    session.flush();
}

When we run the test case, we’ll see the following SQL:

当我们运行测试案例时,我们会看到以下SQL。

Hibernate: delete from Address where id=?
Hibernate: delete from Person where id=?

The address associated with the person also got removed as a result of CascadeType.REMOVE.

相关的地址也因CascadeType.REMOVE的结果而被移除。

3.5. CascadeType.DETACH

3.5.CascadeType.DETACH

The detach operation removes the entity from the persistent context. When we use CascadeType.DETACH, the child entity will also get removed from the persistent context.

detach操作将实体从持久化上下文中移除。当我们使用CascadeType.DETACH时,子实体也将被从持久化上下文中移除。

Let’s see it in action:

让我们看看它的行动。

@Test
public void whenParentDetachedThenChildDetached() {
    Person person = buildPerson("devender");
    Address address = buildAddress(person);
    person.setAddresses(Arrays.asList(address));
    session.persist(person);
    session.flush();
    
    assertThat(session.contains(person)).isTrue();
    assertThat(session.contains(address)).isTrue();

    session.detach(person);
    assertThat(session.contains(person)).isFalse();
    assertThat(session.contains(address)).isFalse();
}

Here, we can see that after detaching person, neither person nor address exists in the persistent context.

在这里,我们可以看到,在分离了person之后,personaddress都不存在于持久化上下文中。

3.6. CascadeType.LOCK

CascadeType.LOCK

Unintuitively, CascadeType.LOCK reattaches the entity and its associated child entity with the persistent context again.

不寻常的是, CascadeType.LOCK再次将实体及其相关的子实体与持久化上下文重新连接起来。

Let’s see the test case to understand CascadeType.LOCK:

让我们看看测试案例来理解CascadeType.LOCK

@Test
public void whenDetachedAndLockedThenBothReattached() {
    Person person = buildPerson("devender");
    Address address = buildAddress(person);
    person.setAddresses(Arrays.asList(address));
    session.persist(person);
    session.flush();
    
    assertThat(session.contains(person)).isTrue();
    assertThat(session.contains(address)).isTrue();

    session.detach(person);
    assertThat(session.contains(person)).isFalse();
    assertThat(session.contains(address)).isFalse();
    session.unwrap(Session.class)
      .buildLockRequest(new LockOptions(LockMode.NONE))
      .lock(person);

    assertThat(session.contains(person)).isTrue();
    assertThat(session.contains(address)).isTrue();
}

As we can see, when using CascadeType.LOCK, we attached the entity person and its associated address back to the persistent context.

我们可以看到,当使用CascadeType.LOCK时,我们将实体person及其相关的address附在持久化上下文上。

3.7. CascadeType.REFRESH

3.7. CascadeType.REFRESH

Refresh operations reread the value of a given instance from the database. In some cases, we may change an instance after persisting in the database, but later we need to undo those changes.

刷新操作从数据库中重读一个给定实例的值。在某些情况下,我们可能在数据库中持久化后改变一个实例,但后来我们需要撤销这些改变。

In that kind of scenario, this may be useful. When we use this operation with Cascade Type REFRESH, the child entity also gets reloaded from the database whenever the parent entity is refreshed.

在这种情况下,这可能是有用的。当我们将这个操作与级联类型REFRESH一起使用时,每当父实体被刷新时,子实体也会被从数据库中重新加载。

For better understanding, let’s see a test case for CascadeType.REFRESH:

为了更好地理解,让我们看看CascadeType.REFRESH的测试案例。

@Test
public void whenParentRefreshedThenChildRefreshed() {
    Person person = buildPerson("devender");
    Address address = buildAddress(person);
    person.setAddresses(Arrays.asList(address));
    session.persist(person);
    session.flush();
    person.setName("Devender Kumar");
    address.setHouseNumber(24);
    session.refresh(person);
    
    assertThat(person.getName()).isEqualTo("devender");
    assertThat(address.getHouseNumber()).isEqualTo(23);
}

Here, we made some changes in the saved entities person and address. When we refresh the person entity, the address also gets refreshed.

在这里,我们对保存的实体personaddress做了一些改变。当我们刷新person实体时,address也被刷新了。

3.8. CascadeType.REPLICATE

3.8. CascadeType.REPLICATE

The replicate operation is used when we have more than one data source and we want the data in sync. With CascadeType.REPLICATE, a sync operation also propagates to child entities whenever performed on the parent entity.

当我们有一个以上的数据源,并且我们希望数据同步时,就会使用复制操作。通过CascadeType.REPLIATE,只要对父实体执行同步操作,就会传播到子实体。

Now let’s test CascadeType.REPLICATE:

现在我们来测试一下CascadeType.REPLICATE

@Test
public void whenParentReplicatedThenChildReplicated() {
    Person person = buildPerson("devender");
    person.setId(2);
    Address address = buildAddress(person);
    address.setId(2);
    person.setAddresses(Arrays.asList(address));
    session.unwrap(Session.class).replicate(person, ReplicationMode.OVERWRITE);
    session.flush();
    
    assertThat(person.getId()).isEqualTo(2);
    assertThat(address.getId()).isEqualTo(2);
}

Because of CascadeType.REPLICATE, when we replicate the person entity, its associated address also gets replicated with the identifier we set.

由于CascadeType.REPLICATE的存在,当我们复制person实体时,其相关的address也会被复制到我们设置的标识符中。

3.9. CascadeType.SAVE_UPDATE

3.9. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE propagates the same operation to the associated child entity. It’s useful when we use Hibernate-specific operations like save, update and saveOrUpdate

CascadeType.SAVE_UPDATE将相同的操作传播给相关的子实体。当我们使用Hibernate特定的操作(如save, updatesaveOrUpdate)时,它很有用。

Let’s see CascadeType.SAVE_UPDATE in action:

让我们看看CascadeType.SAVE_UPDATE的操作。

@Test
public void whenParentSavedThenChildSaved() {
    Person person = buildPerson("devender");
    Address address = buildAddress(person);
    person.setAddresses(Arrays.asList(address));
    session.saveOrUpdate(person);
    session.flush();
}

Because of CascadeType.SAVE_UPDATE, when we run the above test case, we can see that the person and address both got saved.

由于CascadeType.SAVE_UPDATE,当我们运行上述测试案例时,我们可以看到personaddress都得到了保存。

Here’s the resulting SQL:

下面是产生的SQL。

Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: insert into Address (
    city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

4. Conclusion

4.总结

In this article, we discussed cascading and the different cascade type options available in JPA and Hibernate.

在这篇文章中,我们讨论了级联以及JPA和Hibernate中可用的不同级联类型选项。

The source code for the article is available on GitHub.

文章的源代码是在GitHub上提供的