成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JPA Session 一勞永逸

tyheist / 2859人閱讀

摘要:引言再回顧一下問(wèn)題場(chǎng)景教師班級(jí)學(xué)生在單元測(cè)試中跑這段代碼,是報(bào)錯(cuò)的,,說(shuō)明執(zhí)行完之后,就已經(jīng)關(guān)閉了。如果是在單元測(cè)試中,就去想想是不是事務(wù)配錯(cuò)了,導(dǎo)致掛掉了。茲可謂一勞而久逸。班固封燕然山銘一勞永逸,這是程序員最快樂(lè)的時(shí)候。

引言

再回顧一下問(wèn)題場(chǎng)景:

Iterable teachers = teacherRepository.findAll();
for (Teacher teacher : teachers) {
    logger.debug("教師: " + teacher.getName());

    for (Klass klass : teacher.getKlasses()) {
        logger.debug("班級(jí): " + klass.getName());

        for (Student student : klass.getStudents()) {
            logger.debug("學(xué)生: " + student.getName());
        }
    }
}

在單元測(cè)試中跑這段代碼,是報(bào)錯(cuò)的,no Session,說(shuō)明執(zhí)行完teacherRepository.findAll()之后,session就已經(jīng)關(guān)閉了。繼續(xù)執(zhí)行,session已經(jīng)關(guān)閉,再去數(shù)據(jù)庫(kù)查教師關(guān)聯(lián)的班級(jí)信息,就錯(cuò)了。

然而呢?把這段代碼再放到Service里,寫(xiě)一個(gè)接口,交給瀏覽器去調(diào)用,卻正常執(zhí)行,說(shuō)明session還在。

然后就一直研究為什么不好使?如果能把這個(gè)原因分析明白,以后再遇到no session錯(cuò)誤的時(shí)候就可以一勞永逸了。

探究 調(diào)試

調(diào)試最簡(jiǎn)單的方法就是中斷,但是咱水平還不行,也不知道JPA內(nèi)部去找Hibernate怎么調(diào)用的,中斷哪個(gè)方法呢?

后臺(tái)發(fā)現(xiàn)了另一種調(diào)試的方法,JPA的源碼中也是像我們開(kāi)發(fā)時(shí)經(jīng)常寫(xiě)日志的,logger.debug()什么的。

slf4j中常用的日志級(jí)別就ERROR、WARN、INFO、DEBUG四種,我們可以將JPA的日志級(jí)別設(shè)置為DEBUG級(jí)別,這樣我們就可以根據(jù)日志推測(cè)到JPA內(nèi)部到底是怎么執(zhí)行的了。

修改日志級(jí)別
logging.level.org.springframework.orm.jpa=debug

修改配置文件,將JPA的日志級(jí)別設(shè)置為DEBUG。

在單元測(cè)試中執(zhí)行

完整日志:

2019-06-06 11:36:40.415 DEBUG 11391 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2019-06-06 11:36:40.416 DEBUG 11391 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1334204880)] for JPA transaction
2019-06-06 11:36:40.429 DEBUG 11391 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@73b74615]
2019-06-06 11:36:40.449  INFO 11391 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_
2019-06-06 11:36:40.598 DEBUG 11391 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2019-06-06 11:36:40.598 DEBUG 11391 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1334204880)]
2019-06-06 11:36:40.601 DEBUG 11391 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1334204880)] after transaction
2019-06-06 11:36:40.602 DEBUG 11391 --- [           main] com.yunzhiclub.jpa.JpaApplicationTests   : 教師: 張三

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.yunzhiclub.jpa.entity.Teacher.klasses, could not initialize proxy - no Session
分析
Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
Opened new EntityManager [SessionImpl(1334204880)] for JPA transaction
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@73b74615]
HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(1334204880)]

上來(lái)是先執(zhí)行了一個(gè)事務(wù),為什么會(huì)有事務(wù)呢?

JPA創(chuàng)建的倉(cāng)庫(kù)實(shí)現(xiàn)是SimpleJpaRepository,我們看看源碼:

實(shí)現(xiàn)類上添加了事務(wù)注解,并采用了默認(rèn)的REQUIRED傳播級(jí)別。

如果當(dāng)前存在事務(wù),則使用當(dāng)前事務(wù)。如果不存在任何事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。

當(dāng)前不存在事務(wù),所以是teacherRepository.findAll()方法自己創(chuàng)建的事務(wù)。

Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(1334204880)]
Closing JPA EntityManager [SessionImpl(1334204880)] after transaction
教師: 張三

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.yunzhiclub.jpa.entity.Teacher.klasses, could not initialize proxy - no Session

再接著看下面的日志,執(zhí)行了數(shù)據(jù)庫(kù)的查詢操作,提交了一個(gè)事務(wù),然后Closing JPA EntityManager [SessionImpl(1334204880)] after transaction。

事務(wù)執(zhí)行之后,就關(guān)閉了EntityManager,也就是Hibernate中的Session。

Session is a hibernate-specific API, EntityManager is a standardized API for JPA. 

EntityManagerSession還是有一些差別的,但是我們目前還未接觸到底層的實(shí)現(xiàn),只需要把他們當(dāng)成一個(gè)東西,只不過(guò)在不同領(lǐng)域叫法不同罷了。

SpringMVC中執(zhí)行

執(zhí)行得很順利,完整日志如下:

2019-06-06 11:58:28.788 DEBUG 11443 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
2019-06-06 11:58:28.800 DEBUG 11443 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(193939447)] for JPA transaction
2019-06-06 11:58:28.800 DEBUG 11443 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2019-06-06 11:58:28.808 DEBUG 11443 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@56eb5438]
2019-06-06 11:58:28.820  INFO 11443 --- [nio-8080-exec-1] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_
2019-06-06 11:58:28.897 DEBUG 11443 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2019-06-06 11:58:28.898 DEBUG 11443 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(193939447)]
2019-06-06 11:58:28.901 DEBUG 11443 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager        : Not closing pre-bound JPA EntityManager after transaction
2019-06-06 11:58:28.902 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 教師: 張三
Hibernate: select klasses0_.teacher_id as teacher_3_0_0_, klasses0_.id as id1_0_0_, klasses0_.id as id1_0_1_, klasses0_.name as name2_0_1_, klasses0_.teacher_id as teacher_3_0_1_ from klass klasses0_ where klasses0_.teacher_id=?
2019-06-06 11:58:28.915 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 班級(jí): 軟件工程
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
2019-06-06 11:58:28.917 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: Hello Kitty
2019-06-06 11:58:28.917 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 史努比
2019-06-06 11:58:28.917 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 班級(jí): 網(wǎng)絡(luò)工程
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
2019-06-06 11:58:28.919 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 米老鼠
2019-06-06 11:58:28.919 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 唐老鴨
2019-06-06 11:58:28.919 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 教師: 李四
Hibernate: select klasses0_.teacher_id as teacher_3_0_0_, klasses0_.id as id1_0_0_, klasses0_.id as id1_0_1_, klasses0_.name as name2_0_1_, klasses0_.teacher_id as teacher_3_0_1_ from klass klasses0_ where klasses0_.teacher_id=?
2019-06-06 11:58:28.921 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 班級(jí): 計(jì)算機(jī)科學(xué)與技術(shù)
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
2019-06-06 11:58:28.923 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 哪吒
2019-06-06 11:58:28.923 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 小竹熊
2019-06-06 11:58:28.923 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 班級(jí): 物聯(lián)網(wǎng)
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
2019-06-06 11:58:28.925 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 喜羊羊
2019-06-06 11:58:28.925 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl       : 學(xué)生: 灰太狼
2019-06-06 11:58:28.944 DEBUG 11443 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
分析
Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
Found thread-bound EntityManager [SessionImpl(193939447)] for JPA transaction
Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@56eb5438]
HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_
Initiating transaction commit
Committing JPA transaction on EntityManager [SessionImpl(193939447)]

這段沒(méi)什么說(shuō)的,和上面一樣,創(chuàng)建事務(wù),執(zhí)行完提交事務(wù)。

Not closing pre-bound JPA EntityManager after transaction
教師: 張三
Hibernate: select klasses0_.teacher_id as teacher_3_0_0_, klasses0_.id as id1_0_0_, klasses0_.id as id1_0_1_, klasses0_.name as name2_0_1_, klasses0_.teacher_id as teacher_3_0_1_ from klass klasses0_ where klasses0_.teacher_id=?
班級(jí): 軟件工程
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
學(xué)生: Hello Kitty
學(xué)生: 史努比
班級(jí): 網(wǎng)絡(luò)工程
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
學(xué)生: 米老鼠
學(xué)生: 唐老鴨
教師: 李四
Hibernate: select klasses0_.teacher_id as teacher_3_0_0_, klasses0_.id as id1_0_0_, klasses0_.id as id1_0_1_, klasses0_.name as name2_0_1_, klasses0_.teacher_id as teacher_3_0_1_ from klass klasses0_ where klasses0_.teacher_id=?
班級(jí): 計(jì)算機(jī)科學(xué)與技術(shù)
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
學(xué)生: 哪吒
學(xué)生: 小竹熊
班級(jí): 物聯(lián)網(wǎng)
Hibernate: select students0_.klass_id as klass_id4_1_0_, students0_.id as id1_1_0_, students0_.id as id1_1_1_, students0_.email as email2_1_1_, students0_.klass_id as klass_id4_1_1_, students0_.name as name3_1_1_ from student students0_ where students0_.klass_id=?
學(xué)生: 喜羊羊
學(xué)生: 灰太狼
Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

這塊就有意思了。

第一行:Not closing pre-bound JPA EntityManager after transaction

最后一行:Closing JPA EntityManager in OpenEntityManagerInViewInterceptor

在事務(wù)之后沒(méi)有關(guān)閉Session,一直到最后,才將Session關(guān)閉,所以沒(méi)出錯(cuò)。

而在單元測(cè)試中呢?Closing JPA EntityManager [SessionImpl(1334204880)] after transaction,事務(wù)執(zhí)行之后就關(guān)閉了Session,所以出錯(cuò)了。

原因

找著關(guān)鍵了,Service里好使是因?yàn)?b>Session在findAll的事務(wù)執(zhí)行完之后沒(méi)有關(guān)閉。

之前怎么沒(méi)發(fā)現(xiàn)呢?StackOverflow上這老哥和我是一樣的問(wèn)題:Hibernate jpa entity manager not being closed in spring service layer - StackOverflow

UserAddress一對(duì)多。

我寫(xiě)了一個(gè)getUser方法,惰性加載的一對(duì)多,但是為什么序列化的時(shí)候去找addresses的時(shí)候,它不報(bào)lazy initialization的錯(cuò)誤呢?

這是回答,大家注意一下我圈起來(lái)的幾個(gè)關(guān)鍵詞:

OpenEntityManagerInViewInterceptor:在Spring Boot項(xiàng)目中,Session是歸OpenEntityManagerInViewInterceptor管理的,這個(gè)是干什么的呢?

它是確保EntityManager(Session)一直保持開(kāi)啟的狀態(tài),直到請(qǐng)求結(jié)束之后(complete request)。所以session在本次請(qǐng)求中,一直open著,惰性加載的數(shù)據(jù)隨便查。

如果你不想這么干,你可以配置spring.jpa.open-in-view=false來(lái)禁用此行為。

spring.jpa.open-in-view配置為false作一把。

果然,session關(guān)閉了,報(bào)錯(cuò)位置在TeacherServiceImpl28行,就是查詢惰性加載的klasses出錯(cuò)了。

總結(jié)

從上周想到這個(gè)問(wèn)題開(kāi)始,到今天解決,也是花了許久的時(shí)間。

所以以后再遇到no session的問(wèn)題,如果是在項(xiàng)目里的,就先去想想是不是complete request了,請(qǐng)求結(jié)束前,session一直有效。

如果是在單元測(cè)試中,就去想想是不是事務(wù)配錯(cuò)了,導(dǎo)致session掛掉了。

茲可謂一勞而久逸。暫費(fèi)而永無(wú)寧者也。 ——班固《封燕然山銘》

一勞永逸,這是程序員最快樂(lè)的時(shí)候。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/18044.html

相關(guān)文章

  • 貓頭鷹的深夜翻譯:為什么要使用Spring Boot?

    摘要:初次使用的人往往會(huì)困惑,不知道該使用哪種方法。目前來(lái)說(shuō),團(tuán)隊(duì)推薦使用基于的方法來(lái)提供更高的靈活性。配置,從而在應(yīng)用啟動(dòng)時(shí)執(zhí)行腳本來(lái)初始化數(shù)據(jù)庫(kù)。目前為止我們沒(méi)有任何消息需要配置,所以只在文件夾中創(chuàng)建一個(gè)空的文件。將配置為,它包含的上下文。 前言 spring是一個(gè)用于創(chuàng)建web和企業(yè)應(yīng)用的一個(gè)很流行的框架。和別的只關(guān)注于一點(diǎn)的框架不同,Spring框架通過(guò)投資并組合項(xiàng)目提供了大量的功能...

    Jaden 評(píng)論0 收藏0
  • String Data JPA 學(xué)習(xí)筆記

    摘要:說(shuō)明首先來(lái)說(shuō)是一個(gè)持久化規(guī)范,也就是說(shuō)當(dāng)我們用的時(shí)候我們不需要去選面向的編程了,這樣就大大降低了偶和度了引入是一種規(guī)范,那么它的編程有哪些要求呢引入下載的包導(dǎo)入文件夾,然后我們的在下面加上一個(gè)目錄在該文件夾下面加上一個(gè)文件,這個(gè)文件的規(guī)范 說(shuō)明 首先來(lái)說(shuō)JPA是一個(gè)持久化規(guī)范,也就是說(shuō)當(dāng)我們用jpa的時(shí)候我們不需要去選面向hibernate的api編程了,這樣就大大降低了偶和度了 引入...

    Java_oldboy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

tyheist

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<