閱讀411 返回首頁    go 阿裏雲 go 技術社區[雲棲]


第十二章 hibernate緩存

1、一級緩存(Session級緩存)

一級緩存很短和session的生命周期一致,因此也叫session級緩存或事務級緩存

那些方法支持一級緩存:

*get()

*load()

*iterate(查詢實體對象)

如何管理一級緩存:

*session.clear(),session.evict()

如何避免一次性大量的實體數據入庫導致內存溢出

方法1:先flush,再clear

for(int i = 0; i <1000000; i++){
		session.save(user);
		if(i % 20 == 0){
			session.flush();
			session.clear();
		}
	}


方法2:用StatelessSession接口

Hibernate提供了基於命令的API,可以用detachedobject(分離對象)的形式把數據以流的方法加入到數據庫,或從數據庫輸出。StatelessSession沒有持久化上下文,也不提供多少高層的生命周期語義。特別是,無狀態session不實現第一級cache,也不和第二級緩存,或者查詢緩存交互。用StatelessSession進行的操作甚至不級聯到關聯實例。

無狀態session是低層的抽象,和低層JDBC相當接近。

StatelessSession statelessSession = sessionFactory.openStatelessSession();
		statelessSession.insert(user);


方法3:批量更新

session.beginTransaction();
Queryquery = session.createQuery("updateUser as u set u.password=:p");
query.setString("p","000000");
query.executeUpdate();
session.getTransaction().commit();


如果數據量特別大,考慮采用jdbc實現,如果jdbc也不能滿足要求可以考慮采用數據本身的特定導入工具



2、二級緩存

Hibernate默認的二級緩存是開啟的。

二級緩存也稱為進程級的緩存,也可稱為SessionFactory級的緩存(因為SessionFactory可以管理二級緩存),它與session級緩存不一樣,一級緩存隻要session關閉緩存就不存在了。而二級緩存則隻要進程在二級緩存就可用。

二級緩存可以被所有的session共享

二級緩存的生命周期和SessionFactory的生命周期一樣,SessionFactory可以管理二級緩存

二級緩存同session級緩存一樣,隻緩存實體對象,普通屬性的查詢不會緩存

二級緩存一般使用第三方的產品,如EHCache

2.1常見緩存提供商

Cache

Providerclass

Type

ClusterSafe

QueryCache Supported

Hashtable(not intended for production use)

org.hibernate.cache.HashtableCacheProvider

memory

 

yes

EHCache

org.hibernate.cache.EhCacheProvider

memory,disk

 

yes

OSCache

org.hibernate.cache.OSCacheProvider

memory,disk

 

yes

SwarmCache

org.hibernate.cache.SwarmCacheProvider

clustered(ip multicast)

yes(clustered invalidation)

 

JBossTreeCache

org.hibernate.cache.TreeCacheProvider

clustered(ip multicast), transactional

yes(replication)

yes(clock sync req.)


2.2二級緩存的用法

這裏以常見的EHCache為例

(1)hibernate.cfg.xml中開啟二級緩存。

設置啟用二級緩存:

<propertyname="hibernate.cache.use_second_level_cache">true</property>


設置二級緩存的實現類(緩存提供商)

<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
</property>


(2)導入所使用的二級緩存JAR

ehcache-1.2.3.jarcommons-logging.jarcommons-logging-1.0.4.jar


(3)src下寫緩存配置文件:ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
	<diskStore path="C:\\cache" />
	<defaultCache 
		maxElementsInMemory="10000"
	     eternal="false"
		overflowToDisk="true" 
		timeToIdleSeconds="120" 
		timeToLiveSeconds="120"
		diskPersistent="false" />
</ehcache> 

maxElementsInMemory屬性用於指定緩存中最多可放多少個對象。

overflowToDisk當內存中緩存的記錄達到maxElementsInMemory時是否被持久化到硬盤中。保存路徑由diskStore決定的
eternal
屬性指定緩存是否永久有效。
timeToIdleSeconds
屬性指定緩存多久未被使用便清理掉。
timeToLiveSeconds
屬性指定緩存的生命長度。
diskPersistent
屬性指定緩存是否被持久化到硬盤中,保存路徑由diskStore標簽指定。

(4)設置所有緩存的實體類

ahibernate.cfg.xml中設置

<class-cacheusage="read-only"/>


b*.hbm.xml中設置
<hibernate-mapping>
    <class name="cn.framelife.hibernate.entity.User" table="user" catalog="hibernate">
        <cache usage="read-only"/>
        <id name="id" type="java.lang.Integer">
            <column name="id" />
            <generator  />
        </id>
        <property name="username" type="java.lang.String">
            <column name="username" length="45" not-null="true" />
        </property>
        <property name="password" type="java.lang.String">
            <column name="password" length="45" not-null="true" />
        </property>
    </class>
</hibernate-mapping> 

cAnnotation方式
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
@Table(name = "user", catalog = "hibernate")
public class User implements java.io.Serializable {}

d、緩存策略

隻讀緩存(read-only:沒有什麼好說的 ,最常用也最簡單的。
/寫緩存(read-write:程序可能要的更新數據 
不嚴格的讀/寫緩存(nonstrict-read-write:需要更新數據,但是兩個事務更新同一條記錄的可能性很小,性能比讀寫緩存好 
事務緩存(transactional:緩存支持事務,發生異常的時候,緩存也能夠回滾,隻支持jta環境。 

讀寫緩存和不嚴格讀寫緩存在實現上的區別在於,讀寫緩存更新緩存的時候會把緩存裏麵的數據換成一個鎖,其他事務如果去取相應的緩存數據,發現被鎖住了,然後就直接取數據庫查詢。 
hibernate2.1ehcache實現中,如果鎖住部分緩存的事務發生了異常,那麼緩存會一直被鎖住,直到60秒後超時。 
不嚴格讀寫緩存不鎖定緩存中的數據。

(5)測試

測試代碼:


List<User> uesrs = session.createQuery("from User").list();
			System.out.println("--------------");
			Session s2 = sessionFactory.openSession();
			User user = (User) s2.get(User.class, 2);
			System.out.println(user.getUsername());


結果:

Hibernate: select user0_.id as id0_, user0_.first_name as first2_0_, user0_.last_name as last3_0_, user0_.password as password0_, user0_.username as username0_ from hibernate.user user0_
--------------
zhangsan


2.3打開二級緩存統計信息

List<User> uesrs = session.createQuery("from User").list();
			System.out.println("--------------");
			Session s2 = sessionFactory.openSession();
			User user = (User) s2.get(User.class, 2);
			System.out.println(user.getUsername());
			
			Statistics st = sessionFactory.getStatistics();
			System.out.println(st);
						    System.out.println(st.getSecondLevelCacheStatistics("cn.framelife.hibernate.entity.User").getEntries());


3、查詢緩存

         hibernate的查詢緩存是主要是針對普通屬性結果集的緩存,而對於實體對象的結果集隻緩存id。在一級緩存,二級緩存和查詢緩存都打開的情況下作查詢操作時這樣的:查詢普通屬性,會先到查詢緩存中取,如果沒有,則查詢數據庫;查詢實體(對象),會先到查詢緩存中取id,如果有,則根據id到緩存(一級/二級)中取實體(對象),如果緩存中取不到實體,再查詢數據庫。

        查詢緩存的生命周期,是不確定的,當前關聯的表發生改變時,查詢緩存的生命周期結束。

配置和使用:

查詢緩存的配置和使用也是很簡單的:
        1>
查詢緩存的啟用不但要在配置文件中進行配置
            

<propertyname="hibernate.cache.use_query_cache">true</property>


        2>
還要在程序中顯示的進行啟用
            

query.setCacheable(true);

測試:

Query query = session.createQuery("select u.username from User u where u.id > 210");
		//query.setCacheable(true);
		List<String> names = query.list();
		for (String name : names) {
			System.out.println(name);
		}
		System.out.println("================================");
		query = session.createQuery("select u.username from User u where u.id > 210");
		//query.setCacheable(true);
		names = query.list();
		for (String name : names) {
			System.out.println(name);
		}


沒開啟查詢緩存(query.setCacheable(false))時的結果:

Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210
abcd
1111
11111
================================
Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210
abcd
1111
11111

一模一樣的查詢,執行了兩次SQL


開啟查詢緩存(query.setCacheable(true))時的結果

Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210
abcd
1111
11111
================================
abcd
1111
11111

開啟查詢緩存後,一模一樣兩次的查詢,隻需要執行一次sql



注意:

當隻是用Hibernate查詢緩存,而關閉二級緩存的時候:

  第一:如果查詢的是部分屬性結果集:

  那麼當第二次查詢的時候就不會發出SQL直接從Hibernate查詢緩存中取數據

第二:如果查詢的是實體結果集eg(fromStudent) 這個HQL那麼查詢出來的實體,首先Hibernate查詢緩存存放實體的ID

  第二次查詢的時候就到Hibernate查詢緩存中取出ID一條一條的到數據庫查詢這樣 將發出NSQL造成了SQL泛濫

在隻打開查詢緩存,關閉二級緩存的情況下,不要去查詢實體對象。這樣會造成很大的資源浪費。


當都開啟Hibernate查詢緩存和二級緩存的時候

  第一:如果查詢的是部分屬性結果集:這個和上麵隻是用Hibernate查詢緩存而關閉 二級緩存的時候,一致 因為不涉及實體不會用到二級緩存

  第二:如果查詢的是實體結果集eg(fromStudent) 這個HQL那麼查詢出來的實體,首先Hibernate查詢緩存存放實體的ID,第二次查詢,的時候就到Hibernate查詢緩存中取出ID,拿到二級緩存區找數據,如果有數據就不會發出SQL如果都有一條SQL都不會發出直接從二級緩存中取數據

例子:

/**
		 * 開啟查詢緩存,開啟二級緩存, 開啟兩個session,分別調用query.list查詢實體對象
		 */
		// 如果不用查詢緩存的話,那兩個都發出查詢語句,這也是默認的情況.
		try {
			session = sessionFactory.openSession();
			t = session.beginTransaction();
			Query query = session.createQuery("from User as u where u.id < 90");
			// 啟用查詢緩存
			query.setCacheable(true);
			List<User> list = query.list();
			for (User user : list) {
				System.out.println(user.getUsername());
			}
			t.commit();
		} catch (Exception e) {
			e.printStackTrace();
			t.rollback();
		} finally {
			session.close();
		}

		System.out.println("================================");

		try {
			session = sessionFactory.openSession();
			t = session.beginTransaction();
			Query query = session.createQuery("from User as u where u.id < 90");
			// 啟用查詢緩存
			query.setCacheable(true);
			// 不會發出查詢語句,因為這種情況下,查詢過程是這樣的:
			// 在第一次執行list時,會把查詢對象的id緩存到查詢緩存裏
			// 第二次執行list時, 會遍曆查詢緩存裏的id到緩存裏去找實體對象,由於這裏開啟了二級緩存,可以找到目標實體對象,
			// 所以就不會再發出n條查詢語句.
			List<User> list = query.list();
			for (User user : list) {
				System.out.println(user.getUsername());
			}
			t.commit();
		} catch (Exception e) {
			e.printStackTrace();
			t.rollback();
		} finally {
			session.close();
		}


4集群緩存

集群中的每一台機子上都有緩存。修改的成本高。



5中央緩存

服務中使用一台獨立的機器作為緩存。獲取數據的成本高,修改的成本低。



6使用緩存的條件

1、服務中讀取數據多於修改數據。
2、數據量不能大於內存容量。
3、對數據要有獨享的控製。
4、可以容忍無效數據。



7注意事項

        不要想當然的以為緩存一定能提高性能,僅僅在你能夠駕馭它並且條件合適的情況下才是這樣的。hibernate的二級緩存限製還是比較多的,不方便用jdbc可能會大大的降低更新性能。在不了解原理的情況下亂用,可能會有1+N的問題。不當的使用還可能導致讀出髒數據。 
        如果受不了hibernate的諸多限製,那麼還是自己在應用程序的層麵上做緩存吧。 
        在越高的層麵上做緩存,效果就會越好。就好像盡管磁盤有緩存,數據庫還是要實現自己的緩存,盡管數據庫有緩存,咱們的應用程序還是要做緩存。因為底層的緩存它並不知道高層要用這些數據幹什麼,隻能做的比較通用,而高層可以有針對性的實現緩存,所以在更高的級別上做緩存,效果也要好些吧。


最後更新:2017-04-03 18:52:12

  上一篇:go 第十三章 事務並發處理
  下一篇:go 第十一章 Hibernate的查詢 本地SQL查詢