Edited at

Spring Data JPA において多重継承した Entity の継承戦略が SINGLE_TABLE である場合 DiscriminatorColumn が自動生成されない現象について

More than 1 year has passed since last update.


概要

application.properties の spring.jpa.hibernate.ddl-auto キーを "create" などに設定すると該当エンティティのテーブルが自動作成されます。

JPA の継承戦略を SINGLE_TABLE に適用した Entity が多重継承である場合、2層目のクラスの DiscriminatorColumn が自動生成されない現象が発生しました。

下記の例だと、Engineer クラスにて定義した engineer_type 列が、employee テーブルに自動生成されない動作となります。


Employee.java

@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Getter
@NoArgsConstructor
public abstract class Employee {
@Id
private int Id;
}


Engineer.java

@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "engineer_type", discriminatorType = DiscriminatorType.STRING)
@Getter
@DiscriminatorValue("engineer")
public abstract class Engineer extends Employee {
}


SoftWareEngineer.java

@Entity

@DiscriminatorValue("software")
@Getter
public class SoftWareEngineer extends Engineer {
}


回避策

アプローチに誤りがあったと判断しております。

ベストプラクティスについては、こちらのページに紹介されている MappedSuperClass を併用することであると判断しております。

JPA 1.0 の仕様は現在記載されていることを確認できませんでしたが、検証結果から下記の引用通りであると判断しております。


Both abstract and concrete classes can be entities. Both abstract and concrete classes can be annotated with the Entity annotation, mapped as entities, and queried for as entities.

Entities can extend non-entity classes and non-entity classes can extend entity classes.



詳細

@Entity アノテーションが存在するクラスは @Entity アノテーションが存在しないクラスを継承できます。

一方、@Entity アノテーションが存在するクラスについては、@Entity アノテーションが存在するクラスを継承します。

上記の実装だと、@Entity を連続的に継承しているため、Entity として判断されない @MappedSuperClass を一枚挟み込み、ついで、@Entity が存在するクラスが @MappedSuperclass のアノテーションをつけたクラスを継承します。

@MappedSuperclass は該当クラスが @Entity ではないことを明示します。


A mapped superclass is not a persistent class, but allow common mappings to be defined for its subclasses.


下記で、Engineer クラスを Object クラスを継承している Employee クラスと同じような意味合いにすることができているものと考えております。

dtype 列 (規定で作成される DiscriminatorColumn の列名) に DiscriminatorValue ("software") の値を設定することで、想定通りのデータ型で Repository よりエンティティを取得できることを確認しました。


Employee.java

@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Getter
@NoArgsConstructor
public abstract class Employee {
@Id
private int Id;
}


MappedEngineer.java

@Getter

@MappedSuperclass
public abstract class MappedEngineer extends Employee {
}


Engineer.java

@Entity

@Getter
public abstract class Engineer extends MappedEngineer {
    // some engineer members ...
}


SoftWareEngineer.java

@Entity

@Getter
@DiscriminatorValue("software")
public class SoftWareEngineer extends Engineer {
}


補足

@MappedSuperclass の戦略を採用しない場合、下記のように、最初の構成の 2層目 (Engineer クラス) に DiscriminatorColumn の列をフィールドとして定義することで、DiscriminatorColumn が作成されない現象を回避できます。

✳︎下記例では、EngineerType の enum を型とした engineerType フィールドを設定しています。


Engineer.java

@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "engineer_type", discriminatorType = DiscriminatorType.STRING)
@Getter
@DiscriminatorValue("engineer")
public abstract class Engineer extends Employee {
@Enumerated(EnumType.STRING)
private EngineerType engineerType;

}


ただし、Engineer クラス配下のデータを Repository にて取得する際、JPA は Engineer クラスのインスタンスで、SoftwareEngineer クラスなど第3層目のクラスのデータをインスタンス化しようとします。

Engineer クラスは抽象型であるため、 Repository からデータを取得する際に、抽象型、インターフェースはインスタンス化できません例外が発生します。

また、Engineer クラスを具象クラスとする場合、Repository にてエンティティは取得できますが、Engineer 配下のクラスのデータは全て、Engineer クラスのインスタンスとなります。