Hibernate 所有快取機制詳解
Hibernate 所有快取機制詳解
hibernate是一個執行緒對應一個session,一個執行緒可以看成一個使用者。也就是說session級快取(一級快取)只能給一個執行緒用,別的執行緒用不了,一級快取就是和執行緒繫結了。
hibernate一級快取生命週期很短,和session生命週期一樣,一級快取也稱session級的快取或事務級快取。如果tb事務提交或回滾了,我們稱session就關閉了,生命週期結束了。
快取和連線池的區別:快取和池都是放在記憶體裡,實現是一樣的,都是為了提高效能的。但有細微的差別,池是重量級的,裡面的資料是一樣的,比如一個池裡放100個Connection連線物件,這個100個都是一樣的。快取裡的資料,每個都不一樣。比如讀取100條資料庫記錄放到快取裡,這100條記錄都不一樣。
快取主要是用於查詢
//同一個session中,發出兩次load方法查詢 Student student = (Student)session.load(Student.class, 1); System.out.println("student.name=" + student.getName()); //不會發出查詢語句,load使用快取 student = (Student)session.load(Student.class, 1); System.out.println("student.name=" + student.getName()); |
第二次查詢第一次相同的資料,第二次load方法就是從快取裡取資料,不會發出sql語句到資料庫裡查詢。
//同一個session,發出兩次get方法查詢 Student student = (Student)session.get(Student.class, 1); System.out.println("student.name=" + student.getName()); //不會發出查詢語句,get使用快取 student = (Student)session.get(Student.class, 1); System.out.println("student.name=" + student.getName()); |
第二次查詢第一次相同的資料,第二次不會發出sql語句查詢資料庫,而是到快取裡取資料。
//同一個session,發出兩次iterate查詢實體物件 Iterator iter = session.createQuery ("from Student s where s.id<5").iterate(); while (iter.hasNext()) { Student student = (Student)iter.next(); System.out.println(student.getName()); } System.out.println("--------------------------------------"); //它會發出查詢id的語句,但不會發出根據id查詢學生的語句,因為iterate使用快取 iter = session.createQuery("from Student s where s.id<5").iterate(); while (iter.hasNext()) { Student student = (Student)iter.next(); System.out.println(student.getName()); } |
一說到iterater查詢就要立刻想起:iterater查詢在沒有快取的情況下會有N+1的問題。
執行上面程式碼檢視控制檯的sql語句,第一次iterate查詢會發出N+1條sql語句,第一條sql語句查詢所有的id,然後根據id查詢實體物件,有N個id就發出N條語句查詢實體。
第二次iterate查詢,卻只發一條sql語句,查詢所有的id,然後根據id到快取裡取實體物件,不再發sql語句到資料庫裡查詢了。
//同一個session,發出兩次iterate查詢,查詢普通屬性 Iterator iter = session.createQuery( "select s.name from Student s where s.id<5").iterate(); while (iter.hasNext()) { String name = (String)iter.next(); System.out.println(name); } System.out.println("--------------------------------------"); //iterate查詢普通屬性,一級快取不會快取,所以發出查詢語句 //一級快取是快取實體物件的 iter = session.createQuery ("select s.name from Student s where s.id<5").iterate(); while (iter.hasNext()) { String name = (String)iter.next(); System.out.println(name); } |
執行程式碼看控制檯sql語句,第一次發出N+1條sql語句,第二次還是發出了N+1條sql語句。因為一級快取只快取實體物件,tb不會快取普通屬性,所以第二次還是發出sql查詢語句。
//兩個session,每個session發出一個load方法查詢實體物件 try { session = HibernateUtils.getSession(); session.beginTransaction(); Student student = (Student)session.load(Student.class, 1); System.out.println("student.name=" + student.getName()); session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } 第二個session呼叫load方法 try { session = HibernateUtils.getSession(); session.beginTransaction(); Student student = (Student)session.load(Student.class, 1); //會發出查詢語句,session間不能共享一級快取資料 //因為他會伴隨著session的消亡而消亡 System.out.println("student.name=" + student.getName()); session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } |
第一個session的load方法會發出sql語句查詢實體物件,第二個session的load方法也會發出sql語句查詢實體物件。因為session間不能共享一級快取的資料,所以第二個session的load方法查詢相同的資料還是要到資料庫中查詢,因為它找不到第一個session裡快取的資料。
//同一個session,先呼叫save方法再呼叫load方法查詢剛剛save的資料 Student student = new Student(); student.setName("張三"); //save方法返回實體物件的id Serializable id = session.save(student); student = (Student)session.load(Student.class, id); //不會發出查詢語句,因為save支援快取 System.out.println("student.name=" + student.getName()); |
先save儲存實體物件,再用load方法查詢剛剛save的實體物件,則load方法不會發出sql語句到資料庫查詢的,而是到快取裡取資料,因為save方法也支援快取。當然前提是同一個session。
//大批量的資料新增 for (int i=0; i<100; i++) { Student student = new Student(); student.setName("張三" + i); session.save(student); //每20條更新一次 if (i % 20 == 0) { session.flush(); //清除快取的內容 session.clear(); } } |
大批量資料新增時,會造成記憶體溢位的,因為save方法支援快取,每save一個物件就往快取裡放,如果物件足夠多記憶體肯定要溢位。一般的做法是先判斷一下save了多少個物件,如果save了20個物件就對快取手動的清理快取,這樣就不會造成記憶體溢位。
注意:清理快取前,要手動呼叫flush方法同步到資料庫,否則save的物件就沒有儲存到資料庫裡。
注意:大批量資料的新增還是不要使用hibernate,這是hibernate弱項。可以使用jdbc(速度也不會太快,只是比hibernate好一點),或者使用工具產品來實現,比如oracle的Oracle SQL Loader,匯入資料特別快。
二級快取需要sessionFactory來管理,它是進初級的快取,所有人都可以使用,它是共享的。
二級快取比較複雜,一般用第三方產品。hibernate提供了一個簡單實現,用Hashtable做的,只能作為我們的測試使用,商用還是需要第三方產品。
使用快取,肯定是長時間不改變的資料,如果經常變化的資料放到快取裡就沒有太大意義了。因為經常變化,還是需要經常到資料庫裡查詢,那就沒有必要用快取了。
hibernate做了一些優化,和一些第三方的快取產品做了整合。老師採用EHCache快取產品。
和EHCache二級快取產品整合:EHCache的jar檔案在hibernate的lib裡,我們還需要設定一系列的快取使用策略,需要一個配置檔案ehcache.xml來配置。這個檔案放在類路徑下。
//預設配置,所有的類都遵循這個配置 <defaultCache //快取裡可以放10000個物件 maxElementsInMemory="10000" //過不過期,如果是true就是永遠不過期 eternal="false" //一個物件被訪問後多長時間還沒有訪問就失效(120秒還沒有再次訪問就失效) timeToIdleSeconds="120" //物件存活時間(120秒),如果設定永不過期,這個就沒有必要設了 timeToLiveSeconds="120" //溢位的問題,如果設成true,快取裡超過10000個物件就儲存到磁碟裡 overflowToDisk="true" /> |
我們也可以對某個物件單獨配置:
<cache name="com.bjpowernode.hibernate.Student" maxElementsInMemory="100" eternal="false" timeToIdleSeconds="10000" timeToLiveSeconds="10000" overflowToDisk="true" /> |
還需要在hibernate.cfg.xml配置檔案配置快取,讓hibernate知道我們使用的是那個二級快取。
<!-- 配置快取提供商 --> <property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider</property> <!-- 啟用二級快取,這也是它的預設配置 --> <property name="hibernate.cache.use_second_level_cache"> true</property> |
啟用二級快取的配置可以不寫的,因為預設就是true開啟二級快取。
必須還手動指定那些實體類的物件放到快取裡在hibernate.cfg.xml裡:
//在<sessionfactory>標籤裡,在<mapping>標籤後配置 <class-cache class="com.bjpowernode.hibernate.Student" usage="read-only"/> |
或者在實體類對映檔案裡:
//在<class>標籤裡,<id>標籤前配置 <cache usage="read-only"/> |
usage屬性表示使用快取的策略,一般優先使用read-only,表示如果這個資料放到快取裡了,則不允許修改,如果修改就會報錯。這就要注意我們放入快取的資料不允許修改。因為放快取裡的資料經常修改,也就沒有必要放到快取裡。
使用read-only策略效率好,因為不能改快取。但是可能會出現髒資料的問題,這個問題解決方法只能依賴快取的超時,比如上面我們設定了超時為120秒,120後就可以對快取裡物件進行修改,而在120秒之內訪問這個物件可能會查詢髒資料的問題,因為我們修改物件後資料庫裡改變了,而快取卻不能改變,這樣造成資料不同步,也就是髒資料的問題。
第二種快取策略read-write,當持久物件發生變化,快取裡就會跟著變化,資料庫中也改變了。這種方式需要加解鎖,效率要比第一種慢。
還有兩種策略,請看hibernate文件,最常用還是第一二種策略。
二級快取測試程式碼演示:注意上面我們講的兩個session分別呼叫load方法查詢相同的資料,第二個session的load方法還是發了sql語句到資料庫查詢資料,這是因為一級快取只在當前session中共享,也就是說一級快取不能跨session訪問。
//開啟二級快取,二級快取是程式級的快取,可以共享 //兩個session分別呼叫load方法查詢相同的實體物件 try { session = HibernateUtils.getSession(); session.beginTransaction(); Student student = (Student)session.load(Student.class, 1); System.out.println("student.name=" + student.getName()); session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } try { session = HibernateUtils.getSession(); session.beginTransaction(); Student student = (Student)session.load(Student.class, 1); //不會發出查詢語句,因為配置二級快取,session可以共享二級快取中的資料 //二級快取是程式級的快取 System.out.println("student.name=" + student.getName()); session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } |
如果開啟了二級快取,那麼第二個session呼叫的load方法查詢第一次查詢的資料,是不會發出sql語句查詢資料庫的,而是去二級快取中取資料。
//開啟二級快取 //兩個session分別呼叫get方法查詢相同的實體物件 try { session = HibernateUtils.getSession(); session.beginTransaction(); Student student = (Student)session.get(Student.class, 1); System.out.println("student.name=" + student.getName()); session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } try { session = HibernateUtils.getSession(); session.beginTransaction(); Student student = (Student)session.get(Student.class, 1); //不會發出查詢語句,因為配置二級快取,session可以共享二級快取中的資料 //二級快取是程式級的快取 System.out.println("student.name=" + student.getName()); session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } |
注意:二級快取必須讓sessionfactory管理,讓sessionfactory來清除二級快取。sessionFactory.evict(Student.class);//清除二級快取中所有student物件,sessionFactory.evict(Student.class,1);//清除二級快取中id為1的student物件。
如果在第一個session呼叫load或get方法查詢資料後,把二級快取清除了,那麼第二個session呼叫load或get方法查詢相同的資料時,還是會發出sql語句查詢資料庫的,因為快取裡沒有資料只能到資料庫裡查詢。
我們查詢資料後會預設自動的放到二級和一級快取裡,如果我們想查詢的資料不放到快取裡,也是可以的。也就是說我們可以控制一級快取和二級快取的交換。
session.setCacheMode(CacheMode.IGNORE);禁止將一級快取中的資料往二級快取裡放。
還是用上面程式碼測試,在第一個session呼叫load方法前,執行session.setCacheMode(CacheMode.IGNORE);這樣load方法查詢的資料不會放到二級快取裡。那麼第二個session執行load方法查詢相同的資料,會發出sql語句到資料庫中查詢,因為二級快取裡沒有資料,一級快取因為不同的session不能共享,所以只能到資料庫裡查詢。
上面我們講過大批量的資料新增時可能會出現溢位,解決辦法是每當天就20個物件後就清理一次一級快取。如果我們使用了二級快取,光清理一級快取是不夠的,還要禁止一二級快取互動,在save方法前呼叫session.setCacheMode(CacheMode.IGNORE)。
二級快取也不會存放普通屬性的查詢資料,這和一級快取是一樣的,只存放實體物件。session級的快取對效能的提高沒有太大的意義,因為生命週期太短了。
一級快取和二級快取都只是存放實體物件的,如果查詢實體物件的普通屬性的資料,只能放到查詢快取裡,查詢快取還存放查詢實體物件的id。
查詢快取的生命週期不確定,當它關聯的表發生修改,查詢快取的生命週期就結束。這裡表的修改指的是通過hibernate修改,並不是通過資料庫客戶端軟體登陸到資料庫上修改。
hibernate的查詢快取預設是關閉的,如果要使用就要到hibernate.cfg.xml檔案裡配置:
<property name="hibernate.cache.use_query_cache">true</property> |
並且必須在程式中手動啟用查詢快取,在query介面中的setCacheable(true)方法來啟用。
//關閉二級快取,沒有開啟查詢快取,採用list方法查詢普通屬性 //同一個sessin,查詢兩次 List names = session.createQuery("select s.name from Student s") .list(); for (int i=0; i<names.size(); i++) { String name = (String)names.get(i); System.out.println(name); } System.out.println("-----------------------------------------"); //會發出sql語句 names = session.createQuery("select s.name from Student s") .setCacheable(true) .list(); for (int i=0; i<names.size(); i++) { String name = (String)names.get(i); System.out.println(name); } |
上面程式碼執行,由於沒有使用查詢快取,而一、二級快取不會快取普通屬性,所以第二次查詢還是會發出sql語句到資料庫中查詢。
現在開啟查詢快取,關閉二級快取,並且在第一次的list方法前呼叫setCacheable(true),並且第二次list查詢前也呼叫這句程式碼,可以寫出下面這樣:
List names = session.createQuery("select s.name from Student s") .setCacheable(true) .list(); |
其它程式碼不變,執行程式碼後發現第二次list查詢普通屬性沒有發出sql語句,也就是說沒有到資料庫中查詢,而是到查詢快取中取資料。
//開啟查詢快取,關閉二級快取,採用list方法查詢普通屬性 //在兩個session中呼叫list方法 try { session = HibernateUtils.getSession(); session.beginTransaction(); List names = session.createQuery("select s.name from Student s") .setCacheable(true) .list(); for (int i=0; i<names.size(); i++) { String name = (String)names.get(i); System.out.println(name); } session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } System.out.println("----------------------------------------"); try { session = HibernateUtils.getSession(); session.beginTransaction(); //不會發出查詢語句,因為查詢快取和session的生命週期沒有關係 List names = session.createQuery("select s.name from Student s") .setCacheable(true) .list(); for (int i=0; i<names.size(); i++) { String name = (String)names.get(i); System.out.println(name); } session.getTransaction().commit(); }catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); }finally { HibernateUtils.closeSession(session); } |
執行結果是第二個session發出的list方法查詢普通屬性,沒有發出sql語句到資料庫中查詢,而是到查詢快取裡取資料,這說明查詢快取和session生命週期沒有關係。
//開啟快取,關閉二級快取,採用iterate方法查詢普通屬性 //在兩個session中呼叫iterate方法查詢 |
執行結果是第二個session的iterate方法還是發出了sql語句查詢資料庫,這說明iterate迭代查詢普通屬性不支援查詢快取。
//關閉查詢快取,關閉二級快取,採用list方法查詢實體物件 //在兩個session中呼叫list方法查詢 |
執行結果第一個session呼叫list方法查詢實體物件會發出sql語句查詢資料,因為關閉了二級快取,所以第二個session呼叫list方法查詢實體物件,還是會發出sql語句到資料庫中查詢。
//開啟查詢快取,關閉二級快取 //在兩個session中呼叫list方法查詢實體物件 |
執行結果第一個session呼叫list方法查詢實體物件會發出sql語句查詢資料庫的。第二個session呼叫list方法查詢實體物件,卻發出了很多sql語句查詢資料庫,這跟N+1的問題是一樣的,發出了N+1條sql語句。為什麼會出現這樣的情況呢?這是因為我們現在查詢的是實體物件,查詢快取會把第一次查詢的實體物件的id放到快取裡,當第二個session再次呼叫list方法時,它會到查詢快取裡把id一個一個的拿出來,然後到相應的快取裡找(先找一級快取找不到再找二級快取),如果找到了就返回,如果還是沒有找到,則會根據一個一個的id到資料庫中查詢,所以一個id就會有一條sql語句。
注意:如果配置了二級快取,則第一次查詢實體物件後,會往一級快取和二級快取裡都存放。如果沒有二級快取,則只在一級快取裡存放。(一級快取不能跨session共享)
//開啟查詢快取,開啟二級快取 //在兩個session中呼叫list方法查詢實體物件 |
執行結果是第一個session呼叫list方法會發出sql語句到資料庫裡查詢實體物件,因為配置了二級快取,則實體物件會放到二級快取裡,因為配置了查詢快取,則實體物件所有的id放到了查詢快取裡。第二個session呼叫list方法不會發出sql語句,而是到二級快取裡取資料。
查詢快取意義不大,查詢快取說白了就是存放由list方法或iterate方法查詢的資料。我們在查詢時很少出現完全相同條件的查詢,這也就是命中率低,這樣快取裡的資料總是變化的,所以說意義不大。除非是多次查詢都是查詢相同條件的資料,也就是說返回的結果總是一樣,這樣配置查詢快取才有意義。
相關文章
- hibernate快取機制詳細分析快取
- hibernate快取機制書目錄快取
- 瀏覽器快取機制詳解瀏覽器快取
- Nginx 快取機制詳解!非常詳細實用Nginx快取
- Hibernate---快取機制四(一,二級快取的比較)快取
- 瀏覽器快取機制(詳)瀏覽器快取
- Hibernate中一級快取和二級快取使用詳解快取
- 瀏覽器 HTTP 協議快取機制詳解瀏覽器HTTP協議快取
- 程式設計師筆記| 詳解Eureka 快取機制程式設計師筆記快取
- http強制快取、協商快取、指紋ETag詳解HTTP快取
- Web 快取機制 與 快取策略Web快取
- HTTP快取機制HTTP快取
- Mybatis快取機制MyBatis快取
- LRU快取機制快取
- 前端快取機制前端快取
- web快取機制Web快取
- client快取機制client快取
- MyBatis快取機制(一級快取,二級快取)MyBatis快取
- mybatis的快取機制MyBatis快取
- Redis 快取失效機制Redis快取
- mysql的快取機制MySql快取
- MyBatis 的快取機制MyBatis快取
- iOS Cell非同步圖片載入優化,快取機制詳解iOS非同步優化快取
- 從WebView快取聊到Http 的快取機制WebView快取HTTP
- 圖解 HTTP 的快取機制 | 實用 HTTP圖解HTTP快取
- http系列--徹底理解瀏覽器的快取機制(http快取機制)HTTP瀏覽器快取
- 【hibernate】Session快取Session快取
- Mybatis快取詳解MyBatis快取
- Nginx快取原理及機制Nginx快取
- RecyclerView快取機制(scrap view)View快取
- HTTP快取機制及原理HTTP快取
- node中的快取機制快取
- 瀏覽器快取機制瀏覽器快取
- yii2 快取機制快取
- http快取機制及其原理HTTP快取
- HTTP----HTTP快取機制HTTP快取
- 黑科技:LocalStorage 快取機制快取
- SDWebImage 快取機制(筆記)Web快取筆記