天天看點

九、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查詢出來 減少多次查詢。
總結:性能優化 和政策的選擇不是絕對的,根據業務需求,以及盡量的少出錯誤來選擇。有時候需要優化 有的時候可能不優化反而代碼效率更高。

繼續閱讀