読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

生成された主キーを取得する

JPA

JPAを使ってエンティティをINSERTし、更にINSERTしたエンティティの主キーを取得する必要があったとします。
例えば次のようなコードを考えてみます。

@Entity
public class Foo {

    // AUTO INCREMENTなカラムIDを主キーとしている
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    ...
}


public class FooDAO {
    /**
     * FooをINSERTし、生成された主キーを返す。
     */
    public long persistFoo(EntityManager em, Foo foo) {

        //Fooを実際にINSERTする (→ウソ)
        em.persist(company);

        //生成された主キーを取得する (→ウソ。常に0が返る)
        final long generatedFooId = foo.getId();
        
        return generatedFooId;
    }

    ...
}


このとき、FooDAO#persistFooは意図したとおりには動作しません。
具体的には、persistFooが返す値は常に0となります。すなわち主キーを取得できていないわけです。


その理由は、EntityManagerの更新系メソッド(persist, merge, delete)は、呼び出された時点ですぐにDML文を発行するわけではないからです。
JPAのデフォルトでは、コミット直前にまとめてSQLを発行する仕様になっています。
つまり、例えばEntityManager#persistなら『INSERT対象とするエンティティにマークを付ける』だけなのです。

こちらの記事(IBM Developerworks)も参考にしてください。


この場合は、EntityManager#flushを使用して強制的にSQLを実行させればOKです。
修正したコードは次のようになります。

@Entity
public class Foo {

    // AUTO INCREMENTなカラムIDを主キーとしている
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    ...
}


public class FooDAO {
    /**
     * FooをINSERTし、生成された主キーを返す。
     */
    public long persistFoo(EntityManager em, Foo foo) {

        //FooをINSERTすることをマークする
        em.persist(company);
        
        //強制的にFooをINSERTする
        em.flush();

        //生成された主キーを取得する
        final long generatedFooId = foo.getId();
        
        return generatedFooId;
    }

    ...
}