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