最近看到了實體圖相關的博客,國外老哥說JPA
的實體間關聯會執行屢次查詢,當數據量大時性能很是差。java
正好實體圖在以前寫華軟的時候就學習過,只是沒學明白,沒在項目中實際應用,正好藉此機會學習一下。sql
實踐出真知,建一個jpa
的項目,實際測試一下jpa
究竟是怎麼查詢的。數據庫
今日才發現,Spring Boot
都更新到了2.1.5
。api
最簡單的教務系統模型,教師、班級、學生。session
兩個教師,一個教師帶倆班,一個班裏倆學生。app
Iterable<Teacher> teachers = teacherRepository.findAll(); for (Teacher teacher : teachers) { System.out.println(teacher.getName()); }
最簡單的查詢語句,findAll
以後獲取name
字段。性能
Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_
簡單的單表查詢,查出了基礎字段id
和name
。單元測試
OneToMany
多表關聯查詢Iterable<Teacher> teachers = teacherRepository.findAll(); for (Teacher teacher : teachers) { System.out.println(teacher.getName()); for (Klass klass : teacher.getKlasses()) { System.out.println(klass.getName()); for (Student student : klass.getStudents()) { System.out.println(student.getName()); } } }
看着挺普通的一段代碼,你們應該都寫過,其實裏面大有學問。學習
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.yunzhiclub.jpa.entity.Teacher.klasses, could not initialize proxy - no Session
測試環境運行時報錯,no session
。測試
在Hibernate
中,全部對數據庫的操做都須要經過session
來執行,你們能夠把它理解爲一個數據庫的代理對象,經過調用它的方法替代了對數據庫的直接操做。
關於Hibernate
中的Session
和JDBC
中Connection
區別,能夠參考這個老哥的回答:What is the difference between a Session and a Connection in Hibernate? - StackOverflow
由於OneToMany
默認是惰性加載,用到的時候再去查詢。
這裏報錯了,說明在單元測試中session
的做用域只有一行。findAll
以後session
就關了。
兩種解決方案,但都不是最佳實踐。
加事務
加事務確實能解決session
關閉的問題。由於這裏是單元測試,也確實須要事務,因此在單元測試中的惰性加載引起的問題,咱們採用加事務的方式實現。
沒有查到緣由,可是這裏猜測了一下:
Hibernate
說到底就是把原生的jdbc
方法進行了封裝,原理就是生成以下的代碼。
Session session = factory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); // do some work ... tx.commit(); } catch (Exception e) { if (tx != null) tx.rollback(); e.printStackTrace(); } finally { session.close(); }
能夠看到,爲了保持事務的正常運行,事務的提交和回滾都是在session
的有效範圍以內的,換句話說,session
是在事務完成以後才關閉的,在事務的管理下運行方法,天然能正常使用session
了。
EAGER
加載
EAGER
意爲急切的,就是一次都查出來了,這裏也不敢瞎翻譯,意會便可。
/** * 教師所管理的班級 */ @OneToMany(mappedBy = "teacher",fetch = FetchType.EAGER) private List<Klass> klasses = new ArrayList<>(); /** * 本班級中的學生 */ @OneToMany(mappedBy = "klass", fetch = FetchType.EAGER) private List<Student> students = new ArrayList<>();
這樣能夠實現,可是這是最差的一種解決方案。
由於是在實體的註解上加的,因此不一樣的方法用,無論用沒用到關聯的實體,一次性全都查出來。
咱們知道一對多
和多對多
查詢是是須要編輯整個數據表的,因此很差查,改爲EAGER
後會有嚴重的性能問題。
Hibernate
註解設計一塊兒去看看Hibernate
對註解的設計:
多對多註解,默認LAZY
:
一對多註解,默認LAZY
:
多對一註解,默認EAGER
:
一對一註解,默認EAGER
:
在數據庫領域,Hibernate
確定是大牛,既然大牛這麼設計,咱們天然應該也遵循。
同時應該也明白,在性能方面:一對一
、多對一
查詢性能好,多對一
、多對多
查詢性能就沒那麼好了。
把一樣的代碼放到Service
裏,而後再作成api
接口。
而後就正常的運行,用到的時候再去查詢數據庫,沒有發生no session
的錯誤,因此這裏猜測,在Service
中的查詢方法,其session
的做用域確定要比測試中的要廣。
我在華軟中就遇到了no session
的問題,怎麼出現的呢?
loadUserByUsername
方法中根據username
查user
,而後把user
傳給createUser
方法去處理,而後又把user
傳給了getAllAuthMenuByUser
方法獲取這個用戶的全部受權菜單。
而後就no session
了。
我當時想的就是方法之間相互傳對象而後session
不知怎麼就關了,而後就報錯了。由於我記得當時把createUser
裏的代碼都放進loadUserByUsername
裏就正常了,可是改以後太長,就沒有這麼改。最後在方法上加的事務。
而後我就開始嘗試,開始在Service
之間互相傳對象,惋惜,怎麼傳都好使。問題復現失敗,不知道是否是和Spring Security
有什麼關係?
網上有的文章說:多條語句的性能很差。根據教師帶出學生和班級,須要執行多條SQL
語句,能夠將其優化爲一條提升查詢效率。
我闡述一下本身的觀點:一對多慢,不是由於執行了多少條語句,而是數據庫去查一對多、多對多就慢。
即使可使用實體圖優化爲一條SQL
,可是數據庫該怎麼查一對多、多對多仍是怎麼查,只是看上去語句少了,我以爲性能其實也沒什麼提高。
實體圖其實就是配置了一下我這個方法要查出什麼,好比配置上我要教師的name
、班級的name
、學生的name
,而後就像咱們數據庫裏學的那樣。
select teacher.name, klass.name, student.name where xxxxx;
隨着Java
的蓬勃發展,Java
的性能雖然比不上C++
,但運行效率也是極高。再去看效率瓶頸,仍是數據庫。要不怎麼有的Redis
呢?