1. 概要

このチュートリアルでは、JPAが主キーに値を割り当てる瞬間を説明します。 JPA仕様の内容を明確にしてから、主キー生成にさまざまなJPA戦略を使用した例を示します。

2. 問題文

ご存知のとおり、JPA(Java Persistence API)は、 EntityManager を使用して、Entityのライフサイクルを管理します。 ある時点で、JPAプロバイダーは主キーに値を割り当てる必要があります。 それで、これはいつ起こるのでしょうか? そして、これを述べているドキュメントはどこにありますか?

JPA仕様には次のように書かれています。

新しいエンティティインスタンスは、persistメソッドを呼び出すか、persist操作をカスケードすることにより、管理対象と永続化の両方になります。

したがって、この記事では EntityManager.persist()メソッドに焦点を当てます。

3. 価値の生成戦略

EntityManager.persist()メソッドを呼び出すと、エンティティの状態はJPA仕様に従って変更されます。

Xが新しいエンティティの場合、管理対象になります。 エンティティXは、トランザクションのコミット時またはその前、またはフラッシュ操作の結果としてデータベースに入力されます。

これは、主キーを生成するさまざまな方法があることを意味します。 一般に、2つの解決策があります。

  • 主キーを事前に割り当てます
  • データベースに永続化した後、主キーを割り当てます
具体的には、JPAは主キーを生成するための4つの戦略を提供しています。
  • GenerationType.AUTO
  • GenerationType.IDENTITY
  • GenerationType.SEQUENCE
  • GenerationType.TABLE
それらを一つずつ見ていきましょう。

3.1. GenerationType.AUTO

AUTO は、@GeneratedValueデフォルト戦略です。 主キーが必要な場合は、AUTO戦略を使用できます。 JPAプロバイダーは、基盤となるデータベースに適切な戦略を選択します。

@Entity
@Table(name = "app_admin")
public class Admin {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "admin_name")
    private String name;

    // standard getters and setters
}

3.2. GenerationType.IDENTITY

IDENTITY戦略は、データベースの自動インクリメント列に依存しています。 データベースは、各挿入操作の後に主キーを生成します。 JPAは、挿入操作の実行後またはトランザクションのコミット時に主キー値を割り当てます。

@Entity
@Table(name = "app_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name")
    private String name;

    // standard getters and setters
}

ここでは、トランザクションコミットの前後のid値を確認します。

@Test
public void givenIdentityStrategy_whenCommitTransction_thenReturnPrimaryKey() {
    User user = new User();
    user.setName("TestName");
        
    entityManager.getTransaction().begin();
    entityManager.persist(user);
    Assert.assertNull(user.getId());
    entityManager.getTransaction().commit();

    Long expectPrimaryKey = 1L;
    Assert.assertEquals(expectPrimaryKey, user.getId());
}

IDENTITY 戦略は、MySQL、SQL Server、PostgreSQL、DB2、Derby、およびSybaseでサポートされています。

3.3. GenerationType.SEQUENCE

SEQUENCE 戦略を使用することにより、JPAはデータベースシーケンスを使用して主キーを生成します。 この戦略を適用する前に、まずデータベース側でシーケンスを作成する必要があります。

CREATE SEQUENCE article_seq
  MINVALUE 1
  START WITH 50
  INCREMENT BY 50

JPAは、EntityManager.persist()メソッドを呼び出した後、トランザクションをコミットする前に、主キーを設定します。

SEQUENCE戦略でArticleエンティティを定義しましょう。

@Entity
@Table(name = "article")
public class Article {
    
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "article_gen")
    @SequenceGenerator(name="article_gen", sequenceName="article_seq")
    private Long id;

    @Column(name = "article_name")
    private String name

    // standard getters and setters
}

シーケンスは50から始まるため、最初のidは次の値51になります。

それでは、SEQUENCE戦略をテストしてみましょう。

@Test
public void givenSequenceStrategy_whenPersist_thenReturnPrimaryKey() {
    Article article = new Article();
    article.setName("Test Name");

    entityManager.getTransaction().begin();
    entityManager.persist(article);
    Long expectPrimaryKey = 51L;
    Assert.assertEquals(expectPrimaryKey, article.getId());

    entityManager.getTransaction().commit();
}

SEQUENCE 戦略は、Oracle、PostgreSQL、およびDB2でサポートされています。

3.4. GenerationType.TABLE

TABLE 戦略は、テーブルから主キーを生成し、基盤となるデータベースに関係なく同じように機能します。

主キーを生成するには、データベース側でジェネレータテーブルを作成する必要があります。 テーブルには少なくとも2つの列が必要です。1つはジェネレーターの名前を表す列で、もう1つは主キー値を格納する列です。

まず、ジェネレータテーブルを作成しましょう。

@Table(name = "id_gen")
@Entity
public class IdGenerator {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "gen_name")
    private String gen_name;

    @Column(name = "gen_value")
    private Long gen_value;

    // standard getters and setters
}

次に、ジェネレータテーブルに2つの初期値を挿入する必要があります。

INSERT INTO id_gen (gen_name, gen_val) VALUES ('id_generator', 0);
INSERT INTO id_gen (gen_name, gen_val) VALUES ('task_gen', 10000);

JPAは、 EntityManager.persist()メソッドを呼び出した後、トランザクションをコミットする前に主キー値を割り当てます。

次に、TABLE戦略でジェネレーターテーブルを使用してみましょう。 alllocationSize を使用して、いくつかの主キーを事前に割り当てることができます。

@Entity
@Table(name = "task")
public class Task {
    
    @TableGenerator(name = "id_generator", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_value",
        pkColumnValue="task_gen", initialValue=10000, allocationSize=10)
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "id_generator")
    private Long id;

    @Column(name = "name")
    private String name;

    // standard getters and setters
}

そして、 persist メソッドを呼び出した後、idは10,000から始まります。

@Test
public void givenTableStrategy_whenPersist_thenReturnPrimaryKey() {
    Task task = new Task();
    task.setName("Test Task");

    entityManager.getTransaction().begin();
    entityManager.persist(task);
    Long expectPrimaryKey = 10000L;
    Assert.assertEquals(expectPrimaryKey, task.getId());

    entityManager.getTransaction().commit();
}

4. 結論

この記事では、JPAがさまざまな戦略の下で主キーを設定する瞬間を説明します。 さらに、例を通じてこれらの各戦略の使用法についても学びました。

完全なコードは、GitHubにあります。