九、Hibernate检索策略 —— Lazy、batch-size、Fetch

类级别的检索策略:

无论 元素的 lazy 属性是 true 还是 false, Session 的 get() 方法及 Query 的 list() 方法在类级别总是使用立即检索策略; 


若元素的 lazy 属性为 true 或取默认值, Session 的 load() 方法不会执行查询数据表的 SELECT 语句, 仅返回代理类对象的实例, 该代理类实例有如下特征:

由 Hibernate 在运行时采用 CGLIB 工具动态生成 
Hibernate 创建代理类实例时, 仅初始化其 OID 属性 
在应用程序第一次访问代理类实例的非 OID 属性时, Hibernate 会初始化代理类实

1.类级别的检索策略,一般就是用懒加载,(懒加载在用load函数加载的时候,只会用oid来填充代理类,只有使用其他的属性的时候,才会发送sql查询语句,用查询结果来填充代理类对象)

检索策略属性 Lazy

Lazy:true (默认) 延迟检索 ;set 端 一对多
Lazy:false 立即检索;set 端 一对多
Lazy:extra 增强延迟检索; set 端 一对多
Lazy:proxy(默认) 延迟检索;many-to-one 多对一
Lazy:no-proxy 无代理延迟检索;many-to-one 多对一 (需要编译时字节码增强)

举例:班级  学生

Class:

package com.tao.entity;

import java.util.HashSet;
import java.util.Set;

public class Class {
	
	private Integer classId;
	private String ClaaName;
	private Set<Student> students = new HashSet<Student>();
	
	public Integer getClassId() {
		return classId;
	}
	public void setClassId(Integer classId) {
		this.classId = classId;
	}
	public String getClaaName() {
		return ClaaName;
	}
	public void setClaaName(String claaName) {
		ClaaName = claaName;
	}
	public Set<Student> getStudents() {
		return students;
	}
	public void setStudents(Set<Student> students) {
		this.students = students;
	}
	
}

Student:

package com.tao.entity;

public class Student {
	
	private Integer studentId;
	private String studentName;
	private Class c;
	
	public Integer getStudentId() {
		return studentId;
	}
	public void setStudentId(Integer studentId) {
		this.studentId = studentId;
	}
	public String getStudentName() {
		return studentName;
	}
	public void setStudentName(String studentName) {
		this.studentName = studentName;
	}
	public Class getC() {
		return c;
	}
	public void setC(Class c) {
		this.c = c;
	}
	
}

Class.hbm.xml:

<?xml version="1.0"?>
<!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.tao.entity">
	<class name="Class">
		<id name="classId" column="class_id">
			<generator class="native"></generator>
		</id>
		
		<property name="ClaaName" column="class_name"></property>
		<set name="students" cascade="all" inverse="true">
			<key column="c_id"></key>
			<one-to-many class="com.tao.entity.Student"/>
		</set>
	
	</class>	
	

</hibernate-mapping>

Student.hbm.xml:

<?xml version="1.0"?>
<!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.tao.entity">
	<class name="Student">
		<id name="studentId" column="student_id">
			<generator class="native"></generator>
		</id>
		<property name="studentName" column="student_name" length="50"></property>
		
		<many-to-one name="c" column="c_id" class="com.tao.entity.Class" cascade="all"></many-to-one>
	
	</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">

<!-- Hibernate 核心配置文件 -->
<hibernate-configuration>
	
	<session-factory>
		<!-- 配置关于数据库连接的四个项:driverClass  url username password -->
		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
		<property name="connection.username">root</property>
        <property name="connection.password">root</property>
        
       <!-- 方言 -->
	   <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
	
	   <!-- 控制台显示SQL -->
	   <property name="show_sql">true</property>
	
	   <!-- 自动更新表结构 -->
	   <property name="hbm2ddl.auto">update</property>
	   
	   <!-- 引入的映射文件 -->
	   <mapping resource="com/tao/entity/Class.hbm.xml"/>
	   <mapping resource="com/tao/entity/Student.hbm.xml"/>
	   
	</session-factory>
	
</hibernate-configuration>

TestLay:

package com.tao.service;

import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.tao.entity.*;
import com.tao.entity.Class;
import com.tao.util.HibernateUtil;

public class TestLay {
	
	SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
	private Session session;

	@Before
	public void setUp() throws Exception {
		session = sessionFactory.openSession();
		session.beginTransaction();
	}
	
	@After
	public void tearDown() throws Exception {
		session.getTransaction().commit();
		session.close();
	}
    
    @Test
	public void testOne2MantSave() throws Exception {
		Class class1 = new Class();
		class1.setClaaName("精英班");
		Student student1 = new Student();
		student1.setStudentName("孔明");
		student1.setC(class1);
		Student student2 = new Student();
		student2.setStudentName("子房");
		student2.setC(class1);
		
		session.save(student1);
		session.save(student2);
	}

    @Test
	public void testQueryOne2Many() throws Exception {
		List<Class> list = session.createQuery("from Class").list();
		Iterator<Class> iterator = list.iterator();
		while(iterator.hasNext()){
			Class next = iterator.next();
			Set<Student> students = next.getStudents();
			for(Student s:students){
				System.out.println(next.getClaaName()+"学生:"+s.getStudentName());
			}
		}
	}

}

console: 

Hibernate: select class0_.class_id as class_id1_0_, class0_.class_name as class_na2_0_ from Class class0_
Hibernate: select students0_.c_id as c_id3_0_0_, students0_.student_id as student_1_1_0_, students0_.student_id as student_1_1_1_, students0_.student_name as student_2_1_1_, students0_.c_id as c_id3_1_1_ from Student students0_ where students0_.c_id=?
精英班学生:子房
精英班学生:孔明
Hibernate: select students0_.c_id as c_id3_0_0_, students0_.student_id as student_1_1_0_, students0_.student_id as student_1_1_1_, students0_.student_name as student_2_1_1_, students0_.c_id as c_id3_1_1_ from Student students0_ where students0_.c_id=?
培优班学生:李斯
培优班学生:管仲
Hibernate: select students0_.c_id as c_id3_0_0_, students0_.student_id as student_1_1_0_, students0_.student_id as student_1_1_1_, students0_.student_name as student_2_1_1_, students0_.c_id as c_id3_1_1_ from Student students0_ where students0_.c_id=?

基本的准备工作都搭建好了,现在我们来测试一下懒加载。

延迟检索 用到的时候再去查   lazy="true" set端  一对多

级联数据不会先获取

先在Class.hbm.xml里加上 lazy="true",九、Hibernate检索策略 —— Lazy、batch-size、Fetch然后进行测试:

@Test
	public void testLazyTrue() throws Exception {
		Class class1 = (Class) session.get(Class.class, 1);
		Set<Student> studentsList = class1.getStudents();
//		studentsList.iterator();
	}

当注掉最后一句时,控制台只会查班级相关数据,把最后一句放开,遍历student数据,才会发出第二条sql去数据库查询,这就是延迟获取。

立即检索  把级联数据也获取到  lazy="false" set端  一对多

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

@Test
	public void testLazyFalse() throws Exception {
		Class class1 = (Class) session.get(Class.class, 1);
	}

控制台会把Class 数据 和 Student数据都查出来发出两条SQL。

Lazy:extra 增强延迟检索(聪明的检索):set 端 一对多

即调用集合的size/contains等方法的时候,hibernate并不会去加载整个集合的数据,而是发出一条聪明的SQL语句,以便获得需要的值,只有在真正需要用到这些集合元素对象数据的时候,才去发出查询语句加载所有对象的数据。 

当lazy="true"时,执行方法,控制台会打印出两条sql,把Student整个内容进行查询。

@Test
	public void testLazyExtra() throws Exception {
		Class class1 = (Class) session.get(Class.class, 1);
		Set<Student> studentsList = class1.getStudents();
		System.out.println(studentsList.size());
		//studentsList.iterator();
	}

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

但是如果设置成  Lazy="extra",再执行上述方法:看控制台实质上是用count函数来执行。而不去加载全部的student数据,这也是一种延迟策略。

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

 Lazy:proxy(默认) 延迟检索 many-to-one 多对一

在查多的一方时,对应的一的一方为代理类,此时不会查询代理类的内容,当需要访问代理类自身的东西时,再去查。

@Test
	public void testNoProxy() throws Exception {
		Student student = (Student) session.get(Student.class, 1);
		student.getC().getClaaName();
	}

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

 Lazy:no-proxy 无代理延迟检索  many-to-one 多对一 (需要编译时字节码增强)

在eclipse中看来 是没区别的 也是一个代理类 而且 如果不做编译时字节码增强 和proxy是一样的 (了解即可)

@Test
	public void testNoProxy() throws Exception {
		Student student = (Student) session.get(Student.class, 1);
		student.getC().getClaaName();
	}

批量延迟检索 当 lazy = true && 设置了 batch-size 在用到关联对象时 默认会根据batch-size值 来查询 

@Test
	public void testBatch1() throws Exception {
		List<Class> classList = session.createQuery("from Class").list();
		Iterator<Class> it = classList.iterator();
		Class c1 = it.next();
		Class c2 = it.next();
		Class c3 = it.next();
		c1.getStudents().iterator();
		c2.getStudents().iterator();
		c3.getStudents().iterator();
	}

执行上述方法,看console 先查班级,然后再用到学生数据时 每个班学生会发一条SQL去查

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

我们设置一下batch-size = "3"

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

再执行上述方法 会变成一条语句 因为我们还用了懒加载 所以只会在用到的时候去查 这就是批量延迟检索

 批量立即检索  lazy = false 就不会延迟,会根据设置的batch-size 一次性把关联对象也查询出来

这种情况和上面整好相反,就是不用懒加载

@Test
	public void testNoProxy() throws Exception {
		Student student = (Student) session.get(Student.class, 1);
		student.getC().getClaaName();
	}

我们设置  lazy = false 然后执行下面的方法,看console 会立即把学生对应数据给批量查出来

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

Fetch:select(默认) 查询方式 会先查班级然后通过班级去每个班级学生

执行下面的方法:

@Test
	public void testFetch1() throws Exception {
		List<Class> classList = session.createQuery("from Class").list();
		Iterator<Class> it = classList.iterator();
		Class c1 = it.next();
		Class c2 = it.next();
		Class c3 = it.next();
		c1.getStudents().iterator();
		c2.getStudents().iterator();
		c3.getStudents().iterator();
	}

看console 和批量立即检索没什么区别:

INFO: HHH000126: Indexes: [fk_swj9e42aicxndumd3odiux122, primary]
一月 31, 2019 1:47:24 下午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Hibernate: select class0_.class_id as class_id1_0_, class0_.class_name as class_na2_0_ from Class class0_
Hibernate: select students0_.c_id as c_id3_0_1_, students0_.student_id as student_1_1_1_, students0_.student_id as student_1_1_0_, students0_.student_name as student_2_1_0_, students0_.c_id as c_id3_1_0_ from Student students0_ where students0_.c_id in (?, ?, ?)

我们把这边的 fetch = "subselect" 设置一下,

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

然后继续执行上述方法,再看控制台打印的SQL:

INFO: HHH000232: Schema update complete
Hibernate: select class0_.class_id as class_id1_0_, class0_.class_name as class_na2_0_ from Class class0_
Hibernate: select students0_.c_id as c_id3_0_1_, students0_.student_id as student_1_1_1_, students0_.student_id as student_1_1_0_, students0_.student_name as student_2_1_0_, students0_.c_id as c_id3_1_0_ from Student students0_ where students0_.c_id in (select class0_.class_id from Class class0_)

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

发现用了子查询,在某些情况下使用子查询,效率是会提高的

"fetch" = "join" 使用外连接查询

我们先把fetch 改回默认的 fetch= "select" ,然后执行一下测试方法:

@Test
	public void testFetch2() throws Exception {
		Class class1 = (Class) session.get(Class.class, 1);
	}

看console:

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

get 是直接去数据库查 且会把关联对象数据也查出来 所以此处如果默认的fetch = "select" 就应该是两条数据. 

现在我们把fetch 设置成  "fetch" = "join" ,再执行刚刚的测试方法:

九、Hibernate检索策略 —— Lazy、batch-size、Fetch

则会使用左外连接 用一条sql查询出来 减少多次查询。

总结:性能优化 和策略的选择不是绝对的,根据业务需求,以及尽量的少出错误来选择。有时候需要优化 有的时候可能不优化反而代码效率更高。