缓存由ORM框架提供以便用户可以获得更快速的访问速度,同时框架本身也减少了数据库查询操作。hibernate同样在两个层上提供了缓存的功能。
一级缓存:这是在session范围内默认使用的,了解更多可以读这篇文章。
二级缓存:这是在一级缓存之上的缓存,在session factory 范围内有效。
在这篇文章中,我将给出使用ehcache在hibernate配置二级缓存的例子。
这篇文章的结构:
二级缓存是如何工作的
关于ehcache
配置ehcache
配置实体对象
查询缓存
应用例子
源码下载
二级缓存是如何工作的
1、当hibernate Session试着加载实体的时候,会从一级缓存中获取对象的复制(与特定的hibernate Session相关连)
2、如果在一级缓存中存在实体的复制,将会把实体结果返回。
3、如果在一级缓存中不存在缓存的实体,二级缓存会查找是不是有缓存的实体。
4、如果二级缓存中有缓存的实体,将会把实体的结果返回。但是,在返回实体之前,它将会把结果存储到一级缓存中去,下次调用时可以直接从一级缓存中获取相应的结果,这样就没有必要再想二级缓存中获取。
5、如果在一级缓存和二级缓存中都没有找到相应的实体,那么数据库查询语句将会执行,在返回结果之前,实体将会被存储在一二级缓存中。
6、如果通过hibernate操作修改已经完成,对于修改的实体二级缓存会完成相应的验证。
7、如果用户或者进程直接在数据库中做了修改,二级缓存不能更新自己直到timeToLiveSeconds时间结束。在这种情况下最好将所有的缓存失效并让hibernate再一次建立自己的缓存,你可以使用下边的代码片段失效整个hibernate的二级缓存。
2、如果在一级缓存中存在实体的复制,将会把实体结果返回。
3、如果在一级缓存中不存在缓存的实体,二级缓存会查找是不是有缓存的实体。
4、如果二级缓存中有缓存的实体,将会把实体的结果返回。但是,在返回实体之前,它将会把结果存储到一级缓存中去,下次调用时可以直接从一级缓存中获取相应的结果,这样就没有必要再想二级缓存中获取。
5、如果在一级缓存和二级缓存中都没有找到相应的实体,那么数据库查询语句将会执行,在返回结果之前,实体将会被存储在一二级缓存中。
6、如果通过hibernate操作修改已经完成,对于修改的实体二级缓存会完成相应的验证。
7、如果用户或者进程直接在数据库中做了修改,二级缓存不能更新自己直到timeToLiveSeconds时间结束。在这种情况下最好将所有的缓存失效并让hibernate再一次建立自己的缓存,你可以使用下边的代码片段失效整个hibernate的二级缓存。
关于更多二级缓存是如何工作的,可以参考这篇文章。
关于ehcache
Terracotta ehcache是一个流行的开源的Java缓存,可以被用在hibernate的二级缓存中,ehcache也可以被当做独立的二级缓存使用,或者为集群配置提供二级缓存。hibernate可以使用ehcache的库,如果你想要ehcache的某一个具体的版本,可以访问网址
ehcache2.0的maven依赖如下:
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>[2.0.0]</version>
<type>pom</type>
</dependency>
配置ehcache
为了配置ehcache,你需要做下边两个步骤的事情:
1、为二级缓存配置hibernate
2、制定二级缓存的提供者
hibernate3.3以上的版本
<property key="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property>
hibernate3.2和以下版本
<property key="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>
配置实体对象
可以使用两种方式
(1)如果使用hbm.xml文件,使用下边的配置
<class name="com.application.entity.DepartmentEntity" table="...">
<cache usage="read-write"/>
</class>
(2)如果使用注解,是用下边这些注解
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY,
region="department")
public class DepartmentEntity implements Serializable
{
//code
}
对于上边这两种方式,缓存策略可以是下边的类型:
none:不会有缓存发生
read-only:如果你的应用需要读而不是修改,持久类的实例上可以使用read-only缓存
read-write:如果你的缓存需要更新数据,一个read-write缓存是很合适的。
nonstrict-read-write:如果你的应用只在特定的情况下更新数据(例如:一个极特殊的情况,两个事物同时更新数据),并且没有严格是事务独立要求,nonstrict-read-write或许就是合适的选择。
transactional:事务缓存策略为为所有的事务缓存提供者提供支持,比如一个缓存可以仅仅在JTA中使用,你必须指定hibernate.transaction.manager_lookup_class.
查询缓存
你可是使用在hbm.xml中配置如下的语句使查询缓存生效
<property key="hibernate.cache.use_query_cache">true</property>
在你的代码中定义的查询上增加方法setCacheable(true),这样就可以缓存了。
sessionFactory.getCurrentSession().createQuery("...").setCacheable(true).list();
默认情况下,ehcache会为你配置的需要缓存每一个实体划分出不同的缓存区域。你可以在ehcache.xml中增加配置来改变这个默认的区域。
<property name="net.sf.ehcache.configurationResourceName">/ehcache.xml</property>
使用下边的配置覆盖默认的配置
<cache
name="com.somecompany.someproject.domain.Country"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
在ehcache.xml中需要注意:如果eternal="true"那么我们不需要写 timeToIdealSeconds, timeToLiveSeconds,hibernate会处理这些值,如果你想人为的给定缓存的值最好还是使用上边的那种配置方式,这样我们就可以手动的将值指定 timeToIdealSeconds,timeToLiveSeconds 。
timeToIdealSeconds=”seconds”意味着,如果对象在全局缓存中是理想的,在全局中使用的类或者对象将会等待超过我们设置的timeToIdealSeconds 时间值将缓存的值删除。
timeToLiveSeconds=”seconds” 意味着,其他的session或者类使用不是使用这个对象,超过这个时间,hibernate将会从全局缓存中将它删除。
例子
在我们的应用例子中,我有一个DepartmentEntity,在这上边使用ehcache的二级缓存,让我们一步一步操作这个事情
1) hibernate.cfg.xml:
<?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.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernatedemo</property>
<property name="hibernate.connection.password">password</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">create</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<mapping class="hibernate.test.dto.DepartmentEntity"></mapping>
</session-factory>
</hibernate-configuration>
2) DepartmentEntity.java:
package hibernate.test.dto;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity (name = "dept")
@Table(name = "DEPARTMENT", uniqueConstraints = {
@UniqueConstraint(columnNames = "ID"),
@UniqueConstraint(columnNames = "NAME") })
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="department")
public class DepartmentEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID", unique = true, nullable = false)
private Integer id;
@Column(name = "NAME", unique = true, nullable = false, length = 100)
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3) HibernateUtil.java:
package hibernate.test;
import java.io.File;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
public class HibernateUtil
{
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory()
{
try
{
// Create the SessionFactory from hibernate.cfg.xml
return new AnnotationConfiguration().configure(new File("hibernate.cgf.xml")).buildSessionFactory();
}
catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public static void shutdown() {
// Close caches and connection pools
getSessionFactory().close();
}
}
4) TestHibernateEhcache.java:
public class TestHibernateEhcache
{
public static void main(String[] args)
{
storeData();
try
{
//Open the hibernate session
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
//fetch the department entity from database first time
DepartmentEntity department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());
//fetch the department entity again; Fetched from first level cache
department = (DepartmentEntity) session.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());
//Let's close the session
session.getTransaction().commit();
session.close();
//Try to get department in new session
Session anotherSession = HibernateUtil.getSessionFactory().openSession();
anotherSession.beginTransaction();
//Here entity is already in second level cache so no database query will be hit
department = (DepartmentEntity) anotherSession.load(DepartmentEntity.class, new Integer(1));
System.out.println(department.getName());
anotherSession.getTransaction().commit();
anotherSession.close();
}
finally
{
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getEntityFetchCount()); //Prints 1
System.out.println(HibernateUtil.getSessionFactory().getStatistics().getSecondLevelCacheHitCount()); //Prints 1
HibernateUtil.shutdown();
}
}
private static void storeData()
{
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
DepartmentEntity department = new DepartmentEntity();
department.setName("Human Resource");
session.save(department);
session.getTransaction().commit();
}
}
Output:
Hibernate: insert into DEPARTMENT (NAME) values (?)
Hibernate: select department0_.ID as ID0_0_, department0_.NAME as NAME0_0_ from DEPARTMENT department0_ where department0_.ID=?
Human Resource
Human Resource
Human Resource
1
1
在上边的输出结果中,我们可以看到,第一次department是从数据库中获取,下边两次是从缓存中获取,最后一次是从二级缓存中获取。
源码下载