Java

HibernateのNonUniqueObjectExceptionの回避方法|A different object with the same identifier value was already associated with the session エラー

Hibernateで発生する A different object with the same identifier value was already associated with the session (NonUniqueObjectException)エラー。

このエラーは、Hibernate のセッション内で「同じ ID(主キー)」を持つ 2 つの異なるオブジェクトが存在している場合に発生します。

Hibernate は「1つのセッション内では、同じ ID のエンティティは1つだけ」しか管理できません。

つまり、同じ主キーを持つオブジェクトが複数存在すると、どちらを保存すべきかわからなくなり、例外を投げて処理を止めます。

springを古いバージョンからバージョンアップさせた際にも発生する可能性があるので注意が必要です。

よくある発生例

例えば以下のようなケースです。

// ID=1 のProductをセッションから取得
Product productA = session.get(Product.class, 1L);
Product productB = new Product();
// 別インスタンスとしてID=1をセット
productB.setId(1L);
// ここでエラー!
session.saveOrUpdate(productB);

このように、同じIDを持つ別インスタンス(productA と productB)をセッションに関連付けようとすると、Hibernateは混乱します。

原因まとめ

  1. 同じセッション内で、同じ主キーを持つエンティティを複数生成・関連付けてしまっている
  2. DB上では同じ行を指していても、Java上では異なるオブジェクトインスタンスになっている
  3. Hibernate セッションは、一意のIDに対して一意のオブジェクトしか扱えないという制約がある

解決策

このエラーは、Hibernateセッションが「同じIDを持つ別インスタンス」を2つ以上同時に管理しようとしたときに起こるものでした。

では、具体的にどう対処すれば良いのでしょうか?代表的な3つの方法を紹介します。

1.すでにセッションにあるオブジェクトを再利用する

最もシンプルなのは、既にセッションに紐づいているオブジェクトを再利用することです。

// 既に存在するオブジェクトを取得
Product product = session.get(Product.class, 1L);
product.setName("新しい名前");
// 問題なく動作
session.saveOrUpdate(product);

新しいインスタンスを手動で作るのではなく、session.get()やsession.load()でHibernateに管理させたインスタンスを使用すれば、セッションの整合性は維持されます。

「どうせ同じIDだから大丈夫だろう」と思って new するとハマるので、取得して使い回す方法もあります。

2.セッションから一度切り離す

もし既にセッションに登録されているオブジェクト(productAなど)を使わずに、新しいインスタンス(productB)を保存したい場合は、先に古い方をセッションから切り離す(detach)という手もあります。

// セッションから切り離す
session.evict(productA);
// 新しいインスタンスを登録
session.saveOrUpdate(productB);

これにより、HibernateはproductAの管理をやめ、新しく渡されたproductBをセッションに関連付けます。

この方法は「今後productAはもう使わない」という前提で行うべきです。中途半端に両方を扱うと、データの整合性が取れなくなる恐れがあります。

3.merge() を使って Hibernate にマージさせる

Hibernateの merge() メソッドを使うと、新しく渡されたオブジェクトの状態を、セッションが持つ既存のオブジェクトと自動的に統合してくれます。

session.merge(productB);

//注意点:merge() は 新しいインスタンスを返す ので、それを使い回さないと変更が反映されません。
Product managedProduct = session.merge(productB);

merge()は、セッションがすでに管理しているエンティティがあればそこに統合し、なければ新たに管理を開始します。

選び方

方法 特徴 向いているケース
再利用する 安全で明快 既に取得済みなら
切り離す 明示的に上書きしたい時に便利 強制的に置き換えるとき
merge() を使う 柔軟かつスマートに統合できる どちらが存在するか不明なとき

補足

よくあるのは、別のエンティティ(例:B)に、同じ主キーを持つ別のオブジェクト(例:C)を設定しているケースです。

// Cの代わりに別インスタンスを使っている
b.setCategory(new Category(1L));

このような場合も、Cがすでにセッションに存在していればエラーになります。

→ 同じエンティティは同じインスタンスで参照するように心がけることが、Hibernateを正しく使う上で重要です。

まとめ

このエラーは、Hibernateのセッション管理に対する理解がないと、つまずきやすい落とし穴ですが、一度原理を知ってしまえば、

  • 同じIDを持つエンティティはセッションに1つだけ

  • 必要ならevictかmergeを使う

というルールを意識するだけで、安定したデータ操作が可能になります。

ポイント

  1. このエラーは「同じIDを持つ異なるオブジェクトがセッションに関連付けられている」ときに発生
  2. Hibernateセッションは「ID=1」になるよう1つのオブジェクトしか許容しない
  3. 原因は、同じデータの別インスタンスを作ってしまったことが多い
  4. 解決には、get()で取得したオブジェクトの再利用、evict()での切り離し、またはmerge()の使用が効果的

今日の一杯 赤ワイン

2025/7/7

フェウド・アランチョ ネロ・ダーヴォラ レビュー|今日の一杯 赤ワイン

「フェウド・アランチョ ネロ・ダーヴォラ」は、味わい深く、コスパの良い、安定した品質の一本です。 トマト系の煮込み料理や、チーズをたっぷりのせたラザニアとも合い、もちろん、静かな夜にひとりでグラスを傾ける時間にもぴったり。 海外の専門家たちからも高評価を得ており、日常使いからしっかりした食事まで汎用的に活躍します。 味と風味 口に含むと、熟したダークベリーやラズベリーの果実味が広がりながら、後半にかけてほんのりドライハーブのような余韻が続きます。 酸味は控えめで、タンニンはしっかりしています。 飲みごたえ ...

続きを読む

エナジードリンク 今日の一杯

2025/6/30

ZONe ENERGY ABSOLUTE VICTORY 勝 レビュー|今日の一杯 エナジードリンク

ZONe ABSOLUTE VICTORYは2025年1月に発売されたエナジードリンクです。 受験勉強をイメージしたデザインの商品で、学問の神様として知られる菅原道真公を祀る太宰府天満宮に「HYPER ZONe 」を奉納し、受験生の合格を祈願しているそうです。 寒い冬に体を温めるように、成分にジンジャーシロップが配合されており、堅調に生姜の味がします。 カナダドライのジンジャーエールとはまた違う、比較的マイルドな飲みやすい生姜ジュースといった印象です。 ZONeの1本あたりのカフェイン量は75mgとコーヒ ...

続きを読む

  • この記事を書いた人

朝倉卍丸

シングルモルトスコッチなどのお土産を持ってきた人を助けるのが好きです。サービスの分割が重要ですが、まあ昔ながらの方法でやりたいこともありますよね。

よく読まれている記事

条件の0=0は全てが正であるを意味するSQL 1

SQLの条件に0=0のような記述を見かけます。 変わった書き方の条件ですが、これは「全てが正である」事を意味しており、結合条件の場合はCROSS JOINと同じです。 下記の例で言えば、結合するsub ...

DISTINCTを使わないで重複排除を考えるSQL 2

SQLのDISTINCTはEXISTSとかGROUP BYでなんとかする事もできます。 DISTINCTは暗黙的なソートがされますが、何のDBを使うにせよ過去のバージョンならともかく、最近のバージョン ...

RFC 5322に準拠させた正規表現言語別 3

RFC5322で定義されている正規表現を、各言語の正規表現に変化させた形になります。 完全な電子メール正規表現は存在しないので、結局のところ何かの公式基準に従っていたとしても、自分が携わるサービスのル ...

-Java