通过看孙卫琴的精通Hibernate和平时自己的使用,对他的几种检索策略有了更深的认识,再次总结一下
问题的引出:
Customer和Order的经典一对多场景<o:p></o:p>
Customer表
<o:p>
ID<o:p></o:p> | ORDER_NUMBER<o:p></o:p> | CUSTOMER_ID<o:p></o:p> |
1<o:p></o:p> | Tom_order001<o:p></o:p> | 1<o:p></o:p> |
2<o:p></o:p> | Tom_order002<o:p></o:p> | 1<o:p></o:p> |
3<o:p></o:p> | Mike_order001<o:p></o:p> | 2<o:p></o:p> |
4<o:p></o:p> | Jack_order001<o:p></o:p> | 3<o:p></o:p> |
5<o:p></o:p> | TLinda_order001<o:p></o:p> | 4<o:p></o:p> |
6<o:p></o:p> | Unknown_order001<o:p></o:p> | null<o:p></o:p> |
Oder表<o:p></o:p>
ID<o:p></o:p> | NAME<o:p></o:p> |
1<o:p></o:p> | Tom<o:p></o:p> |
2<o:p></o:p> | Mike<o:p></o:p> |
3<o:p></o:p> | Jack<o:p></o:p> |
4<o:p></o:p> | Linda<o:p></o:p> |
在Session缓存中存放的就相互关联的对象图,从数据库中加载Customer时,会同时加载所有关联的Order对象,这就产生了如下问题:<o:p></o:p>
当执行session的find方法查询所有customer对象时<o:p></o:p>
- List customers = session.find(“from Customer as c”);
运行find方法时,Hibernate将先查询CUSTOMERS表中的所有记录,然后根据记录ID到ORDER表里查询相关的记录.Hibernate依次执行一下select语句:<o:p></o:p>
- Select * from CUSTOMERS;
- Select * from ORDERS where CUSTOMER_ID=1;
- Select * from ORDERS where CUSTOMER_ID=2;
- Select * from ORDERS where CUSTOMER_ID=3;
- Select * from ORDERS where CUSTOMER_ID=4;
通过以上5条select语句,Hibernate最后加载了4个Customer对象和5个Order对象.Hiberante在检索语customer关联的Order对象时使用了默认的立即检索策略,这种方式存在两大缺点<o:p></o:p>
1. select语句太多,频繁访问DB会影响检索性能.如果需要查询n个Customer对象,那么必须执行n+1次select语句,这种检索策略没有利用sql的连接查询功能,<o:p></o:p>
如以上5条语句可以用1句左外连接来完成<o:p></o:p>
<o:p>- select * from CUSTOMERS left outer join ORDERS on CUSTOMERS.ID=ORDERS.CUSTOMERS_ID
查询出了所有CUSTOMERS表所有记录和匹配的ORDERS表记录<o:p></o:p>
2.如果只需要访问Customer对象,不需要Order对象时,加载Order对象时多余的操作,而且浪费了许多内存空间.<o:p></o:p>
<o:p> Hibernate的三种检索策略,为了解决立即检索的问题,Hibernate提供了其他两种检索策略,即延迟和迫切左外连接检索.</o:p>
<o:p>
一.立即检索<o:p></o:p>
Hibernate的默认检索策略,当执行session 的查询方法时,会把相关联的表的数据全部查询出来.
二延迟检索<o:p></o:p>
包括类级别和集合级别的延迟加载.<o:p></o:p>
1.类级别<o:p></o:p>
某个类采用延迟加载<o:p></o:p>
<class name="”…’" lazy="”true”"></class><o:p></o:p>
只和session的load方法有关,执行load方法时仅返回Customer的代理类实例.<o:p></o:p>
代理类实例有一下特征<o:p></o:p>
1)有Hibernate动态生成,扩展了Customer类,因此它继承了Customer的所有属性和放,但对于应用程序是透明的.<o:p></o:p>
2)词代理类实例仅初始化了OID属性,其他属性为null,因此它占的内存很少<o:p></o:p>
3)第一次访问代理类实例的属性(除了OID属性,它已有值)时, Hiberante会初始化此实例,产生select语句.<o:p></o:p>
注:Hibernate采用CGLIB工具来生成持久化类的代理类CGLIB是一个功能强大的JAVA字节码生成工具,它能在程序运行时动态生成扩展Java类或者实现Java接口的代理类.关于CGLIB的更多信息,可到网站http://cglib.sourceforge.net了解.,<o:p></o:p>
2.集合级别<o:p></o:p>
当不使用相关表的数据时, 在Custom映射文件中set属性设置的 lazy="true",此时不会立即检索相关表,
需注意<o:p></o:p>
1)并没有创建Order代理类实例,也无法创建因为还不知道与Customer关联的所有Order对象的OID.这时Customer的orders属性引用的是Hibernate提供的集合代理类实例.<o:p></o:p>
2)当应用程序第一次要使用相关表的数据时,才从DB的相关表中检索相关数据。<o:p></o:p>
3)只有当集合代理类的实例处于持久化状态时,才可以初始化,以后才可使用,否则会抛出延迟初始化错误:ERROR:LazerInitiater:63........<o:p></o:p>
三.迫切左外连接<o:p></o:p>
默认情况下,多对一关联采用的方法。如果把映射文件的<many-to-one></many-to-one>many-to-one元素outer-join 值设为"true",则总是采用此策略。<o:p></o:p>
Hibernate允许在应用程序的HQL语句中显示指定迫切左外连接检索,它会覆盖配置文件的检索策略。<o:p></o:p>
如
- session.find(" from Customer as c where c.id=1 " );
- session.find(" from Customer as c left join fetch c.orders where c.id=1 " )
第一句会采用映射文件中的检索策略,而第二句会覆盖映射文件的策略。
下面是三种检索策略的比较
|
总之,对于实际的应用,为了选择合适的检索策略,需要测试应用程序的各个用例,跟踪使用不用检索策略时Hibernate执行的sql语句,可以百Hibernate的配置文件的showsql属性设为true,输出sql。根据特定的关系模型,比较查询性能。
- select * from CUSTOMERS left outer join ORDERS on CUSTOMERS.ID=ORDERS.CUSTOMERS_ID where CUSTOMERS.ID=1
- select * from CUSTOMERS
- select * from ORDERS where CUSTOMER_ID=1