1. 序章

このチュートリアルでは、javax.persistenceパッケージのEntityNotFoundExceptionについて説明します。 この例外が発生する可能性のあるケースについて説明し、その後、それらのケースのテストを作成します。

2. EntityNotFoundException はいつスローされますか?

この例外に関するOracleのドキュメントでは、永続性プロバイダーがEntityNotFoundExceptionをスローする可能性がある3つの状況を定義しています。

  • EntityManager.getReference存在しないエンティティ
  • EntityManager.refreshデータベースに存在しないオブジェクト
  • EntityManager.lockデータベースに存在しないエンティティに対する悲観的なロック

これらの3つのユースケースとは別に、もう少しあいまいなケースが1つあります。この例外は、@ManyToOne関係と遅延読み込みを使用している場合にも発生する可能性があります。

@ManyToOne アノテーションを使用する場合、参照されるエンティティが存在する必要があります。 これは通常、外部キーを使用したデータベースの整合性によって保証されます。 リレーショナルモデルで外部キーを使用しない場合、またはデータベースに一貫性がない場合、エンティティをフェッチするときにEntityNotFoundExceptionが表示されることがあります。 これについては、次のセクションで例を挙げて説明します。

3. EntityNotFoundExceptionの実際

まず、1つの簡単なユースケースについて説明します。 前のセクションでは、getReferenceメソッドについて説明しました。 このメソッドを使用して、特定のエンティティのプロキシをフェッチします。 このプロキシでは、主キーフィールドのみが初期化されています。 このプロキシエンティティでgetterを呼び出すと、永続性プロバイダーが残りのフィールドを初期化します。 エンティティがデータベースに存在しない場合、EntityNotFoundExceptionが発生します。

@Test(expected = EntityNotFoundException.class)
public void givenNonExistingUserId_whenGetReferenceIsUsed_thenExceptionIsThrown() {
    User user = entityManager.getReference(User.class, 1L);
    user.getName();
}

Userエンティティは基本です。 フィールドは2つだけで、関係はありません。 テストの最初の行に主キー値1Lのプロキシエンティティを作成します。 その後、そのプロキシエンティティでgetterを呼び出します。 永続性プロバイダーは主キーでエンティティをフェッチしようとしますが、レコードが存在しないため、EntityNotFoundExceptionがスローされます。

次の例では、さまざまなドメインエンティティを使用します。 ItemエンティティとCategoryエンティティを、双方向の関係で作成します。

@Entity
public class Item implements Serializable {
    @Id
    @Column(unique = true, nullable = false)
    private long id;
    private String name;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Category category;
    // getters and setters
}
@Entity
public class Category implements Serializable {
    @Id
    @Column(unique = true, nullable = false)
    private long id;
    private String name;
    @OneToMany
    @JoinColumn(name = "category_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private List<Item> items = new ArrayList<>();
    // getters and setters
}

@ManyToOneアノテーションでレイジーフェッチを使用していることに注意してください。 また、 @ForeignKey を使用して、データベースから制約を削除します。

@Test(expected = EntityNotFoundException.class)
public void givenItem_whenManyToOneEntityIsMissing_thenExceptionIsThrown() {
    entityManager.createNativeQuery("Insert into Item (category_id, name, id) values (1, 'test', 1)").executeUpdate();
    entityManager.flush();
    Item item = entityManager.find(Item.class, 1L);
    item.getCategory().getName();
}

このテストでは、IDでItemエンティティをフェッチします。 メソッドfindは、 FetchType.LAZY id [X154Xのみ]を使用するため、Categoryが完全に初期化されていないItemオブジェクトを返します。 ]が設定されます。前の例と同様です)。 Category でgetterを呼び出すと、オブジェクト永続性プロバイダーがデータベースからオブジェクトをフェッチしようとしますが、レコードが存在しないため、例外が発生します。

@ManyToOne の関係は、参照されるエンティティが存在することを前提としています。 外部キーとデータベースの整合性により、これらのエンティティが確実に存在します。 そうでない場合は、欠落しているエンティティを無視するための回避策があります。

@NotFound(action = NotFoundAction.IGNORE)と@ManyToOneアノテーションを組み合わせると、永続性プロバイダーがEntityNotFoundExceptionをスローしなくなりますが、ただし、 NullPointerException を回避するには、欠落しているエンティティを手動で処理する必要があります。 。

4. 結論

この記事では、 EntityNotFoundException が発生する可能性のある状況と、それを処理する方法について説明しました。 まず、公式ドキュメントを確認し、通常のユースケースについて説明しました。 その後、より複雑なケースとこの問題を修正する方法について説明します。

いつものように、GitHubでこの記事のコードを見つけることができます。