Hibernate fetch抓取策略


抓取策略,指的是使用Hibernate查询一个对象的时候,查询其关联对象.应该如何查询.是Hibernate的一种优化手段!


一、Hibernate的Fetch抓取策略

连接抓取(Join fetching) - Hibernate通过 在SELECT语句使用OUTER JOIN 
(外连接)来 获得对象的关联实例或者关联集合。


查询抓取(Select fetching) - 另外发送一条 SELECT 语句抓取当前对象的关联实 
体或集合。除非你显式的指定lazy="false"禁止 延迟抓取(lazy fetching),否 
则只有当你真正访问关联关系的时候,才会执行第二条select语句。


子查询抓取(Subselect fetching) - 另外发送一条SELECT 语句抓取在前面查询到 
(或者抓取到)的所有实体对象的关联集合。除非你显式的指定lazy="false" 禁止延迟 
抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条 
select语句。


批量抓取(Batch fetching) - 对查询抓取的优化方案, 通过指定一个主键或外键 
列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。


二、演示Hibernate的Fetch抓取策略实例

下面我用 Customer 与 Order 的一个双向一对多例子来使用四种抓取策略看看他们的不同之处。

Customer实体类:

/**
 * 客户(一方)
 * @author http://www.yiidian.com
 *
 */
public class Customer {
	private Integer id;
	private String name;
	private String gender;
	
	//关联订单
	private Set<Order> orders = new HashSet<Order>();
	
	
	public Set<Order> getOrders() {
		return orders;
	}
	public void setOrders(Set<Order> orders) {
		this.orders = orders;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getGender() {
		return gender;
	}
	public void setGender(String gender) {
		this.gender = gender;
	}
	
	
}

Order实体类:

/**
 * 订单(多方)
 * @author http://www.yiidian.com
 *
 */
public class Order {

	private Integer id;
	private String orderno;
	private String productName;
	
	//关联客户
	private Customer customer;
	
	
	public Customer getCustomer() {
		return customer;
	}
	public void setCustomer(Customer customer) {
		this.customer = customer;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getOrderno() {
		return orderno;
	}
	public void setOrderno(String orderno) {
		this.orderno = orderno;
	}
	public String getProductName() {
		return productName;
	}
	public void setProductName(String productName) {
		this.productName = productName;
	}
	public Order(String orderno, String productName) {
		super();
		this.orderno = orderno;
		this.productName = productName;
	}
	public Order() {
		super();
		// TODO Auto-generated constructor stub
	}
	@Override
	public String toString() {
		return "Order [id=" + id + ", orderno=" + orderno + ", productName="
				+ productName + "]";
	}

}

Order.hbm.xml订单配置(订单的配置在下面的演示过程中是不变的):

<hibernate-mapping auto-import="true">
 
 	<class name="com.yiidian.domain.Order" table="t_order">
 		<id name="id" column="id">
 			<generator class="native"></generator>
 		</id>
 		<property name="orderno" column="orderno"></property>
 		<property name="productName" column="product_name"></property>
 		
 		<!-- 多对一配置 -->
 		<many-to-one name="customer"
 				cascade="save-update" 
 				class="com.yiidian.domain.Customer"
 				column="cust_id" 
 				lazy="proxy"
 				fetch="select"/>
 	</class>
 
</hibernate-mapping>  

2.1、连接抓取(Join fetching)

连接抓取, 使用连接抓取可以将原本需要查询两次(或多次)表的多次查询 整合到只需要一次查询即可完成, 举个例子, 我们在初始化一个含有一对多关系的 Customer 与Order 的时候, 会先查询 Customer 表,找到需要的 Customer , 然后再根据Customer.id 到 Order 表中查询将Order 集合初始化, 那么在此完成初始化则需要发送至少两条 SQL 语句, 而如果使用 join 查询的话, 其会根据需要查询的Customer.id, 将 Customer 表与 Order 表连接起来进行查询,仅仅一条 SQL 语句就可以将需要的数据全部查询回来;

使用连接抓取的Customer.hbm.xml配置文件 :

我们使用如此查询语句 :

@Test
	public void test1() {
		Session session = HibernateUtil.getSession();
		Customer customer = (Customer) session.load(Customer.class, 1);
		customer.getOrders().size();
		session.close();
	}

Hibernate 发出的 SQL 语句为 :

Hibernate: 
    select
        customer0_.id as id1_0_0_,
        customer0_.name as name2_0_0_,
        customer0_.gender as gender3_0_0_,
        orders1_.cust_id as cust_id4_1_1_,
        orders1_.id as id1_1_1_,
        orders1_.id as id1_1_2_,
        orders1_.orderno as orderno2_1_2_,
        orders1_.product_name as product_3_1_2_,
        orders1_.cust_id as cust_id4_1_2_ 
    from
        t_customer customer0_ 
    left outer join
        t_order orders1_ 
            on customer0_.id=orders1_.cust_id 
    where
        customer0_.id=?

在此, Hibernate使用了left outer join 连接两个表以一条SQL语句将Order集合给初始化了;

2.2、查询抓取(Select fetching)

查询抓取, 这种策略是在集合抓取的时候的默认策略, 即如果集合需要初始化, 那么
会重新发出一条 SQL 语句进行查询; 这是集合默认的抓取策略, 也就是我们常会出现
N+1次查询的查询策略;

使用查询抓取的Customer.hbm.xml配置文件配置文件 :

查询语句不变, 看看 Hibernate 发出的 SQL 语句:

Hibernate: 
    select
        customer0_.id as id1_0_0_,
        customer0_.name as name2_0_0_,
        customer0_.gender as gender3_0_0_ 
    from
        t_customer customer0_ 
    where
        customer0_.id=?
Hibernate: 
    select
        orders0_.cust_id as cust_id4_1_0_,
        orders0_.id as id1_1_0_,
        orders0_.id as id1_1_1_,
        orders0_.orderno as orderno2_1_1_,
        orders0_.product_name as product_3_1_1_,
        orders0_.cust_id as cust_id4_1_1_ 
    from
        t_order orders0_ 
    where
        orders0_.cust_id=?

这就是查询抓取策略,看到重新发出一条 SQL 语句, 初始化了 Orders 集合;

2.3、子查询抓取(Subselect fetching)

子查询抓取, 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合. 这个理解起来有点糊涂, 举个例子 : 如果你使用 Query 查询出了4 个 Customer 实体, 由于开启了懒加载,那么他们的 Orders 都没有被初始化, 那么我现在手动初始化一个Customer 的 Orders ,此时由于我选的是 Subselect fetching策略,所以 Hibernate 会将前面查询到的实体对象(4 个 Customer)的关联集合(在<set name="orders" fetch="subselect" /> )使用一条 Select 语句一次性抓取回来, 这样减少了与数据库的交互次数, 一次将每个对象的集合都给初始化了;[他是如何这么智能的呢? 原来,他是将上一次查询的 SQL 语句作为这一次查询的 SQL语句的 where 子查询, 所以上次查询到几个对象,那么这次就初始化几个对象的集合----- 正因为如此, 所以 subselect 只在 <set> 集合中出现 ];

子查询抓取Customer.hbm.xml配置文件:

测试的语句有变化 :

@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		List<Customer> results = session.createQuery(
				"From Customer c where c.id in (1,2,3,4)").list();
		// 这里的四个 id 是我数据库中已经准备好的数据
		Customer c0 = (Customer) results.get(0);
		c0.getOrders().size();
		session.close();
	}

Hibernate: 
    select
        customer0_.id as id1_0_,
        customer0_.name as name2_0_,
        customer0_.gender as gender3_0_ 
    from
        t_customer customer0_ 
    where
        customer0_.id in (
            1 , 2 , 3 , 4
        )
Hibernate: 
    select
        orders0_.cust_id as cust_id4_1_1_,
        orders0_.id as id1_1_1_,
        orders0_.id as id1_1_0_,
        orders0_.orderno as orderno2_1_0_,
        orders0_.product_name as product_3_1_0_,
        orders0_.cust_id as cust_id4_1_0_ 
    from
        t_order orders0_ 
    where
        orders0_.cust_id in (
            select
                customer0_.id 
            from
                t_customer customer0_ 
            where
                customer0_.id in (
                    1 , 2 , 3 , 4
                )
        )

是不是发出的SQL语句形式与这个抓取策略的名字一样? Hibernate 的命名很清晰的;

2.4、批量抓取(Batch fetching)
 
批量抓取:"对查询抓取的优化方案,通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合", 也就是说其本质与 select fetching 是一样的,只不过将一次一条的 select 策略改为一次 N 条的批量 select 查询; 举个例子 : 还是借用 Subselect fetching 的例子,我查询出了 4 个 Customer 实体,Orders 开启了懒加载, 所以我现在来手动初始化一个 Customer 的 orders 属性,这种策略本质上就是 select fetching,所以如此设置 :<set name="orders" fetch="select" batch-size="3" /> 那么此时我初始化一个 Customer 的 orders 集合的时候, Hibernate 还是发出了一条 SQL 语句,不过这条 SQL 与是通过指定了 Order 表中的 Customer_ID 外键列表(2个), 这个时候 Hibernate 会以一条 SQL 语句初始化 batch-size 指定的数量的 orders 集合;[他是如何做到的呢? 通过一个主键或外键 列表 做到的, 他将 4 个 Customer 根据batch-size 分成了两组, 一组有三个 Customer id 值的列表,第二组只有一个,在初始化 orders 集合的时候就是根据这两个列表来初始化的]

批量抓取Customer.hbm.xml配置文件 :

在此,我关闭了集合默认的懒加载, 更有利于试验结果测试代码不变,

@Test
	public void test2() {
		Session session = HibernateUtil.getSession();
		List<Customer> results = session.createQuery(
				"From Customer c where c.id in (1,2,3,4)").list();
		// 这里的四个 id 是我数据库中已经准备好的数据
		Customer c0 = (Customer) results.get(0);
		c0.getOrders().size();
		session.close();
	}

再来看看 Hibernate 发出的 SQL 语句 :

Hibernate: 
    select
        customer0_.id as id1_0_,
        customer0_.name as name2_0_,
        customer0_.gender as gender3_0_ 
    from
        t_customer customer0_ 
    where
        customer0_.id in (
            1 , 2 , 3 , 4
        )
Hibernate: 
    select
        orders0_.cust_id as cust_id4_1_1_,
        orders0_.id as id1_1_1_,
        orders0_.id as id1_1_0_,
        orders0_.orderno as orderno2_1_0_,
        orders0_.product_name as product_3_1_0_,
        orders0_.cust_id as cust_id4_1_0_ 
    from
        t_order orders0_ 
    where
        orders0_.cust_id in (
            ?, ?, ?
        )
Hibernate: 
    select
        orders0_.cust_id as cust_id4_1_1_,
        orders0_.id as id1_1_1_,
        orders0_.id as id1_1_0_,
        orders0_.orderno as orderno2_1_0_,
        orders0_.product_name as product_3_1_0_,
        orders0_.cust_id as cust_id4_1_0_ 
    from
        t_order orders0_ 
    where
        orders0_.cust_id=?

原本需要四次 Select 的查询, 由于 Batch-size=3 只用了两次就完成了;

 

源码下载:https://pan.baidu.com/s/1cEZRKM