緩存簡介
緩存是介於應用程序和物理數據源之間,緩存內的數據是對物理數據源中的數據的復制,其作用是為了降低應用程序對物理數據源訪問的頻次,從而提高了應用的運行性能。
緩存的范圍
事務范圍:緩存只能被當前事務訪問。緩存的生命周期依賴於事務的生命周期,當事務結束時,緩存也就結束生命周期。在此范圍下,緩存的介質是內存。事務可以是數據庫事務或者應用事務,每個事務都有獨自的緩存,緩存內的數據通常采用相互關聯的的對象形式。
進程范圍:緩存被進程內的所有事務共享。這些事務有可能是並發訪問緩存,因此必須對緩存采取必要的事務隔離機制。緩存的生命周期依賴於進程的生命周期,進程結束時,緩存也就結束了生命周期。進程范圍的緩存可能會存放大量的數據,所以存放的介質可以是內存或硬盤。緩存內的數據既可以是相互關聯的對象形式也可以是對象的松散數據形式。松散的對象數據形式有點類似於對象的序列化數據,但是對象分解為松散的算法比對象序列化的算法要求更快。
集群范圍:在集群環境中,緩存被一個機器或者多個機器的進程共享。緩存中的數據被復制到集群環境中的每個進程節點,進程間通過遠程通信來保證緩存中的數據的一致性,緩存中的數據通常采用對象的松散數據形式。
hibernate緩存
一級緩存
:緩存位置:session中
:生命周期:一個事務中。
:緩存規格:{ID:實體}
:默認開啟
2.1 何時數據會進入緩存:事務中加載過的數據,都會進入緩存,並以{ID:實體}存儲在session中。
2.2 何時可以檢查緩存:以ID為條件的查詢可以檢查緩存。
*session.get();//可以檢查
*Query.list();//不能檢查
.uniqueResult();//不能檢查
.iterate();//可以檢查
*細節:iterate()運作流程
String hql="from User u where u.name=?";
Query.iterate(hql);
1>保留查詢條件,到數據庫中查詢ID,
select id from t_user where t_name=?
[1,2,3,4,5]
2>通過查到的ID去檢查緩存。如果有緩存可用,則不用再查詢數據庫。
但是,注意,如果沒有緩存可用,則要再次發起對數據庫的查詢:
select * from t_user where t_id=5;
select * from t_user where t_id=4;
select * from t_user where t_id=3;
select * from t_user where t_id=2;
select * from t_user where t_id=1;
綜上,再使用iterate()方法時,可能導致n+1次查詢問題。n=滿足條件的數據行數。
3>使用:
Iterator it=query2.iterate();
while(it.hasNext()){
User user=(User)it.next();
System.out.println(user);
}
二級緩存
:緩存位置:SessionFactory中
:生命周期:全局可用
:緩存規格:{ID:實體}
:默認關閉:通過配置開啟。
:*開啟二級緩存
<!-- 開啟二級緩存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 二級緩存類別:EhCache,OSCache,JbossCache -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
*導包 並 引入ehcahe.xml
*為要進入二級緩存的實體,增加權限。
//只讀緩存權限
//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
//讀寫緩存權限
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
3.1 何時數據會進入緩存:事務中加載過的數據,都會進入緩存,並以{ID:實體}存儲在session中。
3.2 何時可以檢查緩存:以ID為條件的查詢可以檢查緩存。
get();
iterate();
查詢緩存:依賴二級緩存
: 緩存位置:SessionFactory中
: 生命周期:全局可用,但不穩定,如果和緩存數據相關的表有任何的改動,則緩存數據失效,在一般的應用程序中,sessionfactory會以單例的形式存在,所以在整個應用程序的生命周期里,sessionfactory會一直存在。既二級緩存也一直存在直到關閉應用程序
: 緩存規格: {hql:查詢結果(字段)}
: 默認關閉:<property name="hibernate.cache.use_query_cache">true</property>
在查詢前://本次查詢要使用查詢緩存
query.setCacheable(true);
4.1 何時數據會進入緩存:用hql查詢字段的查詢結果,都可以進入查詢緩存:{HQL:結果(字段)}
4.2 何時可以檢查緩存:只要再次用同樣的hql查詢,則可以檢查查詢緩存。
4.3 使用場景
什么樣的數據適合存放到第二級緩存中?
1、很少被修改的數據
2、不是很重要的數據,允許出現偶爾並發的數據
3、不會被並發訪問的數據
4、參考數據
不適合存放到第二級緩存的數據?
1、經常被修改的數據
2、財務數據,絕對不允許出現並發
3、與其他應用共享的數據。
*細節:如果查詢的實體,則查詢緩存只能緩存:{HQL:實體的ID字段}
緩存總結
5.1 緩存規格:
*一級緩存,二級緩存,緩存的是實體:{ID:實體}
session.get(User.class,1);
"from User u";
*查詢緩存:{HQL:查詢結果}
tx
"select u.id,u.age from User u";
commit();
5.2 使用:
*如果查詢時是查詢字段的話:select a,b,c,d from XXX;
查詢緩存足矣。
*如果查詢時是查詢實體的話:from User u;
二級緩存+查詢緩存。
5.3 查詢緩存和二級緩存的聯合使用:
*如果查詢時是查詢實體的話:from User u;
初次query.list();時,檢查查詢緩存,沒有可用數據,則轉向數據庫,獲得一個實體User,
將{HQL:User的ID}存入查詢緩存,將{User的ID:User}存入二級緩存
再次query.list();時,檢查查詢緩存,獲得緩存數據:User的ID,通過UserID檢查二級緩存,
如果有數據,則直接使用,否則以各個ID為條件分別發起查詢
為什么這樣設計
一般情況下,我們查詢的數據一般是實時的,使用二級緩存肯定不行,使用一級緩存既利用了緩存又不會影響實時。使用二級緩存是為了存儲一些比較穩定的數據,二級緩存策略,是針對於ID查詢的緩存策略,對於條件查詢則毫無作用。為此,Hibernate提供了針對條件查詢的Query緩存。
圖解緩存
分區:是以全類名為單位分區
測試代碼
插件緩存用的是ecache
實體:
package com.c50.entity; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.GenericGenerator; @Entity @Table(name="user50") //只讀緩存權限 //@Cache(usage=CacheConcurrencyStrategy.READ_ONLY) //讀寫緩存權限 @Cache(usage=CacheConcurrencyStrategy.READ_WRITE) public class User { @Id @Column(name="id") @GenericGenerator(name="inc50",strategy="increment") @GeneratedValue(generator="inc50") private Integer userID; private String name ; private Integer age; @Column(name="birth") @Temporal(TemporalType.DATE) private Date birthday; public User(){} public User(Integer userID, String name, Integer age, Date birthday) { super(); this.userID = userID; this.name = name; this.age = age; this.birthday = birthday; } public Integer getUserID() { return userID; } public void setUserID(Integer userID) { this.userID = userID; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User [userID=" + userID + ", name=" + name + ", age=" + age + ", birthday=" + birthday + "]"; } }
二級緩存ecache:
<?xml version="1.0" encoding="UTF-8"?> <!-- maxEntriesLocalHeap: 在內存中緩存的element的最大數目。 maxEntriesLocalDisk: 在磁盤上緩存的element的最大數目,默認值為0,表示不限制。 eternal: 設定緩存的elements是否永遠不過期。如果為true,則緩存的數據始終有效, 如果為false那么還要根據timeToIdleSeconds,timeToLiveSeconds判斷。 <persistence strategy="localTempSwap"/> 內存存滿后將數據存入硬盤 timeToIdleSeconds="10" 緩存空閑時間 默認值0 一直存活 timeToLiveSeconds="15" 緩存最大存活時間 默認值0 一直存活 diskExpiryThreadIntervalSeconds:磁盤數據的有效時間 memoryStoreEvictionPolicy="LFU" FIFO ,first in first out (先進先出). LFU , Less Frequently Used (最少使用).意思是一直以來最少被使用的。緩存的元素有一個hit 屬性,hit 值最小的將會被清出緩存。 LRU ,Least Recently Used(最近最少使用). (ehcache 默認值).緩存的元素有一個時間戳,當緩存容量滿了, 而又需要騰出地方來緩存新的元素的時候,那么現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。 --> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <!-- <diskStore path="java.io.tmpdir"/> --> <diskStore path="E:\\cache4"/> <defaultCache maxEntriesLocalHeap="2" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> </ehcache>
hibernate配置文件:
<?xml version="1.0" encoding="utf-8"?> <!-- 文檔約束:DTD Document Type Definition :標簽,屬性,層級,先后順序 --> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 數據庫連接相關 --> <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property> <property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property> <property name="hibernate.connection.username">hr</property> <property name="hibernate.connection.password">hr</property> <!-- 最大連接數 --> <property name="hibernate.c3p0.max_size">3</property> <!-- 最下連接數 --> <property name="hibernate.c3p0.min_size">1</property> <!-- 獲取鏈接等待超時時間: 毫秒--> <property name="checkoutTimeout">3000</property> <!-- 指示連接池的類型 --> <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <!-- hibernate自身配置信息 方言:指示數據庫種類,便於hibernate對不同的數據庫做出相應的適應。 --> <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property> <!-- 控制台打印出sql語句 --> <property name="hibernate.show_sql">true</property> <!-- 格式化sql語句 --> <property name="hibernate.format_sql">true</property> <!-- 禁用掉javaEE6的bean-validator --> <property name="javax.persistence.validation.mode">none</property> <!-- getCurrentSession --> <property name="hibernate.current_session_context_class">thread</property> <!-- 開啟二級緩存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 二級緩存類別:EhCache,OSCache,JbossCache --> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> <!-- 開啟查詢緩存 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 映射信息的注冊 --> <mapping class="com.c50.entity.User"></mapping> </session-factory> </hibernate-configuration>
測試代碼:
package com.c50.test; import java.util.Iterator; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; import org.junit.Test; import com.c50.entity.User; import com.c50.util.HibernateUtil; /** * * @author Administrator * 緩存測試 */ public class TestCache { /** * 一級緩存測試:不能跨事務 */ @Test public void testFirstLevelCache(){ Session session=HibernateUtil.getCurrentSession(); Transaction tx=session.beginTransaction(); //1.檢查緩存,沒有可用的數據,轉向數據庫,查詢出的數據,進入一級緩存 User user=(User)session.get(User.class,1); System.out.println(user); user.setAge(41); //2.再次發起同樣的查詢,檢查緩存,有可用的數據,則不在查詢數據庫。 User user2=(User)session.get(User.class,1); System.out.println(user2); tx.commit(); /*Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); User user3=(User)session2.get(User.class,109); System.out.println(user3); tx2.commit(); session2.close();*/ } /** * 一級緩存測試:HQL語句執行查詢不能檢查緩存 */ @Test public void testFirstLevelCache2(){ Session session=HibernateUtil.getCurrentSession(); Transaction tx=session.beginTransaction(); //1.檢查緩存,沒有可用的數據,轉向數據庫,查詢出的數據,進入一級緩存 User user=(User)session.get(User.class,1); //2.list和uniqueResult不能檢查緩存,直接轉向數據庫。 String hql="from User u where u.name='zhangjifeng'"; Query query=session.createQuery(hql); User user2=(User)query.uniqueResult(); System.out.println(user); System.out.println(user2); tx.commit(); } /** * 一級緩存測試:iterate(): */ @Test public void testFirstLevelCache3(){ Session session=HibernateUtil.getCurrentSession(); Transaction tx=session.beginTransaction(); //1.HQL查詢多條數據,數據進入一級緩存 String hql="from User u where u.name like ?"; Query query=session.createQuery(hql); query.setString(0,"ji%"); query.list(); String hql2="from User u where u.name like ?"; Query query2=session.createQuery(hql); query2.setString(0,"ji%"); //迭代器中存儲ID //只查詢ID,通過ID檢查緩存。 Iterator it=query2.iterate(); while(it.hasNext()){ User user=(User)it.next(); System.out.println(user); } tx.commit(); } /** * 二級緩存測試 */ @Test public void testSecondLevelCache(){ Session session=HibernateUtil.openSession(); Transaction tx=session.beginTransaction(); User user=(User)session.get(User.class,1); User user3=(User)session.get(User.class,2); System.out.println(user); tx.commit(); session.close(); Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); User user2=(User)session2.get(User.class,1); user2.setName("second222"); System.out.println(user2); tx2.commit(); session2.close(); } /** * 查詢緩存測試 */ @Test public void testQueryCache(){ Session session=HibernateUtil.openSession(); Transaction tx=session.beginTransaction(); String hql="select u.name,u.age from User u where u.age>=?"; Query query=session.createQuery(hql); query.setInteger(0,20); //本次查詢要使用查詢緩存 query.setCacheable(true); //檢查查詢緩存,沒有可用的緩存,則轉向數據庫,將結果緩存在查詢緩存中:{HQL:結果(字段)} query.list(); tx.commit(); session.close(); // Session session3=HibernateUtil.openSession(); // Transaction tx3=session3.beginTransaction(); // session3.save(new User()); // tx3.commit(); // session3.close(); Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); String hql2="select u.name,u.age from User u where u.age>=?"; Query query2=session2.createQuery(hql2); query2.setInteger(0,20); //本次查詢要使用查詢緩存 query2.setCacheable(true); //檢查查詢緩存,發現可用緩存數據,直接使用,不在查詢數據庫 query2.list(); tx2.commit(); session2.close(); } /** * 查詢緩存+二級緩存 */ @Test public void testQueryCache2(){ Session session=HibernateUtil.openSession(); Transaction tx=session.beginTransaction(); String hql="from User u where u.age>=?"; Query query=session.createQuery(hql); query.setInteger(0,20); //本次查詢要使用查詢緩存 query.setCacheable(true); //檢查查詢緩存,沒有可用數據,則轉向數據庫,獲得一個實體User,將{HQL:User的ID}存入查詢緩存 // 將{User的ID:User}存入二級緩存 query.list(); tx.commit(); session.close(); Session session2=HibernateUtil.openSession(); Transaction tx2=session2.beginTransaction(); String hql2="from User u where u.age>=?"; Query query2=session2.createQuery(hql2); query2.setInteger(0,20); //本次查詢要使用查詢緩存 query2.setCacheable(true); //檢查查詢緩存,獲得緩存數據:User的ID,通過UserID檢查二級緩存,如果有數據,則直接使用,否則 //以各個ID為條件分別發起查詢 query2.list(); tx2.commit(); session2.close(); } }