1. 概述

在 Java 持久化开发中,实体之间的关系管理是常见需求。JPA 和 Hibernate 提供了级联(Cascade)机制来简化对关联实体的操作。本文将系统介绍 JPA 和 Hibernate 中的各类级联类型,并通过代码示例说明其行为差异。

适合阅读本文的读者应具备一定 JPA/Hibernate 基础,因此我们将略过基础概念,重点讲解级联类型的使用场景和注意事项。


2. 什么是级联(Cascading)?

当一个实体依赖于另一个实体存在时,比如 PersonAddress 的关系,删除 Person 时我们也希望自动删除其关联的 Address。这就是级联操作的意义所在。

级联的本质:对主实体执行操作时,自动将该操作传播到其关联的子实体。


3. JPA 级联类型

JPA 标准定义了以下级联类型,位于 javax.persistence.CascadeType 枚举中:

  • ALL:所有操作都级联
  • PERSIST:保存时级联
  • MERGE:合并时级联
  • REMOVE:删除时级联
  • REFRESH:刷新时级联
  • DETACH:脱离时级联

4. Hibernate 级联类型

Hibernate 在 JPA 的基础上扩展了三个级联类型,定义在 org.hibernate.annotations.CascadeType 中:

  • REPLICATE:复制操作级联
  • SAVE_UPDATE:保存或更新时级联
  • LOCK:锁定时级联

⚠️ 注意:Hibernate 的 CascadeType.DELETE 与 JPA 的 CascadeType.REMOVE 行为一致,两者可互换使用。


5. 各级联类型详解

以下通过代码和 SQL 输出说明每种级联类型的行为。

5.1. CascadeType.ALL

将所有操作传播到子实体。

@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;
}

子实体 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;
}

使用 CascadeType.ALL 后,对 Person 执行任意操作(如保存、更新、删除等),都会自动传播到其 Address


5.2. CascadeType.PERSIST

用于保存操作时级联。

@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();
}

输出 SQL:

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

结论PERSIST 级联会在保存 Person 时自动保存其 Address


5.3. CascadeType.MERGE

用于合并操作时级联。

@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();
}

输出 SQL:

Hibernate: select ... from Address where id=?
Hibernate: select ... from Person where id=?
Hibernate: update Address set ... where id=?
Hibernate: update Person set ... where id=?

结论MERGE 级联会将父实体的合并操作传播到子实体。


5.4. 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();
}

输出 SQL:

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

结论REMOVE 级联会级联删除关联的子实体。


5.5. CascadeType.DETACH

用于脱离持久化上下文时级联。

@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();
}

结论DETACH 级联会将子实体也从 Session 中移除。


5.6. 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();
}

结论LOCK 级联会将子实体重新附加到 Session。


5.7. 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);
}

结论REFRESH 级联会从数据库中重新加载子实体状态。


5.8. 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);
}

结论REPLICATE 级联会将复制操作传播到子实体。


5.9. CascadeType.SAVE_UPDATE

用于 Hibernate 特有的 saveupdatesaveOrUpdate 操作。

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

输出 SQL:

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

结论SAVE_UPDATE 级联适用于 Hibernate 的特有操作。


6. 总结

级联类型 说明 是否 JPA 标准
ALL 所有操作都级联
PERSIST 保存时级联
MERGE 合并时级联
REMOVE 删除时级联
REFRESH 刷新时级联
DETACH 脱离时级联
REPLICATE 复制时级联 ❌(Hibernate)
SAVE_UPDATE 保存或更新时级联 ❌(Hibernate)
LOCK 锁定时级联 ❌(Hibernate)

⚠️ 使用建议

  • 不要盲目使用 CascadeType.ALL,除非你明确知道每个操作的含义。
  • 小心 REMOVE 级联,它会级联删除子实体,可能导致数据丢失。
  • 对于只读或非关键关系,避免使用级联。

源码地址GitHub 示例项目


原始标题:Overview of JPA/Hibernate Cascade Types.