Spring Data JPAの@MappedSuperclassと@Embeddableの違いを解説する記事のアイキャッチ。Java道場風の空間で若手エンジニアがJPAのEntity設計を学ぶイラスト。

Spring

Spring Data JPAの@MappedSuperclassと@Embeddableの違い|共通項目をどこへまとめる?

AIによる要約

@MappedSuperclassは、作成日時や更新日時のようなEntity共通のマッピングを継承させる仕組みです。@Embeddableは、住所や金額のような意味のある値を、所有Entityの一部として組み込む仕組みです。単なる重複削減ではなく、「Entityとして共通の性質か」「独立した値のまとまりか」で使い分けます。

新人SE
新人SE
複数のEntityに同じカラムがあります。親クラスへまとめるのと@Embeddableにするのは何が違いますか?
ポンコツSE
ポンコツSE
作成日時のように全Entityへ共通する仕組みならMappedSuperclass、住所のようにEntityが所有する値のまとまりならEmbeddableが候補です。

JPAでEntityを増やすと、ID、作成日時、更新日時、住所、担当者コードなど、似たフィールドが繰り返し登場します。重複を減らすために親クラスへまとめたくなりますが、すべてを継承へ寄せると巨大なBaseEntityになりがちです。

@MappedSuperclass@Embeddableは、どちらもテーブルへマッピングする情報を共通化できます。ただし、表している関係が違います。この記事では、監査項目と配送先住所を例に使い分けます。

この記事のポイント

  • @MappedSuperclassはEntityへマッピングを継承させる
  • @Embeddableは所有Entityの一部となる値を表す
  • 監査項目は継承、住所や期間は埋め込みが候補になる
  • @Entityの継承戦略とは別の仕組みとして考える

@MappedSuperclassとは

@MappedSuperclassを付けたクラスはEntityそのものではなく、独自テーブルも持ちません。そこに宣言した永続化フィールドのマッピングが、継承したEntityへ引き継がれます。

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity {

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

    @LastModifiedDate
    @Column(nullable = false)
    private LocalDateTime updatedAt;
}

注文Entityがこのクラスを継承すると、注文テーブルへcreated_atupdated_atがマッピングされます。親クラス用のテーブルが作られるわけではありません。

@Entity
public class Order extends AuditableEntity {
    @Id
    @GeneratedValue
    private Long id;

    private String orderNumber;
}

Spring Data JPAの監査機能を使う場合は、プロジェクト側で@EnableJpaAuditingなど必要な設定も確認します。アノテーションを付けただけで日時が自動設定されるとは限りません。

@Embeddableとは

@Embeddableは、所有Entityの一部として保存する値の型を表します。埋め込み型自身はIDや独自テーブルを持たず、フィールドは所有Entityのテーブルへ保存されます。

@Embeddable
public class Address {
    @Column(name = "shipping_postal_code")
    private String postalCode;

    @Column(name = "shipping_city")
    private String city;

    @Column(name = "shipping_street")
    private String street;

    protected Address() {
    }

    public Address(String postalCode, String city, String street) {
        this.postalCode = postalCode;
        this.city = city;
        this.street = street;
    }
}

Entity側では@Embeddedを使います。

@Entity
public class Order {
    @Id
    private Long id;

    @Embedded
    private Address shippingAddress;
}

AddressはOrderの一部ですが、Javaコード上では郵便番号、市区町村、番地を1つの意味ある値として扱えます。Stringフィールドを3つばらばらに渡すより、引数の取り違えも防ぎやすくなります。

違いを表で整理する

観点@MappedSuperclass@Embeddable
関係継承する所有して組み込む
向く内容Entity共通のマッピング・仕組み意味のある値のまとまり
独自テーブル持たない持たない
独自ID子Entity側の一部として定義可能持てない
代表例作成日時、更新日時、共通ID住所、期間、金額、氏名

重複している列を見つけたら、まず「すべてのEntityが同じ性質として持つものか」「Entityが所有する値のまとまりか」を判断します。

監査項目はMappedSuperclassが向きやすい

作成日時、更新日時、作成者、更新者は、多くのEntityで共通する技術的な監査情報です。これらは親クラスへまとめることで、アノテーションや更新方法を統一しやすくなります。

ただし、「共通だから」と論理削除フラグ、ステータス、承認者、部署コードまで追加すると、使わないEntityにも項目が広がります。BaseEntityは何でも置き場にせず、本当に横断的な情報へ絞ります。

住所や期間はEmbeddableが向きやすい

住所、利用期間、金額、氏名などは、複数の値で1つの意味を作ります。これらは継承関係ではなく、Entityが持つ値として表現する方が自然です。

@Embeddable
public class UsagePeriod {
    private LocalDate startDate;
    private LocalDate endDate;

    protected UsagePeriod() {
    }

    public boolean includes(LocalDate target) {
        return !target.isBefore(startDate)
                && !target.isAfter(endDate);
    }
}

日付を2つ保持するだけでなく、「期間内か」というルールも同じ型へ置けます。業務上の意味と処理を近づけられるのが利点です。

@Entityの継承とは違う

@MappedSuperclassはマッピング情報を子Entityへ引き継ぎますが、親クラス自体はEntityではありません。親型を対象にJPQLで一括検索するようなEntity継承の用途とは違います。

@Inheritanceを使うEntity継承は、単一テーブル、結合テーブルなど継承戦略の検討が必要です。共通カラムを減らしたいだけで、Entity継承へ進まないよう注意します。

よくある失敗

  • 重複列を見つけるたびにBaseEntityへ追加する
  • 業務上は別物の値を、列構成が同じという理由だけで共通化する
  • 同じEmbeddableを1つのEntityで複数回使い、列名が衝突する
  • JPA用の引数なしコンストラクタを用意していない
  • Entity継承とMappedSuperclassを同じものとして説明する

請求先住所と配送先住所のように同じ埋め込み型を2回使う場合は、@AttributeOverrideで列名を分ける必要があります。既存DBへ合わせる案件では特に確認します。

現場レビューでの見られ方

// レビューコメント例
作成日時と更新日時は全Entity共通の監査項目なので、
MappedSuperclassへ寄せる候補です。

// レビューコメント例
住所はEntityの共通親クラスというより値のまとまりです。
Embeddableとして所有させる方が業務上の意味を表現できます。

// レビューコメント例
BaseEntityへ業務項目が増え続けています。
本当に全Entityが同じ理由で変更される項目か確認してください。

レビュー前のセルフチェック

確認リスト

  • 共通化する対象がEntity共通の性質か、値のまとまりか
  • MappedSuperclass自体をEntityとして検索しようとしていないか
  • Embeddableへ独自IDを持たせようとしていないか
  • 列名の衝突や既存DBとの対応を確認したか
  • BaseEntityが業務項目の寄せ集めになっていないか
書籍「Spring徹底入門 第2版」の表紙
商品画像:Amazon.co.jp
PR Springを体系的に学ぶ若手向け

Spring徹底入門 第2版

株式会社NTTデータ (著), 株式会社NTTデータグループ (編集)

Spring Frameworkの主要機能を、実務レベルで整理する。

DI、MVC、データアクセス、テスト、セキュリティまで幅広く確認できます。ピンポイント検索から体系学習へ進みたい人向けです。

  • Springの全体像を整理したい
  • 現場で使う機能を体系的に学びたい

当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。

この記事とあわせて読みたい

まとめ

@MappedSuperclassはEntity共通のマッピングを継承させ、@EmbeddableはEntityが所有する値のまとまりを表します。どちらも独自テーブルを持ちませんが、継承と所有という関係が違います。

作成日時などの横断的な監査項目はMappedSuperclass、住所や期間など業務上意味のある値はEmbeddableを候補にします。重複を消すことより、モデルを読んだ時に意味が伝わることを優先してください。

-Spring
-, , , , ,