Hibernate一级缓存
一级缓存也称为session级别的缓存,随着session的关闭而消失,load() / get() / iterator() 操作,会从一级缓存中查找数据,如果找不到,再到数据库里面查找。list()操作,如果没有配置查询缓存,将直接从数据库中获取数据。
一级缓存无法消除,但是可以通过使用Session的clear()、evict()等方法对其进行管理。
一级缓存很短和session的生命周期一致,一级缓存也叫session级的缓存或事务级缓存。一级缓存中只存放实体对象,除此之外什么都不存储。
哪些方法支持(使用)一级缓存:
get()、load()、iterate(查询实体对象)
如何管理一级缓存:
session.clear()、session.evict()
如何避免一次性大量的实体数据入库导致内存溢出
先flush,再clear
如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具。
Hibernate二级缓存
Hibernate内置实现了一个HashTable的二级缓存,该二级缓存一般只适用于测试、开发阶段的使用,在项目部署时应该使用第3方的缓存产品,例如:Jboss、Ehcache等。
二级缓存与一级缓存(Session级别缓存)是一样的,都是存储实体对象的,但是二级缓存通常称为进程级缓存(或SessionFactory级缓存),Session级别的缓存,两个Session之间是不能共享数据的,而二级缓存(SessionFactory级别的缓存)两个Session之间是可以共享数据的。
二级缓存由于属于进程级缓存(SessionFactory级缓存)所以可以通过SessionFactory来进行管理。
二级缓存的使用:
首先要打开二级缓存
默认情况下,二级缓存是打开的,可以通过如下配置来关闭二级缓存:
<property name="hibernate.cache.use_second_level_cache">false</property>
其次要指定缓存提供商,即有谁来提供缓存的功能。(详细可参考帮助文档19.2节)
配置如下:
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
注: Ehcache配置(不支持分布式查询)
EhCache配置:
将ehcache.xml文件拷入到项目的classPath路径中
主要部分如下:
<!-- 缺省配置,会对所有对象起作用 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!--
* maxElementsInMemory:指定缓存中最多存放多少个实体对象,默认值为:1万个
* eternal:表示缓存中的实体对象是否为永久有效(即永远都在缓存中),false:表示非永久有效,一般设置为:false
* timeToIdleSeconds:指定空闲时间,即当第1次访问之后,过了timeToIdleSeconds指定的时间后,该实体对象就失效,例如:设定存活时间为1000称,空闲时间为500秒,当第1次访问该对象后,在500秒之间没有访问第2次,则该对象就会失效,该对象将从缓存中被清除
* timeToLiveSeconds:指定缓存中的实体对象在缓存中能够存活多少时间,即缓存中实体对象的生命周期,单位为:秒
* overflowToDisk:指定如果超出maxElementsInMemory设定的数值后,多余的对象是否保存到磁盘上,那么保存的路径在哪?
说明:一般将timeToIdleSeconds属性值与timeToLiveSeconds属性值设为一致即可,
即就是<diskStore path="java.io.tmpdir"/>指定的,即系统临时路径,一般不需修改;该属性默认值为:true,表示进行磁盘保存
需要说明的是:timeToIdleSeconds属性与timeToLiveSeconds同eternal互斥,即配置timeToIdleSeconds属性与timeToLiveSeconds就需要将eternal设为false
设置eternal="true",则timeToIdleSeconds属性与timeToLiveSeconds将失效
-->
在映射文件中指定缓存策略:
最后要在映射文件中指定缓存策略,缓存策略有如下两种方式:
在实体映射文件中加入:
<cache usage="read-only"/>
这个缓存策略的配置一定要加上,否则便不会有缓存的作用,load()、list()、iterator()等操作的结果将都不会缓存。
注意,在实体映射文件中的class配置中添加<cache>配置,表示的是类缓存,如果把这个配置删除,将只缓存ID,不缓存整个对象,这里指的是查询缓存,即查询缓存除了缓存HQL语言查询出来的普通属性结果集外,还缓存实体对象的ID,前提是需要把查询缓存打开。(这个时候对list操作,也可能有n+1查询问题,参见查询缓存示例的testCache5()方法,即打开查询缓存,关闭二级缓存,执行list()方法)。
在<sessionFactory>标签里面嵌套定义这样的标签:
<class-cache class="com.domain.User" usage="read-only" />
代替直接将<cache>定义嵌套在实体映射文件中的<class>标签的内部。
注:建议使用该方式,因为这种方式便于查找和维护哪些实体类使用了二级缓存。
缓存策略:
缓存有几种形式,可以在映射文件中配置:
read-only
只读,适用于很少变更的静态数据/历史数据,效率高。
read-write
提供了Read-Committed事务隔离级别,适用于非集群的环境中使用,对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题,这种策略,当数据库中的数据一发生变化,会马上更新缓存中的实体对象。比较普遍的形式,效率一般。
nonstrict-read-write(不严格读写缓存)
如果基本不会发生有两个事务同时修改一个数据的时候,比read-write的性能要好,但是这种策略它不保证缓存与数据库中数据的一致性,如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读,对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
transactional
该策略仅仅在分布式的环境中适用(即支持JTA事务的容器中使用),它提供了可重复读事务隔离级别,对于经常被读便很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
支持该策略的缓存产品比较少,大部分都属于非免费的产品。
一级缓存与二级缓存之间的交互:
Session如何与二级缓存交互?
Session接口通过CacheMode来定制与二级缓存之间的交互方法:
session.setCacheMode(CacheMode.GET);
CacheMode.NORMAL:默认值,从二级缓存中读数据,也向二级缓存中写数据。
CacheMode.GET:只从二级缓存中读数据,仅在数据更新时向二级缓存中写入新数据
CacheMode.PUT:仅向二级缓存中写数据,但不从二级缓存中读数据,即使二级缓存有该数据也不读仍发出SQL语句查询数据,然后再将数据写入二级缓存。
二级缓存也称进程级的缓存或SessionFactory级的缓存,二级缓存可以被所有的session共享;二级缓存的生命周期和SessionFactory的生命周期一致,SessionFactory可以管理二级缓存。
二级缓存的配置和使用:
将echcache.xml文件拷贝到src下
开启二级缓存,修改hibernate.cfg.xml文件
<property name="hibernate.cache.use_second_level_cache">true</property>
指定缓存产品提供商,修改hibernate.cfg.xml文件
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
指定那些实体类使用二级缓存(两种方法)
在实体映射文件中采用<cache>标签
在hibernate.cfg.xml文件中,采用<class-cache>标签(推荐)
二级缓存是缓存实体对象的
了解一级缓存和二级缓存的交互:
CacheMode.GET
CacheMode.PUT
CacheMode.NORMAL
Hibernate查询缓存
查询缓存,它缓存的是普通属性结果集(即普通属性查询),那么查询缓存还缓存什么?
当进行实体对象查询时,它还缓存实体对象的id,而不缓存实体对象。
所谓查询缓存,即让Hibernate缓存list()、iterate()、createQuery()等方法的查询结果集。如果没有打开查询缓存,Hibernate将只缓存获得的持久对象。
在打开了查询缓存之后,需要注意,调用query.list()操作之前,必须显式调用query.setCachable(true)来标识某个查询使用缓存。
查询缓存默认情况下关闭,需要打开。查询缓存,对list()、iterator()这样的操作会起作用
可以使用:
<property name="hibernate.cache.use_query_cache">true</property>
来打开查询缓存,默认的情况下是关闭的。
【小结】
查询缓存(默认是关闭的)缓存的是:普通属性结果集,实体对象的id
list() 使用查询缓存,iterate() 不使用查询缓存
缓存中存储的数据:持久不变的数据
查询缓存的生命周期无法人为控制,与属性变化相关,与Session无关。
Hibernate 3.x——批量抓取策略
单端代理的批量抓取
(1)fetch="select"
如:
<many-to-one name="dept" column="deptid" fetch="select"/>
fetch属性的默认值为:select
含义:会另外发送一条select语句抓取当前对象关联的实体或集合。即指定采用什么方式抓取当前对象所关联的实体或集合(是发一条SQL语句还是发送两条SQL语句进行加载关联对象)
(2)fetch="join"
如:
<many-to-one name="dept" column="deptid" fetch="join"/>
含义: Hibernate会通过select语句使用外连接来加载其关联实体或集合。(即:采用的方式是发送一条SQL语句加载自己的普通属性和它的关联对象)
注意:此时lazy会失效
集合上的fetch策略
(1)fetch="select"
如:
<set name="users" inverse="true" cascade="delete" fetch="select">
<key column="deptid"/>
<one-to-many class="com.domain.User"/>
</set>
fetch属性的默认值为:select
含义:会另外发送一条select语句抓取当前对象关联的实体或集合。即指定采用什么方式抓取当前对象所关联的实体或集合(是发一条SQL语句还是发送两条SQL语句进行加载)
(2)fetch="join"
如:
<set name="users" inverse="true" cascade="delete" fetch="join">
<key column="deptid"/>
<one-to-many class="com.domain.User"/>
</set>
含义: Hibernate会通过select语句使用外连接来加载其关联实体或集合(即:采用的方式是发送一条SQL语句加载自己的普通属性及和它的关联对象)
注意:此时lazy会失效
(3)fetch="subselect"
如:
<set name="users" inverse="true" cascade="delete" fetch="subselect">
<key column="deptid"/>
<one-to-many class="com.domain.User"/>
</set>
含义:会另外发送一条select语句抓取前面查询到的所有实体对象所关联的实体或集合。即指定采用什么方式抓取当前对象所关联的实体或集合(是发一条SQL语句还是发送两条SQL语句进行加载)
fetch="subselect":采用子查询的方式加载前面查询到的所有实体对象所关联的实体或集合。
注意:fetch="subselect"只能在集合上设置,只有该值才会影响到HQL语句, fetch="subselect"不能在单端关联上设置。
单端代理的批量抓取
实例A引用实例B,B如果是代理的话(比如多对一关联中):
如果遍历A的查询结果集(假设有10条记录),在遍历A的时候,访问B变量,将会导致n次查询语句的发出!这个时候,如果在B一端的class上配置batch-size,Hibernate将会减少SQL语句的数量。(单端代理只能在对方的class属性上设置batch-size)
Hibernate可以充分有效的使用批量抓取,也就是说,如果仅一个访问代理(或集合),那么Hibernate将不载入其他未实例化的代理。批量抓取是延迟查询抓取的优化方案,你可以在两种批量抓取方案之间进行选择:在类级别和集合级别。
类/实体级别的批量抓取很容易理解。假设你在运行时将需要面对下面的问题:你在一个Session中载入了25个Cat实例,每个Cat实例都拥有一个引用成员owner,其指向Person,而Person类是代理,同时lazy="true"。 如果你必须遍历整个cats集合,对每个元素调用getOwner()方法,Hibernate将会默认的执行25次SELECT查询,得到其owner的代理对象。这时,你可以通过在映射文件的Person属性,显式声明batch-size,改变其行为:
<class name="Person" batch-size="10">...</class>随之,Hibernate将只需要执行三次查询,分别为10、10、 5。
集合代理的批量抓取
你也可以在集合级别定义批量抓取。例如,如果每个Person都拥有一个延迟载入的Cats集合,现在,Session中载入了10个person对象,遍历person集合将会引起10次SELECT查询, 每次查询都会调用getCats()方法。如果你在Person的映射定义部分,允许对cats批量抓取,那么,Hibernate将可以预先抓取整个集合。
batch-size在class上的应用
示例:
<class name="Person" batch-size="3">
<set name="cats" >
...
</set>
</class>
说明:如果整个的batch-size是3,那么Hibernate将会分四次执行SELECT查询,按照3、3、3、1的大小分别载入数据。
注: batch-size属性,可以批量加载所关联的实体对象,即发出的SQL语句(该SQL语句是由fetch属性指定的)每次加载batch-size指定数量的关联对象。
batch-size在集合上的应用
示例:
<set name="students" inverse="true" cascade="all" batch-size="3">
<key column="classesid"/>
<one-to-many class="Student"/>
</set>
说明:如果整个的batch-size是3,那么Hibernate将会分四次执行SELECT查询,按照3、3、3、1的大小分别载入数据。
batch-size属性,可以批量加载所关联的实体对象,即发出的SQL语句(该SQL语句是由fetch属性指定的)每次加载batch-size指定数量的关联对象。
fetch与batch-size区别:
fetch指明发送什么样的SQL语句加载关联对象,例如fetch=“select”,表示当加载关联对象时,采用发送另外的SQL语句加载所联联的对象,
而batch-size指的是每次发出这样的SQL语句(即fetch指明的SQL语句)加载多少个所关联的对象,即可以有效减少发出SQL语句的条数。
注:batch-size属性只能在<class>标签以及集合标签上进行设置。
批量更新(了解)
Jdbc fetch size:
每次取多少条数据,需要JDBC和底层数据库的支持。不会一次性把全部数据读入内存,而是按照一定的数量来批量读取相应的数据。
fetch size建议值是50
hibernate.jdbc.fetch_size
Jdbc batch size:
批量更新
建议取值30
hibernate.jdbc.batch_size
【总结】
使用Configuration装载映射文件时,不要使用绝对路径装载。最好的方式是通过getResourceAsStream()装载映射文件,这样Hibernate会从classpath中寻找已配置的映射文件。
SessionFactory的创建非常消耗资源,整个应用一般只要一个SessionFactory就够了,只有多个数据库的时候才会使用多个SessionFactory。
在整个应用中,Session和事务应该能够统一管理。(Spring为Hibernate提供了非常好的支持)
将所有的集合属性配置设置为懒加载(lazy=”true”或lazy=“extra”)。在hibernate2.x版本中,lazy默认值是“false”,但hibernate3.x已经将lazy的默认改为“true”了。
在定义关联关系时,集合首选Set,如果集合中的实体存在重复,则选择List(在定义配置文件时,可以将List定义为bag),数组的性能最差。
在一对多的双向关联中,一般将集合的inverse属性设置为true,让集合的对方维护关联关系。例如:Dept-User,由User来维护Dept和User的关联关系。
HQL子句本身大小写无关,但是其中出现的类名和属性名必须注意大小写区分。
如果要精通Hibernate,熟练掌握关系数据库理论和SQL是前提条件。