N+1问题
所谓N+1问题,使用hibernate查询数据,首先返回数据的id信息,并没有返回所有的对象信息,只有在真正使用的时候,在使用这个id对数据库中查询数据,一次查询多次调用数据库(缓存)的情况,就是所谓的N+1问题。以下方法或者策略会出现N+1问题:
1.使用iterate()方法
存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来
,如果使用iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取
/**
* Return the query results as an <tt>Iterator</tt>. If the query
* contains multiple results per row, the results are returned in
* an instance of <tt>Object[]</tt>.<br>
* <br>
* Entities returned as results are initialized on demand. The first
* SQL query returns identifiers only.<br>
*
* @return the result iterator
*/
Iterator<R> iterate();
2.使用查询缓存策略
一级缓存,session级别缓存
首次查询数据,会查询数据库返回数据,并且保存到缓存中;再次查询该数据时,直接从缓存中获取(同一个session)。
由于一级缓存是session级别的缓存,只有在同一个session中才能起到再次查询从缓存中获取数据。
比如:list()放入缓存,load查询从缓存中获取数据。
由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。
Session级缓存是必需的,不允许而且事实上也无法卸除。在Session级缓存中,持久化类的每个实例都具有唯一的ID
调用session的方法会加入一级缓存:save()、update()、savaeOrUpdate()、get()或load();调用查询接口的list()、iterate()或filter()方法
二级缓存(sessionFactory级别)
相关配置
下载ehcache相关包
在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 二级缓存的提供类 在hibernate4.0版本以后我们都是配置这个属性来指定二级缓存的提供类-->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 4.0之前版本的配置 <property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.EhCacheProvider</property>-->
<!-- 二级缓存配置文件的位置 -->
property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
配置ehcache.xml
<ehcache>
<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by
its value in the running VM.
The following properties are translated:
user.home - User's home directory
user.dir - User's current working directory
java.io.tmpdir - Default temp file path -->
<!--指定二级缓存存放在磁盘上的位置-->
<diskStore path="user.dir"/>
<!--我们可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置-->
<defaultCache
maxElementsInMemory="10000" //在内存中存放的最大对象数
eternal="false" //是否永久保存缓存,设置成false
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true" //如果对象数量超过内存中最大的数,是否将其保存到磁盘中,设置成true
/>
<!--
1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长;
2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长;
3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;
4、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;
5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。
-->
<!--可以给每个实体类指定一个配置文件,通过name属性指定,要使用类的全名-->
<cache name="com.sunld.bean.User"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/> -->
</ehcache>
开启缓存
①如果使用xml配置,我们需要在 *.hbm.xml 中加上一下配置
②如果使用annotation配置,我们需要在类上加上这样一个注解
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
缓存策略
read-only、nonstrict-read-write、read-write、transactional
二级缓存触发的方法
save、update、saveOrupdate、load、get、list、query、Criteria方法都会填充二级缓存
get、load、iterate会从二级缓存中取数据
session.save(user)
如果user主键使用“native”生成,则不放入二级缓存
执行顺序
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;
查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
其他
二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的查询缓存(Query Cache)。
解决N+1问题
当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题
先list在iterator
查询缓存(sessionFactory级别)
开启缓存
hibernate.cfg.xml
<!-- 开启查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
查询中需要调用方法
.setCacheable(true) //开启查询缓存,查询缓存也是SessionFactory级别的缓存
如果使用注解方式,需要在类上加入在类上加注解:@Cacheable
其他
只有当 HQL 查询语句完全相同时,连参数设置都要相同,此时查询缓存才有效
查询缓存也能引起 N+1 的问题,需要开启二级缓存。
查询普通属性,会先到查询缓存中取,如果没有,则查询数据库;
查询实体,会先到查询缓存中取id,如果有,则根据id到缓存(一级/二级)中取实体,如果缓存中取不到实体,再查询数据库。
缓存策略提供商
org.hibernate.cache.HashtableCacheProvider(内存)
org.hibernate.cache.EhCacheProvider(内存,硬盘)
org.hibernate.cache.OSCacheProvider(内存,硬盘)
org.hibernate.cache.SwarmCacheProvider(能用于集群环境)
org.hibernate.cache.TreeCacheProvider(能用于集群环境)
org.hibernate.cache.jbc.JBossCacheRegionFactory(能用于集群环境)