(注:このブログはもう更新していません)この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

[クラウド帳票エンジンDocurain]

続・@ManyToManyでConcurrentModificationExceptionが出る件


昔悩んでいたこのエラー。やっと原因がわかりました。


例えば以下のようなエンティティがあったとします。

@Entity
public class Shop {

    @ManyToMany // fetchを指定しない場合、デフォルトはLAZYであることに注意
    private Set<Product> products;

    ...
}

@Entity
public class Product {
    ...
}

見ての通り、ShopとProductはN:Nの関連です。


このとき、以下のようなコードを書くと上記のエラーが発生するようです。

// 新しくセットするShop.productsの値を作成
Set<Product> newProducts = new HashSet();
newProducts.add(...);
newProducts.add(...);


EntityManager em = ...;

Shop shop = em.createTypedQuery("select s from Shop s where ...", Shop.class).getSingleResult();

shop.setProducts(newProducts);

em.merge(shop); 

em.flush();// ←ココでConcurrentModificationExceptionが発生!!


で、コレを解決する秘訣は「遅延ロード対象かつ更新したいフィールドをロードしてしまう」こと。つまり、

Set<Product> newProducts = new HashSet();
newProducts.add(...);
newProducts.add(...);


EntityManager em = ...;

Shop shop = em.createTypedQuery("select s from Shop s where ...", Shop.class).getSingleResult();

shop.getProducts().clear(); // ←この行に注目!!

shop.setProducts(newProducts);

として、強制的にProductをロードしてしまいます。
(ここではclearメソッドを使って強制ロード。isEmptyとかでも行けるかも)


パフォーマンス的には確かに無駄な処理なのですが、ひとまずはこれで動くようになります。