EntityManager無法remove entity的問題

來源:互聯網
上載者:User

標籤:jpa   entitymanager.remove()   delete entity   bidirectional relationship   

今天遇到一個奇怪的事情,利用EntityManager.remove(entity)方法刪除一個entity時,刪不掉,也不報錯。後來經過多方查證,解決了這個問題。


ERD

650) this.width=650;" src="http://s3.51cto.com/wyfs02/M00/7F/49/wKioL1cY4PXDaMjhAAA3p6ZllUk448.png" title="Image 1.png" alt="wKioL1cY4PXDaMjhAAA3p6ZllUk448.png" />


Entity定義

------------- 第一個Entity A ---------------@Entitypublic class A {    @Id    private Long id;        @Column(nullable = false, unique = true, length = 60)    private String internalKey;        @OneToMany(mappedBy = "b", cascade = CascadeType.ALL, orphanRemoval = true)    private List<B> bs = new ArrayList<>();    ...}------------- 第二個Entity B ---------------@Entitypublic class B {    @Id    private Long id;        @ManyToOne    @JoinColumn(name = "A_internalKey", referencedColumnName = "internalKey")    private A a;    ...}


資料

Table A:id       internalKey-------- -------------1        a1Table B:id       A_internalKey-------- -------------1        a12        a1


問題

按照多年SQL指令碼操作資料的經驗,直接從B表中刪除記錄b(id:2)是可行的。A表上不存在任何對B表的外鍵引用,所以可以直接刪除B表上的資料,資料庫管理系統不會不開心。但是,使用JPA中EntityManager的remove(entity)方法來刪除b(id:2)時,問題發生了。remove根本刪不掉b(id:2)記錄,別說資料庫中的記錄了,連SQL語句都沒有從JEE container中發出來。而且,更要命的是,沒!有!報!錯!

我做了一個替換方案,用JPQL語句直接刪除B(id:2),結果成功了。呵呵,到此可不算完結,不然我也不用大費周章的把這件事情記錄下來。在刪除B(id:2)之後,我又嘗試儲存對A所做的變更,這麼一儲存,又出問題了。JPA報錯,說是B(id:2)找不到,我暈。這又是什麼情況,B(id:2)明明已經被我刪掉了,怎麼在persist A的時候JPA卻要去檢查一個已經被刪掉的object?我確信在用JPQL刪掉了B(id:2)後,我手動從A.bs集合中剔除了B(id:2),為啥這個B(id:2)陰魂不散呢?


分析

在翻閱了一些文檔後,我隱約意識到,問題應該與entity的幾種狀態(尤其是detached狀態)以及O/R Mapping架構中的緩衝有關。說白了,就是程式哪裡產生資料不一致了。一般,之所以產生這種不一致問題可能與受管對象的狀態、生命週期或是存取範圍等有關。那麼,代入JPA中考慮,對應的應該是Entity的生命週期或訪問機制(緩衝機制)。

繼續深究發現,這個issue是由於多個方面綜合作用下產生的。

首先,問題的最關鍵之處:A與B的bidirectional OneToMany(雙向一對多關聯性)。

這其實很好理解,就像Java中的記憶體回收機制一樣,被用到的Object不會被GC。同理,被引用的child,也就是這個B(id:2)啦,一直被A(id:1)引用著呢,JPA怎麼會讓你把他幹掉?!。前面未曾提及,在刪除B(id:2)之前,A(id:1)被JPA讀取過。當我試圖刪除B(id:2)時A(id:1)應該還在JPA的緩衝裡待著。根據Entity上的annotation標註,A(id:1)應該同時保有B(id:1)、B(id:2)的引用(就是那個List<B> bs集合中的兩個元素)。JPA的remove出於某種保護,並不會讓你把被引用的B(id:2)刪掉。

當然,如果你執意要刪除,那麼可以用entityManager.createQuery("DELETE FROM B WHERE B.id=2").executeUpdate();來強行刪除指定的資料庫記錄。因為createQuery().executeUpdate()會向DBMS發送指定的sql,如果有報錯,異常會由DBMS通過底層JDBC報給JPA架構最終通過EntityManager冒出來。我就是用了這種方法強行把B(id:2)給幹掉了。

接下來要說的,就是因素二:緩衝與實際資料庫不一致

看上面的那段標紅的內容。是不是想到了什嗎?在刪除B(id:2)之前,A(id:1)帶著對B(id:1)和B(id:2)的引用一直待在緩衝裡。當B(id:2)被我用JPQL強行刪除之後,並沒有任何代碼去更新緩衝裡的A(id:1),所以A(id:1)上應該還有B(id:2)的引用。接下來,要persist A(id:1)的改動。雖然我後來手動做了A.bs.remove(B(id:2))(從bs集合中剔除了B(id:2)的引用),但很遺憾,A(id:1)已經處於detached狀態(即游離狀態,姑且把已經處於游離狀態的A(id:1)叫做a(id:1))。對一個已經處於游離狀態的object進行的改動,不會映射到對應的Entity上,換句話說,不論我怎樣操作a(id:1),在JPA緩衝中的A(id:1)不會被更新。而且,戲劇性的一幕發生了,當我嘗試著去persist一個游離對象a(id:1)時,JPA通過a(id:1).equals(A(id:1))的比較,認為a(id:1) == A(id:1),因為兩個對象的id一樣,hashcode一樣,所以JPA從緩衝中找到A(id:1),試圖再次persist一遍,接下來的事情也就不用我說了,JPA報錯,並提示我找不到B(id:2)。(什嗎?為什麼會去找B(id:2)?哦,那是因為A上的Cascade定義,)


解決方案

我這裡有兩種解決方案:

1、以更新A為起始點,剔除B(id:2)後,persist A。由於A上設定的Cascade,在更新A的同時,JPA會串聯刪除B(id:2)

2、可以強行刪除B(id:2),但在對A(id:1)進行任何操作前,先去fecth一下A(id:1),也就是強行重新整理一下JPA的緩衝。


我個人推薦第一種方案。


本文出自 “BitterJava” 部落格,請務必保留此出處http://rickqin.blog.51cto.com/1096449/1766492

EntityManager無法remove entity的問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.