最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2023年10月15日 14:34:49   作者:weiweiyi  
這篇文章主要為大家介紹了深入理解Hibernate中的懶加載異常及解決方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<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 加載關聯(lián)對象的2種方式有關,一個是 懶加載,一個是 立即加載

我們知道,hibernate的實體關聯(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的屬性時,加載策略默認都是 EAGER, 即立即加載, 如@OneToOne, @ManyToOne。

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

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

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

我們把錯誤信息來詳細看一下

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

重點為后面的 no Session

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

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

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

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

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

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

是因為用了切面, 還是因為我將對象轉(zhuǎn)為了Object,或者其他原因?

模擬代碼環(huán)境: 因為我用了切面,注解,@Async等東西,控制變量測試一下是什么原因?qū)е碌膯栴}

測試:

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

1.測試切面 + 強制 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ù)的返回值,并強制轉(zhuǎn)換成(List<Training>).

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

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

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

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ù)庫會話通常與線程相關聯(lián)。當一個方法被標記為異步并在不同的線程中執(zhí)行時,數(shù)據(jù)庫會話上下文可能不會正確傳播到新的線程。

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

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

@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)建新的事務。

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

猜測可能是因為該對象的代理對象屬于上一個會話,即使創(chuàng)建新的事務也不能重新查庫。

源碼分析

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

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

@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,則可以進入 permissiveInitialization() 方法另起session和事務,最終避免懶加載異常。

而當我們配置 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true時,

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

倉庫層刪除異常

"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"

沒有實際有效的事務。

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

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

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

相關文章

  • 如何解決redisTemplate注入為空問題

    如何解決redisTemplate注入為空問題

    這篇文章主要介紹了如何解決redisTemplate注入為空問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-07-07
  • Java設計模式中責任鏈模式詳解

    Java設計模式中責任鏈模式詳解

    責任鏈模式是將鏈中的每一個節(jié)點看做是一個對象,每個節(jié)點處理的請求均不相同,且內(nèi)部自動維護下一個節(jié)點對象,當一個請求從鏈式的首段發(fā)出時,會沿著鏈的路徑依次傳遞給每一個節(jié)點對象。本文將通過示例和大家詳細聊聊責任鏈模式,需要的可以參考一下
    2022-11-11
  • Java實現(xiàn)順序棧原理解析

    Java實現(xiàn)順序棧原理解析

    這篇文章主要介紹了Java實現(xiàn)順序棧原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • java?System類和Arrays類詳解

    java?System類和Arrays類詳解

    這篇文章主要介紹了java?System類和Arrays類詳解,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • SpringBoot 創(chuàng)建及登錄、攔截器的實現(xiàn)

    SpringBoot 創(chuàng)建及登錄、攔截器的實現(xiàn)

    本文主要介紹了Java開發(fā)中使用JDK 1.8和Spring Boot框架進行Web應用開發(fā)時,如何處理無狀態(tài)的HTTP協(xié)議以及如何實現(xiàn)用戶狀態(tài)的管理和維護,本文給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • IDEA自定義Maven倉庫的實現(xiàn)

    IDEA自定義Maven倉庫的實現(xiàn)

    使用Maven進行Java程序開發(fā)時,開發(fā)者能夠極大地提高開發(fā)效率,本文主要介紹了IDEA自定義Maven倉庫的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2024-03-03
  • Spring?Boot?整合?Fisco?Bcos部署、調(diào)用區(qū)塊鏈合約的案例

    Spring?Boot?整合?Fisco?Bcos部署、調(diào)用區(qū)塊鏈合約的案例

    本篇文章介紹?Spring?Boot?整合?Fisco?Bcos?的相關技術,最最重要的技術點,部署、調(diào)用區(qū)塊鏈合約的工程案例,本文通過流程分析給大家介紹的非常詳細,需要的朋友參考下吧
    2022-01-01
  • 帶你詳細了解Spring Security的注解方式開發(fā)

    帶你詳細了解Spring Security的注解方式開發(fā)

    這篇文章主要介紹了詳解spring security四種實現(xiàn)方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-08-08
  • 從log4j切換到logback后項目無法啟動的問題及解決方法

    從log4j切換到logback后項目無法啟動的問題及解決方法

    這篇文章主要介紹了從log4j切換到logback后項目無法啟動的問題及解決方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-01-01
  • java局部變量表的基礎知識點及實例

    java局部變量表的基礎知識點及實例

    在本篇文章里小編給大家整理的是一篇關于java局部變量表的基礎知識點及實例,有需要的朋友們可以學習參考下。
    2021-06-06

最新評論

隆昌县| 塔城市| 色达县| 新干县| 任丘市| 佛冈县| 平泉县| 连山| 军事| 竹溪县| 湘乡市| 安龙县| 卓尼县| 吴桥县| 和田市| 新巴尔虎右旗| 永兴县| 南昌县| 江华| 湟中县| 监利县| 连南| 盐亭县| 炉霍县| 青阳县| 武威市| 高雄市| 隆回县| 旌德县| 玛多县| 堆龙德庆县| 简阳市| 包头市| 宜昌市| 吉隆县| 金山区| 博白县| 平阳县| 秭归县| 彭水| 广东省|