Deleting Objects with Hibernate – 用Hibernate删除对象

最后修改: 2016年 12月 7日

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

1. Overview

1.概述

As a full-featured ORM framework, Hibernate is responsible for lifecycle management of persistent objects (entities), including CRUD operations such as read, save, update and delete.

作为一个全功能的ORM框架,Hibernate负责持久性对象(实体)的生命周期管理,包括CRUD操作,如readsaveupdatedelete

In this article, we explore various ways in which objects may be deleted from a database using Hibernate and we explain common issues and pitfalls that may occur.

在本文中,我们探讨了使用Hibernate从数据库中删除对象的各种方式,并解释了可能发生的常见问题和误区。

We use JPA and only step back and use the Hibernate native API for those features that are not standardized in JPA.

我们使用JPA,只在那些JPA中没有标准化的功能上退一步使用Hibernate本地API。

2. Different Ways of Deleting Objects

2.删除对象的不同方式

Objects may be deleted in the following scenarios:

在下列情况下可以删除对象。

  • By using EntityManager.remove
  • When a deletion is cascaded from other entity instances
  • When an orphanRemoval is applied
  • By executing a delete JPQL statement
  • By executing native queries
  • By applying a soft deletion technique (filtering soft-deleted entities by a condition in an @Where clause)

In the remainder of the article, we look at these points in detail.

在文章的其余部分,我们将详细研究这些要点。

3. Deletion Using the Entity Manager

3.使用实体管理器进行删除

Deletion with the EntityManager is the most straightforward way to remove an entity instance:

使用EntityManager进行删除是删除实体实例的最直接的方式。

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
assertThat(foo, notNullValue());
entityManager.remove(foo);
flushAndClear();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

In the examples in this article we use a helper method to flush and clear the persistence context when needed:

在本文的例子中,我们使用了一个辅助方法,在需要时刷新和清除持久化上下文。

void flushAndClear() {
    entityManager.flush();
    entityManager.clear();
}

After calling the EntityManager.remove method, the supplied instance transitions to the removed state and the associated deletion from the database occurs on the next flush.

在调用EntityManager.remove方法后,所提供的实例过渡到removed状态,并且在下次刷新时从数据库中删除相关内容。

Note that deleted instance is re-persisted if a PERSIST operation is applied to it. A common mistake is to ignore that a PERSIST operation has been applied to a removed instance (usually, because it is cascaded from another instance at the flush time), because the section 3.2.2 of the JPA specification mandates that such instance is to be persisted again in such a case.

请注意,如果一个PERSIST操作被应用到它,那么被删除的实例会被重新保存。一个常见的错误是忽略了PERSIST操作已经被应用于一个被删除的实例(通常,因为它是在冲洗时从另一个实例级联的),因为JPA规范3.2.2部分规定,在这种情况下这种实例将被再次持久化。

We illustrate this by defining a @ManyToOne association from Foo to Bar:

我们通过定义一个从FooBar@ManyToOne关联来说明。

@Entity
public class Foo {
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Bar bar;

    // other mappings, getters and setters
}

When we delete a Bar instance referenced by a Foo instance which is also loaded in the persistence context, the Bar instance will not be removed from the database:

当我们删除一个由Foo实例引用的Bar实例时,Bar实例不会被从数据库中删除。

Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
bar = entityManager.find(Bar.class, bar.getId());
entityManager.remove(bar);
flushAndClear();

bar = entityManager.find(Bar.class, bar.getId());
assertThat(bar, notNullValue());

foo = entityManager.find(Foo.class, foo.getId());
foo.setBar(null);
entityManager.remove(bar);
flushAndClear();

assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

If the removed Bar is referenced by a Foo, the PERSIST operation is cascaded from Foo to Bar because the association is marked with cascade = CascadeType.ALL and the deletion is unscheduled. To verify that this is happening, we may enable trace log level for the org.hibernate package and search for entries such as un-scheduling entity deletion.

如果被删除的BarFoo引用,那么PERSIST操作将从Foo级联到Bar,因为该关联被标记为cascade = CascadeType.ALL,并且该删除是未计划的。为了验证这种情况是否发生,我们可以启用org.hibernate包的跟踪日志级别,并搜索诸如un-scheduling entity deletion的条目。

4. Cascaded Deletion

4.逐级删除

Deletion can be cascaded to children entities when parents are removed:

当父母被移除时,删除可以级联到子实体。

Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
entityManager.remove(foo);
flushAndClear();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Here bar is removed because removal is cascaded from foo, as the association is declared to cascade all life cycle operations from Foo to Bar.

这里bar被移除,因为移除是由foo级联的,因为关联被声明为将所有生命周期操作从Foo级联到Bar

Note that it is almost always a bug to cascade REMOVE operation in a @ManyToMany association, because that would trigger removing child instances which may be associated with other parent instances. This also applies to CascadeType.ALL, as it means that all operations are to be cascaded, including the REMOVE operation.

请注意,@ManyToMany关联中级联操作几乎总是一个错误,因为这将触发移除可能与其他父实例关联的子实例。这也适用于CascadeType.ALL,因为它意味着所有操作都要级联,包括REMOVE操作。

5. Removal of Orphans

5.移除孤儿

The orphanRemoval directive declares that associated entity instances are to be removed when they are disassociated from the parent, or equivalently when the parent is removed.

orphanRemoval指令声明,当关联的实体实例与父类脱离关系时,或者等同于当父类被移除时,关联的实体实例将被移除。

We show this by defining such an association from Bar to Baz:

我们通过定义从BarBaz的这样一个关联来说明这一点:

@Entity
public class Bar {
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Baz> bazList = new ArrayList<>();

    // other mappings, getters and setters
}

Then a Baz instance is deleted automatically when it is removed from the list of a parent Bar instance:

那么当一个Baz实例从父Bar实例的列表中移除时,它将被自动删除。

Bar bar = new Bar("bar");
Baz baz = new Baz("baz");
bar.getBazList().add(baz);
entityManager.persist(bar);
flushAndClear();

bar = entityManager.find(Bar.class, bar.getId());
baz = bar.getBazList().get(0);
bar.getBazList().remove(baz);
flushAndClear();

assertThat(entityManager.find(Baz.class, baz.getId()), nullValue());

The semantics of the orphanRemoval operation is completely similar to a REMOVE operation applied directly to the affected child instances, which means that the REMOVE operation is further cascaded to nested children. As a consequence, you have to ensure that no other instances reference the removed ones (otherwise they are re-persisted).

orphanRemoval操作的语义与直接应用于受影响的子实例的REMOVE操作完全类似,这意味着REMOVE操作会进一步级联到嵌套的子实例。因此,你必须确保没有其他实例引用被移除的实例(否则它们会被重新挂起)。

6. Deletion Using a JPQL Statement

6.使用JPGL语句进行删除

Hibernate supports DML-style delete operations:

Hibernate支持DML式的删除操作。

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

entityManager.createQuery("delete from Foo where id = :id")
  .setParameter("id", foo.getId())
  .executeUpdate();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

It is important to note that DML-style JPQL statements affect neither the state nor life cycle of entity instances that are already loaded into the persistence context, so it is recommended that they are executed prior to loading the affected entities.

需要注意的是,DML风格的JPQL语句既不影响已经加载到持久化上下文中的实体实例的状态,也不影响其生命周期,所以建议在加载受影响的实体之前执行它们。

7. Deletion Using Native Queries

7.使用本地查询进行删除

Sometimes we need to fall back to native queries to achieve something that is not supported by Hibernate or is specific to a database vendor. We may also delete data in the database with native queries:

有时我们需要回到本地查询来实现Hibernate不支持的东西,或者是数据库供应商特有的东西。我们也可以用本地查询删除数据库中的数据。

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

entityManager.createNativeQuery("delete from FOO where ID = :id")
  .setParameter("id", foo.getId())
  .executeUpdate();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

The same recommendation applies to native queries as for JPA DML-style statements, i.e. native queries affect neither the state nor life cycle of entity instances which are loaded into the persistence context prior to execution of the queries.

同样的建议也适用于本地查询和JPA DML风格的语句,即本地查询既不影响实体实例的状态,也不影响其生命周期,这些实体实例在执行查询之前已被加载到持久化上下文中

8. Soft Deletion

8.软删除

Often it is not desirable to remove data from a database because of auditing purposes and keeping history. In such situations, we may apply a technique called soft deletes. Basically, we just mark a row as removed and we filter it out when retrieving data.

通常情况下,由于审计目的和保留历史,从数据库中删除数据是不可取的。在这种情况下,我们可以应用一种叫做软删除的技术。基本上,我们只是将某行标记为已删除,并在检索数据时将其过滤掉。

In order to avoid lots of redundant conditions in where clauses in all the queries that read soft-deletable entities, Hibernate provides the @Where annotation which can be placed on an entity and which contains an SQL fragment that is automatically added to SQL queries generated for that entity.

为了避免在所有读取软可删除实体的查询中的where子句中出现大量的冗余条件,Hibernate提供了@Where注解,该注解可以放在实体上,它包含一个SQL片段,会自动添加到为该实体生成的SQL查询中。

To demonstrate this, we add the @Where annotation and a column named DELETED to the Foo entity:

为了证明这一点,我们向Foo实体添加@Where注解和一个名为DELETED的列。

@Entity
@Where(clause = "DELETED = 0")
public class Foo {
    // other mappings

    @Column(name = "DELETED")
    private Integer deleted = 0;
    
    // getters and setters

    public void setDeleted() {
        this.deleted = 1;
    }
}

The following test confirms that everything works as expected:

下面的测试证实了一切都按预期进行。

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
foo.setDeleted();
flushAndClear();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

9. Conclusion

9.结论

In this article, we looked at different ways in which data can be deleted with Hibernate. We explained basic concepts and some best practices. We also demonstrated how soft-deletes can be easily implemented with Hibernate.

在这篇文章中,我们研究了用Hibernate删除数据的不同方式。我们解释了基本概念和一些最佳实践。我们还演示了如何用Hibernate轻松实现软删除。

The implementation of this Deleting Objects with Hibernate Tutorial is available over on Github. This is a Maven based project, so it should be easy to import and run as is.

这个用Hibernate删除对象的教程的实现可在Github上找到。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。