類級别的檢索政策:
無論 元素的 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:
Student: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; } }
Class.hbm.xml: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; } }
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="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>
hibernate.cfg.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>
TestLay:<?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>
console: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()); } } } }
基本的準備工作都搭建好了,現在我們來測試一下懶加載。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",
然後進行測試:當注掉最後一句時,控制台隻會查班級相關資料,把最後一句放開,周遊student資料,才會發出第二條sql去資料庫查詢,這就是延遲擷取。@Test public void testLazyTrue() throws Exception { Class class1 = (Class) session.get(Class.class, 1); Set<Student> studentsList = class1.getStudents(); // studentsList.iterator(); }
立即檢索 把級聯資料也擷取到 lazy="false" set端 一對多控制台會把Class 資料 和 Student資料都查出來發出兩條SQL。@Test public void testLazyFalse() throws Exception { Class class1 = (Class) session.get(Class.class, 1); }
Lazy:extra 增強延遲檢索(聰明的檢索):set 端 一對多
即調用集合的size/contains等方法的時候,hibernate并不會去加載整個集合的資料,而是發出一條聰明的SQL語句,以便獲得需要的值,隻有在真正需要用到這些集合元素對象資料的時候,才去發出查詢語句加載所有對象的資料。
當lazy="true"時,執行方法,控制台會列印出兩條sql,把Student整個内容進行查詢。
但是如果設定成 Lazy="extra",再執行上述方法:看控制台實質上是用count函數來執行。而不去加載全部的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(); }
Lazy:proxy(預設) 延遲檢索 many-to-one 多對一
在查多的一方時,對應的一的一方為代理類,此時不會查詢代理類的内容,當需要通路代理類自身的東西時,再去查。
@Test public void testNoProxy() throws Exception { Student student = (Student) session.get(Student.class, 1); student.getC().getClaaName(); }
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值 來查詢執行上述方法,看console 先查班級,然後再用到學生資料時 每個班學生會發一條SQL去查 我們設定一下batch-size = "3" 再執行上述方法 會變成一條語句 因為我們還用了懶加載 是以隻會在用到的時候去查 這就是批量延遲檢索@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(); }
批量立即檢索 lazy = false 就不會延遲,會根據設定的batch-size 一次性把關聯對象也查詢出來
這種情況和上面整好相反,就是不用懶加載
我們設定 lazy = false 然後執行下面的方法,看console 會立即把學生對應資料給批量查出來@Test public void testNoProxy() throws Exception { Student student = (Student) session.get(Student.class, 1); student.getC().getClaaName(); }
Fetch:select(預設) 查詢方式 會先查班級然後通過班級去每個班級學生
執行下面的方法:
看console 和批量立即檢索沒什麼差別:@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(); }
我們把這邊的 fetch = "subselect" 設定一下, 然後繼續執行上述方法,再看控制台列印的SQL: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 (?, ?, ?)
發現用了子查詢,在某些情況下使用子查詢,效率是會提高的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_)
"fetch" = "join" 使用外連接配接查詢
我們先把fetch 改回預設的 fetch= "select" ,然後執行一下測試方法:
看console:@Test public void testFetch2() throws Exception { Class class1 = (Class) session.get(Class.class, 1); }
get 是直接去資料庫查 且會把關聯對象資料也查出來 是以此處如果預設的fetch = "select" 就應該是兩條資料.
現在我們把fetch 設定成 "fetch" = "join" ,再執行剛剛的測試方法:
則會使用左外連接配接 用一條sql查詢出來 減少多次查詢。
總結:性能優化 和政策的選擇不是絕對的,根據業務需求,以及盡量的少出錯誤來選擇。有時候需要優化 有的時候可能不優化反而代碼效率更高。