Hibernate检索策略
Hibernate检索策略
它是为了让我们的查询更加的有效率,是优化我们查询用的。 它是靠配置的方式来实现的
延迟加载
延迟加载(lazy load)是(也称为懒加载 )Hibernate关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
通常将延迟加载分为两类 :一类叫做类级别延迟 ,另一类叫做关联级别的延迟。类级别的延迟指的是查询某个对象的时候,是否采用有延迟,这个通常在<class>标签上配置lazy属性。关联级别的延迟指的是,查询一个对象的关联对象的时候是否采用延迟加载。这个通 常在<set>或<many-to-one>上配置lazy属性。
类级别的延迟加载
使用load方法检索某个对象的时候,这个类是否采用延迟加载的策略,就是类级别的延迟。类级别的延迟一般在<class>上配置lazy属性,lazy的默认值是 true 。默认是延迟加载的 ,所以使用 load方法去查询的时候 ,不会马上发送SQL语句。当真正使用该对象的时候,才会发送SQL语句。
Customer customer = session.load(Customer.class,1L);
其实如果不想使用延迟加载也有很多种方法,当然最简单的就是将这个类的映射文件上的lazy设置为false,当然也可以将这个持久化类改为final修饰,如果改为final修饰的话。就无法生成代理类,就会使延迟加载失效。
这是类级别的延迟加载, 类级别的延迟加载一般不进行修改 ,采用默认值 lazy="true"就可以了。
关联级别的延迟加载
关键级别的延迟加载指的是查询到某个对象以后,检索它的关联对象的时候是否采用延迟加载 。
Customer customer = session.get(Customer.class,1L);
Set<LinkMan> linkMans = customer.getLinkMans();
通过客户查询其关联的联系人对象,在查询联系人的时候是否采用延迟加载称为是关联级别的延迟。关联级别的延迟通常是在<set>和<many-to-one>上来进行配置 。
- true : 默认值,采用延迟加载
- false: 检索关联对象 的时候,不采用延迟加载 。
- extra : 极其懒惰的,用到什么加载什么。
- proxy:默认值,是否采用延迟取决于一的一方类上的lazy属性的值 。
- false:检索关联对象的时候,不采用延迟加载。
- no-proxy: 不用研究。
关联级别的抓取策略
抓取策略指的是查询到某个对象的时候,如何抓取其关联对象。这个也可以通过配置完成 。在关联对象的标签上配置fetch属性。关联上就分为是在<set>和<many-to-one>上,也都有不同的取值 。
- select:默认值,发送的是普通的 select 语句查询。
- join:发送一条迫切左外连接去查询。
- subselect:发送一条子查询语句查询其关联对象。
- select:默认值,发送一条普通的select 语句查询关联对象。
- join:发送一条迫切左外连接语句查询其关联对象。
set上配置fetch有三个值,lazy有三个值,这样就会产生很多种的效果。其实不用担心,因为fetch如果设置为join,lazy就会失效了。
简单的总结一 下fetch和lazy的作用,其实fetch主要控制抓取关联对象的时候的发送SQL语句的格式的。lazy主要控制查询其关联对象的时候是否采用延迟加载的。
批量抓取
在抓取的策略中有一种叫做批量抓取,就是同时查询多个对象的关联对象的时候,可以采用批量抓取进行优化 。当然这不是特别的重要。如果要实现批量的抓取效果,可以通过配置batch-size来完成。
可以在class和set上配置
- 在一的那方的class配置,表示在查询多方时可以同时获取几个一方的对象。
- 在一的那方的set上配置,表示在查询一方时可以同时获取几个多方的对象。
实际开发中推荐的取值
一对多(多对多)的情况:
lazy:true
fetch:select
多对一的情况:
lazy:false
fetch:select
一对多(多对多) 情况
序号 | lazy的取值 | fetch的取值 | 说明(都是以客户订单的一对多关系为例) |
1 | true(默认值) | select(默认值) | 时机:用时才真正去查询订单。 语句形式:有1条查询客户的和多条查询订单的select语句。 batch-size:设置批量检索的深度。(建议3~10之间) |
2 | false | select(默认值) | 时机:不管用不用订单,查询客户时都立即查询订单。 语句形式:有1条查询客户的和多条查询订单的select语句。 batch-size:设置批量检索的深度。(建议3~10之间) |
3 | extra | select(默认值) | 时机:用什么信息,查什么信息。只查询必要的。 语句形式:有1条查询客户的和多条查询订单的select语句。 batch-size:设置批量检索的深度。(建议3~10之间) |
4 | true | subselect | 时机:用时才真正去查询订单。 语句形式:子查询 batch-size:无效 |
5 | false | subselect | 时机:不管用不用订单,查询客户时都立即查询订单。 语句形式:子查询 batch-size:无效 |
6 | extra | subselect | 时机:用什么信息,查什么信息。只查询必要的。 语句形式:子查询 batch-size:无效 |
7 | true|false|extra | join(当join有效时,根本不看lazy属性) | 时机:无效。因为连接查询,一次就是两张表及以上。 语句:left outer join batch-size:无效 注意:Query查询会忽略join的存在。当join无效时,lazy就有效了。 |
多对一(一对一) 情况
序号 | lazy的取值 | fetch的取值 | 说明(都是以客户订单的一对多关系为例) |
1 | proxy | select | 时机: 对象类级别的策略:true。 延迟加载。得到的是代理对象。 对象类级别的策略:false。 立即加载。得到就是类对象。 语句: 多条SQL语句 |
2 | false | select | 时机:立即加载。与对象类级别的策略无关。 语句:多条SQL语句。 |
3 | true|false|extra | join(当join有效时,根本不看lazy属性) | 时机:无效。因为连接查询,一次就是两张表及以上。 语句:left outer join batch-size:无效 注意:Query查询会忽略join的存在。当join无效时,lazy就有效了。 |
测试Hibernate检索策略
package com.pc.hibernate.test.querystrategy;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.pc.hibernate.domain.Customer;
import com.pc.hibernate.domain.LinkMan;
import com.pc.hibernate.utils.HibernateUtils;
/**
* Hibernate的检索策略
* 明确:
* 它是为了让我们的查询更加的有效率,是优化我们查询用的。
* 它是靠配置的方式来实现的。
* 关联级别的检索策略关心的是:
* 实体类中的那个关联对象。
* 例如:
* 客户实体类中,关心的是联系人
* 联系人实体类中,关心的是客户
* 用户实体类中,关心的是角色
* 角色实体类中,关心的是用户
* 类级别检索策略关心的是:
* 是当前实体类中所对应数据库表中所包含字段。
* 类级别解决的问题:
* 什么时候真正去查询,查询实体类中除了OID外的其他字段信息。
*
* 关联级别的检索策略解决的问题:(从哪些方面优化)
* 1、查询的时机:
* 到底什么时候真正的去查询
* 立即加载
* 延迟加载:惰性加载,懒加载
* 2、采用什么方式去查询
* 多条SQL语句
* 子查询
* 表连接
* 关联级别的检索策略分几种情况:
* 分为两种情况:
* 一对多和多对多:
* 有少的一方,根据少的一方获取多的一方。
* 多对一和一对一
* 有多的一方,根据多的一方获取少的一方。
* 关联级别涉及的配置
* 一对多(多对多)情况:
* 涉及的标签:set
* 涉及的属性:lazy:什么时候去查询
* 取值:
* true:延迟加载(默认值)
* false:立即加载
* extra:极懒加载 用什么数据,查什么数据,不用的都不查
* fetch:用什么方式去查询
* 取值:
* select:多条SQL语句(默认值)
* subselect:子查询
* join:表连接
* 多对一(一对一)情况:
* 涉及的标签:many-to-one
* 涉及的属性:lazy:什么时候去查询
* false:立即加载
* proxy:看关联对象的类级别检索策略
* no-proxy:不用管,实际开发根本不用
* fetch:用什么方式去查询
* select:多条SQL语句
* join:表连接
*
*
* 实际开发中:
* 一对多(多对多)的情况:
* lazy:用true
* fetch:用select
* 多对一的情况:
* lazy:false
* fetch:select
*
* @author Switch
*
*/
public class TestQueryStrategy1 {
/**
* 需求:
* 查询id为1的客户下的所有联系人
* 查询的时机:
* 延迟加载
* 查询的方式:
* 多条语句。2条,一条查询了客户,一条查询了联系人
*/
@Test
public void test1(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//1.查询id为1的客户
Customer customer = session.get(Customer.class, 1L);
//2.使用对象导航查询,查询该客户的联系人
//类级别检索策略
System.out.println(customer);
System.out.println(customer.getLinkMans().size());
//关联级别检索策略
System.out.println(customer.getLinkMans());
tx.commit();
}
/**
* 需求:
* 查询所有客户,获取每个客户的联系人
* 查询的时机
* 延迟加载
* 查询的方式
* 多条语句。5条SQL语句。查询联系人只有4条语句,还有一条是查询客户的。
* Hibernate的N+1问题:
* 当我们查询一个从表关联对象集合时,正常查询应该是N条语句,但是由于需要查询主表,所以会多出一条语句。
* 解决N+1问题:
* 在Hibernate的映射配置文件中,添加一个batch-size属性。该属性的含义:批量抓取。简单的说就是问号的个数。
* batch-size属性的取值:建议3~10之间。
*/
@Test
public void test2(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//1.查询所有客户
Query query = session.createQuery("from Customer");
@SuppressWarnings("unchecked")
List<Customer> customers = query.list();
for (Customer customer : customers) {
//2.使用对象导航查询,查询该客户的联系人
//类级别检索策略
System.out.println(customer);
//关联级别检索策略
System.out.println(customer.getLinkMans());
}
tx.commit();
}
/**
* 需求:
* 查询id为1的联系人的所属客户
* 查询的时机:false
* 立即加载
* 查询的方式:select
* 多条语句
*/
@Test
public void test3(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//1.查询id为1的联系人
LinkMan l1 = session.get(LinkMan.class, 1L);
//2.使用对象导航查询,获取该联系人的客户
System.out.println(l1);
System.out.println(l1.getCustomer());
tx.commit();
}
/**
* 需求:
* 查询id为1的联系人的所属客户
* 查询的时机:proxy
* 看关联对象的类级别检索策略
* 查询的方式:select
* 多条语句
*/
@Test
public void test4(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//1.查询id为1的联系人
LinkMan l1 = session.get(LinkMan.class, 1L);
//2.使用对象导航查询,获取该联系人的客户
System.out.println(l1);
System.out.println(l1.getCustomer());
tx.commit();
}
}
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--
1、导入dtd约束
在Hibernate的核心jar包中:hibernate-mapping-3.0.dtd
2、编写配置文件
实体类和数据库表的对应关系
实体类中属性和表的字段的对应关系
-->
<hibernate-mapping package="com.pc.hibernate.domain">
<!-- class标签:
作用:用于配置实体类和表之间的对应关系
属性:
name:实体类的名称。它应该写全限定类名
table:数据库表的名称
-->
<class name="Customer" table="cst_customer" lazy="true" batch-size="3">
<!-- id标签:
作用:映射主键
属性:
name:实体类的属性名称
column:数据库表的字段名称
type:主键的类型
-->
<id name="custId" column="cust_id" type="java.lang.Long">
<!-- generator标签:
作用:主键的生成方式
属性:
class:指定方式
取值:native
含义:使用本地数据库的自动增长能力。
-->
<generator class="native"/>
</id>
<!-- property标签:
作用:映射其他字段
属性:
name:实体类的属性名称
column:数据库表的字段名称
type:字段的类型
length:数据库中对应列的长度
-->
<property name="custName" column="cust_name" type="java.lang.String" length="32"/>
<property name="custSource" column="cust_source" type="java.lang.String" length="32"/>
<property name="custIndustry" column="cust_industry" type="java.lang.String" length="32"/>
<property name="custLevel" column="cust_level" type="java.lang.String" length="32"/>
<property name="custAddress" column="cust_address" type="java.lang.String" length="128"/>
<property name="custPhone" column="cust_phone" type="java.lang.String" length="64"/>
<!-- 配置一对多 -->
<set name="linkMans" inverse="false" cascade="save-update" batch-size="4" lazy="true" fetch="select">
<key column="lkm_cust_id" />
<one-to-many class="LinkMan"/>
</set>
</class>
</hibernate-mapping>
LinkMan.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.pc.hibernate.domain">
<class name="LinkMan" table="cst_linkman">
<id name="lkmId" column="lkm_id" type="java.lang.Long">
<generator class="native" />
</id>
<property name="lkmName" column="lkm_name" type="java.lang.String" length="16"/>
<property name="lkmGender" column="lkm_gender" type="java.lang.String" length="10"/>
<property name="lkmPhone" column="lkm_phone" type="java.lang.String" length="16"/>
<property name="lkmMobile" column="lkm_mobile" type="java.lang.String" length="16"/>
<property name="lkmEmail" column="lkm_email" type="java.lang.String" length="64"/>
<property name="lkmPosition" column="lkm_position" type="java.lang.String" length="16"/>
<property name="lkmMemo" column="lkm_memo" type="java.lang.String" length="512"/>
<!-- 配置多对一 -->
<many-to-one name="customer" class="Customer" column="lkm_cust_id" cascade="save-update" lazy="proxy" fetch="select"/>
</class>
</hibernate-mapping>
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!-- 1、导入dtd约束 在hibernate核心jar包中:hibernate-configuration-3.0.dtd 2、编写配置文件 -->
<hibernate-configuration>
<!-- 配置SessionFactory -->
<!-- 创建SessionFactory需要3部分信息。 而配置文件中要写哪些配置,我们可以翻阅资料。 -->
<!-- 第一部分:连接数据库的基本信息,第二部分:hibernate的基本配置,第三部分:映射文件的位置 -->
<session-factory>
<!-- 1、连接数据库的基本信息 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mycrm</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<!-- 数据库的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 2、hibernate的基本配置 -->
<!-- 是否显示SQL语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 是否格式化显示SQL语句 -->
<!-- <property name="hibernate.format_sql">true</property> -->
<!-- 采用何种策略来创建表结构: -->
<!-- update:检查表结构和实体类映射文件的变化,如果发现映射文件和表结构不一致,更新表结构。 -->
<!-- 注意:hibernate不能创建数据库,只能在有数据库的情况下创建表结构。 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 配置hibernate使用连接池:告知Hibernate使用连接池的厂商 -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- 配置把Session绑定到当前线程上,从而保证一个线程只有一个Session -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 3、映射文件的位置 -->
<mapping resource="com/pc/hibernate/domain/Customer.hbm.xml" />
<mapping resource="com/pc/hibernate/domain/LinkMan.hbm.xml" />
</session-factory>
</hibernate-configuration>
还没有评论,来说两句吧...