欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Hibernate中的懶加載異常及解決方法

 更新時間:2023年10月15日 14:34:49   作者:weiweiyi  
這篇文章主要為大家介紹了深入理解Hibernate中的懶加載異常及解決方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>

懶加載異常

寫切面代碼測試的時候發(fā)生了一個異常: LazyInitializationException

@AfterReturning(value = "@annotation(sendWebhookNotification)", returning = "returnValue")
@Async
public void sendWebHookNotification(SendWebHookNotification sendWebhookNotification, Object returnValue) { }

錯誤信息如下

failed to lazily initialize a collection of role: could not initialize proxy - no Session

這個異常與 hibernate 加載關(guān)聯(lián)對象的2種方式有關(guān),一個是 懶加載,一個是 立即加載

我們知道,hibernate的實(shí)體關(guān)聯(lián)有幾種方式, @OneToOne, @OneToMany, @ManyToOne @ManyToMany

我們查看一下這些注解的屬性

@OneToOne

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface OneToOne {
   ...
    /** 
     * (Optional) Whether the association should be lazily 
     * loaded or must be eagerly fetched. The EAGER 
     * strategy is a requirement on the persistence provider runtime that 
     * the associated entity must be eagerly fetched. The LAZY 
     * strategy is a hint to the persistence provider runtime.
     */
    FetchType fetch() default EAGER;

@OneToMany

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)

public @interface OneToMany {
   ...
    FetchType fetch() default LAZY;

@ManyToOne

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)

public @interface ManyToOne {
    ...
    FetchType fetch() default EAGER;

@ManyToMany

@Target({METHOD, FIELD}) 
@Retention(RUNTIME)
public @interface ManyToMany {
    ...
    FetchType fetch() default LAZY;

可以發(fā)現(xiàn),需要加載數(shù)量為1的屬性時,加載策略默認(rèn)都是 EAGER, 即立即加載, 如@OneToOne, @ManyToOne。

但是如果需要加載數(shù)量為 n 時,加載策略默認(rèn)都是 LAZY, 即懶加載, 如@OneToMany, @ManyToMany。

原因也很容易想到,如果每一次查詢都加載n方的話,無疑會給數(shù)據(jù)庫帶來壓力。

那么,為什么會發(fā)生懶加載異常呢?

我們把錯誤信息來詳細(xì)看一下

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.xxx.xxx, could not initialize proxy - no Session

重點(diǎn)為后面的 no Session

看到session相關(guān)的,我們會想到數(shù)據(jù)庫中的事務(wù)。

先來看一下hibernate執(zhí)行流程:

當(dāng)我們從數(shù)據(jù)庫查詢時,一般會發(fā)生如下事情

  • hibernate 開啟一個 session(會話),
  • 然后開啟transaction(事務(wù)), 查詢默認(rèn)只讀事務(wù),修改操作需要讀寫事務(wù)
  • 接著發(fā)出sql找回數(shù)據(jù)并組裝成pojo(或者說entity、model)
  • 這時候如果pojo里有懶加載的對象,并不會去發(fā)出sql查詢db,而是直接返回一個懶加載的代理對象,這個對象里只有id。如果接下來沒有其他的操作去訪問這個代理對象除了id以外的屬性,就不會去初始化這個代理對象,也就不會去發(fā)出sql查找db
  • 事務(wù)提交,session 關(guān)閉

如果這時候再去訪問代理對象除了id以外的屬性時,就會報上述的懶加載異常,原因是這時候已經(jīng)沒有session了,無法初始化懶加載的代理對象。

所以為什么會出現(xiàn)no session呢?

是因?yàn)橛昧饲忻妫?還是因?yàn)槲覍ο筠D(zhuǎn)為了Object,或者其他原因?

模擬代碼環(huán)境: 因?yàn)槲矣昧饲忻?,注解,@Async等東西,控制變量測試一下是什么原因?qū)е碌膯栴}

測試:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestAnnotation {
}
@TestAnnotation
public List<Training> findAll() {
    return (List<Training>) this.trainingRepository.findAll();
}

1.測試切面 + 強(qiáng)制 Object 轉(zhuǎn) List 是否會報錯

@AfterReturning(value = "@annotation(TestAnnotation)", returning = "returnValue")
    public void testAspect(TestAnnotation TestAnnotation, Object returnValue) {
        List<Training> list = (List<Training>) returnValue;

        list.stream().forEach((v) -> {
            ((Training) v).getNotice().getTrainingResources();
        });
        list.stream().forEach((v) -> {
            ((Training) v).getNotice().getNoticeResources();
        });

我這里用了 Object 來接收被切函數(shù)的返回值,并強(qiáng)制轉(zhuǎn)換成(List<Training>).

debug 可以看到,即使從Object轉(zhuǎn)換過來,但是運(yùn)行時類型并不會丟失

結(jié)果:不報錯, 說明不是切面和類型的問題。

同樣,測試了轉(zhuǎn)為List<?> 也不會丟失,因?yàn)檫\(yùn)行時類型不變.

2.測試@Async

@AfterReturning(value = "@annotation(TestAnnotation)", returning = "returnValue")
@Async
public void testAspect(TestAnnotation TestAnnotation, Object returnValue) {

    List<?> list = (List<?>) returnValue;

    list.stream().forEach((v) -> {
        ((Training) v).getNotice().getTrainingResources();
    });
    list.stream().forEach((v) -> {
        ((Training) v).getNotice().getNoticeResources();
    });

結(jié)果: 報錯

雖然不是一模一樣的報錯,但是足以說明問題

這時候,我才想起來 @Async會啟用新的線程

而數(shù)據(jù)庫會話通常與線程相關(guān)聯(lián)。當(dāng)一個方法被標(biāo)記為異步并在不同的線程中執(zhí)行時,數(shù)據(jù)庫會話上下文可能不會正確傳播到新的線程。

根據(jù)錯誤原因來解決:

方法1: 在切面之前,就調(diào)用相關(guān)屬性的get方法,也就是說,在沒有進(jìn)入@Async方法之前,就進(jìn)行查庫

@TestAnnotation
public List<Training> findAll() {
    List<Training> list =  (List<Training>) this.trainingRepository.findAll();
    // 調(diào)用get函數(shù)
    list.stream().forEach((v) -> {
        v.getNotice().getTrainingResources();
    });
    
    return list;
}

方法2: 根據(jù)id, 重新查數(shù)據(jù)庫,建立會話

@AfterReturning(value = "@annotation(TestAnnotation)", returning = "returnValue")
public void testAspect(TestAnnotation TestAnnotation, Object returnValue) {
// 重新調(diào)用數(shù)據(jù)庫查詢方法
 List<Training> list = (List<Training>) this.trainingRepository.findAllById(((List<Training>)returnValue).stream().map(BaseEntity::getId).collect(Collectors.toList()));

失敗案例:使用:@Transactional(propagation = Propagation.REQUIRES_NEW) 創(chuàng)建新的事務(wù)。

@AfterReturning(value = "@annotation(TestAnnotation)", returning = "returnValue")
@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testAspect(TestAnnotation TestAnnotation, Object returnValue) {
    List<?> list = (List<?>) returnValue;

猜測可能是因?yàn)樵搶ο蟮拇韺ο髮儆谏弦粋€會話,即使創(chuàng)建新的事務(wù)也不能重新查庫。

源碼分析

可以從源碼的角度看 LazyInitializationException,是如何發(fā)生的。

在組裝pojo時, 會為懶加載對象創(chuàng)建對應(yīng)的代理對象 ,當(dāng)需要獲取該代理對象除id以外的屬性時,就會調(diào)用 AbstractLazyInitializer#initialize()進(jìn)行初始化

@Override
    public final void initialize() throws HibernateException {
        if ( !initialized ) {
            if ( allowLoadOutsideTransaction ) {
                permissiveInitialization();
            }
            else if ( session == null ) {
                throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" );
            }
            else if ( !session.isOpenOrWaitingForAutoClose() ) {
                throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session was closed" );
            }
            else if ( !session.isConnected() ) {
                throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session is disconnected" );
            }
            else {
                target = session.immediateLoad( entityName, id );
                initialized = true;
                checkTargetState(session);
            }
        }
        else {
            checkTargetState(session);
        }
    }

如果這時,session 為null的話,會拋出 LazyInitializationException。

我們可以看到它有一個例外,那就是 allowLoadOutsideTransaction 為 true 時。

這個變量值true,則可以進(jìn)入 permissiveInitialization() 方法另起session和事務(wù),最終避免懶加載異常。

而當(dāng)我們配置 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true時,

allowLoadOutsideTransaction 就為 true, 從而新建會話。 但是不推薦,這種全局設(shè)置應(yīng)該慎重配置。

倉庫層刪除異常

"No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call"

沒有實(shí)際有效的事務(wù)。

解決: delete方法都需要用@Transactional

public interface TrainingNoticeResourceRepository extends PagingAndSortingRepository<TrainingNoticeResource, Long>,
    JpaSpecificationExecutor<TrainingNoticeResource> {
    
    @Transactional()
    void deleteAllByTrainingNoticeId(Long id);
}

以上就是深入理解Hibernate中的懶加載異常及解決方法的詳細(xì)內(nèi)容,更多關(guān)于Hibernate懶加載異常的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于SpringAOP+Caffeine實(shí)現(xiàn)本地緩存的實(shí)例代碼

    基于SpringAOP+Caffeine實(shí)現(xiàn)本地緩存的實(shí)例代碼

    公司想對一些不經(jīng)常變動的數(shù)據(jù)做一些本地緩存,我們使用AOP+Caffeine來實(shí)現(xiàn),所以本文給大家介紹了
    基于SpringAOP+Caffeine實(shí)現(xiàn)本地緩存的實(shí)例,文中有詳細(xì)的代碼供大家參考,需要的朋友可以參考下
    2024-03-03
  • Java去重排序之Comparable與Comparator的使用及說明

    Java去重排序之Comparable與Comparator的使用及說明

    這篇文章主要介紹了Java去重排序之Comparable與Comparator的使用及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • 一次java異步任務(wù)的實(shí)戰(zhàn)記錄

    一次java異步任務(wù)的實(shí)戰(zhàn)記錄

    最近做項(xiàng)目的時候遇到了一個小問題,從前臺提交到服務(wù)端A,A調(diào)用服務(wù)端B處理超時,下面這篇文章主要給大家介紹了一次java異步任務(wù)的實(shí)戰(zhàn)記錄,需要的朋友可以參考下
    2022-05-05
  • java中并發(fā)Queue種類與各自API特點(diǎn)以及使用場景說明

    java中并發(fā)Queue種類與各自API特點(diǎn)以及使用場景說明

    這篇文章主要介紹了java中并發(fā)Queue種類與各自API特點(diǎn)以及使用場景說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • SpringBoot生成License的實(shí)現(xiàn)示例

    SpringBoot生成License的實(shí)現(xiàn)示例

    License指的是版權(quán)許可證,那么對于SpringBoot項(xiàng)目,如何增加License呢?本文就來介紹一下,感興趣的可以了解一下
    2021-06-06
  • Java service層獲取HttpServletRequest工具類的方法

    Java service層獲取HttpServletRequest工具類的方法

    今天小編就為大家分享一篇關(guān)于Java service層獲取HttpServletRequest工具類的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • JDK17安裝教程以及其環(huán)境變量配置教程

    JDK17安裝教程以及其環(huán)境變量配置教程

    環(huán)境變量對Java初學(xué)者來說真的是一件頭疼的事,本人也經(jīng)歷過這樣的事情,這篇文章主要給大家介紹了關(guān)于JDK17安裝教程以及其環(huán)境變量配置的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • 解決IntellIJ IDEA提示內(nèi)存不足的圖文教程

    解決IntellIJ IDEA提示內(nèi)存不足的圖文教程

    現(xiàn)在越來越多的人投入了 IntellIJ Idea 的懷抱, 它給我們的日常開發(fā)帶來了諸多便利,但是我們在開發(fā)過程中,總是能碰到idea內(nèi)存不足問題,所以本文給大家介紹了解決IntellIJ IDEA提示內(nèi)存不足的圖文教程,需要的朋友可以參考下
    2025-03-03
  • Eclipse項(xiàng)目有紅感嘆號的解決方法

    Eclipse項(xiàng)目有紅感嘆號的解決方法

    這篇文章主要為大家詳細(xì)介紹了Eclipse項(xiàng)目有紅感嘆號的解決方法,給出了Eclipse項(xiàng)目有紅感嘆號的原因,以及如何解決?,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • 拳皇(Java簡單的小程序)代碼實(shí)例

    拳皇(Java簡單的小程序)代碼實(shí)例

    這篇文章主要介紹了拳皇Java簡單小程序,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03

最新評論