抓取策略可以在O/R映射的元数据中声明,也可以在特定的HQL或者QBC中声明。
1.首先区分两个正交的概念:关联何时被抓取、关联如何被抓取
1.1如何抓取(hibernate支持的抓取方式):
- Join fetching:在select语句中使用外连接来获得对象的关联实例或关联集合
- select fetching:在访问关联关系的时候发出一条sql语句获取当前对象的关联实体或集合
- SubSelect fetching:在访问关联关系的时候发出一条sql语句获取在前面查询到(或者抓取到)的所有实体对象的关联实体或集合
- Batch fetching:通过指定主外键列表,发送一条sql来获取多行数据(select * from users u where u.id in (?,?,?,?,?))
1.2何时抓取(hibernate支持的方式):
- 立即抓取:当宿主被加载时,关联集合或实体立即被加载
- 延迟集合抓取:只有当程序访问关联属性时,才抓取整个集合 (一对多,hql查询一方默认)
- "Extra-lazy" 集合抓取:只有当程序访问关联属性时,才抓取集合中所需要的元素(不是整个集合)
- 代理抓取:对返回单值的关联对象而言,当访问其非主键的内容时才抓取 (多对一,hql查询多方默认)
- 非代理抓取:对返回单值的关联对象而言,当访问其任何内容时都会被抓取
2.通常使用的抓取策略(解决N+1问题的方法一)
通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用 HQL 的左连接抓取(left join fetch)
对其进行重载。
List<Topic> topics = (List<Topic>)session.createQuery("from Topic t left join fetch t.category c").list();
这样做使hibernate查询宿主对象时的sql语句使用outer join直接获取到了关联对象的数据
3.实例化集合和代理
3.1首先,我们需要明确
- 使用load()方法返回的是代理对象,使用get()方法返回的是Entity实例对象。
3.2避免异常的措施
- 使用静态方法
Hibernate.initialize(c1.getTopics()); //强制抓取关联对象
- 保证session处于open状态:
使用open session in view 模式
<!-- openSessionInView -->
<filter>
<filter-name>OpenSessionInView</filter-name>
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
必须配置在所有Filter之前
在一个有单独业务层的应用中,使用Hibernate.initialize()方法在业务返回前为web准备好了所有所需要的数据
4.使用批量抓取(解决N+1问题的方法二)
使用 @BatchSize 注解加到关联对象上
@Entity
@BatchSize(size=5)
public class Category {
......
}
这样hibernate将会采取
Batch fetching抓取策略
5.get()和load()的区别
- get()返回的是Entity实例对象,load()返回的是代理对象
- 当使用get()查询先找一级缓存,若没有立即发出sql语句查询;使用load()先后去一级缓存、二级缓存查找,若找不到返回代理对象,当访问对象的非id内容时发出sql
- 当数据库中没有相应记录时,get()返回null, load() 抛出 ObjectNotFoundException