• 22
    いいね
  • 0
    コメント

この記事は 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 などの基本的な型

jpa.JPG

EntityAlpha.java
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

jpa.JPG

EnumAlpha.java
package sample.jpa;

public enum EnumAlpha {
    ALPHA,
    BETA,
    GAMMA,
}
EntityAlpha.java
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

ソート指定なし

jpa.JPG

EntityAlpha.java
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;
}

ソート指定あり(インデックス値を保存)

jpa.JPG

EntityAlpha.java
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;
}

ソート指定あり(リスト内の値でソート)

jpa.JPG

EntityAlpha.java
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("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 が正解?

関連テーブルを使う

jpa.JPG

@CollectionTable@JoinTableinverseJoinColumns 相当のものがないので無理っぽい?

Map

基本

jpa.JPG

EntityAlpha.java
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

jpa.JPG

EntityAlpha.java
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

埋め込み可能クラス

基本

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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(値)

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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(キー)

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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;
}

入れ子

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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;
}
EmbeddableBeta.java
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;
}

マッピング定義の上書き

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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 を使用する。

エンティティ→埋め込み可能クラス→エンティティ

jpa.JPG

EntityAlpha.java
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;
}
EmbeddableAlpha.java
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;
}
EntityBeta.java
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 を埋め込み可能クラスにする

jpa.JPG

EmbeddableId.java
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;
}
EntityAlpha.java
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() 時にエラーが発生する。
IDがnullの場合に発生する例外
■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 は問題なく動く。

結構致命的な気がするんだけど、何か間違っているのだろうか。。。

キーが基本型で値がエンティティ

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}

キーが埋め込み可能クラスで値がエンティティ

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}
EmbeddableAlpha.java
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;
}

キーがエンティティで値が埋め込み可能クラス

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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();
    }
}
EmbeddableAlpha.java
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 の場合、 cascadeorphanRemoval は Map の値に適用される。

11.1.40 OneToMany Annotation (P.473)

ということで、 EntityAlpha を登録する前に EntityBetapersist() しておく必要がある。

実装イメージ
// 先に 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);

キーも値もエンティティ

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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();
    }
}
EntityGamma.java
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

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}

主キーが複合キー

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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 が埋め込み可能クラス

jpa.JPG

EntityAlpha.java
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();
    }
}
EntityBeta.java
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();
    }
}
EmbeddableId.java
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 が埋め込み可能クラスのオブジェクトと関連を定義する場合は、 @JoinColumnreferencedColumnName を指定しないといけない点に注意。
  • 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)

双方向

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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対多

単方向

結合テーブルを使用する

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}

結合テーブルを使用しない

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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 であれば、 @JoinColumnnullable=false を設定することで INSERT, や DELETE も動くようになる。
    • Eclipse Link では、 nullable=false を設定してもやはり INSERT でエラーになった。
  • table_beta.alpha_id が NULL 可の場合は、 Eclipse Link, Hibernate ともに上記実装で INSERT, DELETE が動作した。

双方向

結合テーブルを使用する

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}

結合テーブルを使用しない

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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 自体を EntityManagerremove() で削除してあげる必要がある。

entityAlpha.remove(entityBeta);   // betaList から削除
entityManager.remove(entityBeta); // entityBeta を削除

多対多

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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つのテーブルにマッピングする

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}
EntityGamma.java
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;
}

サブクラスごとにテーブルをマッピングする

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}
EntityGamma.java
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 columndtype についての言及がある。

関連

SINGLE

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}
EntityGamma.java
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;
}
EntityDelta.java
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;
}
EntityEpsilon.java
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;
}
EntityZeta.java
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

jpa.JPG

EntityAlpha.java
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;
}
EntityBeta.java
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;
}
EntityGamma.java
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;
}
EntityDelta.java
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 は EntityDeltapersist() したときにエラーで落ちる。。。
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_idtable_alpha に登録したときの ID (9) をセットしようとしているが、 table_beta にはレコードがまだないので外部参照制約違反で落ちている。

バグっぽいなぁ。。。

なお、エラーが起こった時の Java の実装は↓のような感じ。

EntityBeta beta = new EntityBeta("name", "code");
EntityDelta delta = new EntityDelta("name", beta);
entityManager.persist(delta);

EntityBeta をあらかじめ persist() しておけば、エラーは起こらなくなり、その後検索や更新・削除は Hibernate 同様問題なく動作するようになる。

この投稿は Java EE Advent Calendar 20163日目の記事です。