この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:T
更新日時:2026-06-11 07:12:00

タイトル: エラー一覧 (Hibernate)
SEOタイトル: Hibernate よくあるエラー集 — LazyInitialization / NonUniqueObject / StaleObjectState

この記事の要点
  • 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, &quot;renamed&quot;);
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 エラーが大量に出る。