当你的系统出现性能瓶颈时,业务逻辑往往是难于改善的,而进行数据库的相关的改善,往往能大幅提高性能。在与数据库进行IO频率非常大的系统中,往往会加入缓存,缓存分为三个级别:事务级、应用程序级、分布式级(集群环境)。
事务级:缓存只在事务开启后,事务未提交或者关闭前起作用。
应用程序级:缓存在整个程序运行的过程中都起作用,仅局限于单个JVM。
分布式级:可以让缓存在多个JVM中共享。
Hibernate一级缓存Session
Hibernate中本身自带一级缓存Session,一般不用我们去理会,只要是在事务内的持久化操作,都会将对象加载到一级缓存中。常用的持久化操作有save、saveOrUpdate、update、get、load这几个方法的调用。Hibernate会自己去维护这一级缓存,并且在Session关闭的时候,将缓存清空。如果想要手工地去清空里面的内容,可以采用以下的两个方法:
evictI(Object obj):将某个持久化对象从Session中清除
clear():清空Session中所有的缓存
Hibernate二级缓存
Hibernate中带有二级缓存的简单实现,那就是SessionFactory,但是这个二级缓存只会将load方法持久化的对象加入到缓存中,并不能将一些其他的常用持久化操作得到的持久化对象加载到二级缓存中。所以,Hibernate中二级缓存SessionFactory只是测试使用,不过Hibernate为我们提供了二级缓存的多个第三方实现,不同的第三方二级缓存的实现支持不同的缓存级别,在这里不讲述其他的第三方二级缓存,只讲述Hibernate官方推荐的EhCache。在加入第三方二级缓存实现后,我们的以下操作都会将对象加入到二级缓存中:
save(Object obj)、saveOrUpdate(Object obj)、load(Class cls,Serializable id)、get(Class cls,Serializable id)、update(Object obj)、Query.list()、Query.uniqueResult()
在hibernate.cfg.xml配置文件中加入二级缓存,并将Query的查询也加入到二级缓存中
<!-- 开启二级缓存 -->
<property name="cache.use_second_level_cache">true</property>
<!-- 开启query缓存 -->
<property name="cache.use_query_cache">true</property>
<!-- 使用ehcache作为二级缓存实现 -->
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- ehcache配置文件所在路径 -->
<property name="cache.provider_configuration_file_resource_path">ehcache.xml</property>
EhCache需要自己的配置文件,来进行配置说明
本人的配置ehcache.xml配置文件如下:
<ehcache>
<diskStore path="java.io.tmpdir"></diskStore>
<!-- 默认设置 -->
<defaultCache maxElementsInMemory="10000"
memoryStoreEvictionPolicy="LRU"
eternal="true"
diskPersistent="false"
overflowToDisk="true"
maxElementsOnDisk="10000"
/>
<cache name="com.daniel.model.persistence.*" maxElementsInMemory="10000"
memoryStoreEvictionPolicy="LRU" eternal="false"
timeToIdleSeconds="86300" timeToLiveSeconds="86400"
diskPersistent="false" overflowToDisk="true"
maxElementsOnDisk="10000"
/>
</ehcache>
关于EhCache的配置说明
缓存在正常情况下是存储在内存中的,如果你设置的缓存内存被用完了,那么就会将一些数据放置到磁盘上,所以<diskStore>标签是用来指定如果数据要存放到磁盘上,存放的路径,java.io.tmpdir将会在项目的classpath下形成一个java/io/tmdir的文件夹树,磁盘数据将会被写到这里。
<defaultCache>是用来配置默认的缓存区域的,这个是必须配置的,所有没有指定缓存区域的类,在进行持久化操作后都会被缓存到默认的缓存区域,这个元素有很多属性,maxElementsInMemory指的是在内存中存放的持久化对象个数。memoryStoryEvictionPolicy指的是缓存的算法选择,这个值有三个选择:LRU(最近最长时间没有被使用)、LFU(最近使用频度最低,也就是使用次数最少)、FIFO(先进先出),在通常情况下,LRU有这比较优的性能,是首选。eternal指的是内存中的对象是否可以更改,一般选择true,如果某个区域的对象是只读的,那么可以选择false。overFlowToDisk是否在缓存满时写到磁盘,true为写到 磁盘,false为不写到磁盘。maxElementsOnDisk指的是在磁盘上可以存放的最大缓存对象数目。
<cache>元素是声明一个缓存区域,name属性是类名,指的是某个类的缓存存放到这块缓存区域。其他的几点配置同<defaultCache>。timeToIdleSeconds是数据钝化时间,单位为秒。timeToLiveSeconds是数据存活时间,单位为秒。
配置持久化类的关系映射文件
Hibernate需要在持久化类关系映射文件中配置缓存方式,同样,分为在实体中配置缓存方式和在实体的关联实体中配置缓存方式。缓存方式分为以下的四种:
read-only:只读缓存,如果更改会引发异常。
read-write:严格读写缓存,使用基于时间戳的机制,并且支持事务隔离,如果缓存中有数据被更改,那么将重新查询数据库。
nonstrict-read-write:非严格读写,如果数据更新频度较低,可以使用该方式,来获取不错的性能。
transaction:事务型缓存,只有在使用JTA时能使用。
下面是一个配置文件的实例:
<hibernate-mapping>
<class name="com.daniel.model.persistence.User" table="t_user" dynamic-update="true">
<cache usage="read-write"/>
<id column="id" name="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="username" column="username" type="java.lang.String" length="20" unique="true"></property>
<property name="password" column="password" type="java.lang.String" length="20"></property>
<set name="boards" lazy="false" cascade="all" outer-join="true">
<cache usage="read-write"/>
<key column="user_id"></key>
<one-to-many class="com.daniel.model.persistence.Board"/>
</set>
<set name="articles" lazy="false" cascade="all" outer-join="true">
<cache usage="read-write"/>
<key column="user_id"></key>
<one-to-many class="com.daniel.model.persistence.Article"/>
</set>
</class>
</hibernate-mapping>
需要知道的是,无论是实体缓存方式还是实体的关联实体的缓存方式,都是默认为read-only的。而且,<cache>必须出现在class元素的一个孩子节点位置。同理,如果存在关联实体,那么关联实体的<cache>元素也只能放在第一个孩子节点处。一般来说,选择的缓存方式都是read-write。
在代码中开启Query缓存
即时你在上述所有的东西都配置好了,你要在Query中加入缓存,也需要在代码中这样子写:
Query.setCacheable(true);
这样,Query在二级缓存才起到作用。