この記事は Java EE Advent Calendar 2016 - Qiita の3日目の記事です。
昨日は @n_agetsu さんの コネクションプールの挙動差分によるバグ顕在化 でした。
明日は @lbtc_xxx さんです。
以前 JPA のマッピングについて勉強したときのメモ を書いたことがあるのですが、これはマッピングの仕組みや意味を理解することが主目的でした。
そのため、後でマッピングの方法を探そうとしたときに、ノイズ(不要な情報)が多いなぁと思っていました。
そこで、今回は「あのマッピングどうするんだっけ」と思ったときに Java の実装と DB の構造がパッと分かるようにすることを目標にして、いろいろなマッピングを一覧にしてみました。
また、うまく動くものだけでなく、うまく動かないもの、実現できないものもまとめています。
できることだけでなく、できないことも早々に分かればハマる時間を減らせると思うので。
うまく動かない可能性があったり、実装によって挙動が異なるなど何かしら注意が必要なものについては、先頭に  というアイコンをつけています。
 多すぎじゃね?!
動作確認環境
JPA 実装
- EclipseLink 2.6.4
- Hibernate 5.2.2
データベース
- MySQL (mysql Ver 14.14 Distrib 5.5.28, for Win64 (x86))
基本型
プリミティブ型・および String などの基本的な型
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(name="string_value")
    private String stringValue;
    @Column(name="boolean_value")
    private boolean booleanValue;
    @Column(name="int_value")
    private int intValue;
    @Column(name="double_value")
    private double doubleValue;
    @Column(name="big_integer_value")
    private BigInteger bigIntegerValue;
    @Column(name="big_decimal_value")
    private BigDecimal bigDecimalValue;
    @Column(name="date_value")
    @Temporal(TemporalType.DATE)
    private Date dateValue;
    @Column(name="datetime_value")
    @Temporal(TemporalType.TIMESTAMP)
    private Date dateTimeValue;
}
enum
package sample.jpa;
public enum EnumAlpha {
    ALPHA,
    BETA,
    GAMMA,
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(name="ordinal_enum_value")
    private EnumAlpha ordinalEnumValue;
    @Column(name="string_enum_value")
    @Enumerated(EnumType.STRING) // name() が返した文字列で保存
    private EnumAlpha stringEnumValue;
}
- 
ordinalの場合は0始まり
List
ソート指定なし
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="list_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @Column(name="value")
    private List<String> list;
}
ソート指定あり(インデックス値を保存)
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OrderColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="list_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @OrderColumn(name="index_value") // ★
    @Column(name="value")
    private List<String> list;
}
ソート指定あり(リスト内の値でソート)
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="list_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @OrderBy("value DESC") // ★
    @Column(name="value")
    private List<String> list;
}
@OrderBy("orderby_list")
orderby_list ::= orderby_item [, orderby_item]*
orderby_item ::= [property_or_field_name] [ASC|DESC]
JPA 仕様書の 11.1.42 参照。
EclipseLink は value を書かなくても動いたが、 Hibernate は value を書かないとエラーになった。
仕様的には、 EclipseLink が正解?
関連テーブルを使う
@CollectionTable に @JoinTable の inverseJoinColumns 相当のものがないので無理っぽい?
Map
基本
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="map_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @MapKeyColumn(name="map_key")
    @Column(name="map_value")
    private Map<String, String> map;
}
キーが enum
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="map_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @MapKeyEnumerated(EnumType.STRING) // ★
    @MapKeyColumn(name="map_key")
    @Column(name="map_value")
    private Map<EnumAlpha, String> map;
}
Map<String, List<String>> みたいなこと
無理っぽい
Java Persistence/Relationships - Wikibooks, open books for an open world
JPA does not support nested collection relationships
埋め込み可能クラス
基本
package sample.jpa;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Embedded
    private EmbeddableAlpha embeddableAlpha;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="embeddable_value")
    private String value;
}
List
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Embedded
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="list_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    private List<EmbeddableAlpha> list;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="list_value")
    private String value;
}
Map(値)
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="map_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @MapKeyColumn(name="map_key")
    private Map<String, EmbeddableAlpha> map;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="map_value")
    private String value;
}
上記実装は、検索だけなら EclipseLink, Hiberante 共に動作した。
しかし、更新になると EclipseLink がうまく動作しないケースが発生した。
いろいろ試したところ、どうも値が同じエントリが複数存在する場合の動作があやしい。
キー違いで同じ値のエントリを追加した場合は INSERT が実行されない。
直接 DB にキー違いで同じ値のエントリを追加した場合は、逆にエントリを片方削除しても DELETE が実行されない。
つまり、 DB が
| table_alpha_id | map_key | map_value | 
|---|---|---|
| 1 | foo | TEST | 
の状態だと、値が TEST のエントリを追加しても INSERT が発行されず、
| table_alpha_id | map_key | map_value | 
|---|---|---|
| 1 | foo | TEST | 
| 1 | bar | TEST | 
のような状態だと key=foo, key=bar のいずれかのエントリを remove() で削除しても DELETE が発行されない。
(両方とも remove() したら DELETE が実行された)
Hibernate だと、うまく動いている。
EclipseLink の場合は、 Map の値の方を基本型(プリミティブ型や String など)にすればうまく動くようになった。。。
バグだろぉ...
Map(キー)
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection(fetch=FetchType.EAGER)
    @CollectionTable(
        name="map_table",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @Column(name="map_value")
    private Map<EmbeddableAlpha, String> map;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="map_key")
    private String value;
}
入れ子
package sample.jpa;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Embedded
    private EmbeddableAlpha alpha;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="alpha_value")
    private String value;
    @Embedded
    private EmbeddableBeta beta;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableBeta implements Serializable {
    @Column(name="beta_value")
    private String value;
}
マッピング定義の上書き
package sample.jpa;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Embedded
    private EmbeddableAlpha foo;
    @Embedded
    @AttributeOverride(name="value", column=@Column(name="bar"))
    private EmbeddableAlpha bar;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="foo")
    private String value;
}
- 
@AttributeOverrideで設定を上書きできる。
- 
nameに上書きするプロパティ名、columnに上書き内容を設定。
- 複数指定する場合は @AttributeOverridesを使用する。
エンティティ→埋め込み可能クラス→エンティティ
package sample.jpa;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Embedded
    private EmbeddableAlpha alpha;
}
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="alpha")
    private String value;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="table_beta_id")
    private EntityBeta beta;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
}
エンティティ
ID を埋め込み可能クラスにする
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import java.io.Serializable;
@Embeddable
public class EmbeddableId implements Serializable {
    @Column(name="id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long value;
}
package sample.jpa;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @EmbeddedId
    private EmbeddableId id;
    private String name;
    @PrePersist
    void setupId() {
        this.id = new EmbeddableId();
    }
}
- Entity 側の ID フィールドが nullだと、persist()時にエラーが発生する。
■Hibernate の場合
javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class sample.jpa.EntityAlpha
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:147)
■Eclipse Link の場合
Caused by: Exception [EclipseLink-71] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: A NullPointerException was thrown while setting the value of the instance variable [value] to the value [5].
Internal Exception: java.lang.NullPointerException
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[value-->table_alpha.id]
Descriptor: RelationalDescriptor(sample.jpa.EmbeddableId --> [DatabaseTable(table_alpha)])
	at org.eclipse.persistence.exceptions.DescriptorException.nullPointerWhileSettingValueThruInstanceVariableAccessor(DescriptorException.java:1321)
- 埋め込み可能クラス(EmbeddableId)のインスタンスまでは作ってくれないみたいなので、事前に ID の値が空のインスタンスを生成してあげる必要があるっぽい。
- フィールド宣言に直接 newして代入しておいても良いが、@PrePersistでセットするほうが意味的にも合致していて自然なのでそっちの方が良い気がする。
spring - null id generated for composite PK - Stack Overflow
Map
どれも EclipseLink で動かしたときに Map の値を埋め込み可能クラスにした場合 と同じ問題が発生した。
Hibernate は問題なく動く。
結構致命的な気がするんだけど、何か間違っているのだろうか。。。
キーが基本型で値がエンティティ
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL)
    @JoinTable(
        name="alpha_beta_map",
        joinColumns = @JoinColumn(name="table_alpha_id"),
        inverseJoinColumns = @JoinColumn(name="table_beta_id")
    )
    @MapKeyColumn(name="map_key")
    private Map<String, EntityBeta> map;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
キーが埋め込み可能クラスで値がエンティティ
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL)
    @JoinTable(
        name="alpha_beta_map",
        joinColumns = @JoinColumn(name="table_alpha_id"),
        inverseJoinColumns = @JoinColumn(name="table_beta_id")
    )
    private Map<EmbeddableAlpha, EntityBeta> map;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="map_key")
    private String value;
}
キーがエンティティで値が埋め込み可能クラス
package sample.jpa;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ElementCollection
    @CollectionTable(
        name="alpha_beta_map",
        joinColumns=@JoinColumn(name="table_alpha_id")
    )
    @MapKeyJoinColumn(name="table_beta_id")
    private Map<EntityBeta, EmbeddableAlpha> map;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Override
    public boolean equals(Object obj) {
        return obj instanceof EntityBeta
                && Objects.equals(this.id, ((EntityBeta) obj).id);
    }
    @Override
    public int hashCode() {
        return this.id == null ? 0 : this.id.hashCode();
    }
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmbeddableAlpha implements Serializable {
    @Column(name="map_value")
    private String value;
}
エンティティが Map のキーに使われている場合、 cascade は無理っぽい。
When the collection is a java.util.Map, the cascade element and the orphanRemoval element
apply to the map value.
コレクションがjava.util.Mapの場合、cascadeとorphanRemovalは Map の値に適用される。
11.1.40 OneToMany Annotation (P.473)
ということで、 EntityAlpha を登録する前に EntityBeta を persist() しておく必要がある。
// 先に EntityBeta を persist()
EntityBeta beta = new EntityBeta("beta");
entityManager.persist(beta);
entityManager.flush(); // DB に反映しておかないと、 alpha_beta_map テーブルを登録するときに table_beta にデータが無くて外部参照制約違反でエラーになる
// persist() 済みの EntityBeta をキーに使う
Map<EntityBeta, EmbeddableAlpha> map = new HashMap<>();
map.put(beta, new EmbeddableAlpha("map value"));
// EntityAlpha に設定して persist()
alpha.setMap(map);
entityManager.persist(alpha);
キーも値もエンティティ
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Map;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL)
    @JoinTable(
        name="alpha_beta_gamma",
        joinColumns=@JoinColumn(name="table_alpha_id"),
        inverseJoinColumns=@JoinColumn(name="table_gamma_id")
    )
    @MapKeyJoinColumn(name="table_beta_id")
    private Map<EntityBeta, EntityGamma> map;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Override
    public boolean equals(Object obj) {
        return obj instanceof EntityBeta
                && Objects.equals(this.id, ((EntityBeta) obj).id);
    }
    @Override
    public int hashCode() {
        return this.id == null ? 0 : this.id.hashCode();
    }
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_gamma")
public class EntityGamma implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
}
1対1
単方向
ID が Long
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="beta_id")
    private EntityBeta beta;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
}
主キーが複合キー
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name="beta_key_1", referencedColumnName="key_1"),
        @JoinColumn(name="beta_key_2", referencedColumnName="key_2")
    })
    private EntityBeta beta;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @Column(name="key_1")
    private String key1;
    @Id
    @Column(name="key_2")
    private String key2;
    private String name;
}
ID が埋め込み可能クラス
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @EmbeddedId
    private EmbeddableId id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="beta_id", referencedColumnName="id")
    private EntityBeta beta;
    @PrePersist
    private void prePersist() {
        this.id = new EmbeddableId();
    }
}
package sample.jpa;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @EmbeddedId
    private EmbeddableId id;
    private String name;
    @PrePersist
    private void prePersist() {
        this.id = new EmbeddableId();
    }
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import java.io.Serializable;
@Embeddable
public class EmbeddableId implements Serializable {
    @Column(name="id")
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long value;
}
- ID が埋め込み可能クラスのオブジェクトと関連を定義する場合は、 @JoinColumnのreferencedColumnNameを指定しないといけない点に注意。
- EclipseLink ならこれで動くが、 Hibernate だと INSERT 時に table_alpha.beta_idがセットされない。- 検索だけならどちらも OK。
 
- そもそも、 EmbeddedIdでリレーションを持たせることは仕様上サポート外っぽい。。。
Relationship mappings defined within an embedded id class are not supported.
11.1.17 EmbeddedId Annotation (P.444)
双方向
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="beta_id")
    private EntityBeta beta;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(mappedBy="beta")
    private EntityAlpha alpha;
}
1対多
単方向
結合テーブルを使用する
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL)
    @JoinTable(
        name="alpha_beta",
        joinColumns=@JoinColumn(name="alpha_id"),
        inverseJoinColumns=@JoinColumn(name="beta_id")
    )
    private List<EntityBeta> betaList;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
}
結合テーブルを使用しない
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL)
    // ※使っているライブラリと table_beta.alpha_id が NULL 不可か
    //   どうかで、 nullable を指定する必要が出てくる(詳細後述)
    @JoinColumn(name="alpha_id")
    private List<EntityBeta> betaList;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
}
- 自分が確かめた限りでは、これが確実に動くのは検索のみ。
- 
table_beta.alpha_idが NULL 不可の場合、上記実装だと INSERT 時にエラーになる(table_beta.alpha_idに NULL をセットしようとしてエラーになる)。- Hibernate であれば、 @JoinColumnにnullable=falseを設定することで INSERT, や DELETE も動くようになる。
- Eclipse Link では、 nullable=falseを設定してもやはり INSERT でエラーになった。
 
- Hibernate であれば、 
- 
table_beta.alpha_idが NULL 可の場合は、 Eclipse Link, Hibernate ともに上記実装で INSERT, DELETE が動作した。
双方向
結合テーブルを使用する
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL)
    @JoinTable(
        name="alpha_beta",
        joinColumns=@JoinColumn(name="table_alpha_id"),
        inverseJoinColumns=@JoinColumn(name="table_beta_id")
    )
    private List<EntityBeta> betaList;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToOne
    @JoinTable(
        name="alpha_beta",
        joinColumns=@JoinColumn(name="table_beta_id"),
        inverseJoinColumns=@JoinColumn(name="table_alpha_id")
    )
    private EntityAlpha alpha;
}
結合テーブルを使用しない
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(cascade=CascadeType.ALL, mappedBy = "alpha")
    private List<EntityBeta> betaList;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToOne
    @JoinColumn(name="table_alpha_id")
    private EntityAlpha alpha;
}
こちらは単方向の場合とは異なり、うまく動作するっぽい。
ただし、EntityAlpha.betaList から要素を削除したい場合は、単純に List から要素を remove() するだけだと EntityBeta 自体は削除されない。
例えば、以下のような感じで remove() した EntityBeta 自体を EntityManager の remove() で削除してあげる必要がある。
entityAlpha.remove(entityBeta);   // betaList から削除
entityManager.remove(entityBeta); // entityBeta を削除
多対多
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(
        name="alpha_beta",
        joinColumns=@JoinColumn(name="table_alpha_id"),
        inverseJoinColumns=@JoinColumn(name="table_beta_id")
    )
    private List<EntityBeta> betaList;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.List;
@Entity
@Table(name="table_beta")
public class EntityBeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToMany(mappedBy="betaList")
    private List<EntityAlpha> alphaList;
}
継承のマッピング
細かい仕様の話(dtype が何かとか)は、 JavaEE使い方メモ(JPA その2 - マッピング) を参照。
クラス階層に属するエンティティを1つのテーブルにマッピングする
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="alpha_beta_gamma")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(name="alpha_name")
    protected String name;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import java.io.Serializable;
@Entity
public class EntityBeta extends EntityAlpha implements Serializable {
    @Column(name="beta_code")
    protected String code;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import java.io.Serializable;
@Entity
public class EntityGamma extends EntityBeta implements Serializable {
    @Column(name="gamma_value")
    private String value;
}
サブクラスごとにテーブルをマッピングする
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
@Inheritance(strategy=InheritanceType.JOINED)
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(name="alpha_name")
    protected String name;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta extends EntityAlpha implements Serializable {
    @Column(name="beta_code")
    protected String code;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_gamma")
public class EntityGamma extends EntityBeta implements Serializable {
    @Column(name="gamma_value")
    private String value;
}
- EclipseLink の場合は dtypeカラムが必要。
- Hibernate の場合は不要(カラムが存在しても、 null になる)。
仕様書(P.58 2.12.2 Joined Subclass Strategy)を読む限り、 dtype カラムの有無については言及されていない。
つまり、仕様上は「カラムを設けろ」とも「設けるな」とも書かれていないということになるので、どちらも間違ってはいないということになるのか。。。(無くても識別は可能なので、 Hibernate のほうが合っている気はする)
ちなみに、同じく P.58 の「2.12.1 Single Table per Class Hierarchy Strategy」には The Table has a column that serves as a discriminator column と dtype についての言及がある。
関連
SINGLE
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(name="alpha_name")
    protected String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="table_epsilon_id")
    protected EntityEpsilon epsilon;
}
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta extends EntityAlpha implements Serializable {
    @Column(name="beta_code")
    protected String code;
    @OneToOne(cascade= CascadeType.ALL)
    @JoinColumn(name="table_zeta_id")
    protected EntityZeta zeta;
}
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_gamma")
public class EntityGamma implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade= CascadeType.ALL)
    @JoinColumn(name="table_alpha_id")
    private EntityAlpha alpha;
}
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_delta")
public class EntityDelta implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="table_alpha_id")
    private EntityBeta beta;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_epsilon")
public class EntityEpsilon implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
}
package sample.jpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_zeta")
public class EntityZeta implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
}
JOINED
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_alpha")
@Inheritance(strategy=InheritanceType.JOINED)
public class EntityAlpha implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    @Column(name="alpha_name")
    protected String name;
}
package sample.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_beta")
public class EntityBeta extends EntityAlpha implements Serializable {
    @Column(name="beta_code")
    protected String code;
}
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_gamma")
public class EntityGamma implements Serializable {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="table_alpha_id")
    private EntityAlpha alpha;
}
package sample.jpa;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.io.Serializable;
@Entity
@Table(name="table_delta")
public class EntityDelta implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="table_beta_id")
    private EntityBeta beta;
}
- Hibernate の方はこれで INSERT, UPDATE, DELETE が問題なく動く。
- EclipseLink は EntityDeltaをpersist()したときにエラーで落ちる。。。
[EL Fine]: INSERT INTO table_alpha (alpha_name, DTYPE) VALUES (?, ?)
	bind => [name(eclipselink), EntityBeta]
[EL Fine]: SELECT LAST_INSERT_ID()
[EL Fine]: INSERT INTO table_delta (NAME, table_beta_id) VALUES (?, ?)
	bind => [eclipselink, 9]
[EL Warning]: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`jpa`.`table_delta`, CONSTRAINT `dfk1` FOREIGN KEY (`table_beta_id`) REFERENCES `table_beta` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
Error Code: 1452
Call: INSERT INTO table_delta (NAME, table_beta_id) VALUES (?, ?)
	bind => [eclipselink, 9]
Query: InsertObjectQuery(EntityDelta(id=null, name=eclipselink, beta=EntityBeta(super=EntityAlpha(id=9, name=name(eclipselink)), code=code(eclipselink))))
:
:
table_alpha (親クラス用のテーブル)に INSERT した後に table_beta (子クラス用のテーブル)に INSERT せずに table_delta に INSERT しようとしている。
table_delta の FK 項目である table_beta_id に table_alpha に登録したときの ID (9) をセットしようとしているが、 table_beta にはレコードがまだないので外部参照制約違反で落ちている。
バグっぽいなぁ。。。
なお、エラーが起こった時の Java の実装は↓のような感じ。
EntityBeta beta = new EntityBeta("name", "code");
EntityDelta delta = new EntityDelta("name", beta);
entityManager.persist(delta);
EntityBeta をあらかじめ persist() しておけば、エラーは起こらなくなり、その後検索や更新・削除は Hibernate 同様問題なく動作するようになる。

































