Hibernate二级缓存


Hibernate中的一级缓存是Session范围内的,而二级缓存是SessionFactory范围的,
需要使用第三方的实现。本文通过注解的方式为Hibernate配置二级缓存,采用的
第三方实现是Ehcache。

项目的结构如下,本文主要用到了:
Account.java
CachedAccount.java
SecondaryCache.java
ehcache.xml
hibernate.cfg.xml



为一个实体类进行二级缓存配置可以分为三步:

1.首先,要在hibernate.cfg.xml中开启二级缓存,并设置好Hibernate的provider。
因为Hibernate没有自己实现二级缓存,而只是为不同的第三方缓存提供了不同
的provider类。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
        <property name="hibernate.connection.url">jdbc:sqlserver://192.168.1.102:1433;databaseName=Bank</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="hibernate.connection.password">1qaz2wsx</property>
        <property name="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</property>
        <property name="connection.pool_size">1</property>
        <property name="show_sql">true</property>
        <!-- <property name="hbm2ddl.auto">create</property> -->
        
        <property name="hibernate.cache.use_second_level_cache">true</property>  
        <property name="hibernate.cache.use_query_cache">true</property>
    	<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
    	  
    </session-factory>
</hibernate-configuration>
虽然已经启用了二级缓存,但是它不会默认就对所有实体类都进行缓存,那样
的话内存开销太大,所有接下来我们还需要对具体的实体类进行缓存策略和
并发策略的配置。

2.编写ehcache.xml的配置文件,在这里除了可以对默认缓存策略进行配置外,
还可以对每个实体类进行不同的配置。具体可以配置的选项请参加ehcache的
xml schema文件:http://ehcache.org/ehcache.xsd 
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>

	<!-- 如果内存放不下,就放到磁盘上的一个路径 -->
	<!-- <diskStore path="e:/ehcache" /> -->

	<!-- 内存中存放最多的对象个数 -->
	<defaultCache maxElementsInMemory="2000" eternal="false"
		timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="false" />
		
	<!-- 保存的对象 -->
	<cache name="com.cdai.orm.hibernate.annotation.Account" maxElementsInMemory="200"
		eternal="false" timeToIdleSeconds="50" timeToLiveSeconds="60"
		overflowToDisk="false" />
		
	<cache name="com.cdai.orm.hibernate.transaction.AccountVersion" maxElementsInMemory="0"/>
	
</ehcache>

3.在实体类上加上Cache注解,并指定并发策略。因为二级缓存是SessionFactory
范围内的,所以不同Session同时修改一个实体类就会产生并发问题。正因为对共享
数据的并发访问从底层数据库提前到了应用程序中的二级缓存层,所以在数据库
层面上涉及的各种并发问题,提前在二级缓存应用程序层上出现了。
package com.cdai.orm.hibernate.cache;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Entity
@Table(name = "tb_cached_account")
public class CachedAccount implements Serializable {

	private static final long serialVersionUID = 5018821760412231859L;

	@Id
	@Column(name = "col_id")
	private long id;
	
	@Column(name = "col_balance")
	private long balance;

	public CachedAccount() {
	}
	
	public CachedAccount(long id, long balance) {
		this.id = id;
		this.balance = balance;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public long getBalance() {
		return balance;
	}

	public void setBalance(long balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "CachedAccount [id=" + id + ", balance=" + balance + "]";
	}
	
}
CachedAccount.java只是比Account.java多了Cache注解,其余代码完全相同。

下面来看一个例子,验证二级缓存是否配置成功。
package com.cdai.orm.hibernate.cache;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;

import com.cdai.orm.hibernate.annotation.Account;

public class SecondaryCache {

	public static void main(String[] args) {

		SessionFactory sessionFactory = 
				new AnnotationConfiguration().
					addFile("hibernate/hibernate.cfg.xml").				
					configure().
					addAnnotatedClass(CachedAccount.class).
					addAnnotatedClass(Account.class).
					buildSessionFactory();

		Session session1 = sessionFactory.openSession();
		Session session2 = sessionFactory.openSession();

		// Cached get
		CachedAccount accountc1 = (CachedAccount) session1.get(CachedAccount.class, new Long(1));
		CachedAccount accountc2 = (CachedAccount) session2.get(CachedAccount.class, new Long(1));
		CachedAccount accountc3 = (CachedAccount) session2.get(CachedAccount.class, new Long(1));
		System.out.println(accountc1 == accountc2);
		System.out.println(accountc3 == accountc2);

		// Cached query
		Query query = session1.createQuery(" from CachedAccount acct where acct.id=:id ");
		query.setCacheable(true);
		query.setParameter("id", new Long(1));
		accountc1 = (CachedAccount) query.uniqueResult();
		System.out.println(accountc1);
		
		query.setParameter("id", new Long(1));
		accountc1 = (CachedAccount) query.uniqueResult();
		System.out.println(accountc1);
		
		// Not-cached
		Account account1 = (Account) session1.get(Account.class, new Long(1));
		Account account2 = (Account) session2.get(Account.class, new Long(1));
		System.out.println(account1 == account2);
		
		session1.close();
		session2.close();
		sessionFactory.close();
	}

}

log输出为:

Hibernate: select cachedacco0_.col_id as col1_0_0_, cachedacco0_.col_balance as col2_0_0_ from tb_cached_account cachedacco0_ where cachedacco0_.col_id=?
false
true
Hibernate: select cachedacco0_.col_id as col1_0_, cachedacco0_.col_balance as col2_0_ from tb_cached_account cachedacco0_ where cachedacco0_.col_id=?
CachedAccount [id=1, balance=1000]
CachedAccount [id=1, balance=1000]
Hibernate: select account0_.col_id as col1_1_0_, account0_.col_balance as col2_1_0_ from tb_account account0_ where account0_.col_id=?
Hibernate: select account0_.col_id as col1_1_0_, account0_.col_balance as col2_1_0_ from tb_account account0_ where account0_.col_id=?
false

可以看到对实体类CachedAccount配置了Cache注解,二级缓存对它已经生效,
三次get()调用只执行了一次真正的SQL查询语句。而之后的Account实体类每次
调用get()都会执行一次SQL语句。

另外我们也注意到,虽然CachedAccount已经保存在二级缓存中,但是我们在不同
Session查询得到的却是不同的对象。CachedAccount不是直接缓存在二级缓存中的
吗?这是为什么呢?

因为如果直接将实体类对象缓存在二级缓存中,然后将同一个实体类返回给不同的
Session的话,虽然比较节省缓存,但是当不同的Session都可能长时间操作这一个对象
,这样就需要对这些不同线程中的操作进行同步,性能会很差。

所以二级缓存一般只是保存散装的数据(对象的属性),当Session加载时将散装数据
组装成一个新的实体类对象返回给它。虽然耗费内存,但是不需要同步了,二级缓存
只需要在每个Session获得对象时同步,之后每个Session的事务都操纵各自的对象,就
无需同步了。

此外,对查询缓存还要注意一点,除了在hibernate.cfg.xml中开启外,还要在查询前
调用query.setCacheable(true);才能使用查询缓存。


结束语

摘录一段别人的总结:

“不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。
hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理
的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。 如果受不了hibernate的
诸多限制,那么还是自己在应用程序的层面上做缓存吧。 

在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,
尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据
干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也
要好些吧。”

今天先到这,具体什么是1+N问题以后再进行专门的研究。


posted on 2012-08-13 11:37  毛小娃  阅读(106)  评论(0编辑  收藏  举报

导航