hibernate Hibernate检索策略(类级别,关联级别,批量检索)详解
Posted 路虽远,梦还在!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hibernate Hibernate检索策略(类级别,关联级别,批量检索)详解相关的知识,希望对你有一定的参考价值。
序言
很多看起来很难的东西其实并不难,关键是看自己是否花费了时间和精力去看,如果一个东西你能看得懂,同样的,别人也能看得懂,体现不出和别人的差距,所以当你觉得自己看了很多书或者学了很多东西的时候,你要想想,你花费的也就那么一点时间,别人花你这么多时间也能够学到你所学到的东西,所以还是要继续努力。既然不是天才,唯有靠勤奋来弥补。
--WZY
一、概述
检索策略分三大块,类级别检索策略和关联级别检测策略。
类级别检索策略:get、load、
关联级别检索策略:order.getCustomer().getName()
上面这两种应该是看得懂的。很容易去理解,现在就具体来说说这两种其中的细节。
批量检索解决n+1问题。
二、类级别检索策略
2.1、立即检索 get
直接发送sql语句,到数据库中去查询数据。
例如
1 Staff staff = (Staff)session.get(Staff.class, 3);//执行完这句,就会发送sql语句,到数据库表中查询相应的数据加入一级缓存中 2 3 //结果 4 Hibernate: 5 select 6 staff0_.id as id1_0_, 7 staff0_.name as name1_0_, 8 staff0_.deptId as deptId1_0_ 9 from 10 staff staff0_ 11 where 12 staff0_.id=?
2.2、延迟检索 load
不会直接发送sql语句,而是等到用的时候在发送sql语句,如果一直没用,就永远不会发送sql语句。
1 Staff staff = (Staff)session.load(Staff.class, 3);//执行完这句,不会发送sql语句 2 System.out.println("load后还没发送sql语句,等用到的时候才会发送。"); 3 System.out.println(staff.getName());//现在需要用staff。则会发送sql语句。 4 5 //结果 6 7 load后还没发送sql语句,等用到的时候才会发送。 8 Hibernate: 9 select 10 staff0_.id as id1_0_, 11 staff0_.name as name1_0_, 12 staff0_.deptId as deptId1_0_ 13 from 14 staff staff0_ 15 where 16 staff0_.id=? 17 qqq2
2.3、深入讲解get和load
上面两个只是简单讲解一下立即加载和延迟加载两个概念。现在来讲点深入的东西。
1、load检索返回的代理对象,而不是一个pojo对象,get返回的是pojo对象,这个的前提是一级缓存中没有我们要查询的对象。
2、get和load都是先从一级缓存中拿数据,而不是每次都从数据库中拿,也就是说如果一级缓存有我们需要的数据,就不会在发送sql语句了。并且返回就是一级缓存对象中对象的状态,也就是说如果在一级缓存中该对象的状态是pojo对象,那么就算是用load加载的,返回的也就是pojo对象,如果该对象是代理对象,那么就算get加载的,返回的也就是代理对象,不过会将代理对象的数据初始化。也就是会向数据库中发送sql语句查询数据。 解释:代理对象数据初始化:代理对象中包含了我们想要的pojo对象的所有信息。
例子:一级缓存中的是代理对象,使用get获得
1 Staff staff = (Staff)session.load(Staff.class, 3);//staff代理对象加入缓存中了 2 System.out.println("没有发送sql语句,get之后就会发送。"); 3 Staff staff1 = (Staff)session.get(Staff.class, 3);//get获取了缓存中的代理对象,并且初始化 4 5 //结果 6 没有发送sql语句,get之后就会发送。 7 Hibernate: 8 select 9 staff0_.id as id1_0_, 10 staff0_.name as name1_0_, 11 staff0_.deptId as deptId1_0_ 12 from 13 staff staff0_ 14 where 15 staff0_.id=?
例子: 一级缓存中是pojo对象,通过load获得
1 Staff staff = (Staff)session.get(Staff.class, 3);//staff pojo对象加入缓存中了 2 System.out.println("get后发送sql语句,并且该pojo对象在一级缓存中了。"); 3 Staff staff1 = (Staff)session.load(Staff.class, 3);//load获取了缓存中的pojo对象 4 5 //结果 6 Hibernate: 7 select 8 staff0_.id as id1_0_, 9 staff0_.name as name1_0_, 10 staff0_.deptId as deptId1_0_ 11 from 12 staff staff0_ 13 where 14 staff0_.id=? 15 get后发送sql语句,并且该pojo对象在一级缓存中了。
3、只有在使用时,代理对象才会初始化,其实还有一种方式可以不使用代理对象而初始化数据,
、
因为没有初始化代理对象,在关闭session后,在使用staff1,就会报错,报错内容为不能够初始化代码对象,没有session,
使用Hibernate.initialize(proxy);来对代理对象进行初始化,这个的效果和使用代理对象是一样的,但是会使代码看起来更好,如果你在这里system.out.println(代理对象),也有也可以,但是看起来总觉得乖乖的,所以hibernate就有了这个方法来对代理对象进行初始化。
4、可以通过lazy属性来设置让load不延迟加载,而跟get一样立即加载,因为是类级别检索,所以在hbm映射文件中的class位置进行属性设置。
在staff.hbm.xml中设置了lazy=false。意思是让其延迟加载失效,所以在对staff进行查询时,使用load也是立即检索
5、我们常说的,get如果查询数据库中没有的记录的话,返回是null,而load将会报错。这句话是正确的,但是概念很模糊,来看下面的路子看会不会报错。
例子一:查询数据库中没有的数据,id=100,load的时候会不会报错? 报错
例子二:查询数据库中没有的数据,id=100,并且将其取出id 不抱错
例子三:查询数据库中没有的数据,id=100,并且取出name 报错
总结load:load加载返回的是一个代理对象,并且我们说的用load查询一个数据库中没有的数据,并不是load这条语句报异常,而是在使用时,代理对象在数据库表中找不到数据而报的异常,所以单纯只写load语句,是不会报错的,并且代理对象的id是我们手动输入进去的,不用往数据库中查也知道,所以在代理对象.getId()时也不会发送sql语句,而是拿到我们一开始的id值。
2.4、load和get的区别(面试题)
1、get是立即加载、load是延迟加载
2、get和load都是先从缓存中查找对象,如果有该对象的缓存,则不向数据库中查询,并且返回的是缓存中对象的状态(是代理对象就返回代理对象,是pojo对象就返回pojo对象)
3、在缓存中没有对象时,get返回的是pojo对象,load返回的是代理对象
三、关联级别检索策略
在<set>标签上或者在<many-to-one>上设置两个属性值来控制其检索策略, fetch、lazy
fetch:代表检索时的语句的方式,比如左外连接等。
fetch:join、select 、subselect
lazy:是否延迟加载。
lazy:true、false、extra
分两种情况
3.1、一对多或多对多时
<set fetch="",lazy="">
fetch = join时,采取迫切左外连接查询
lazy不管取什么值都失效,就是取默认值为false。
fetch=select时,生成多条简单sql查询语句
lazy=false:立即检索
lazy=true:延迟检索
lazy=extra:加强延迟检索,非常懒惰,比延迟检索更加延迟
fetch=subselect时,生成子查询
lazy=false:立即检索
lazy=true;延迟检索
lazy=extra:加强延迟检索,非常懒惰,比延迟检索更加延迟
实验一:fetch=join 发送左外迫切连接
一、hql的query查询
使用hql的query查询,会让fetch=join失效,lazy重新启用。如果生成结果是延迟检索,那么就说明我们说的这个是正确的。
1 //使用hql查询,fetch=join是无效的,并且lazy重新启用,本来lazy失效的话, 2 //则使用默认的,就是立即加载,现在因为重新启用了,所以会是延迟加载 3 Query query = session.createQuery("from Dept where id = 2"); 4 Dept dept = (Dept) query.uniqueResult(); 5 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 6 dept.getStaffSet().size(); 7 8 //结果 9 10 Hibernate: 11 select 12 dept0_.id as id0_, 13 dept0_.name as name0_ 14 from 15 dept dept0_ 16 where 17 dept0_.id=2 18 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 19 Hibernate: 20 select 21 staffset0_.deptId as deptId0_1_, 22 staffset0_.id as id1_, 23 staffset0_.id as id1_0_, 24 staffset0_.name as name1_0_, 25 staffset0_.deptId as deptId1_0_ 26 from 27 staff staffset0_ 28 where 29 staffset0_.deptId=?
二、使用get查询。fetch=join生效,lazy就会失效,并且会发送左外迫切连接。这里要注意,要看set中存放的东西是什么,而不是看发送的语句是不是含有fetch来判断是不是左外迫切连接,因为hibernate中,左外迫切连接发送的语句跟左外连接发送的语句是一样的,从这里是区分不出来了。如果不信的话,自己可以去尝试一下,手动写一个左外迫切连接,然后看发送的语句是什么样的,我试过了,跟我说的一样
1 Dept dept = (Dept) session.get(Dept.class, 2); 2 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 3 Set<Staff> set = dept.getStaffSet(); 4 System.out.println(set); 5 6 //结果 7 Hibernate: 8 select 9 dept0_.id as id0_1_, 10 dept0_.name as name0_1_, 11 staffset1_.deptId as deptId0_3_, 12 staffset1_.id as id3_, 13 staffset1_.id as id1_0_, 14 staffset1_.name as name1_0_, 15 staffset1_.deptId as deptId1_0_ 16 from 17 dept dept0_ 18 left outer join 19 staff staffset1_ 20 on dept0_.id=staffset1_.deptId 21 where 22 dept0_.id=? 23 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 24 [oneToMany.Staff@367e4144, oneToMany.Staff@20a709f3, oneToMany.Staff@1dc39acc, oneToMany.Staff@1aeef34f, oneToMany.Staff@1d82e71, oneToMany.Staff@55a7e5ae, oneToMany.Staff@3da7d559, oneToMany.Staff@782d5c85, oneToMany.Staff@6a155d66]
总结第一种情况fetch=join。
1、注意我们这里讨论的是关联级别的检索方式,所以重点是看关联的时候发送的sql语句,重心不在get和load上面了
2、fetch=join让lazy失效的前提是使用的不是hql的query查询。
实验二:fetch=select时 发送简单sql语句
lazy=false;也就是立即检索,发送简单sql语句
1 Dept dept = (Dept) session.get(Dept.class, 2); 2 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 3 Set<Staff> set = dept.getStaffSet(); 4 System.out.println(set); 5 6 //结果 7 Hibernate: 8 select 9 dept0_.id as id0_0_, 10 dept0_.name as name0_0_ 11 from 12 dept dept0_ 13 where 14 dept0_.id=? 15 Hibernate: 16 select 17 staffset0_.deptId as deptId0_1_, 18 staffset0_.id as id1_, 19 staffset0_.id as id1_0_, 20 staffset0_.name as name1_0_, 21 staffset0_.deptId as deptId1_0_ 22 from 23 staff staffset0_ 24 where 25 staffset0_.deptId=? 26 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 27 [oneToMany.Staff@5091be16, oneToMany.Staff@5c1a555, oneToMany.Staff@753f827a, oneToMany.Staff@6c4d7266, oneToMany.Staff@58e83637, oneToMany.Staff@15dbd461, oneToMany.Staff@1056bfad, oneToMany.Staff@2f41ff3c, oneToMany.Staff@1c8f53b9]
lazy=true;延迟检索,发送简单sql语句
1 Dept dept = (Dept) session.get(Dept.class, 2); 2 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 3 Set<Staff> set = dept.getStaffSet(); 4 System.out.println(set); 5 6 //结果 7 Hibernate: 8 select 9 dept0_.id as id0_0_, 10 dept0_.name as name0_0_ 11 from 12 dept dept0_ 13 where 14 dept0_.id=? 15 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 16 Hibernate: 17 select 18 staffset0_.deptId as deptId0_1_, 19 staffset0_.id as id1_, 20 staffset0_.id as id1_0_, 21 staffset0_.name as name1_0_, 22 staffset0_.deptId as deptId1_0_ 23 from 24 staff staffset0_ 25 where 26 staffset0_.deptId=? 27 [oneToMany.Staff@5091be16, oneToMany.Staff@5c1a555, oneToMany.Staff@753f827a, oneToMany.Staff@6c4d7266, oneToMany.Staff@58e83637, oneToMany.Staff@1056bfad, oneToMany.Staff@2f41ff3c, oneToMany.Staff@1c8f53b9, oneToMany.Staff@645c1312]
lazy=extra;超级懒惰,能尽量少查就绝对不会多查,比如,size(),就会使用count()函数,而不会全部查表中的字段,就是这个意思
1 Dept dept = (Dept) session.get(Dept.class, 2); 2 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 3 Set<Staff> set = dept.getStaffSet(); 4 System.out.println(set.size()); 5 6 //结果 7 Hibernate: 8 select 9 dept0_.id as id0_0_, 10 dept0_.name as name0_0_ 11 from 12 dept dept0_ 13 where 14 dept0_.id=? 15 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 16 Hibernate: 17 select 18 count(id) 19 from 20 staff 21 where 22 deptId =? 23 9
实验三、fetch=subselect 生成子查询,注意使用get方式不生成子查询,使用query().list().get(),并且数据库表中还得不止一条记录才会生成子查询,如果只有一条记录,hibernate也很聪明,就没必要用子查询了。
lazy=false:立即检索
1 Dept dept = (Dept) session.createQuery("from Dept").list().get(0); 2 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 3 Set<Staff> set = dept.getStaffSet(); 4 System.out.println(set); 5 6 //结果 7 Hibernate: 8 select 9 dept0_.id as id0_, 10 dept0_.name as name0_ 11 from 12 dept dept0_ 13 Hibernate: 14 select 15 staffset0_.deptId as deptId0_1_, 16 staffset0_.id as id1_, 17 staffset0_.id as id1_0_, 18 staffset0_.name as name1_0_, 19 staffset0_.deptId as deptId1_0_ 20 from 21 staff staffset0_ 22 where 23 staffset0_.deptId in ( 24 select 25 dept0_.id 26 from 27 dept dept0_ 28 ) 29 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 30 [oneToMany.Staff@62c2bff3, oneToMany.Staff@583c72d7, oneToMany.Staff@6897ae82, oneToMany.Staff@19d5f3ea, oneToMany.Staff@3160c0bd, oneToMany.Staff@4af364d4, oneToMany.Staff@4afbc8ce, oneToMany.Staff@5fc81d2c, oneToMany.Staff@3e420e73]
lazy=true:延迟检索
lazy=extra;超级懒惰,比延迟检索还延迟
这两个其实也就差不多了,自己可以试试
3.2、多对一或一对一时
fetch可以取值为:join,select
lazy:false,proxy,no-proxy
当fetch=join,lazy会失效,生成的sql是迫切左外连接
如果我们使用query时,hql是我们自己指定的,那么fetch=join是无效的,不会生成迫切左外连接,这时lazy重新启用
当fetch=select,lazy不失效,生成简单sql语句,
lazy=false:立即检索
lazy=proxy:这时关联对象采用什么样的检索策略取决于关联对象的类级别检索策略.就是说参考<class>上的lazy的值
其实跟上面一样,我们是测试一个fetch=select,lazy=proxy的把。
staff,也就是多方,
dept,也就是一方
按照我们所配置的,关联级别检索应该是延迟检索,结果正如我们所想的。
1 Staff staff = (Staff) session.createQuery("from Staff").list().get(0); 2 System.out.println("如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载"); 3 Dept dept = staff.getDept(); 4 System.out.println(dept); 5 //结果 6 Hibernate: 7 select 8 staff0_.id as id1_, 9 staff0_.name as name1_, 10 staff0_.deptId as deptId1_ 11 from 12 staff staff0_ 13 如果sql语句在这句话之上说明是立即加载,如果在之下就是延迟加载 14 Hibernate: 15 select 16 dept0_.id as id0_0_, 17 dept0_.name as name0_0_ 18 from 19 dept dept0_ 20 where 21 dept0_.id=? 22 Dept [id=2, name=1部门]
总结:
1、为什么需要分(一对多,多对多)和(多对一,一对一)两组情况呢?
注意:这里说的一对多,那么就是单向一对多,也就是占在一的角度去考虑东西,上面说的四种都是从左边往右边看。
因为一对多和多对多,所拿到的关联对象度是一个集合,查询的记录就有很多个,也就多了一个fetch=subselect这个特性,查询方式的变化也就多一点,
而多对一,一对一,所拿到的关联对象就是一个对象,也就是一条记录,查询的方式比较单一和简单
因为上面的原因就把他们两个给分开来以处理不同的情况。达到更高的效率。
2、为什么需要搞这样的检索方式,不很麻烦吗?
根据不同的业务需求,来让开发者自己控制用什么样的检索方式,这样让程序的性能更好
四、批量检索
什么叫批量检索,就是多条sql语句才能完成的查询,现在一条sql语句就能解决多条sql语句才能完成的事情,看下面例子就明白了,
例子:n+1问题,什么叫n+1问题?
就拿我们上面这个例子来说,Dept和Staff,现在有5个部门,每个部门中的人数可能一样,也可能不一样,要求,查询每个部门中的员工。那么我们写的话就需要发送6条sql语句,哪6条呢?第一条查询部门表中所有的部门,剩下5条,拿到每一个部门的ID,去员工表中查找每个部门中的员工,要查5次,因为有5个部门。本来只有5个部门,现在需要发送6条sql语句,这就是n+1问题,看下面代码
总之就发送了6条sql语句,我已经数过了。
1 List<Dept> list = session.createQuery("from Dept").list(); 2 3 for(Dept dept : list){ 4 System.out.println(dept.getStaffSet()); 5 } 6 7 //结果 8 Hibernate: 9 select 10 dept0_.id as id0_, 11 dept0_.name as name0_ 12 from 13 dept dept0_ 14 Hibernate: 15 select 16 staffset0_.deptId as deptId0_1_, 17 staffset0_.id as id1_, 18 staffset0_.id as id1_0_, 19 staffset0_.name as name1_0_, 20 staffset0_.deptId as deptId1_0_ 21 from 22 staff staffset0_ 23 where 24 staffset0_.deptId=? 25 [oneToMany.Staff@ffdde88, oneToMany.Staff@1a33cda6, oneToMany.Staff@3160c0bd, oneToMany.Staff@48860e29, oneToMany.Staff@1538c1e3, oneToMany.Staff@339289a7, oneToMany.Staff@3f025aba, oneToMany.Staff@598b4d64, oneToMany.Staff@641cbaeb, oneToMany.Staff@590bcaf1] 26 Hibernate: 27 select 28 staffset0_.deptId as deptId0_1_, 29 staffset0_.id as id1_, 30 staffset0_.id as id1_0_, 31 staffset0_.name as name1_0_, 32 staffset0_.deptId as deptId1_0_ 33 from 34 staff staffset0_ 35 where 36 staffset0_.deptId=? 37 [oneToMany.Staff@650e7ac9, oneToMany.Staff@456ffab9, oneToMany.Staff@3ab5ab4c, oneToMany.Staff@5456df43, oneToMany.Staff@718bc0c4, oneToMany.Staff@7fde1684, oneToMany.Staff@6d0128b0, oneToMany.Staff@157f068f, oneToMany.Staff@55cfffa2, oneToMany.Staff@46e1d5d9, oneToMany.Staff@4d9875b1, oneToMany.Staff@104628c, oneToMany.Staff@4687a14f, oneToMany.Staff@135bd2f7, oneToMany.Staff@149ebdea, oneToMany.Staff@726f6db5, oneToMany.Staff@4a9810b1, oneToMany.Staff@72c5c2e7, oneToMany.Staff@e1cbe19, oneToMany.Staff@671672b8] 38 Hibernate: 39 select 40 staffset0_.deptId as deptId0_1_, 41 staffset0_.id as id1_, 42 staffset0_.id as id1_0_, 43 staffset0_.name as name1_0_, 44 staffset0_.deptId as deptId1_0_ 45 from 46 staff staffset0_ 47 where 48 staffset0_.deptId=? 49 [oneToMany.Staff@5ce4c86b, oneToMany.Staff@23081b81, oneToMany.Staff@132ff4b6, oneToMany.Staff@316ae291, oneToMany.Staff@480955df, oneToMany.Staff@30221872, oneToMany.Staff@6945c41e, oneToMany.Staff@1f43a98b, oneToMany.Staff@634ec390, oneToMany.Staff@e72fd0e] 50 Hibernate: 51 select 52 staffset0_.deptId as deptId0_1_, 53 staffset0_.id as id1_, 54 staffset0_.id as id1_0_, 55 staffset0_.name as name1_0_, 56 staffset0_.deptId as deptId1_0_ 57 from 58 staff staffset0_ 59 where 60 staffset0_.deptId=? 61 [oneToMany.Staff@269c2a55, oneToMany.Staff@4d45222c, oneToMany.Staff@1a66421c, oneToMany.Staff@2f7e49ce, oneToMany.Staff@42c51adb, oneToMany.Staff@5103c049, oneToMany.Staff@c1f8bbe, oneToMany.Staff@75c69e55, oneToMany.Staff@41c7d5a8, oneToMany.Staff@6b0f6d29] 62 Hibernate: 63 select 64 staffset0_.deptId as deptId0_1_, 65 staffset0_.id as id1_, 66 staffset0_.id as id1_0_, 67 staffset0_.name as name1_0_, 68 staffset0_.deptId as deptId1_0_ 69 from 70 staff staffset0_ 71 where 72 staffset0_.以上是关于hibernate Hibernate检索策略(类级别,关联级别,批量检索)详解的主要内容,如果未能解决你的问题,请参考以下文章