JPA 查詢問題探究

引言

最近看到了實體圖相關的博客,國外老哥說JPA的實體間關聯會執行屢次查詢,當數據量大時性能很是差。java

正好實體圖在以前寫華軟的時候就學習過,只是沒學明白,沒在項目中實際應用,正好藉此機會學習一下。sql

clipboard.png

實踐出真知,建一個jpa的項目,實際測試一下jpa究竟是怎麼查詢的。數據庫

今日才發現,Spring Boot都更新到了2.1.5api

clipboard.png

探究

實體關係

最簡單的教務系統模型,教師、班級、學生。session

clipboard.png

基礎數據

兩個教師,一個教師帶倆班,一個班裏倆學生。app

clipboard.png

clipboard.png

clipboard.png

單表查詢

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_

簡單的單表查詢,查出了基礎字段idname單元測試

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中的SessionJDBCConnection區別,能夠參考這個老哥的回答:What is the difference between a Session and a Connection in Hibernate? - StackOverflow

clipboard.png

由於OneToMany默認是惰性加載,用到的時候再去查詢。

這裏報錯了,說明在單元測試中session的做用域只有一行。findAll以後session就關了。

clipboard.png

解決方案

兩種解決方案,但都不是最佳實踐。

加事務

加事務確實能解決session關閉的問題。由於這裏是單元測試,也確實須要事務,因此在單元測試中的惰性加載引起的問題,咱們採用加事務的方式實現。

clipboard.png

沒有查到緣由,可是這裏猜測了一下:

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<>();

clipboard.png

這樣能夠實現,可是這是最差的一種解決方案。

由於是在實體的註解上加的,因此不一樣的方法用,無論用沒用到關聯的實體,一次性全都查出來。

咱們知道一對多多對多查詢是是須要編輯整個數據表的,因此很差查,改爲EAGER後會有嚴重的性能問題。

Hibernate註解設計

一塊兒去看看Hibernate對註解的設計:

多對多註解,默認LAZY

clipboard.png

一對多註解,默認LAZY

clipboard.png

多對一註解,默認EAGER

clipboard.png

一對一註解,默認EAGER

clipboard.png

在數據庫領域,Hibernate確定是大牛,既然大牛這麼設計,咱們天然應該也遵循。

同時應該也明白,在性能方面:一對一多對一查詢性能好,多對一多對多查詢性能就沒那麼好了。

運行環境

把一樣的代碼放到Service裏,而後再作成api接口。

clipboard.png

而後就正常的運行,用到的時候再去查詢數據庫,沒有發生no session的錯誤,因此這裏猜測,在Service中的查詢方法,其session的做用域確定要比測試中的要廣。

clipboard.png

問題復現

我在華軟中就遇到了no session的問題,怎麼出現的呢?

clipboard.png

loadUserByUsername方法中根據usernameuser,而後把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呢?

相關文章
相關標籤/搜索