0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JPA Auditing - JPAで作成日・更新日を自動管理する方法

Last updated at Posted at 2025-06-04

はじめに

JPA Auditing 機能を通じて、繰り返し発生する日時管理コードを削除し、生産性を高める方法を整理してみました。

問題の状況

開発時にはほぼすべてのテーブルで次のような共通メタデータフィールドが必要になります。

    created_at                 TIMESTAMP     NOT NULL,
    updated_at                 TIMESTAMP     NOT NULL 
  • created_at : データ作成時刻

  • updated_at : データ最終更新時刻

作成日時や更新日時といったフィールドはどのように管理すればよいでしょうか?

通常、このような場合には2つのアプローチ方法で解決します。

コード(アプリケーション)側での責任

コード(アプリケーション)側で責任を持つ例を確認してみましょう。

@Entity
@Getter
@Setter 
public class User {

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

    private String name;

   /*
    省略
    */


    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

次のような User エンティティのコードがある場合、オブジェクトを生成すると、
作成・更新フィールドは以下のように管理されます。

 public User createUser(String name) {
        User user = new User();
        user.setName(name);
         /*
        省略
        */
    
        user.setCreatedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());

        return user;
}

user オブジェクトの値を修正する際にも、次のようにメソッドを定義する必要があります。

public void updateName(String name){
    this.name = name;
    user.setUpdatedAt(LocalDateTime.now());
}

作成と削除、この2つの場合だけを見ても、繰り返し記述するコードが発生します。

// 繰り返し発生するコード
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());

もしコード側で上記の例のように管理する場合、次のような問題が発生します。

コード側で管理する際に発生する問題

  • ヒューマンエラーの可能性が増加
    • もし開発者が作業中に該当コードを漏らしてしまった場合、データの一貫性が失われる可能性があります
  • ボイラープレートコードの増加
    • 繰り返し冗長なコードが記述されることで、開発者の可読性と保守性が低下します
  • 関心の分離 (SoC, Separation of Concerns) の原則違反
    • 日付管理ロジックはビジネスロジックではなく「技術的関心事」であるにもかかわらず、エンティティ内部でドメインロジックと一緒に管理されています
    • 各ビジネスメソッドは 単一責任原則(SRP) も違反することになります

データベース側での責任

それでは、コード側ではなくデータベースに責任を持たせた場合はどうでしょうか?
次のように定義できます。

    created_at                 timestamp   default CURRENT_TIMESTAMP not null,
    updated_at                 timestamp   default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP,

これにより、以前のコードで発生した問題を解決できます。

 public User createUser(String name) {
        User user = new User();
        user.setName(name);
      /*
        省略
        */
    
        return user;
}
public void updateName(String name){
    this.name = name;
}

このようにすれば、以前のコードで発生していた問題を解決することができます。

DBで管理する際に発生する問題

  • DB依存性が高まる
    • DBに依存する機能であるため、他のデータベースに移行する場合、動作方式が異なる可能性があります(実装方式、動作方式など)
  • ビジネスロジックとの統合が難しい
    • もし特定のドメインにおいて、他のフィールドが更新されても更新日時に反映させたくない場合、DBに依存していると制御が難しくなります
public void addFollower(User user) {
    this.followers.add(user);
    this.followerCount += 1;

   // ここで updated_at は更新したくありません。
   // しかし DB の ON UPDATE CURRENT_TIMESTAMP を使用すると、強制的に updated_at が変更されてしまいます。
}
  • テストが困難になる
    • アプリケーションレベルで時間を管理すれば、オブジェクトを簡単に mock して制御し、テストすることができます
    • しかしこれをDBで管理すると、テストはほぼ不可能になります

例えば、1日前に作成された投稿が正しく取得されるか?

  • 情報の隠蔽が発生する
    • 開発者は通常、エンティティクラスやサービスコード(ドメイン)を見て動作を予測します
    • DB側にロジックが隠れていると、コードだけではどのタイミングで updateAt などのフィールドが変更されるのか把握できません。これはヒューマンエラーを誘発し、生産性低下につながります

最小驚きの原則(Principle of least astonishment)

システムのすべての構成要素は、構文が示唆する通りに正確に動作すべきである。

  • アプリケーションとデータベースの状態不整合
    • DBの ON UPDATE 機能で updated_at が変更された場合
    • DBの値は新しい値に更新されますが...
    • JAVA オブジェクトの updateAT フィールドは依然として以前の値を保持しています
    • つまり、DBとアプリケーション間でデータの整合性問題が発生します

解決方法 : JPA Auditing

上記の問題を解決するために JPA Auditing 機能を活用することができます。

JPA Auditing とは?

  • Spring Data JPA Auditing は、エンティティの作成/更新時に自動的に日付やユーザー情報を注入してくれる機能です

JPA Auditing の利点

  • 開発者が直接 createdAtupdatedAt などのフィールドを管理する必要がありません
  • JPAエンティティライフサイクルイベントを通じて、コード側で即座に自動処理されます(つまりデータ整合性問題が解決されます)
  • アプリケーション側で管理されるため、DB移行などのインフラ変更やテスト時の mock も自由に行えます
  • ボイラープレートコードがなく、コードに意図が明確に示され、予測可能なコードを書くことができます

適用方法

@Configuration
@EnableJpaAuditing
public class JpaConfig {
}

次のように @EnableJpaAuditing を有効化し、

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

次のように共通の日時管理用の抽象クラスを作成します。

@EntityListeners(AuditingEntityListener.class)
このアノテーションは、JPAが自動的に発生させるライフサイクルイベントを AuditingEntityListener が検知し、特定のロジックを実行するように指定するアノテーションです。
つまり、JPAのライフサイクルイベントが発生するたびに AuditingEntityListener が動作するように登録するものであり、このリスナーは @CreatedDate@LastModifiedDate フィールドに値を注入するロジックを実行します。


@Entity
public class User extends BaseEntity {

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

    private String name;

    // その他のフィールドおよびメソッド
}

継承して使用します。

さらに追加で

    @CreatedBy
    @Column(updatable = false)
    private String createdBy; // 作成したユーザー

    @LastModifiedBy
    private String lastModifiedBy; // 更新したユーザー

@CreatedBy@LastModifiedBy アノテーションを活用すれば、Spring Security と連携してデータを作成・更新したユーザーを自動的に記録し、データの責任追跡性を高めることもできます。

適用事例

私の場合は、もう少し明確に表現し、さらに柔軟性(将来的に user エンティティで createAt のようなカラムが別途管理される場合など)を考慮するため、次のように使用しています。

image.png
image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?