JPA Entity Lifecycle Events – JPA实体生命周期事件

最后修改: 2020年 1月 29日

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

1. Introduction

1.绪论

When working with JPA, there are several events that we can be notified of during an entity’s lifecycle. In this tutorial, we’ll discuss the JPA entity lifecycle events and how we can use annotations to handle the callbacks and execute code when these events occur.

在使用JPA时,在实体的生命周期中,我们可以得到几个事件的通知。在本教程中,我们将讨论JPA实体生命周期事件以及我们如何使用注解来处理回调并在这些事件发生时执行代码。

We’ll start by annotating methods on the entity itself and then move on to using an entity listener.

我们将从注释实体本身的方法开始,然后转到使用实体监听器。

2. JPA Entity Lifecycle Events

2.JPA实体生命周期事件

JPA specifies seven optional lifecycle events that are called:

JPA规定了7个可选的生命周期事件,这些事件被调用。

  • before persist is called for a new entity – @PrePersist
  • after persist is called for a new entity – @PostPersist
  • before an entity is removed – @PreRemove
  • after an entity has been deleted – @PostRemove
  • before the update operation – @PreUpdate
  • after an entity is updated – @PostUpdate
  • after an entity has been loaded – @PostLoad

There are two approaches for using the lifecycle event annotations: annotating methods in the entity and creating an EntityListener with annotated callback methods. We can also use both at the same time. Regardless of where they are, callback methods are required to have a void return type.

有两种方法可以使用生命周期事件注释。注释实体中的方法和创建带有注释回调方法的EntityListener。我们也可以同时使用这两种方法。无论它们在哪里,回调方法都需要有一个void返回类型。

So, if we create a new entity and call the save method of our repository, our method annotated with @PrePersist is called, then the record is inserted into the database, and finally, our @PostPersist method is called. If we’re using @GeneratedValue to automatically generate our primary keys, we can expect that key to be available in the @PostPersist method.

因此,如果我们创建一个新的实体并调用我们存储库的save方法,我们用@PrePersist注释的方法被调用,然后记录被插入数据库,最后,我们的@PostPersist方法被调用。如果我们使用@GeneratedValue来自动生成我们的主键,我们可以期待该键在@PostPersist方法中可用。

For the @PostPersist, @PostRemove and @PostUpdate operations, the documentation mentions that these events can happen right after the operation occurs, after a flush, or at the end of a transaction.

对于@PostPersist@PostRemove@PostUpdate操作,文档中提到这些事件可以在操作发生后立即发生,也可以在刷新后发生,或者在事务结束后发生。

We should note that the @PreUpdate callback is only called if the data is actually changed — that is if there’s an actual SQL update statement to run. The @PostUpdate callback is called regardless of whether anything actually changed.

我们应该注意,@PreUpdate回调只有在数据实际发生变化时才会被调用–也就是说,如果有一个实际的SQL更新语句要运行。@PostUpdate回调被调用,而不管是否有实际的变化。

If any of our callbacks for persisting or removing an entity throw an exception, the transaction will be rolled back.

如果我们用于持久化或移除实体的任何回调抛出了一个异常,那么事务将被回滚。

3. Annotating the Entity

3.对实体的注释

Let’s start by using the callback annotations directly in our entity. In our example, we’re going to leave a log trail when User records are changed, so we’re going to add simple logging statements in our callback methods.

让我们先在我们的实体中直接使用回调注解。在我们的例子中,当User记录被改变时,我们要留下日志记录,所以我们要在回调方法中添加简单的日志记录语句。

Additionally, we want to make sure we assemble the user’s full name after they’re loaded from the database. We’ll do that by annotating a method with @PostLoad.

此外,我们要确保在用户从数据库中加载后,我们会将他们的全名集合起来。我们将通过注释一个带有@PostLoad的方法来做到这一点。

We’ll start by defining our User entity:

我们将从定义我们的User实体开始。

@Entity
public class User {
    private static Log log = LogFactory.getLog(User.class);

    @Id
    @GeneratedValue
    private int id;
    
    private String userName;
    private String firstName;
    private String lastName;
    @Transient
    private String fullName;

    // Standard getters/setters
}

Next, we need to create a UserRepository interface:

接下来,我们需要创建一个UserRepository接口。

public interface UserRepository extends JpaRepository<User, Integer> {
    public User findByUserName(String userName);
}

Now, let’s return to our User class and add our callback methods:

现在,让我们回到我们的User类,添加我们的回调方法。

@PrePersist
public void logNewUserAttempt() {
    log.info("Attempting to add new user with username: " + userName);
}
    
@PostPersist
public void logNewUserAdded() {
    log.info("Added user '" + userName + "' with ID: " + id);
}
    
@PreRemove
public void logUserRemovalAttempt() {
    log.info("Attempting to delete user: " + userName);
}
    
@PostRemove
public void logUserRemoval() {
    log.info("Deleted user: " + userName);
}

@PreUpdate
public void logUserUpdateAttempt() {
    log.info("Attempting to update user: " + userName);
}

@PostUpdate
public void logUserUpdate() {
    log.info("Updated user: " + userName);
}

@PostLoad
public void logUserLoad() {
    fullName = firstName + " " + lastName;
}

When we run our tests, we’ll see a series of logging statements coming from our annotated methods. Additionally, we can reliably expect our user’s full name to be populated when we load a user from the database.

当我们运行我们的测试时,我们会看到一系列来自我们注释方法的日志语句。此外,当我们从数据库加载一个用户时,我们可以可靠地期望我们的用户的全名被填充。

4. Annotating an EntityListener

4.注释一个EntityListener

We’re going to expand on our example now and use a separate EntityListener to handle our update callbacks. We might favor this approach over placing the methods in our entity if we have some operation we want to apply to all of our entities.

我们现在要扩展我们的例子,使用一个单独的EntityListener来处理我们的更新回调。如果我们有一些想要应用于所有实体的操作,我们可能会倾向于采用这种方法,而不是将这些方法放在我们的实体中。

Let’s create our AuditTrailListener to log all the activity on the User table:

让我们创建我们的AuditTrailListener来记录User表中的所有活动。

public class AuditTrailListener {
    private static Log log = LogFactory.getLog(AuditTrailListener.class);
    
    @PrePersist
    @PreUpdate
    @PreRemove
    private void beforeAnyUpdate(User user) {
        if (user.getId() == 0) {
            log.info("[USER AUDIT] About to add a user");
        } else {
            log.info("[USER AUDIT] About to update/delete user: " + user.getId());
        }
    }
    
    @PostPersist
    @PostUpdate
    @PostRemove
    private void afterAnyUpdate(User user) {
        log.info("[USER AUDIT] add/update/delete complete for user: " + user.getId());
    }
    
    @PostLoad
    private void afterLoad(User user) {
        log.info("[USER AUDIT] user loaded from database: " + user.getId());
    }
}

As we can see from the example, we can apply multiple annotations to a method.

正如我们从例子中看到的,我们可以对一个方法应用多个注释

Now, we need to go back to our User entity and add the @EntityListener annotation to the class:

现在,我们需要回到我们的User实体,将@EntityListener注解添加到类中。

@EntityListeners(AuditTrailListener.class)
@Entity
public class User {
    //...
}

And, when we run our tests, we’ll get two sets of log messages for each update action and a log message after a user is loaded from the database.

而且,当我们运行测试时,我们会得到两组日志信息,用于每个更新动作,以及从数据库加载用户后的日志信息。

5. Conclusion

5.总结

In this article, we’ve learned what the JPA entity lifecycle callbacks are and when they’re called. We looked at the annotations and talked about the rules for using them. We’ve also experimented with using them in both an entity class and with an EntityListener class.

在这篇文章中,我们已经了解了什么是JPA实体生命周期的回调,以及什么时候会被调用。我们看了注解并讨论了使用它们的规则。我们还试验了在实体类和EntityListener类中使用它们。

The example code is available over on GitHub.

示例代码可在GitHub上获得over