LoginSignup
14
12

More than 5 years have passed since last update.

OneToManyで外部キーを保存するための試行錯誤の記録

Last updated at Posted at 2015-01-08

環境

  • GlassFish 4.1
  • JDK 1.8

課題

こんなテーブルがあったとします(太字は主キーです)。

HUMAN(ID)
CELL(CELL_ID, HUMAN_ID)

HUMAN対CELLは1:nの関係です。

CELL.HUMAN_IDが外部キーとなっていてHUMAN.IDを参照しています。

エンティティは2つになります。

ここで、CELL.HUMAN_IDにHUMAN.IDの値を自動でぶっこみたいなぁというお話です。

試行

試行1

@Idアノテーションを複数つけて、@IdClassで複合主キーを指定します。

Human.java
@Entity
public class Human implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Integer id;

    @Id
    @OneToMany(mappedBy = "human", cascade = CascadeType.ALL)
    private List<Cell> cellList;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void addCell(Cell cell) {
        if (cell.getHuman() != this) {
            cell.setHuman(this);
        }
        if (cellList == null ){
            cellList = new ArrayList<>();
        }

        if (!cellList.contains(cell))  {
            cellList.add(cell);
        }
    }
    // 略 hashCodeやequals
}
Cell.java
@Entity
@IdClass(CellPK.class)
public class Cell implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "CELL_ID")
    private Integer id;

    @Id
    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "HUMAN_ID", referencedColumnName = "ID")
    private Human human;
    // 略
}
CellPK.java
public class CellPK implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private Human human;
    // 略
}

結果

ここで実行すると、そもそもデプロイできないです。

Severe:   Exception while invoking class org.glassfish.persistence.jpa.JPADeployer prepare method
Severe:   javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Predeployment of PersistenceUnit [jpa-composite-onetomany_war_1.0-SNAPSHOTPU] failed.
Internal Exception: Exception [EclipseLink-7332] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The derived composite primary key attribute [human] of type [jpa.composite.onetomany.Human] from [jpa.composite.onetomany.CellPK] should be of the same type as its parent id field from [jpa.composite.onetomany.Human]. That is, it should be of type [java.lang.Integer].
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.createPredeployFailedPersistenceException(EntityManagerSetupImpl.java:1954)
    at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:1945)

型を合わせないといけないらしい。

試行2

型をあわせて、@JoinColumn@PrimaryKeyJoinColumnへ変更しました。@JoinColumnのままだと、更新できるフィールドが複数あるからと怒られます。

CellPK.java
public class CellPK implements Serializable {
    private static final long serialVersionUID = 1L;
    private Integer id;
    private Integer humanId;
    // 略
}
Cell.java
@Entity
@IdClass(CellPK.class)
public class Cell implements Serializable {
    // 略
    @Id
    @Column(name = "HUMAN_ID")
    private Integer humanId;

    @ManyToOne(cascade = CascadeType.ALL)
    @PrimaryKeyJoinColumn(name = "HUMAN_ID", referencedColumnName = "ID")
    private Human human;
    // 略
}

結果

CELL.HUMAN_IDがセットされないので、SQLエラーになりました。

javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'HUMAN_ID' cannot be null
Error Code: 1048
Call: INSERT INTO CELL (HUMAN_ID, CELL_ID) VALUES (?, ?)
    bind => [2 parameters bound]
Query: InsertObjectQuery(jpa.composite.onetomany.Cell[ id=2 ])

試行3

Cellエンティティのセッターの中で、humanIdをセットするように変えてみました。

Cell.java
@Entity
@IdClass(CellPK.class)
public class Cell implements Serializable {
    // 略
    public void setHuman(Human human) {
        this.human = human;
        this.humanId = human.getId();
        human.addCell(this);
    }
    // 略
}

結果

状況変わらず……。

Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'HUMAN_ID' cannot be null
Error Code: 1048
Call: INSERT INTO CELL (HUMAN_ID, CELL_ID) VALUES (?, ?)
    bind => [2 parameters bound]
Query: InsertObjectQuery(jpa.composite.onetomany.Cell[ id=2 ])

試行4

そもそも呼び方間違ってね?

        Human human = new Human();
        human.addCell(new Cell());
        human.addCell(new Cell());
        em.persist(human);        

を、

        Human human = new Human();
        em.persist(human);   
        human.addCell(new Cell());
        human.addCell(new Cell());

のように書き換えました。emEntityManagerです。

結果

成功しました~~~(喜)

@OneToOneなら関連テーブルもいっぺんにpersistできるけど、@OneToManyは先にpersistしないといけないみたい。

結論

OneToManyを保存するときは、先に親エンティティをpersistすべし。

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12