亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

懶加載異常

寫(xiě)切面代碼測(cè)試的時(shí)候發(fā)生了一個(gè)異常: LazyInitializationException

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

錯(cuò)誤信息如下

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

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

我們知道,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的屬性時(shí),加載策略默認(rèn)都是 EAGER, 即立即加載, 如@OneToOne, @ManyToOne。

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

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

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

我們把錯(cuò)誤信息來(lái)詳細(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)的,我們會(huì)想到數(shù)據(jù)庫(kù)中的事務(wù)。

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

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

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

如果這時(shí)候再去訪問(wèn)代理對(duì)象除了id以外的屬性時(shí),就會(huì)報(bào)上述的懶加載異常,原因是這時(shí)候已經(jīng)沒(méi)有session了,無(wú)法初始化懶加載的代理對(duì)象。

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

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

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

測(cè)試:

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

1.測(cè)試切面 + 強(qiáng)制 Object 轉(zhuǎn) List 是否會(huì)報(bào)錯(cuò)

@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 來(lái)接收被切函數(shù)的返回值,并強(qiáng)制轉(zhuǎn)換成(List<Training>).

debug 可以看到,即使從Object轉(zhuǎn)換過(guò)來(lái),但是運(yùn)行時(shí)類型并不會(huì)丟失

結(jié)果:不報(bào)錯(cuò), 說(shuō)明不是切面和類型的問(wèn)題。

同樣,測(cè)試了轉(zhuǎn)為L(zhǎng)ist<?> 也不會(huì)丟失,因?yàn)檫\(yùn)行時(shí)類型不變.

2.測(cè)試@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é)果: 報(bào)錯(cuò)

雖然不是一模一樣的報(bào)錯(cuò),但是足以說(shuō)明問(wèn)題

這時(shí)候,我才想起來(lái) @Async會(huì)啟用新的線程

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

根據(jù)錯(cuò)誤原因來(lái)解決:

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

@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ù)庫(kù),建立會(huì)話

@AfterReturning(value = "@annotation(TestAnnotation)", returning = "returnValue")
public void testAspect(TestAnnotation TestAnnotation, Object returnValue) {
// 重新調(diào)用數(shù)據(jù)庫(kù)查詢方法
 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;

猜測(cè)可能是因?yàn)樵搶?duì)象的代理對(duì)象屬于上一個(gè)會(huì)話,即使創(chuàng)建新的事務(wù)也不能重新查庫(kù)。

源碼分析

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

在組裝pojo時(shí), 會(huì)為懶加載對(duì)象創(chuàng)建對(duì)應(yīng)的代理對(duì)象 ,當(dāng)需要獲取該代理對(duì)象除id以外的屬性時(shí),就會(huì)調(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);
        }
    }

如果這時(shí),session 為null的話,會(huì)拋出 LazyInitializationException。

我們可以看到它有一個(gè)例外,那就是 allowLoadOutsideTransaction 為 true 時(shí)。

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

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

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

倉(cāng)庫(kù)層刪除異常

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

沒(méi)有實(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懶加載異常的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

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

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

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

    Java去重排序之Comparable與Comparator的使用及說(shuō)明

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新評(píng)論