1、項目背景概述
事情是這樣子的,使用了spring data jpa的項目jeesite
jeesite的實體中使用了懶加載模式。
並且一個實體類中還不止一個屬性設置了懶加載模式。
項目本身已經存在登錄頁面,但是我的目的是把此項目當成中間層來給一個.net項目提供服務,不需要一個有頁面的登錄接口。所以現在我需要重新寫個servlet處理登錄請求。
如下
如果用戶已經登錄,用如下方式處理:
如果用戶未登錄,則進行登錄驗證:
2、錯誤描述
然后在vs上寫了個winform版的測試程序用來發送登錄請求,包括用戶名密碼。注意不是瀏覽器版本。
然后我啟動vs上的測試程序,發送請求,第一次請求,很正常的返回了數據,第二次請求(此時已經登錄了)報錯。
錯誤提示如下:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
3、原因分析
百度上一搜,說是因為實體上設置了懶加載模式,調試一下是在out.write(gson.toJson(loginInfo));處報錯的,具體原因是獲取與user實例關聯的area實體設置了懶加載的屬性時出錯。
fetch=FetchType.LAZY情況下,jpa查詢時盡可能創建一個Entity的 Proxy(僅含ID),而不是一個Instance(包含狀態數據)。在一個Session中,如果你訪問未初始化的 Proxy 時,jpa 會先進行Initialization(加載數據)。如果Session關閉了,這個時候再去獲取Entity的實例,jpa就會拋出一個異常 org.hibernate.LazyInitializationException。
下面分析一下登錄,在處理登錄時,獲取到的user實際上只是一個代理,而不是一個實例。設置了懶加載的屬性實際上都只是返回了一個獲取數據的方法,而不是實際的數據。而這個方法只有在session存在的時候才能獲取到數據。而在二次登錄時,seesion已經不存在,此時再獲取數據就會出現上面所說的異常。
4、解決方案
4.1方法一
根據網上的答案,把fetch=FetchType.LAZY改成fetch=FetchType.EAGER即可,但是area實體中有兩個地方設置了懶加載,若都改成FetchType.EAGER,則又會出現另外一個錯誤:org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
百度一下,是由於當(fetch = FetchType.EAGER)多余一個時,持久框架抓取一方的對象時,同時又將多方的對象加載進容器中,多方又可能關聯其它對象,Hibernate實現的JPA,默認最高抓取深度含本身級為四級(它有個屬性配置是0-3),若多方(第二級)存在重復值,則第三級中抓取的值就無法映射,就會出現 multiple bags。
4.2方法二
於是又找到另外一種方法,就是在配置文件web.xml中加入如下配置:
但是查看配置文件,發現已有這樣的配置,查原因發現此種配置只適用於web項目,而我的測試程序是winform.
4.3方法三
於是再查,網上說,只要調用一下實體中除getid()之外的屬性方法,就會獲取得到一個實體的實例,於是在登錄驗證時,額外的調用了一下user的getOffice()屬性方法,但是結果並不如人意,二次登錄時還是報一樣的錯誤。這個我也不知道為什么。
4.4方法四
百般無奈之下,我修改了當用戶已經登錄時的處理代碼,直接通過SystemService中的UserDao類獲取user實例,但是執行到out.write(gson.toJson(loginInfo));時還是報錯。
調試至已登錄條件下的user,發現它所有的屬性都是有值的。百思不得其解,明明有值,為什么寫入到測試程序時為什么又會報錯呢?
查看user實體類,發現它有兩個構造函數,一個是無參的,一個是帶ID參數的構造函數。於是從原來的user中取得ID,再使用帶ID參數的構造函數重新實例化一個user。試圖把原來user的值都賦給另外一個新建的user,然后再輸出。即改成如下代碼:
奇跡發生了,竟然可以正常的輸出信息。
原因分析:
待查