摘要:引言再回顧一下問題場景教師班級學生在單元測試中跑這段代碼,是報錯的,,說明執(zhí)行完之后,就已經關閉了。如果是在單元測試中,就去想想是不是事務配錯了,導致掛掉了。茲可謂一勞而久逸。班固封燕然山銘一勞永逸,這是程序員最快樂的時候。
引言
再回顧一下問題場景:
Iterableteachers = teacherRepository.findAll(); for (Teacher teacher : teachers) { logger.debug("教師: " + teacher.getName()); for (Klass klass : teacher.getKlasses()) { logger.debug("班級: " + klass.getName()); for (Student student : klass.getStudents()) { logger.debug("學生: " + student.getName()); } } }
在單元測試中跑這段代碼,是報錯的,no Session,說明執(zhí)行完teacherRepository.findAll()之后,session就已經關閉了。繼續(xù)執(zhí)行,session已經關閉,再去數據庫查教師關聯的班級信息,就錯了。
然而呢?把這段代碼再放到Service里,寫一個接口,交給瀏覽器去調用,卻正常執(zhí)行,說明session還在。
然后就一直研究為什么不好使?如果能把這個原因分析明白,以后再遇到no session錯誤的時候就可以一勞永逸了。
探究 調試調試最簡單的方法就是中斷,但是咱水平還不行,也不知道JPA內部去找Hibernate怎么調用的,中斷哪個方法呢?
后臺發(fā)現了另一種調試的方法,JPA的源碼中也是像我們開發(fā)時經常寫日志的,logger.debug()什么的。
slf4j中常用的日志級別就ERROR、WARN、INFO、DEBUG四種,我們可以將JPA的日志級別設置為DEBUG級別,這樣我們就可以根據日志推測到JPA內部到底是怎么執(zhí)行的了。
修改日志級別logging.level.org.springframework.orm.jpa=debug
修改配置文件,將JPA的日志級別設置為DEBUG。
在單元測試中執(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 )]
上來是先執(zhí)行了一個事務,為什么會有事務呢?
JPA創(chuàng)建的倉庫實現是SimpleJpaRepository,我們看看源碼:
實現類上添加了事務注解,并采用了默認的REQUIRED傳播級別。
如果當前存在事務,則使用當前事務。如果不存在任何事務,則創(chuàng)建一個新的事務。
當前不存在事務,所以是teacherRepository.findAll()方法自己創(chuàng)建的事務。
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í)行了數據庫的查詢操作,提交了一個事務,然后Closing JPA EntityManager [SessionImpl(1334204880
事務執(zhí)行之后,就關閉了EntityManager,也就是Hibernate中的Session。
Session is a hibernate-specific API, EntityManager is a standardized API for JPA.
EntityManager和Session還是有一些差別的,但是我們目前還未接觸到底層的實現,只需要把他們當成一個東西,只不過在不同領域叫法不同罷了。
在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 : 班級: 軟件工程 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 : 學生: Hello Kitty 2019-06-06 11:58:28.917 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl : 學生: 史努比 2019-06-06 11:58:28.917 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl : 班級: 網絡工程 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 : 學生: 米老鼠 2019-06-06 11:58:28.919 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl : 學生: 唐老鴨 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 : 班級: 計算機科學與技術 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 : 學生: 哪吒 2019-06-06 11:58:28.923 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl : 學生: 小竹熊 2019-06-06 11:58:28.923 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl : 班級: 物聯網 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 : 學生: 喜羊羊 2019-06-06 11:58:28.925 DEBUG 11443 --- [nio-8080-exec-1] c.y.jpa.service.TeacherServiceImpl : 學生: 灰太狼 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 )]
這段沒什么說的,和上面一樣,創(chuàng)建事務,執(zhí)行完提交事務。
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=? 班級: 軟件工程 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=? 學生: Hello Kitty 學生: 史努比 班級: 網絡工程 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=? 學生: 米老鼠 學生: 唐老鴨 教師: 李四 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=? 班級: 計算機科學與技術 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=? 學生: 哪吒 學生: 小竹熊 班級: 物聯網 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=? 學生: 喜羊羊 學生: 灰太狼 Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
這塊就有意思了。
第一行:Not closing pre-bound JPA EntityManager after transaction
最后一行:Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
在事務之后沒有關閉Session,一直到最后,才將Session關閉,所以沒出錯。
而在單元測試中呢?Closing JPA EntityManager [SessionImpl(1334204880
找著關鍵了,Service里好使是因為Session在findAll的事務執(zhí)行完之后沒有關閉。
之前怎么沒發(fā)現呢?StackOverflow上這老哥和我是一樣的問題:Hibernate jpa entity manager not being closed in spring service layer - StackOverflow
User和Address一對多。
我寫了一個getUser方法,惰性加載的一對多,但是為什么序列化的時候去找addresses的時候,它不報lazy initialization的錯誤呢?
這是回答,大家注意一下我圈起來的幾個關鍵詞:
OpenEntityManagerInViewInterceptor:在Spring Boot項目中,Session是歸OpenEntityManagerInViewInterceptor管理的,這個是干什么的呢?
它是確保EntityManager(Session)一直保持開啟的狀態(tài),直到請求結束之后(complete request)。所以session在本次請求中,一直open著,惰性加載的數據隨便查。
如果你不想這么干,你可以配置spring.jpa.open-in-view=false來禁用此行為。
作將spring.jpa.open-in-view配置為false作一把。
果然,session關閉了,報錯位置在TeacherServiceImpl第28行,就是查詢惰性加載的klasses出錯了。
總結從上周想到這個問題開始,到今天解決,也是花了許久的時間。
所以以后再遇到no session的問題,如果是在項目里的,就先去想想是不是complete request了,請求結束前,session一直有效。
如果是在單元測試中,就去想想是不是事務配錯了,導致session掛掉了。
茲可謂一勞而久逸。暫費而永無寧者也。 ——班固《封燕然山銘》
一勞永逸,這是程序員最快樂的時候。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/18044.html
摘要:初次使用的人往往會困惑,不知道該使用哪種方法。目前來說,團隊推薦使用基于的方法來提供更高的靈活性。配置,從而在應用啟動時執(zhí)行腳本來初始化數據庫。目前為止我們沒有任何消息需要配置,所以只在文件夾中創(chuàng)建一個空的文件。將配置為,它包含的上下文。 前言 spring是一個用于創(chuàng)建web和企業(yè)應用的一個很流行的框架。和別的只關注于一點的框架不同,Spring框架通過投資并組合項目提供了大量的功能...
摘要:說明首先來說是一個持久化規(guī)范,也就是說當我們用的時候我們不需要去選面向的編程了,這樣就大大降低了偶和度了引入是一種規(guī)范,那么它的編程有哪些要求呢引入下載的包導入文件夾,然后我們的在下面加上一個目錄在該文件夾下面加上一個文件,這個文件的規(guī)范 說明 首先來說JPA是一個持久化規(guī)范,也就是說當我們用jpa的時候我們不需要去選面向hibernate的api編程了,這樣就大大降低了偶和度了 引入...
閱讀 5256·2021-09-22 15:50
閱讀 1861·2021-09-02 15:15
閱讀 1163·2019-08-29 12:49
閱讀 2542·2019-08-26 13:31
閱讀 3457·2019-08-26 12:09
閱讀 1209·2019-08-23 18:17
閱讀 2735·2019-08-23 17:56
閱讀 2928·2019-08-23 16:02