1.

Hibernateのよくあるエラー集|LazyInitialization・NonUniqueObject

編集
この記事の要点
  • MappingException: アノテーション・テーブル列名不一致 / 型不一致が原因
  • LazyInitializationException: Session 閉じた後で Lazy フィールドにアクセス。@Transactional / FETCH JOIN で対処
  • NonUniqueObjectException: 同じ ID の Entity を同じ Session に 2 つ登録した
  • StaleObjectStateException: @Version 楽観ロックで競合検知
  • N+1 問題: List をループ中に Lazy ロード → JOIN FETCH / EntityGraph で解決

Hibernate でよくあるエラー一覧

例外主な原因対処
MappingExceptionアノテーション/XML設定ミステーブル列名 / 型 / @Id 確認
LazyInitializationExceptionSession 閉じ後の Lazy アクセス@Transactional 範囲拡大 / FETCH JOIN
NonUniqueObjectException同 ID Entity を同 Session に重複登録merge() で更新 or evict()
StaleObjectStateException楽観ロック (@Version) で版違いリトライ / 競合解消 UI
ConstraintViolationExceptionNotNull / Unique / 外部キー違反バリデーション / DB エラー扱い
HibernateException (汎用)Connection / Session 不正スタックトレース確認
TransactionRequiredExceptionupdate/delete を tx 外で実行@Transactional 付与
QuerySyntaxExceptionJPQL / HQL の構文ミスクエリ修正、エンティティ名確認

1. MappingException

org.hibernate.MappingException: Could not determine type for: java.util.List,
  at table: user, for columns: [org.hibernate.mapping.Column(roles)]

原因: List<Role> 等のコレクションに @OneToMany / @ManyToMany を付け忘れた。

// ❌
private List<Role> roles;

// ✅
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Role> roles;

2. LazyInitializationException

org.hibernate.LazyInitializationException:
  could not initialize proxy [User#42] - no Session

原因: Session が閉じた後で Lazy ロード対象(@OneToMany / @ManyToOne Lazy)にアクセス。よくあるのは Controller でレスポンス JSON 化中に発生。

// ❌ Controller (Session 終了済) で Lazy 取得 → 例外
@GetMapping("/{id}")
public User get(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow();
    return user;  // user.getRoles() を JSON 化中に LazyInit
}

// ✅ 対処1: @Transactional でメソッド全体を tx に
@Transactional(readOnly = true)
@GetMapping("/{id}")
public UserDto get(@PathVariable Long id) {
    User user = userRepository.findById(id).orElseThrow();
    return new UserDto(user, user.getRoles());
}

// ✅ 対処2: FETCH JOIN で先読み
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id")
Optional<User> findByIdWithRoles(@Param("id") Long id);

// ✅ 対処3: EntityGraph
@EntityGraph(attributePaths = {"roles"})
Optional<User> findById(Long id);

// ✅ 対処4: DTO Projection で必要列だけ取得
@Query("SELECT new com.app.UserDto(u.name, r.name) FROM User u JOIN u.roles r")
List<UserDto> findAllAsDto();

3. NonUniqueObjectException

org.hibernate.NonUniqueObjectException:
  A different object with the same identifier value was already
  associated with the session : [User#42]

原因: 同じ ID の Entity を同じ Session に 2 つ登録。save() で新しい instance を渡したが、既に Session 内で別 instance が管理されている。

// ❌ 別 instance を save
User u1 = userRepository.findById(42L).get();  // managed
User u2 = new User(42L, "renamed");
userRepository.save(u2);  // → NonUniqueObjectException

// ✅ merge() を使う
userRepository.save(u2);    // JPA save は merge と同等
// または明示的に
entityManager.merge(u2);

4. StaleObjectStateException (楽観ロック)

org.hibernate.StaleObjectStateException:
  Row was updated or deleted by another transaction
  (or unsaved-value mapping was incorrect) : [User#42]

原因: @Version 楽観ロックで他 tx が先に更新したのを検知。

@Entity
public class User {
    @Id Long id;
    String name;

    @Version
    Long version;   // ★ Hibernate が自動 +1 / UPDATE 時に WHERE で照合
}

// 対処: リトライ
@Retryable(value = ObjectOptimisticLockingFailureException.class, maxAttempts = 3)
public void update(Long id, String name) {
    User u = userRepository.findById(id).get();
    u.setName(name);
    userRepository.save(u);
}

// または UI で「他の人が更新しました。リロードしてください」を出す

5. ConstraintViolationException

org.hibernate.exception.ConstraintViolationException:
  could not execute statement
  ... Duplicate entry 'taro@example.com' for key 'user.uk_email'

原因: DB の UNIQUE / NOT NULL / FOREIGN KEY 制約違反。Bean Validation で同じ名前のクラスもあるので注意。

try {
    userRepository.save(new User(email, name));
} catch (DataIntegrityViolationException e) {
    // メールアドレス重複として 422 を返す
    throw new EmailAlreadyUsedException(email);
}

6. N+1 問題(エラーではないが致命的遅延)

// ❌ N+1: User 1 件 + Roles N 件で N+1 クエリ
List<User> users = userRepository.findAll();   // SELECT * FROM user
for (User u : users) {
    System.out.println(u.getRoles().size());   // SELECT * FROM role WHERE user_id=? を N 回
}

// ✅ JOIN FETCH で 1 クエリ
@Query("SELECT u FROM User u JOIN FETCH u.roles")
List<User> findAllWithRoles();

// ✅ EntityGraph
@EntityGraph(attributePaths = {"roles"})
List<User> findAll();

// ✅ BatchSize で SELECT IN
@OneToMany
@BatchSize(size = 100)
private List<Role> roles;

デバッグ Tips

# application.properties
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
logging.level.org.springframework.transaction=DEBUG

# 統計: 何クエリ発行したか
spring.jpa.properties.hibernate.generate_statistics=true

FAQ

Q: @Transactional を付けると LazyInit が出ない理由
A: @Transactional の範囲では Session が生存するため。OSIV (Open Session In View) パターンも同じ仕組みだが、controllers でクエリが発火するのでアンチパターン扱い。

Q: save したのに DB に反映されない
A: tx をコミットしていない / flush していない / catch でロールバック中。@Transactional の境界とログを確認。

Q: Hibernate 6 と 5 で挙動が違うエラー
A: Hibernate 6 は Jakarta EE に移行しjavax.persistencejakarta.persistence。アノテーションの import エラーが大量に出る。

編集
Post Share
子ページ
  1. ids for this class must be manually assigned before calling save()
  2. Number of positional parameter types (1 does not match number of positional parameters (2)
  3. net.sf.hibernate.MappingException: No persister for ~
  4. net.sf.hibernate.QueryException: unexpected token: as [~]
  5. net.sf.hibernate.MappingException: Error reading resource
  6. IllegalArgumentException occurred while calling setter of
同階層のページ

同階層のページはありません

最近更新/作成されたページ