1. 序章

In this tutorial, we’ll discuss how to handle auto-generated ids with JPA. There are two key concepts that we must understand before we take a look at a practical example, namely life cycle and id generation strategy.

2. エンティティのライフサイクルとIDの生成

各エンティティには、そのライフサイクル中に4つの可能な状態があります。 これらの状態は、新規、管理、分離、および削除です。 Our focus will be on the new and managed states. オブジェクトの作成中、エンティティは新しい状態になります。 したがって、EntityManagerはこのオブジェクトを認識しません。 Calling the persist method on EntityManager, the object transitions from a new to managed state. このメソッドにはアクティブなトランザクションが必要です。

JPAは、ID生成の4つの戦略を定義しています。 これらの4つの戦略を2つのカテゴリにグループ化できます。

  • IDは事前に割り当てられており、commitの前にEntityManagerで使用できます。
  • IDは、トランザクションcommitの後に割り当てられます

各ID生成戦略の詳細については、記事JPAが主キーを設定するタイミングを参照してください。

3. 問題文

オブジェクトのIDを返すことは、面倒な作業になる可能性があります。 問題を回避するには、前のセクションで説明した原則を理解する必要があります。 JPA構成によっては、サービスはidがゼロ(またはnull)に等しいオブジェクトを返す場合があります。 焦点は、サービスクラスの実装と、さまざまな変更によってソリューションを提供する方法にあります。

JPA仕様とHibernateを実装として使用してMavenモジュールを作成します。 For simplicity, we’ll use an H2 in-memory database.

ドメインエンティティを作成し、それをデータベーステーブルにマッピングすることから始めましょう。 この例では、いくつかの基本的なプロパティを持つUserエンティティを作成します。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
 
    //...
}

After the domain class, we’ll create a UserService class. この単純なサービスには、 EntityManager への参照と、Userオブジェクトをデータベースに保存するメソッドがあります。

public class UserService {
    EntityManager entityManager;
 
    public UserService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
 
    @Transactional
    public long saveUser(User user){
        entityManager.persist(user);
        return user.getId();
    }
}

This setup is a common pitfall that we previously mentioned. saveUserメソッドの戻り値がゼロであることをテストで証明できます。

@Test
public void whenNewUserIsPersisted_thenEntityHasNoId() {
    User user = new User();
    user.setUsername("test");
    user.setPassword(UUID.randomUUID().toString());
 
    long index = service.saveUser(user);
 
    Assert.assertEquals(0L, index);
}

In the following sections, we’ll step back to understand why this happened, and how we can solve it.

4. 手動トランザクション制御

オブジェクトの作成後、Userエンティティはnew状態になります。 saveUserメソッドでpersistメソッドを呼び出した後、エンティティの状態がmanagedに変わります。 要約セクションから、管理対象オブジェクトはトランザクションコミットの後にIDを取得することを思い出します。 Since the saveUser method is still running, the transaction created by the @Transactional annotation isn’t yet committed. saveUser の実行が終了すると、管理対象エンティティはIDを取得します。

考えられる解決策の1つは、EntityManagerのflushメソッドを手動で呼び出すことです。 一方、手動でトランザクションを制御して、メソッドがIDを正しく返すことを保証できます。 これは、EntityManagerで実行できます。

@Test
public void whenTransactionIsControlled_thenEntityHasId() {
    User user = new User();
    user.setUsername("test");
    user.setPassword(UUID.randomUUID().toString());
     
    entityManager.getTransaction().begin();
    long index = service.saveUser(user);
    entityManager.getTransaction().commit();
     
    Assert.assertEquals(2L, index);
}

5. ID生成戦略の使用

これまでは、トランザクションcommitの後にID割り当てが行われる2番目のカテゴリを使用していました。 Pre-allocating strategies can provide us with ids before the transaction commit, since they keep a handful of ids in memory. This option isn’t always possible to implement because not all database engines support all generation strategies. 戦略をGenerationType.SEQUENCEに変更すると、問題を解決できます。 この戦略では、GenerationType.IDENTITY。のように自動インクリメント列の代わりにデータベースシーケンスを使用します

戦略を変更するには、ドメインエンティティクラスを編集します。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
 
    //...
}

6. 結論

この記事では、JPAでのID生成手法について説明しました。 まず、ID生成の最も重要な重要な側面について少し要約しました。 Then we covered common configurations used in JPA, along with their advantages and disadvantages. All code referenced in this article can be found over on GitHub.