Hibernate 配置详解(4)

本文详细解析Hibernate配置中的default_batch_fetch_size属性,探讨如何通过批量抓取优化查询效率,避免N+1问题。通过实例说明如何设置该属性以减少SQL执行次数,提升应用程序性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

8) hibernate.default_batch_fetch_size:

设置Hibernate默认的对关系的批量抓取大小值,建议设置值为4,8,16

要理解default_batch_fetch_size就必须要理解什么叫做batch_fetch。首先来看在关系映射中会出现的几个问题。下面是一个简单的one2many关系的映射:

public class Department {
private Long id;
private String name;
private Set emps = new HashSet();
}
   对应的Employee对象:
public class Employee {
private Long id;
private String name;
}
   很简单,EmployeeDepartment是一个单向的many2one的关系。要完成关系的映射很简单:

 

<hibernate-mapping package="cd.itcast.hibernate.day2.batchfetch">

<class name="Department">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set name="emps">
<key column="DEPT_ID"/>
<one-to-many class="Employee"/>
</set>
</class>

 
<class name="Employee">
<id name="id">
<generator class="native" />
</id>
</class>
</hibernate-mapping>

       假如现在有7Department对象,分别有7Employee对象和其对应:

   
@Before

public void save() {
Session session = HibernateUtil.getInstance().getSession();
session.beginTransaction();

for(int i=1;i<8;i++){
    Department d=new Department();
    d.setName("d"+i);
    Employee e=new Employee();
    e.setName("e"+i);
    
    d.getEmps().add(e);
    session.save(d);
    session.save(e);
}
session.getTransaction().commit();
session.close();
}

假如现在有这样一个需求,在部门列表中,要列出部门的名称,并且列出部门中员工的数量,我们用一段测试代码模拟如下:

@Test
public void testBatchFetch() {
Session session = HibernateUtil.getInstance().getSession();

List depts=session.createQuery("FROM Department").list();
for(Department dept:depts){
System.out.printf("department:%s has %s employees \r\n",dept.getName(),dept.getEmps().size());
}
session.close();
}
 

代码很简单,首先查询出所有的Department对象,然后打印出部门的名称和部门里面员工的数量。但是我们知道,因为是one2many,所以集合是proxy的,得到集合的大小(.size())方法会触发一条SELECT语句,所以生成的SQL是:

Hibernate: select department0_.id as id1_0_, department0_.name as name2_0_ from Department department0_
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d1 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d2 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d3 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d4 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d5 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d6 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID=?
department:d7 has 1 employees 

一共生成了8SQL,典型的N+1问题。要处理这个问题,就可以使用hibernate提供的batch-fetch,即批量操作。先简单的修改一下映射文件:

      
<class name="Department">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set name="emps" batch-size="4">
<key column="DEPT_ID"/>
<one-to-many class="Employee"/>
</set>
</class>
   在集合上面添加了batch-size属性,设置了4这个值。我们再次运行测试:

Hibernate: select department0_.id as id1_0_, department0_.name as name2_0_ from Department department0_
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID in (?, ?, ?, ?)
department:d1 has 1 employees 
department:d2 has 1 employees 
department:d3 has 1 employees 
department:d4 has 1 employees 
Hibernate: select emps0_.DEPT_ID as DEPT2_0_1_, emps0_.id as id1_1_1_, emps0_.id as id1_1_0_ from Employee emps0_ where emps0_.DEPT_ID in (?, ?, ?)
department:d5 has 1 employees 
department:d6 has 1 employees 
department:d7 has 1 employees

         可以看到,这次只使用了3SQL就查询出了结果。可以很容易理解,batch-size就是设置批量一起查询多少个对象对应的延迟加载集合,使用SELECT ... FROM ... WHERE ... IN(?,?,?)的方式来查询。Batch-size就是设置in中批量查询的个数。使用恰当的batch-size,就可以大大的减少SQL的数量。

理解了集合上的batch-size属性,就来看看hibernate.default_batch_fetch_size,设置了default_batch_fetch_size就相当于在所有的集合上面添加了batch_size属性:

 

hibernate.default_batch_fetch_size 4


这时候,映射文件还原为:

<class name="Department">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set name="emps" batch-size="4">
<key column="DEPT_ID"/>
<one-to-many class="Employee"/>
</set>
</class>


再次运行测试,结果和设置了batch-size=4的效果相同。


上面我们看到的是batch-fetch在处理集合延迟加载时候得表现。我们来看另外一种情况,同样是one2many,我们把方向反过来,做成双向的many2one/one2many,现在的Employee就变成了:

   
public class Employee {
private Long id;
private String name;
private Department dept;
}
   映射文件修改为:

<class name="Employee">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<many-to-one name="dept" column="DEPT_ID" />
</class>
      我们假设现在的业务变成了:得到所有的员工,并遍历的打印出员工的名称和所属的部门,测试代码为
  
@Test
public void testBatchFetch() {
Session session = HibernateUtil.getInstance().getSession();
List emps=session.createQuery("FROM Employee").list();
for(Employee emp:emps){
System.out.printf("employee:%s belong %s department \r\n",emp.getId(),emp.getDept().getName());
}

session.close();
}
   我们知道,得到many方,通过many方得one方,使用延迟加载,所以控制台输出:

Hibernate: select employee0_.id as id1_1_, employee0_.name as name2_1_, employee0_.DEPT_ID as DEPT3_1_ from Employee employee0_
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:1 belong d1 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:2 belong d2 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:3 belong d3 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:4 belong d4 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:5 belong d5 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:6 belong d6 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id=?
employee:7 belong d7 department

       这又变成了一个N+1问题,要处理这个问题,我们只需要修改一下映射文件:

<class name="Department" batch-size="4">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set name="emps" batch-size="4">
<key column="DEPT_ID"/>
<one-to-many class="Employee"/>
</set>
</class>
我们在one方得类上面添加了一个batch-size=4,再次运行测试:

Hibernate: select employee0_.id as id1_1_, employee0_.name as name2_1_, employee0_.DEPT_ID as DEPT3_1_ from Employee employee0_
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id in (?, ?, ?, ?)
employee:1 belong d1 department 
employee:2 belong d2 department 
employee:3 belong d3 department 
employee:4 belong d4 department 
Hibernate: select department0_.id as id1_0_0_, department0_.name as name2_0_0_ from Department department0_ where department0_.id in (?, ?, ?)
employee:5 belong d5 department 
employee:6 belong d6 department 
employee:7 belong d7 department

         Hibernate只使用了3SQL就完成了数据的加载,可以很简单的理解,在one方得class上面添加batch-size,那么在从many方得到one方得时候,就会批量的通过IN去查询many方对应的one方,减少SQL

理解了class上面的batch-size,同理,设置了hibernate.default_batch_fetch_size,也就相当于在one方得class上面设置了batch-size(注意,在hibernate4.2之后,添加了一个hibernate.batch_fetch_style参数用于控制从many方批量获取one方得SQL的生成方式,这个参数在下一个配置中讲解)。

综上,我们说hibernate.default_batch_fetch_size就是用于控制批量获取延迟加载的关联方对象,就应该很好理解了。





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值