通过测试用例和执行结果,让你正确推测和理解Session中Load和get的区别,不再困惑

来源:互联网 发布:老域名的好处 编辑:程序博客网 时间:2024/05/16 14:14

     我们知道Session是Hibernate框架的核心类,也是初学hibernate时最重要、接触最多的类,它提供了load()和get()方法,根据主键从数据库中查询记录。这2个方法存在一些特性和差别,需要开发者注意,否则很容易出错。网上有很多介绍load和get区别的帖子,虽然很多帖子介绍的都很好,但遗憾的是缺少对应单元测试和执行结果的佐证,而且有些博客之间还是相互矛盾的。出现矛盾,往往是大家使用的Hibernate版本不一致,或者不同的人的理解不一致。如果出现这种情况,不知道该相信谁,最好的解决方式就是:自己去搭建测试环境,按照自己的理解去编写单元测试,看看实际的执行结果是否符合预期。在这种推测-测试-理解中,我们能够加深对所学知识的理解。扯的有点远呵呵,进入正题吧。

       我使用是hibernate4.1.6版本和mysql-essential-5.0.87数据库,测试是基于student表的,只有3条记录.


hibernate.cfg.xml的配置如下,这里开启了二级缓存和查询缓存,如果测试的时候不需要,我们可以去关闭。

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><hibernate-configuration><session-factory><!-- Database connection settings --><property name="connection.driver_class">com.mysql.jdbc.Driver</property><property name="connection.url">jdbc:mysql://localhost/hibernate</property><property name="connection.username">root</property><property name="connection.password">root</property><!-- JDBC connection pool (use the built-in) --><property name="connection.pool_size">1</property><!-- SQL dialect --><property name="dialect">org.hibernate.dialect.MySQLDialect</property><!-- Enable Hibernate's automatic session context management --><property name="current_session_context_class">thread</property><!-- Disable the second-level cache --><property name="cache.use_second_level_cache">true</property><property name="hibernate.cache.use_query_cache">true</property>  <property name="cache.region.factory_class">org.hibernate.cache.EhCacheRegionFactory</property><!-- Echo all executed SQL to stdout --><property name="show_sql">true</property><property name="format_sql">true</property><!-- Drop and re-create the database schema on startup --><property name="hbm2ddl.auto">update</property><mapping resource="hibernate/Student.hbm.xml" /></session-factory></hibernate-configuration>
Student的实体映射文件如下,默认使用了延迟加载,如果不需要,对应的单元测试会提示关闭.
<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="hibernate.Student" lazy="true"><cache usage="read-only" /><id name="id" /><property name="name" /><property name="age" /></class></hibernate-mapping>

1、如果没有查询到记录,load会报异常,get返回null

public class TestLoadAndGetOfSession{private SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();@Testpublic void testNotExsitUseGet(){Session session = sessionFactory.openSession();Student student = (Student) session.get(Student.class, 100);System.out.println(student);// nullsession.close();}@Testpublic void testNotExsitUseLoad(){Session session = sessionFactory.openSession();// org.hibernate.ObjectNotFoundException: No row with the given// identifier existsStudent student = (Student) session.load(Student.class, 100);System.out.println(student);session.close();}}
数据中没有主键100对应的记录,load会抛异常,get返回null。显然这种情况下,get方法的表现更符合调用者的预期。为什么找不到数据,load会抛异常呢?这主要跟load使用到代理有关,后面在介绍吧。

2、load可以使用实体对象的延迟加载,get不能使用延迟加载

@Test// 测试该方法需要将Student.hbm.xml中lazy设置成false或truepublic void testProxyWhetherNotLazy(){Session session = sessionFactory.openSession();// 1.没有延迟加载的情况下,load和get都会发出sql语// 2.使用延迟加载,load不会发出sql,get会发出sql查询session.get(Student.class, 1);session.load(Student.class, 2);session.close();}
如果实体对象配置了lazy="true",则load会使用延迟加载,真正需要使用到数据的时候才会发出sql查询,而get方法不依赖与是否使用延迟加载,一旦调用get,就会发出sql语句。注意:这里不考虑一级缓存和二级缓存的影响,因为如果有缓存的话且能够命中,load和get都不会发出sql查询。

3、load一定返回实体对象的代理,get一定返回实体对象本身吗?不一定,从一级缓存来看

@Test// 需要设置Student.hbm.xml中lazy属性,是否使用延迟加载public void testClassWhetherUseLazy(){<span style="white-space:pre"></span>Session session = sessionFactory.openSession();Student student1 = (Student) session.get(Student.class, 1);Student student2 = (Student) session.load(Student.class, 1);System.out.println("student1==" + student1.getClass());System.out.println("student2==" + student2.getClass());Student student3 = (Student) session.load(Student.class, 2);Student student4 = (Student) session.get(Student.class, 2);System.out.println("student3==" + student3.getClass());System.out.println("student4==" + student4.getClass());session.close();}
如果设置lazy="false",执行结果是:
Hibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?student1==class hibernate.Studentstudent2==class hibernate.StudentHibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?student3==class hibernate.Studentstudent4==class hibernate.Student

如果设置lazy="true",执行结果是:
Hibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?student1==class hibernate.Studentstudent2==class hibernate.StudentHibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?student3==class hibernate.Student_$$_javassist_0student4==class hibernate.Student_$$_javassist_0
对比以上执行结果,我们可以得到以下结论:
  • 如果实体对象不使用延迟加载,load和get返回的都是对象本身,两者没有区别。也就说是否使用代理,取决于是否配置了延迟加载。
  • load和get执行结果都会存入session缓存(一级缓存)。先执行get,后执行load,由于get已经将全部实体信息存入了缓存,之后load的时候就没有必要再去创建代理了,直接使用get存入的缓存即可,这种情况下,get和load返回的都是实体对象本身,如student1和student2。
  • 先执行load,后执行get。由于开启了延迟加载,load会返回代理对象并将代理对象存入缓存,不过由于我们没有真正使用到实体对象的值,所以缓存的对象中,除了主键外,其余属性值都是空的,这一点可以通过执行load并没有发出sql来证明。之后使用get的时候,由于session缓存中已经有代理对象了,所以get方法直接使用代理。但是因为代理对象属性值都是空的,所以get会发出sql查询获取对应的值,然后更新到代理对象中。这种情况下load和get返回的都是代理对象。也就是说:load返回了空的代理对象,get发出sql为代理对象的属性赋值。
至此我们明白了一级缓存、延迟加载对load和get的影响。这里我们先不考虑二级缓存对load和get的影响,后面会分析。

4、如果开启了二级缓存,load和get都会使用二级缓存

很多帖子说,get不会利用二级缓存,load会使用二级缓存。这其实是不对,通过我的测试,发现load和get都能够有效的使用二级缓存。
@Testpublic void testSecondCache(){Session anotherSession = sessionFactory.openSession();Student anotherStu = (Student) anotherSession.get(Student.class, 2);System.out.println("模拟别的session查询:" + anotherStu);anotherSession.close();System.out.println("----------测试load-----------");// 在新的session中使用load,开启了二级缓存,不会再发出sqlSession loadSession = sessionFactory.openSession();Student loadStu = (Student) loadSession.load(Student.class, 2);System.out.println("load查询,session中无缓存" + loadStu);loadSession.close();System.out.println("----------测试get-----------");// 在新的session中使用get,开启了二级缓存,不会再发出sqlSession getSession = sessionFactory.openSession();Student getStu = (Student) getSession.get(Student.class, 2);System.out.println("get查询,session中无缓存" + getStu);getSession.close();}
执行该方法需要开启hibernate的二级缓存配置,执行结果如下:
Hibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?模拟别的session查询:hibernate.Student@40b181[id=2, name=zhangsan111, age=18]----------测试load-----------load查询,session中无缓存hibernate.Student@14ed577[id=2, name=zhangsan111, age=18]----------测试get-----------get查询,session中无缓存hibernate.Student@a09e41[id=2, name=zhangsan111, age=18]
很显然,3个不同的session,都查询id=2的student,只发出了一条sql语句。这就是说load和get都会使用二级缓存。

5、load一定返回实体对象的代理,get一定返回实体对象本身吗?不一定,从二级缓存来看

现在我们来讨论下,第3条结论中的遗留问题:二级缓存和延迟加载对load和get的影响,主要是生成代理对象还是对象本身的问题。
@Test// 测试二级缓存、延迟加载对load和get是否生成代理对象的差别,与testClassWhetherUseLazy对应public void testProxyWhenUsetSecondCache(){// 使用二级缓存Session oneSession = sessionFactory.openSession();Student s1 = (Student) oneSession.get(Student.class, 1);Student s2 = (Student) oneSession.load(Student.class, 2);System.out.println("s1==" + s1.getClass());System.out.println("s2==" + s2.getClass());oneSession.close();Session twoSession = sessionFactory.openSession();Student student1 = (Student) twoSession.load(Student.class, 1);System.out.println("student1 == " + student1.getClass());Student student2 = (Student) twoSession.get(Student.class, 2);System.out.println("student2 == " + student2.getClass());twoSession.close();}
如果lazy=false,效果跟第三条结论一样:无论是get还是load返回的都是实体对象本身,因为这时返回代理已经没有什么意义,只能是浪费空间和性能。如果lazy=true,执行结果如下:
Hibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?s1==class hibernate.Students2==class hibernate.Student_$$_javassist_0student1 == class hibernate.Student_$$_javassist_0Hibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?student2 == class hibernate.Student
通过执行结果可以看出,我们在第3条结论中的分析结果不适用于二级缓存的情况。在使用二级缓存和延迟加载的情况下,load永远返回的是代理对象,而get返回的是实体对象本身。不知道为什么会这样?感觉有点奇怪,一级缓存和二级缓存的这种特性,是hibernate的bug,还是有意为之呢?

6、一级缓存,直接获取内存中的缓存对象

@Testpublic void testSameObjectInSession(){// 在新的session中使用get,开启了二级缓存,不会再发出sqlSession getSession = sessionFactory.openSession();Student getStu1 = (Student) getSession.get(Student.class, 2);System.out.println("getStu1==" + getStu1);Student getStu2 = (Student) getSession.get(Student.class, 2);System.out.println("getStu2==" + getStu2);getSession.close();System.out.println(getStu1 == getStu2);// trueSystem.out.println(getStu1.hashCode() == getStu2.hashCode());// true}@Testpublic void testSameObjectInSession2(){// 在新的session中使用get,开启了二级缓存,不会再发出sqlSession getSession = sessionFactory.openSession();Student getStu1 = (Student) getSession.load(Student.class, 2);//System.out.println("getStu1==" + getStu1);Student getStu2 = (Student) getSession.get(Student.class, 2);System.out.println("getStu2==" + getStu2);getSession.close();System.out.println(getStu1 == getStu2);// trueSystem.out.println(getStu1.hashCode() == getStu2.hashCode());// true}
无论是否配置延迟加载效果都是一样的。这说明session级别的缓存,是共享同一个内存对象的。

7、二级缓存,具有copy-on-write特征

@Test// 缓存确实命中了,但返回的对象hashcode不相同,这说明获取缓存的时候,使用了copy-on-write.// 这样是合理的,防止1个session改动了数据,影响到其他session中对应的数据public void testNotSameObject(){// 在新的session中使用get,开启了二级缓存,不会再发出sqlSession getSession = sessionFactory.openSession();Student getStu = (Student) getSession.get(Student.class, 2);System.out.println("getStu==" + getStu);getSession.close();// 使用了二级缓存,不会发出sql语句Session getSession2 = sessionFactory.openSession();Student getStu2 = (Student) getSession2.get(Student.class, 2);System.out.println("getStu2==" + getStu2);getSession2.close();System.out.println(getStu == getStu2);// falseSystem.out.println(getStu.hashCode() == getStu2.hashCode());// false}
执行结果如下:
Hibernate:     select        student0_.id as id0_0_,        student0_.name as name0_0_,        student0_.age as age0_0_     from        Student student0_     where        student0_.id=?getStu==hibernate.Student@31ff23[id=2, name=zhangsan111, age=18]getStu2==hibernate.Student@1a3aa2c[id=2, name=zhangsan111, age=18]falsefalse
发现不同的session直接,的确命中了二级缓存,因为只查了一次数据库。但是不同session中获取的对象是不同的,这也就是说,二级缓存具有cpoy-on-write特征。如果session发现了二级缓存中有需要的数据,那么会直接新建1个对象,然后用二级缓存中对应的实体对象,对新创建的对象进行赋值。


好累啊!终于测完了这几种情况,也弄懂了load和get的区别了。发出来跟大家共享下,如果理解的有错误,欢迎大牛们指正。

1 0