緩存它的作用在於提高性能系統性能,介於應用系統與數據庫之間而存在於內存或磁盤上的數據。
我們編程的模式一般是這樣的page-->filter-->action-->server-->dao-->db,可以在這一個請求過程中的任何一點加入緩存,上一篇介紹的是在server層加緩存:service利用aop加緩存。
為頁面增加緩存:為頁面加緩存
首先,來看一下一級緩存它默認開啟且很常用。
一級緩存
當session關閉后緩存中的對象會丟失,也就是說兩個不同的session中的緩存數據都是不一樣的,緩存數據不能夠跨session訪問。
緩存數據的數據類型
在一級緩存中緩存的是實體對象,在使用查詢方法get() 、load() 、iterate()三個方法查詢時都會先查詢session緩存,如果有對象則從緩存里面取出來,如果緩存中沒有再去數據庫里面查詢。load()測試:測試注意一定要在同一個事務里面,當我在Spring管理的session測試時調用兩次load()總是查詢兩次發出兩條SQL語句,還以為session級緩存沒有起作用,原來是因為hibernate集成spring之后事務、session都由spring管理,每次調用前后事務一級session都自動打開和關閉,自己控制不了中間過程,於是將spring去掉拿到hibernate原session,再手動開發關閉事務這樣做可以保證在同一個session、同一個事務里面操作方法,確實是發了一條SQL語句,看下面代碼:
load()、get()方法:
@Test public void testLoad() { Session session=sf.openSession(); session.beginTransaction(); Category category1=(Category)session.load(Category.class,1); Category category2=(Category)session.load(Category.class,1); System.out.println(category1); System.out.println(category2); session.getTransaction().commit(); session.close(); }
結果
Hibernate: select category0_.id as id0_0_, category0_.name as name0_0_ from Category category0_ where category0_.id=? hibernate.Category@10efd7c hibernate.Category@10efd7c
結果不僅發送了一條語句而且兩個對象打印出來也是一樣的。
load()、get()第一次查詢時會發出sql語句,從數據庫表里面查詢;第二次查詢時會先去緩存里面查找,如果沒有發生更新修改操作,那么將從緩存中讀取數據,否則查詢數據庫。
save方法
@Test public void testGet() { Session session=sf.openSession(); session.beginTransaction(); Category category1=new Category(); category1.setName("新聞"); session.save(category1); Category category2=(Category)session.load(Category.class,category1.getId()); System.out.println(category2.getName()); session.getTransaction().commit(); session.close(); }
save也支持緩存,當執行save方法時首先往session緩存里面添加一條數據,等事務提交或者緩存刷新時才往數據庫里面更新,從上面執行過程可以看出只發出了一條插入語句沒有發查詢語句,因為第二次是從緩存中查詢出來的。
PS:save之后執行get或者load需要知道對象的ID,此時save方法執行后雖然數據庫里沒有數據,但是對象的ID已經生成可以通過這個ID查詢對象。
批量插入數據
在批量插入數據的時候采取每次插入一部分數據,如下,每次插入20條數據不需要一條一條插入。
public void testInserBatch() { Session session = sf.openSession(); session.beginTransaction(); for(int i=0; i<1000; i++) { Category c = new Category(); c.setName("test" + i); session.save(c); if (i%20==0) { session.flush(); } } session.getTransaction().commit(); session.close(); }
每次20條數據清理一下緩存,每次清理緩存調用session.flush()方法會發出20條insert語句,但是數據庫里面還沒有數據等所有數據都發出insert語句統一提交事務,事務同session是一個等級的因此需統一控制事務。
hibernate N+1問題
Hibernate 中常會用到 set 等集合表示 1 對多的關系,在我們做的這個鐵科院項目中,在獲取實體的時候就能根據關系將關聯的對象或者對象集合取出,還可以設定 cacade 進行關聯更新和刪除。這不得不說 hibernate 的 orm 做得很好,很貼近 oo 的使用習慣了。
但是對數據庫訪問還是必須考慮性能問題的,在設定了 1 對多這種關系之后, 查詢就會出現傳說中的 n+1 問題。
一對多:在一方,查找得到了 n 個對象,那么又需要將 n 個對象關聯的集合取出,於是本來的一條 sql 查詢變成了 n+1 條;
多對一:在多方,查詢得到了 m 個對象,那么也會將 m 個對象對應的 1 方的對象取出, 也變成了 m+1 ;
解決問題的方法:
1、 使用 fetch 抓取, Hibernate 抓取策略分為單端代理和集合代理的抓取策略。
Hibernate 抓取策略 ( 單端代理的抓取策略) :
保持默認也就是如下 :
<many-to-one name="clazz"cascade="save-update" fetch="select" />
fetch="select" 就是另外發送一條 select 語句抓取當前對象關聯實體或者集合設置 fetch="join"
<many-to-one name="clazz"cascade="save-update" fetch="join"/>
Hibernate 會通過 select 語句使用外連接來加載器關聯實體活集合此時 lazy 會失效
Hibernate 抓取策略 ( 集合代理的抓取策略 ) :
保持默認( fetch="select" )也就是如下 :
<set name="students"inverse="true">
<key column="clazz"/>
<one-to-many class="com.june.hibernate.Student"/>
</set>
1)fetch="select" 會另外發出一條語句查詢集合
2) 設置fetch="join" 采用外連接集合的 lazy 失效
3) 這只fetch="subselect" 另外發出一條 select 語句抓取前面查詢到的所有的實體對象的關聯集合 fetch 只對 HQL 查詢產生影響其他的則不會
OpenSessionInview問題
這個問題出現是由於load()懶加載導致的,第一次查詢數據時使用了懶加載至查詢出來數據的ID,當使用數據的時候還需要去數據庫里面查詢但是此時數據庫的session已經關閉,解決此問題兩種思路一種是不使用懶加載;其二是在web層開發關閉session,延長session的生命周期。
二級緩存
二級緩存也稱為進程級緩存或sessionFactory緩存,也可以叫做集群范圍內的緩存,需要第三方來實現,hibernate默認的二級緩存插件為ehcache這個緩存,由於二級緩存是進程級的可能出現多線程並發問題,需要設置緩存的並發策略。
hibernate二級緩存需要第三方插件支持,hibernate默認支持為ehcache關於配置請參考:Spring AOP +EHcache為Service層方法增加緩存
開啟二級緩存后對方法的影響
get()/load()
對於這兩個方法沒啥影響,第一次從數據庫里面查詢,第二次先判斷緩存里面有沒有數據如果沒有再去數據庫里面查詢。
查詢緩存
查詢緩存是針對普通屬性結果集的緩存,不緩存實體對象,當和查詢緩存關聯的表發生修改的時候,查詢緩存生命周期結束,里面的數據也隨即被清空了。
查詢緩存的配置和使用:
List方法讀寫查詢緩存,Iterator不使用查詢緩存(查詢緩存只對query.list()有效)
查詢緩存的配置,默認不開啟hibernate3配置:
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <property name="hibernate.cache.use_second_level_cache">false</property> <property name="hibernate.cache.use_query_cache">true</property>
代碼中,加上一句話
query.setCacheable(true)
一級、二級、查詢之間的關系
開啟二級緩存時,如果兩個session先后執行load或者get方法,只執行一條語句第二次會從緩存中查找,先從一級緩存中查詢,如果沒有再去二級緩存中查找。
一級緩存同二級緩存交互
禁止一級緩存與二級緩存交互,如下設置
session.setCacheMode(CacheMode.IGNORE);打開一個session執行查詢,它會先將查詢結果保存到一級緩存,待session關閉后,一級緩存中數據清空,由於禁止了一級緩存同二級緩存數據交互,因此,一級緩存關閉后不會將結構保存到二級緩存,打開第二個session后,后再發送一條查詢語句,因此二級緩存中沒有數據。
查詢緩存與二級緩存
如果兩次執行query.list(),第一次發送查詢語句會將結果對象的id保存到查詢緩存中,第二次會先從查詢緩存中取出ID,根據id先去一級緩存查找,再二級緩存,如果沒有找到會去數據庫中查找,一級緩存同session沒有關系,只和表有關系。
開啟查詢,開啟二級緩存
兩次執行query.list(),第一次發送查詢語句將結果
總結:
緩存在一個項目中對於提高系統性能很重要,除了ehcache之外還有memcache、redis等緩存產品目前都很常用,redis具有豐富的數據類型以及單線程高效能訪問效率,memcache雖然是多線程但效率還是沒有redis高。
這些緩存產品都可以實現分布式緩存,ehcache+rmi可以分布式緩存同步;memcache+redis都支持分布式,redis還提供了高可用性的解決方案:主從復制幾個服務器直接愛你可以切換。