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
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问题以后再进行专门的研究。