Hibernate的抓取策略总结

今天在做hibernate的性能优化的时候,hibernate的抓取策略又派上用场了,于是顺便总结了一下并作了如下记录。
 
hibernate的抓取策略通常有下面四种:

     1.连接抓取(Join fetching) ,即Hibernate通过 在select语句使用outer join(外连接)来 获得对象的关联实例或  者关联集合.
 
     2.查询抓取(Select fetching) ,即另外发送一条 select 语句抓取当前对象的关联实体或集合。除非你显式的指定lazy="false"禁止 延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句.
 
     3.子查询抓取(Subselect fetching) ,即 另外发送一条select 语句抓取在前面查询到 的所有实体对象的关联集合。除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条
select语句 .


      4.批量抓取(Batch fetching) ,这种策略跟查询抓取本质上是一样的,只不过是 对查询抓取的优化而已, 通过指定一个主键或外键 列表,Hibernate使用单条select语句获取一批对象实例或集合.

 Join fetching , Select fetching 与 Batch-size 可以为单个实体的抓取进 行性能优化;
 Join fetching , Select fetching ,Subselect fetching , Batch fetching 都可以为集合的抓取进行性能优化;
 
下面将以一个多对一双向关联映射(多个员工属于同一部门)进行说明:

关联映射后的关系模型:
 t_employee(id int pk, name varchar, departmentid int fk->t_department(id))
 t_department(id int pk, name varchar)
 
实体模型:
 com.lrh.hibernate.Employee(int id, String name, Department department)
 com.lrh.hibernate.Department(int id, String name, Set employees)
 
配置文件:
  <!-- com.lrh.hibernate/Employee.hbm.xml -->
 
  <hibernate-mapping> 
      <class name="com.lrh.hibernate.Employee" table="t_employee"> 
          <id name="id"><generator class="native"/></id> 
          <property name="name"/> 
          <!-- <many-to-one>映射多对一关系 -->
          <many-to-one name="department" column="departmentid"/> 
      </class> 
 </hibernate-mapping>
 
  <!-- com.lrh.hibernate/Department.hbm.xml -->
 
  <hibernate-mapping> 
      <class name="com.lrh.hibernate.Department" table="t_department"> 
          <id name="id"><generator class="native"/></id> 
          <property name="name"/> 
           <!--  双向关联中,为<set>加入”inverse=true”可以反转维护关系:
                 Hibernate将放弃从一的一端维护。也就是employee和department的关系必须使用employee维护,
                              操作department时Hibernate将不维护这个关系。
             -->
          <set name="employees" inverse="true" > 
              <!-- <key>指定引用至自身的外键表(t_employee)中的外键 -->
             <key column="departmentid"/> 
              <!-- <one-to-many>映射一对多关系 -->
              <one-to-many class="com.lrh.hibernate.Employee"/> 
          </set> 
      </class> 
  </hibernate-mapping>

1.连接抓取(Join fetching)

     连接抓取, 使用连接抓取可以将本来需要查询两次(或多次)表的多次查询 ,现在只需要一次查询即可完成,以上面的关系模型为例,在初始化一个含有一对多关系的 Department 与Employee 的时候, 会先加载Department , 然后再根据Department.id 将对应的Employee 集合初始化, 一般完成初始化需要发送至少两条 SQL 语句, 但如果用 连接抓取, 则会根据需要查询的Department.id, 将 Department 表与 Employee 表连接起来进行查询,仅仅一条 SQL 语句就可以将需要的数据全部加载出来;
             
     使用连接抓取的com.lrh.hibernate/Department.hbm.xml配置文件修改<set></set>节点如下:
    <!--添加fetch="join" -->
    <set name="employees" inverse="true" fetch="join"> 
       <key column="departmentid"/> 
       <one-to-many class="com.lrh.hibernate.Employee"/> 
     </set>  
     测试核心代码:
     Department d = (Department)session.get(Department.class,1); 
     System.out.println(d.getEmployees.size());


2.查询抓取(Select fetching)
      查询抓取是在集合抓取的时候使用的默认策略, 即如果集合需要初始化, 那么会重新发出一条 SQL 语句初始化Employee 集合;也就是常说的N+1次查询的查询策略;
          
    使用连接抓取的com.lrh.hibernate/Department.hbm.xml配置文件修改<set></set>节点如下:
    <!--添加fetch="select" -->
    <set name="employees" inverse="true" fetch="select"> 
       <key column="departmentid"/> 
       <one-to-many class="com.lrh.hibernate.Employee"/> 
     </set>  
     测试核心代码:
     Department d = (Department)session.get(Department.class,1); 
     System.out.println(d.getEmployees.size());
   

3.子查询抓取(Subselect fetching)

           子查询抓取, 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合. 还是以上面的关系模型例子 : 如果查询出了3 个 Department 实体,由于开启了懒加载,那么他们的 Employee 都没有被初始化, 那么我现在手动初始化一个Department 的 Employee ,此时由于我选的是 Subselect fetching策略,所以 Hibernate 会将前面查询到的实体对象(3 个 Department)的关联集合(在 <set name="Employee" fetch="subselect" /> )使用一条 Select 语句一次性抓取出来, 这样减少了与数据库的交互次数, 一次将每个对象的集合都给初始化了;subselect 只在 <set> 集合中出现 。
 
  使用连接抓取的com.lrh.hibernate/Department.hbm.xml配置文件修改<set></set>节点如下:
    <!--添加fetch="subselect" -->
    <set name="employees" inverse="true" fetch="subselect"> 
       <key column="departmentid"/> 
       <one-to-many class="com.lrh.hibernate.Employee"/> 
    </set>  
    测试核心代码:
     List results = session.createQuery("From Department d where d.id in (1,2,3)").list(); 
     Department d = (Department)results.get(0); 
     System.out.println(d.getEmployees.size());

4.批量抓取(Batch fetching)
          批量抓取跟查询抓取本质上是一样的,只不过将一次一条的 查询策略改为一次 N 条的批量 查询; 同样以上面的关系模型为例,我查询出了 3个 Department 实体,Employee 默认开启了懒加载,我们可以如此设置 :

<set name="Employee" fetch="select" batch-size="2" />  当初始化一个 Department 的 Employee 集合的时候, Hibernate 还是发出了一条SQL 语句,跟查询抓取不同的是:这条 SQL 与是通过指定了 Employee 表中的 departmentid 外键列表(2个),Hibernate 会以一条 SQL 语句初始化 batch-size 指定的数量的 Employee 集合;即根据batch-size 分成了两组(一组有2个 Department id 值的列表,第二组只有1个)来初始化 Employee 集合。
 
  使用连接抓取的com.lrh.hibernate/Department.hbm.xml配置文件修改<set></set>节点如下:
    <!--添加fetch="select" batch-size="2"-->
    <set name="employees" inverse="true" fetch="select"  batch-size="2"> 
       <key column="departmentid"/> 
       <one-to-many class="com.lrh.hibernate.Employee"/> 
    </set>  
 
  测试核心代码:
     List results = session.createQuery("From Department d where d.id in (1,2,3)").list(); 
     Department d = (Department)results.get(0); 
     System.out.println(d.getEmployees.size());

猜你喜欢

转载自jenhui.iteye.com/blog/1214055