1. 序章

JPAを使用する場合、エンティティのライフサイクル中に通知される可能性のあるイベントがいくつかあります。 このチュートリアルでは、JPAエンティティのライフサイクルイベントと、アノテーションを使用してコールバックを処理し、これらのイベントが発生したときにコードを実行する方法について説明します。

まず、エンティティ自体のメソッドに注釈を付けてから、エンティティリスナーの使用に移ります。

2. JPAエンティティのライフサイクルイベント

JPAは、次の7つのオプションのライフサイクルイベントを指定します。

  • 新しいエンティティに対してpersistが呼び出される前– @PrePersist
  • 新しいエンティティに対してpersistが呼び出された後– @PostPersist
  • エンティティが削除される前– @PreRemove
  • エンティティが削除された後– @PostRemove
  • 更新操作の前– @PreUpdate
  • エンティティが更新された後– @PostUpdate
  • エンティティがロードされた後– @PostLoad

ライフサイクルイベントアノテーションを使用するには、エンティティのメソッドにアノテーションを付ける方法と、EntityListenerにアノテーション付きのコールバックメソッドを作成する方法の2つがあります。 両方を同時に使用することもできます。 それらがどこにあるかに関係なく、コールバックメソッドはvoidリターンタイプを持つ必要があります。

したがって、新しいエンティティを作成してリポジトリの save メソッドを呼び出すと、 @PrePersist で注釈が付けられたメソッドが呼び出され、レコードがデータベースに挿入され、最後に、 @PostPersistメソッドが呼び出されます。 @GeneratedValueを使用して主キーを自動的に生成している場合、そのキーは@PostPersistメソッドで使用可能であると期待できます。

@PostPersist @PostRemove 、および @PostUpdate 操作の場合、これらのイベントは、操作の発生直後、フラッシュ後、またはトランザクションの終了。

@PreUpdateコールバックは、データが実際に変更された場合にのみ呼び出されることに注意してください。つまり、実行する実際のSQL更新ステートメントがある場合です。 @PostUpdate コールバックは、実際に何かが変更されたかどうかに関係なく呼び出されます。

エンティティを永続化または削除するためのコールバックのいずれかが例外をスローした場合、トランザクションはロールバックされます。

3. エンティティに注釈を付ける

エンティティで直接コールバックアノテーションを使用することから始めましょう。 この例では、 User レコードが変更されたときにログトレイルを残すため、コールバックメソッドに簡単なログステートメントを追加します。

さらに、ユーザーがデータベースからロードされた後、ユーザーのフルネームを確実にアセンブルする必要があります。 これを行うには、メソッドに@PostLoadアノテーションを付けます。

まず、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
}

次に、UserRepositoryインターフェイスを作成する必要があります。

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

それでは、 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;
}

テストを実行すると、アノテーションが付けられたメソッドからの一連のロギングステートメントが表示されます。 さらに、データベースからユーザーをロードするときに、ユーザーのフルネームが確実に入力されることを期待できます。

4. EntityListenerに注釈を付ける

ここで例を拡張し、別のEntityListenerを使用して更新コールバックを処理します。 すべてのエンティティに適用したい操作がある場合は、エンティティにメソッドを配置するよりもこのアプローチを優先する可能性があります。

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

例からわかるように、メソッドに複数のアノテーションを適用できます。

次に、 User エンティティに戻り、@EntityListenerアノテーションをクラスに追加する必要があります。

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

また、テストを実行すると、更新アクションごとに2セットのログメッセージと、ユーザーがデータベースから読み込まれた後のログメッセージが表示されます。

5. 結論

この記事では、JPAエンティティのライフサイクルコールバックとは何か、いつ呼び出されるかを学びました。 注釈を見て、それらを使用するためのルールについて話しました。 また、エンティティクラスとEntityListenerクラスの両方でそれらを使用して実験しました。

サンプルコードは、GitHubから入手できます。