类级别的检索策略:
无论 元素的 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",然后进行测试:
@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端 一对多
@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(); }
但是如果设置成 Lazy="extra",再执行上述方法:看控制台实质上是用count函数来执行。而不去加载全部的student数据,这也是一种延迟策略。
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值 来查询
@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去查
我们设置一下batch-size = "3"
再执行上述方法 会变成一条语句 因为我们还用了懒加载 所以只会在用到的时候去查 这就是批量延迟检索
批量立即检索 lazy = false 就不会延迟,会根据设置的batch-size 一次性把关联对象也查询出来
这种情况和上面整好相反,就是不用懒加载
@Test public void testNoProxy() throws Exception { Student student = (Student) session.get(Student.class, 1); student.getC().getClaaName(); }
我们设置 lazy = false 然后执行下面的方法,看console 会立即把学生对应数据给批量查出来
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" 设置一下,
然后继续执行上述方法,再看控制台打印的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_)
发现用了子查询,在某些情况下使用子查询,效率是会提高的
"fetch" = "join" 使用外连接查询
我们先把fetch 改回默认的 fetch= "select" ,然后执行一下测试方法:
@Test public void testFetch2() throws Exception { Class class1 = (Class) session.get(Class.class, 1); }
看console:
get 是直接去数据库查 且会把关联对象数据也查出来 所以此处如果默认的fetch = "select" 就应该是两条数据.
现在我们把fetch 设置成 "fetch" = "join" ,再执行刚刚的测试方法:
则会使用左外连接 用一条sql查询出来 减少多次查询。
总结:性能优化 和策略的选择不是绝对的,根据业务需求,以及尽量的少出错误来选择。有时候需要优化 有的时候可能不优化反而代码效率更高。