您的位置:首页 > 其它

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

2014-04-17 01:27 471 查看
我们知道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>
现在我们来介绍load和get的区别吧

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

public class TestLoadAndGetOfSession
{
	private SessionFactory sessionFactory = new Configuration().configure()
			.buildSessionFactory();

	@Test
	public void testNotExsitUseGet()
	{
		Session session = sessionFactory.openSession();

		Student student = (Student) session.get(Student.class, 100);

		System.out.println(student);// null

		session.close();
	}

	@Test
	public void testNotExsitUseLoad()
	{
		Session session = sessionFactory.openSession();

		// org.hibernate.ObjectNotFoundException: No row with the given
		// identifier exists
		Student 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或true
public 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()
{
	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.Student
student2==class hibernate.Student
Hibernate: 
    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
student4==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.Student
student2==class hibernate.Student
Hibernate: 
    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_0
student4==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都能够有效的使用二级缓存。
@Test
public 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,开启了二级缓存,不会再发出sql
	Session 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,开启了二级缓存,不会再发出sql
	Session 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.Student
s2==class hibernate.Student_$$_javassist_0
student1 == class hibernate.Student_$$_javassist_0
Hibernate: 
    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、一级缓存,直接获取内存中的缓存对象

@Test
public void testSameObjectInSession()
{
	// 在新的session中使用get,开启了二级缓存,不会再发出sql
	Session 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);// true
	System.out.println(getStu1.hashCode() == getStu2.hashCode());// true
}
	
@Test
public void testSameObjectInSession2()
{
	// 在新的session中使用get,开启了二级缓存,不会再发出sql
	Session 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);// true
	System.out.println(getStu1.hashCode() == getStu2.hashCode());// true
}
无论是否配置延迟加载效果都是一样的。这说明session级别的缓存,是共享同一个内存对象的。

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

@Test
// 缓存确实命中了,但返回的对象hashcode不相同,这说明获取缓存的时候,使用了copy-on-write.
// 这样是合理的,防止1个session改动了数据,影响到其他session中对应的数据
public void testNotSameObject()
{
	// 在新的session中使用get,开启了二级缓存,不会再发出sql
	Session 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);// false
	System.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]
false
false
发现不同的session直接,的确命中了二级缓存,因为只查了一次数据库。但是不同session中获取的对象是不同的,这也就是说,二级缓存具有cpoy-on-write特征。如果session发现了二级缓存中有需要的数据,那么会直接新建1个对象,然后用二级缓存中对应的实体对象,对新创建的对象进行赋值。

好累啊!终于测完了这几种情况,也弄懂了load和get的区别了。发出来跟大家共享下,如果理解的有错误,欢迎大牛们指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: