1. 概述

在应用程序中处理数据库时,我们通常需要处理不再需要的记录。然而,由于业务或监管要求,如数据恢复、审计追踪或参照完整性目的,我们可能需要隐藏这些记录,而不是删除它们。

在这个教程中,我们将研究Hibernate中的@SoftDelete注解,并学习如何实现它。

2. @SoftDelete注解的理解

@SoftDelete注解提供了一种方便的机制,用于标记任何记录为活动或已删除。它有三个不同的配置部分:

  • 策略配置了是跟踪活动行还是已删除行。我们可以通过设置strategyACTIVEDELETED来配置。
  • 指示列标识将用于跟踪行的列。如果没有指定列,则策略会使用默认列(activedeleted)。
  • 转换器定义了如何在数据库中设置指示列。域类型是一个boolean值,表示记录是否活跃或已删除。然而,通过实现AttributeConverter,我们可以将关系类型设置为转换器定义的任何类型。可用的转换器包括NumericBooleanConverterYesNoConverterTrueFalseConverter

3. 实现@SoftDelete

让我们看看如何在不同配置下使用@SoftDelete的一些示例。

3.1. 模型

首先定义一个实体类SoftDeletePerson,并为其添加@SoftDelete注解。我们不提供额外配置,注解使用所有默认值,如策略为DELETEDdeleted指示列,以及存储为boolean类型。

@SoftDelete注解支持@ElementCollection,我们在其中配置了策略为ACTIVE,默认指示列,并使用YesNoConverter将存储类型设置为'Y''N'

@SoftDelete
public class SoftDeletePerson {

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

    private String name;

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "Emails", joinColumns = @JoinColumn(name = "id"))
    @Column(name = "emailId")
    @SoftDelete(strategy = SoftDeleteType.ACTIVE,converter = YesNoConverter.class)
    private List<String> emailIds;

    // standard getters and setters
}

3.2. 数据设置

现在创建一些SoftDeletePerson实体的数据库条目,看看Hibernate如何将它们保存到数据库中:

@BeforeEach
public void setup() {
    session = sessionFactory.openSession();
    session.beginTransaction();
    
    SoftDeletePerson person1 = new SoftDeletePerson();
    person1.setName("Person1");
    List<String> emailIds = new ArrayList<>();
    emailIds.add("[email protected]");
    emailIds.add("[email protected]");
    person1.setEmailIds(emailIds);
    
    SoftDeletePerson person2 = new SoftDeletePerson();
    person2.setName("Person2");
    List<String> emailIdsPerson2 = new ArrayList<>();
    emailIdsPerson2.add("[email protected]");
    emailIdsPerson2.add("[email protected]");
    person2.setEmailIds(emailIdsPerson2);
    
    session.save(person1);
    session.save(person2);
    session.getTransaction()
      .commit();

    assertNotNull(person1.getName());
    assertNotNull(person2.getName());
    System.out.println(person1);
    System.out.println(person2);
}

在上述测试用例中,我们持久化了两个SoftDeletePerson实体,并打印出来以可视化数据库中保存的内容。输出显示Hibernate将SoftDeletePerson保存为deleted列设置为false。此外,emailIds集合的active列设置为'Y':

3.3. 测试

在上一步中,我们在数据库中持久化了一些行。现在,让我们看看@SoftDelete如何处理记录的删除:

@Test
void whenDeletingUsingSoftDelete_ThenEntityAndCollectionAreDeleted() {
    session.beginTransaction();
    person1 = session.createQuery("from SoftDeletePerson where name='Person1'", SoftDeletePerson.class)
      .getSingleResult();
    person2 = session.createQuery("from SoftDeletePerson where name='Person2'", SoftDeletePerson.class)
      .getSingleResult();

    assertNotNull(person1);
    assertNotNull(person2);

    session.delete(person2);
    List<String> emailIds = person1.getEmailIds();
    emailIds.remove(0);
    person1.setEmailIds(emailIds);
    session.save(person1);
    session.getTransaction()
      .commit();
    List<SoftDeletePerson> activeRows = session.createQuery("from SoftDeletePerson")
      .list();
    List<SoftDeletePerson> deletedRows = session.createNamedQuery("getDeletedPerson", SoftDeletePerson.class)
      .getResultList();
    session.close();

    assertNotNull(person1.getName());
    System.out.println("-------------Active Rows-----------");
    activeRows.forEach(row -> System.out.println(row));
    System.out.println("-------------Deleted Rows-----------");
    deletedRows.forEach(row -> System.out.println(row));
}

首先,我们从数据库中获取现有行。接下来,我们删除了一个实体,而另一个实体更新了emailIds

然后,当我们删除一个SoftDeletePerson实体时,Hibernate设置deleted=true。类似地,当我们移除其中一个电子邮件ID时,Hibernate将之前的行设置为active='N',并在数据库中插入一个新的行,其active='Y'

最后,当我们检索活动和已删除的行时,可以看到预期的结果:

4. 总结

在这篇文章中,我们探讨了Hibernate中@SoftDelete注解的实现。默认配置是使用DELETED策略,并在数据库的deleted列中以boolean值存储

我们还查看了此注解如何支持@ElementCollection。最后,我们通过测试用例验证了不同配置下的结果。

如往常一样,所有示例的源代码可以在GitHub上的此处找到。


原始标题:A Guide to the @SoftDelete Annotation in Hibernate | Baeldung