原文链接:http://guxiangdiyu87.iteye.com/blog/1630638
注:其实hibernate的二级缓存还有其他的解决方案,这个只是现阶段使用得比较多的而已。二级缓存属于sessionfactory级别的缓存,基本就同属全局缓存了。如果使用得当能够极大的提供使用效率,但是,二级缓存 显而易见得会造成数据刷新延迟。。之类的各种问题,这个得让大家自己综合自己的业务水平选择比较恰当的解决方案才是王道。
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 + "]";
- }
- }
下面来看一个例子,验证二级缓存是否配置成功。
- 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的
诸多限制,那么还是自己在应用程序的层面上做缓存吧。
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,
尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据
干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也
要好些吧。”