详解Hibernate缓存与性能优化
缓存概念
缓存 介于应用程序和永久性数据源(文件,数据库等)之间,作用就是降低应用程序直接读取数据源的频率,从而提高应用程序的运行性能。缓存中的数据就是数据源中数据的复制,应用程序在运行时直接读取缓存中的数据。
缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘或磁盘,应用程序读写内存的速度显然比读写硬盘的速度快。如果缓存存放的数据非常大,也会用硬盘作为缓存的物理介质。
hibernate缓存分类
在hibernate中提供了二种缓存机制:一级缓存、二级缓存,因为二级缓存策略是针对于id查询的缓存策略,对于条件查询则毫无作用,为此,hibernate提供了针对条件查询的query cache(查询缓存)
1、一级缓存。session缓存就是一级缓存。由于session对象的生命周期通常对应一个数据库事物,因此他的缓存范围是事物范围的缓存。一级缓存是必需的,在一级缓存中,持久化类的每个实例都具有唯一的oid;
2、二级缓存。sessionfactory分为内置缓存和外置缓存。
内置缓存是hibernate自带的,不可拆卸,是只读缓存,用来存放映射元数据和预定义sql语句。
外置缓存是一个可配置的缓存插件,默认sessionfactory不会启用这个缓存插件,外置缓存中的数据就是数据库数据的复制。sessionfactory的外置缓存称为hibernate的二级缓存
二级缓存由sessionfactory负责管理,sessionfactory的生命周期和应用程序的整个进程对应。二级缓存是可选的,可以在每个类或者每个集合的粒度上配置
3、查询缓存 它是hibernate为查询结果提供的,依赖于二级缓存。
缓存的作用范围
- 事物范围 每个事物都有自己的缓存,缓存内数据不会被多个事物并发访问。例如,hibernate的一级缓存,事物是不能跨多个session的,session内数据只能被当前事物访问,因此它属于事物范围内的缓存。
- 进程范围 进程内的所有事物共享缓存,进程结束,缓存结束生命周期。例如,hibernate的二级缓存,sessionfactory对象的生命周期对应应用程序的整个进程,因此它属于进程范围的缓存。
- 集群范围 缓存被一个或多个机器上的进程共享。hibernate的二级缓存也可以作为集群范围的缓存。
hibernate 一级缓存
session内的缓存即一级缓存。位于缓存中的对象称为持久化对象,它和数据库中的相关记录对应。session能够在某些时间点(session.flush();
,tx.commit();
),按照缓存中对象的变化来执行相关的sql语句,从而同步更新数据库,这一过程称为刷新缓存。
当应用程序调用 session的 ‘save()
,update()
,saveorupdate()
,load()
,get()
'等方法,以及调用query查询接口的' getresultlist()
'时,如果在'session'缓存中还不存在相应的对象,hibernate就会把该对象加入到缓存中,在刷新缓存时,hibernate会根据缓存中对象的状态变化来同步更新数据库。
综上所述,session缓存有两大作用:
- 减少访问数据库的频率
- 保证数据库中的相关记录和缓存中的相应对象同步
session缓存管理方法
-
evict();
从session缓存中清除某个对象 -
clear();
清空session缓存
ps: flush()
强制进行从缓存到数据库的同步
hibernate 二级缓存
二级缓存是进程或集群范围内的缓存,可以被所有的session共享,其生命周期和sessionfactory一样。
二级缓存是可配置的插件,hibernate打包了一些开源缓存实现,提供对他们的内置支持
缓存插件 | 缓存实现类 | 查询缓存 |
---|---|---|
ehcache | org.hibernate.cache.ehcacheprovider | 支持 |
oscache | org.hibernate.cache.oscacheprovider | 支持 |
swarmcache | org.hibernate.cache.swarmcacheprovider | 不支持 |
jbosscache | org.hibernate.cache.treecacheprovider | 支持 |
为了把上边的缓存插件集成到hibernate中,hibernate提供了cacheprovider接口,它是缓存插件与hibernate之间的适配器。
表格中的实现类是cacheprovider接口的不同实现。
配置二级缓存的步骤如下:
- 选择合适的缓存插件,配置其自带的配置文件
- 选择需要使用二级缓存的持久化类,设置它的二级缓存的并发访问策略。
以ehcache配置为例,步骤如下
1、将ehcache.xml文件添加到类路径下
在路径'hibernate-release-5.2.6.final\project\etc\'下复制'ehcache.xml'
标签为每个需要二级缓存的类和集合设定缓存的数据过期策略,配置如下
<cache name="samplecache1" -- 缓存的名称,取值为类的完整名称或类的集合名称 maxelementsinmemory="10000" -- 基于缓存可存放的对象的最大数目 eternal="false" -- 如果为true,表示对象永不过期,默认为false timetoidleseconds="300" -- 设置允许对象处于空闲状态的最长时间,单位是秒 timetoliveseconds="600" -- 设置对象允许存在于缓存中最长时间,单位是秒 overflowtodisk="true" -- 是否将溢出的对象写到基于硬盘的缓存中 />
2、开启二级缓存,在hibernate.cfg.xml配置
<!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property>
3、指定缓存产品提供商
<!-- 指定缓存产品提供商 --> <property name="hibernate.cache.provider_class"> <!-- net.sf.ehcache.hibernate.ehcacheprovider --> org.hibernate.cache.ehcacheprovider </property> <property name="cache.region.factory_class">org.hibernate.cache.ehcacheregionfactory</property>
4、指定使用二级缓存的持久化类。修改持久化类的映射文件,为元素添加元素,配置如下:
<?xml version="1.0" encoding="utf-8"?> <!doctype hibernate-mapping public "-//hibernate/hibernate mapping dtd 3.0//en" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name= "com.ytzl.demo.entity.dept" table ="dept" dynamic-update="true" > <cache usage="read-write"/> <id name="id" column="d_id" type="java.lang.integer"> <generator class="increment"></generator> </id> <property name="name" column="d_name" type="java.lang.string"></property> </class> </hibernate-mapping>
cache 的属性
- usage 是必须的,指定并发访问策略,取值为 transactional(事物缓存),read-write(读/写缓存),nonstrict-read-wirte(非严格读/写缓存),或read-only(只读缓存)。
- region 可选,默认为类或集合的名字
- include 可选,取值为non-lazy(当缓存一个对象时,不会缓存它的映射为延迟加载的属性)、all,默认值为all
或者在 hibernate.cfg.xml 的mapping元素后面统一配置 (推荐)
<class-cache usage="read-write" class="com.ytzl.demo.entity.dept"/>
关闭二级缓存交互
有时候考虑到内存开销问题,需要关闭与二级缓存的交互,可以调用session的' setcachemode(cachemode.ignore)
方法关闭与二级缓存的交互;
cachemode.ignore
参数的意思是当前session和二级缓存不再相互作用
二级缓存使用场景
二级缓存并非适合所有场景,使用不当,反而会降低性能。符合如下条件就适合放入二级缓存
- 很少修改的数据
- 不是很关键的数据,能容忍短时间内督导过期数据
- 应用参考的常量数据。它的实例数目有限,实例会被许多其他类的实例引用,实例极少或从来不被修改
二级缓存不适用场景
- 经常修改的数据
- 财务数据,绝对不允许读到过期数据
- 与其他应用共享的数据。如果其他应用修改了数据库中的数据,hibernate无法自动保证二级缓存的数据与数据库一致
如果不设置“查询缓存”,那么hibernate只会缓存单个持久化对象,如果想缓存使用 findall()
、list()
、iterator()
、createcriteria()
、createquery()
等方法获得的数据结果集的话, 就需要在配置文件中设置 hibernate.cache.use_query_cache true
才行
hibernate查询缓存
上面说到的二级查询,只有在基于id查找对象时才会用到,对于查询则毫无用处。为此,hibernate提供了针对的查询的查询缓存。
查询缓存依赖于二级缓存,因此使用查询缓存之前要按步骤配置好二级缓存
使用查询缓存的步骤如下
1、在hibernate.cfg.xml中开启查询缓存
<!-- 查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property>
2、在程序中启用查询缓存
query.setcacheable(true);
查询缓存的使用场景
- 经常使用的查询语句
- 对于查询的数据很少有插入、删除或者更新操作
hibernate性能优化
hibernate主要从一下几个方面来优化查询性能
- 使用迫切左外链接或迫切内链接查询策略、查询缓存等方式,减少select语句的数目,降低访问数据库的频率
- 使用延迟加载查询策略等方式避免加载多余的不需要访问的数据
- 使用query接口的
iterate()
方法减少select语句中的字段,从而降低访问数据库的数据量
hql优化
hql优化hibernate程序性能优化的一个方面,hql的语法和sql非常类似。hql是基于sql的,只是增加了面向对象的封装,如果抛开hql通hibernate本身一些缓存机制的关联,hql的优化技巧通sql的优化技巧一样,在编写hql时,需要主要以下几个原则
- 避免 or操作的使用不当。如果where子句中有多个条件,并且其中某个条件没有索引,使用or,将导致全表扫描。
- 避免使用 not 。如果where子句的条件包含not关键字,那么执行时该字段的索引失效。这些需要分成不同情况区别对待,对于 不大于(不多于)、不小于(不少于)建议使用运算符来替代not
- 避免like的特殊形式。某些情况下,会在where子句条件中使用用like。如果like以一个“%”或“_”开始即前模糊,则该字段的索引不起作用。目前没有什么解决 办法。
- 避免 having子句。在分组查询中,可在两个位置指定条件,一是where子句中,二是having子句中。尽可能的在where子句而不是在having子句中指定条件。having是在检索出所有记录后对结果集进行过滤。这个处理需要一定的开销,而where子句限制记录数目,能减少这方面的开销
- 避免使用 distinct 。指定distinct会导致在结果中删除重复的行,这会对处理时间造成一定的影响。
- 索引在以下情况失效,应注意使用
只要对字段使用函数,该字段的索引将不起作用。
只要对该字段进行计算,该字段的索引将不起作用。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。