Hibernate知识点复习之四

Hibernate的检索

检索方式分类:对象图导航检索方式,OID检索方式,HQL检索方式,QBC检索方式,SQL检索方式

1 对象图导航检索方式:

根据已加载的对象,导航到他的关联对象,它是利用类与类的关系来检索对象,如要查找一个联系人对应的客户,
就可以由联系人对象自动导航找到联系人所属的客户对象,前提是必须在映射文件中配置多对一的关系,其检索方式如下:
LinkMan linkMan=(LinkMan)session.get(Customer.class,1L); 
Customer customer=linkMan.getCustomer();

2 OID检索方式

概述:OID检索方式主要指用Session的get()和load()方法加载某条记录对应的对象,方式如下:
		Customer customer1 = session.get(Customer.class,3L);
		Customer customer2 = session.load(Customer.class,4L);
底层深入分析get()和load()的区别:

(1)get不支持lazyd(懒加载/延迟加载),load支持lazy
    lazy表示只有在用到的时候才加载数据,
    如:Student student = (Student)session.load(Student.class,1);  //不会发出SQL语句
		  student.getName();   //这条语句才会发出SQL语句
     而使用get方法,Student student = (Student)session.get(Student.class,1);  //会发出SQL语句
(2) 采用get加载数据,如果数据库中不存在相应的数据,那么返回null;
    采用load加载数据,如果数据库中不存在相应的数据,那么抛出ObjectNotFoundException
 (3)load方法可返回实体的代理类实例,而get方法永远直接返回实体类。
 (4) load方法可以充分利用内部缓存和二级缓存中的现有数据
     get方法则仅仅在内部缓存中进行数据查找,如没有发现对应数据,将越过二级缓存,直接调用SQL完成数据读取。
Session在加载实体对象时,将经过的过程:
	首先,Hibernate中维持了两级缓存。第一级缓存由Session实例维护,其中保持了Session当前所有关联实体的数据,也称为内部缓存。
			而第二级缓存则存在于SessionFactory层次,由当前所有由本SessionFactory构造的Session实例共享。
			出于性能考虑,避免无谓的数据库访问,Session在调用数据库查询功能之前,会先在缓存中进行查询。
			首先在第一级缓存中,通过实体类型和id进行查找,如果第一级缓存查找命中,且数据状态合法,则直接返回。
			之后,Session会在当前“NonExists”记录中进行查找,如果“NonExists”记录中存在同样的查询条件,则返回null。
		 “NonExists”记录了当前Session实例在之前所有查询操作中,未能查询到有效数据的查询条件(相当于一个查询黑名单列表)。
			如此一来,如果Session中一个无效的查询条件重复出现,即可迅速作出判断,从而获得最佳的性能表现。
		对于load方法而言
			如果内部缓存中未发现有效数据,则查询第二级缓存,如果第二级缓存命中,则返回。
			如在缓存中未发现有效数据,则发起数据库查询操作(Select SQL),如经过查询未发现对应记录,
			则将此次查询的信息在“NonExists”中加以记录,并返回null。
			根据映射配置和Select SQL得到的ResultSet,创建对应的数据对象。
			将其数据对象纳入当前Session实体管理容器(一级缓存)。
			执行Interceptor.onLoad方法(如果有对应的Interceptor)。
			将数据对象纳入二级缓存。
			如果数据对象实现了LifeCycle接口,则调用数据对象的onLoad方法。
			返回数据对象。

3 HQL检索方式(Hibernate官方推荐的查询语言):

概述:HQL是面对对象的查询语言,它和SQL查询语言相似,但它使用的是类,对象和属性概念,没有表和字段概念。
功能:
		 * 在查询语句中设定查询条件
		  * 支持动态绑定参数
		  * 支持分组查询,允许使用group by和having关键字
		 * 提供内置的聚合函数,如sum()和min(),max()
		  * 支持子查询,即嵌套查询
		 * 支持分页查询
		 * 支持投影查询,即仅检索出对象的部分属性
		 * 能够调用用户定义的sql函数
完整的hql语句结构:
	select ...from...where...group by ...having... order by ...asc/desc
HQl检索API示例:
	1 hql基本检索
		String hql="from Customer";
		//String hql="select * from Customer";此句Hibernate无法识别*通配符
		//String hql="select c from Customer c;使用select 需要为Customer起别名
		Query query = session.createQuery(hql);
		List<Customer> list = query.list();
	2 排序检索
		String hql="from Customer order by cust_id desc";
		Query query = session.createQuery(hql);
		List<Customer> list = query.list();
	3 条件查询:
		按位置绑定参数
		String hql1="from Customer where cust_id = ?";
		Query query1 = session.createQuery(hql1);
		query1.setParameter(0,9L);
		Customer c1 = (Customer) query1.uniqueResult();
		按名称绑定参数
		String hql2="from Customer where cust_id =:myId";
		Query query2 = session.createQuery(hql2);
		query2.setParameter("myId",8L);
		Customer c2 = (Customer) query2.uniqueResult();
	4 分页检索
		String hql="from Customer";
		Query query = session.createQuery(hql);
		//设置分页
		query.setFirstResult(0);
		query.setMaxResults(5);
		List<Customer> list = query.list();
	5 统计检索:count()总记录,sum()算术总和,avg()算术平均值,min()算术最小值,max()算术最大值
		String hql="select count(*) from Customer";
		String hql3="select sum(cust_id) from Customer";
		String hql4="select avg(cust_id) from Customer";
		String hql5="select max(cust_id) from Customer";
		String hql6="select min(cust_id) from Customer";
	6 投影查询--也就是查询对象的某一个属性
		查询单个属性
		String hql="select cust_name from cn.itheima.domain.Customer"; //所有Customer的cust_name属性
		查询多个属性
		String hql2="select cust_name,cust_id from cn.itheima.domain.Customer";
	7 投影的构造方式查询:
		结果识别为Customer实体的属性
		但前提是:Customer实体中一定要存在 相应的构造方法(空参和实参都要有)
		String hql3="select new Customer(cust_id,cust_name) from cn.itheima.domain.Customer";

4 QBC检索:

	1 概述:QBC(Query By Criteria)它由Criteria接口,Criterion接口,Expression类组成。
		Criteria接口是HibernateAPI的一个查询接口,它由Session进行创建。
		Criterion是Criteria的查询条件,在Criteria中提供add(Criterion criterion)方法来添加查询条件
		而Criterion查询条件的创建是通过Restrictions工具类的静态方法:
				Restrictions.eq						等于
				Restrictions.allEq				对象是Map,使用key/value进行多个等于比较
				Restrictions.gt					大于>
				Restrictions.ge					大于等于>=
				Restrictions.lt					小于
				Restrictions.le					小于等于
				Restrictions.between			对应sql的between子句
				Restrictions.like					对应sql的like子句
				Restrictions.in					对应sql的in子句
				Restrictions.and				and关系
				Restrictions.or					or关系
				Restrictions.sqlRestriction	 sql限定查询
	2 QBC检索API示例:
		(1)QBC基本查询
				//创建Criteria查询对象
				Criteria criteria = session.createCriteria(Customer.class);//相当于select * from customer
				//获取查询结果
				List list = criteria.list();
		(2)Criteria条件查询
				//创建Criteria查询对象
				Criteria criteria = session.createCriteria(Customer.class);
				//设置查询条件
				//Criterion criterion2 = Restrictions.idEq(2l);
				Criterion criterion = Restrictions.eq("cust_id",2l);
				//把查询条件criterion添加进Criteria对象中
				criteria.add(criterion);
				//获取查询结果
				List list = criteria.list();
		(3)Criteria的分页查询
				创建Criteria查询对象
				Criteria criteria = session.createCriteria(Customer.class);
				设置分页参数
				criteria.setFirstResult(0);
				criteria.setMaxResults(1);
				List list = criteria.list();
		(4)Criteria的排序查询
				获取Criteria查询对象
				Criteria criteria = session.createCriteria(Customer.class);
				排序
				//criteria.addOrder(Order.asc("cust_id")); //升序
				criteria.addOrder(Order.desc("cust_id")); //降序
				List list = criteria.list();
		(5)criteria的统计
			criteria.setProjection(Projections.rowCount());  
			//criteria.setProjection(Projections.max("cust_id")); 
			//criteria.setProjection(Projections.min("cust_id"));
			//criteria.setProjection(Projections.avg("cust_id"));
			//criteria.setProjection(Projections.sum("cust_id"));
			List list = criteria.list();
		
	3 离线的QBC条件查询DetachedCriteria(SSH整合后经常用)
		概述:DetachedCriteria是一种脱离Session来使用的条件查询对象(传统的Criteria对象必须由Session创建)
				  其不受限于session,易于在不同架构层中封装数据进行传递
		API:
				离线DetachedCriteria的创建
					DetachedCriteria dc=DetachedCriteria.forClass(Xxx.class);
				离线DetachedCriteria转换为正常Criteria,接收Session
				Criteria criteria =dc.getExecutableCriteria(session);
		示例:
				模拟此处在service/web层
				//创建离线DetachedCriteria
				DetachedCriteria dtCriteria = DetachedCriteria.forClass(Customer.class);
				//进行条件 id查询(和普通的查询方式一样)
				dtCriteria.add(Restrictions.idEq(2l));
				
				模拟此处在dao层
				//获取session对象
				Session session = HibernateUtils.openSession();
				//开启事务
				Transaction tx = session.beginTransaction();
				//操作-------------------------
				//离线DetachedCriteria传递到  dao层,接收session变成正常的 Criteria查询对象
				Criteria criteria = dtCriteria.getExecutableCriteria(session);
				List list = criteria.list();
				//事务提交,资源释放------------------------
				tx.commit();
				session.close();

5 Hibernate本地SQL检索方式:

	概述:采用HQL或QBC检索方式时,Hiberante底层会生成标准的SQL查询语句,适用于所有数据库平台,它们是跨平台的。
			  但有的应用程序需根据底层数据库sql方言来生成一些特殊查询语句,这就需用到Hibernate提供的SQL检索方式。
	本地SQL检索的示例代码:
			SQLQuery sqlQuery=session.createSQLQuery("xxxx");

6 Hibernate的多表查询

(1)原生的SQL多表查询:
	& 连接查询:
		<1>交叉连接(开发一般不使用)
				概述:返回的结果是被连接的两个表中所有数据行的笛卡尔积,如A表有10条数据,B表有8条数据,返回的结果就是10*8。
				语法格式
						格式一:select * from 表1 cross join 表2;
						格式二:select * from 表1,表2;
		<2>内连接(又称简单连接或自然连接)
				概述:内连接使用比较运算符对两个表的数据进行比较,并列出与连接条件相匹配的数据行,组合成新的记录。
				语法格式:select  查询字段  from  表1  [inner]  join  表2  on 连接条件 
				内连接可再细分两类
					隐式内连接(隐式就是看不见inner join关键字,用where关键字代替)
						select * from 表1,表2   where 	表1.关系字段=表2.关系字段	
					显示内连接(存在inner join关键字)
						select * from 表1 inner join 表2  on  表1.关系字段=表2.关系字段
		<3>外连接
				概述:返回的结果不仅包含符合查询条件的数据,而且还包含左表(左外连接),右表(右外连接),或两个表(全外连接)中所有数据
				外连接完整格式:
					select * from  表1   left | right   outer  join  表2  on  表1.关系字段=表2.关系字段	where 条件
				左连接:返回左表中所有记录和右表中符合连接条件的记录
					select * from  表1  left  outer  join  表2  on  表1.关系字段=表2.关系字段	where 条件
				右连接:返回右表中所有记录和左表中符合连接条件的记录
					select * from  表1  right  outer  join  表2  on  表1.关系字段=表2.关系字段	where 条件
(2)HQL连接查询:
	& 交叉查询
	& 内连接
		显示内连接
		隐式内连接
		迫切内连接
	& 外连接
		左外连接
		右外连接
		迫切外连接
	&查询语法:
		* 概述:HQL连接查询语法和原生的SQL语法差别不大,区别在于HQL连接查询作用的是对象而不是表
		* SQL和HQL内连接示例比较
			SQL显示内连接:
				select  *  from  cst_customer c  inner join  cst_linkman l on c.cust_id = l.lkm_id
			HQL内连接:
				from Customer c inner join c.linkMans
		* HQL的连接不用写关联字段,不用写具体的on条件,直接写关联属性即可。
		* 迫切内连接(在内连接 inner  join 后添加一个fetch关键字。)
				fetch关键字分析:我们会发现无论是内连接还是迫切内连接生成的sql语句是一样的,fetch关键字不会出现在sql语句中,
										   因为sql语句没有fetch关键字,fetch只能在hql中使用,生成sql后就消失。
				fetch的作用:
						普通内连接封装数据时,会将属于客户的数据封装到Customer实体中,会将属于联系人的数据封装到LinkMan实体中
							所以封装后获得的数据为List<Object[]{Customer,LinkMan}>
							示例:	
									String hql="from Customer c inner join c.linkMans";
									//返回的List集合存储着一个数组,存储着客户和联系人两个对象 Object[]{Customer,LinkMan}
									List<Object[]> list = session.createQuery(hql).list();
									for (Object[] objects : list) {
										Customer c= (Customer) objects[0];
										LinkMan l=(LinkMan) objects[1];
										System.out.println(c);
										System.out.println(l);
									}
						迫切内连接封装数据时,会将属于客户的数据封装到Customer实体中,会将属于联系人的数据封装到Customer实体中的联系人集合里
						   所以封装后获得的数据为List<Customer>,但要注意的是,迫切内连接会出现重复的记录,这就需我们用到 distinct关键字进行解决
						   示例:
									String hql2="select distinct c from Customer c inner join fetch c.linkMans";
									List<Customer> list2 = session.createQuery(hql2).list();
									for (Customer customer : list2) {
										System.out.println(customer);
									}
		* Hibernate的内连接和迫切内连接总结:
				两者主要区别在于封装数据的方式,但查询的结果集是一样的,底层生成的sql也一样

Hibernate的查询优化

概述:在很多CRM案例中要用到查询操作,但Hibernate本身的查询效率不是很好,特别在获取关联对象的方面,我们需要对查询语句进行一些优化

Hibernate的抓取策略

1 抓取策略概述:Hibernate的抓取策略是一种提升Hibernate性能的手段,在Hibernate获取关联对象时,对发送语句进行优化,需使用到延迟加载。
2 延迟加载(lazy load 懒加载)概述:Hibernate使用load方法关联对象默认的加载方式,所谓的延迟加载就是当真正需要数据时,才真正执行数据加载操作。
3 延迟加载分类
	<1> 类级别延迟:
			概述:类级别延迟指的是查询某个对象时,是否采用延迟,通常在<class>标签上配置lazy属性
			分析:& Hibernate查询某个对象时,默认选择类级别延迟(<class lazy="true">),所以使用load方法检索某个对象时,
							不会马上发送sql语句,真正调用调用该对象时才会发送sql语句
					 & 若不想使用延迟加载,可以直接在映射文件上设置<class lazy="false">,也可以用final修饰持久类,
						  使之无法生成代理类,	就会使延迟加载失效。	
					 & 值得一提的是,类级别的延迟加载我们一般不进行修改,采用默认值 lazy="true"
	<2> 关联级别延迟
			概述:关联级别延迟指的是,查询一个对象的关联对象时是否采用延迟加载,这个通常在<set>或<many-to-one>上配置lazy属性
			分析:
					& <set>标签上的lazy取值
						% true:默认值,采用延迟加载
						% false:检索关联对象的时候,不采用延迟加载
						% extra:极其懒惰
					&<many-to-one>标签上的lazy取值
						% proxy:默认值,是否采用延迟取决于一方上的延迟加载lazy值
						% false:检索关联对象时,不采用延迟加载
						% no-proxy:不用研究
	<3>延迟加载总结:
			 * 延迟加载:仅仅获得没有使用,不进行查询,使用才进行查询
			 * 是否对类进行延迟加载:可以通过在实体类的class元素配置lazy属性来控制
			 * lazy配置只作用于 load方法,执行该方法时,不发送任何sql语句,只返回一个对象,使用该对象才执行查询
			 * lazy="true"--进行延迟加载:加载时不执行,使用时才查询
			 * lazy="false"--不进行延迟加载:加载时立即执行
			 * 延迟加载的原理是:
			 * 它会把获取到的Customer实体对象变成一个 具有加强功能的超级代理类型对象 Customer_$$
			 * (增强的功能就是:使用这个对象才执行查询,若不使用则不执行查询) 
			 * 值得注意的是:使用懒加载调用属性时要确保 session还在打开,因为它生成的代理对象是根据关联的 session
			 * 	查询数据库的,否则会报异常 
			 * 结论:建议使用延迟加载,因为它可以把节省不必要浪费的资源,提高效率
3 抓取策略指的是查询某个对象时,如何抓取其关联对象,也可通过配置实现:
	* <set>标签上的fetch取值:
		& select :默认值,发送的是普通的select语句
		& join:发送一条迫切左外连接去查询,值得一提的是,set上设置了fetch=join,lazy就会失效。
		& subselect:发送一条子查询语句查询其关联对象
	* <many-to-one>标签上的fetch有两个取值
		& select :默认值,发送的是普通的select语句
		& join:发送一条迫切左外连接去查询
4 抓取策略fetch和延迟加载lazy配合使用分析:
	<1>单表查询加载:fetch="select"
		   延迟加载:lazy="true"
	<2>单表查询加载:fetch="select"
		   立即加载:lazy="false"
	<3>单表查询加载:fetch="select"
		   极其懒加载:lazy="extra",与懒加载效果基本一致,如果想获得集合size(),则仅用count语句进行查询
	<4>多表查询加载:fetch="join"  一次性查询所有的数据,使懒加载失效
			延迟加载:lazy="true"
	<5>子查询:fetch="subselect"  主要用于重复繁杂查询数据过程中,否则和 select效果一样
			延迟加载:lazy="true"
	<5>子查询:fetch="subselect"  主要用于重复繁杂查询数据过程中,否则和 select效果一样
			 立即加载:lazy="false"
5 <set>集合上的fetch和lazy特点总结
	(1)fetch:控制的是 查询其关联对象时采用的sql语句格式
		* select:默认值,发送一条select语句查询其关联对象
		* join:发送一条迫切左外连接查询其关联对象
		* subselect:发送一条子查询查询其关联对象
	(2)lazy:控制的是 查询其关联对象时采用的延迟加载策略
		* true:延迟加载
		* false:立即加载
		* extra:极其懒惰
6 批量抓取(同时查询多个对象的关联对象)
	(1)实现批量查询一方关联的多方:
		<set>标签内配置 batch-size属性,如:
		<!--批量抓取 查询一次,抓取集合数量为3
			抓取客户的集合时,一次抓取多个客户联系人的集合
		-->
		<set name="linkMans" batch-size="3" >
			<key column="lkm_cust_id" ></key>
			<one-to-many class="LinkMan"/>
		</set>
	(2)实现批量查询多方关联的一方:
		在多方的<class>标签中配置  batch-size即可
		注意,并不是在<many-to-one>标签内配置