1. 序章

JPA を使用すると、Javaアプリケーションからのリレーショナルデータベースモデルの処理が簡単になります。 すべてのテーブルを単一のエンティティクラスにマップすると、作業は簡単になります。 ただし、エンティティとテーブルを異なる方法でモデル化する理由がある場合があります。

この短いチュートリアルでは、この最後のシナリオに取り組む方法を説明します。

2. データ・モデル

たとえば、レストランを経営していて、提供するすべての食事に関するデータを保存したいとします。

  • 名前
  • 説明
  • 価格
  • どんなアレルゲンが含まれているのか

考えられるアレルゲンは多数あるため、このデータセットをグループ化します。 さらに、次のテーブル定義を使用してこれもモデル化します。

次に、標準のJPAアノテーションを使用してこれらのテーブルをエンティティにマップする方法を見てみましょう。

3. 複数のエンティティを作成する

最も明白な解決策は、両方のクラスのエンティティを作成することです。

食事エンティティを定義することから始めましょう:

@Entity
@Table(name = "meal")
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @OneToOne(mappedBy = "meal")
    Allergens allergens;

    // standard getters and setters
}

次に、Allergensエンティティを追加します。

@Entity
@Table(name = "allergens")
class Allergens {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "meal_id")
    Long mealId;

    @OneToOne
    @PrimaryKeyJoinColumn(name = "meal_id")
    Meal meal;

    @Column(name = "peanuts")
    boolean peanuts;

    @Column(name = "celery")
    boolean celery;

    @Column(name = "sesame_seeds")
    boolean sesameSeeds;

    // standard getters and setters
}

上記の例では、mean_idが主キーであると同時に外部キーでもあることがわかります。 つまり、@PrimaryKeyJoinColumnを使用して1対1の関係列を定義する必要があります。

ただし、このソリューションには2つの問題があります。

  • 私たちは常に食事のためにアレルゲンを保存したいと思っています、そしてこの解決策はこの規則を強制しません
  • 食事とアレルゲンのデータは論理的に一緒に属します– したがって、複数のテーブルを作成したとしても、この情報を同じJavaクラスに格納したい場合があります

最初の問題に対する考えられる解決策の1つは、Mealエンティティのallergensフィールドに@NotNullアノテーションを追加することです。 null アレルゲンがある場合、JPAは食事を永続化させません。

ただし、これは理想的なソリューションではありません。 アレルゲンなしで食事を持続させようとする機会さえない、より制限的なものが必要です。

4. @SecondaryTableを使用して単一のエンティティを作成する

@SecondaryTable アノテーションを使用して、異なるテーブルに列があることを指定する単一のエンティティを作成できます。

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

舞台裏では、JPAはプライマリテーブルとセカンダリテーブルを結合し、フィールドにデータを入力します。このソリューションは@OneToOne関係に似ていますが、このようにして、すべてのプロパティを同じクラスに含めることができます。

セカンダリテーブルに列がある場合は、@Columnアノテーションのテーブル引数で指定する必要があることに注意してください。列がプライマリテーブルの場合、JPAはデフォルトでプライマリテーブルの列を検索するため、table引数を省略できます。

また、 @SecondaryTables に埋め込むと、複数のセカンダリテーブルを持つことができることに注意してください。 または、Java 8から、繰り返し可能なアノテーションであるため、エンティティに複数の@SecondaryTableアノテーションを付けることができます。

5. @SecondaryTable@Embeddedの組み合わせ

これまで見てきたように、@SecondaryTableは複数のテーブルを同じエンティティにマップします。 また、@Embeddedと@Embeddable は逆のことを行い、単一のテーブルを複数のクラスにマップすることもわかっています。

@SecondaryTable@Embeddedおよび@Embeddableと組み合わせたときに得られるものを見てみましょう。

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Embedded
    Allergens allergens;

    // standard getters and setters

}

@Embeddable
class Allergens {

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

これは、@OneToOneを使用して見たものと同様のアプローチです。 ただし、いくつかの利点があります。

  • JPAは2つのテーブルを一緒に管理するため、両方のテーブルに各食事の行があることを確認できます
  • また、必要な構成が少ないため、コードは少し単純です。

それでも、この1対1のようなソリューションは、2つのテーブルのIDが一致する場合にのみ機能します。

Allergens クラスを再利用する場合は、Mealクラスのセカンダリテーブルの列を@AttributeOverrideで定義した方がよいことに注意してください。 ]。

6. 結論

この短いチュートリアルでは、 @SecondaryTableJPAアノテーションを使用して複数のテーブルを同じエンティティにマップする方法を説明しました。

また、@SecondaryTable@Embeddedおよび@Embeddableと組み合わせて、1対1のような関係を築くことの利点もわかりました。

いつものように、例はGitHubから入手できます。