版权声明:转载时请以超连接形式标明文章原始出处和做者信息及本声明
http://www.blogbus.com/fallenlord-logs/57543854.html
html
最近面试别人,正好出的笔试题中有道关于Hibernate延迟加载的问题,聊天过程当中发现不少人对Hibernate的延迟加载有些理解误区,写些东东在这里,但愿对你们有所帮助。java
首先是第一个误区:延迟加载只能做用于关联实体
看到这个是否是在想:非关联实体延迟加载有什么用?
为了解答上面这个问题,咱们能够先考虑另外一个问题:Hibernate Session的get和load方法有什么区别?
若是你的回答是:当方法参数为数据库不存在的id时,get会返回null,load会抛出异常,那么恭喜你,进入了第二个误区
若是此时你还想补充一下:load会从缓存中取出数据而get不会,再次恭喜,进入第三个误区面试
若是你在上面三个误区中有一个踏入了,那么我敢打赌,你必定是被网上那些半吊子的工程师们写的博客给戕害了。。。。
此时是否是很愤怒?这些长久以来你牢记在心的Hibernate的特性原来都是浮云。。。。sql
呵呵,接下来咱们一个个来走出这些误区。
Mop上无图无真相,咱们这里无码无真相——不要误会,我是说代码数据库
首先看看第二个误区:当方法参数为数据库不存在的id时,get会返回null,load会抛出异常
若是你如今想说:没错啊,我本身就测试过,get确实返回了null,load确实抛出了异常。
那么请回答:load是在执行load语句时抛出异常的吗?为何?若是你答不上来,那么接着看下面的代码吧:缓存
@Test(expected = IllegalArgumentException.class) public void 延迟加载() throws Exception { // 启动 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); User user = (User)session.load(User.class, 100L); // 不存在的ID try { user.getName(); } catch (ObjectNotFoundException ex) { // 命中数据库发现没有对象即抛出ObjectNotFoundException异常 throw new IllegalArgumentException("随便抛出一个不可能的异常"); } tx.commit(); session.close(); }
由这个test case咱们能够知道load并非在执行时就立刻抛出不存在数据的异常的(ObjectNotFoundException),这是为何呢?再看代码:session
@Test(expected = IllegalArgumentException.class) public void 延迟加载() throws Exception { // 启动 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); User user = (User)session.load(User.class, 100L); // 不存在的ID Assert.assertTrue(user instanceof HibernateProxy); user.getId(); // 因为ID是不被延迟加载的属性,所以不会抛出异常 try { Hibernate.initialize(user); // 此时才会触发命中数据库 //user.getName(); } catch (ObjectNotFoundException ex) { // 命中数据库发现没有对象即抛出ObjectNotFoundException异常 throw new IllegalArgumentException("随便抛出一个不可能的异常"); } tx.commit(); session.close(); }
看高亮的几行,代码已经把问题说得很清楚了,get和load最大的区别是(假设缓存皆空的状况):get是当即命中数据库去查询这条记录,而load则是直接返回一个代理对象(HibernateProxy)而不命中数据库,换句话来讲load是为单个对象进行了延迟加载,若是你不去访问这个对象的除ID外的属性,即便目标记录不存在它也永远都不会抛出异常。因为load不当即命中数据库,它确实有必定概率提升效率测试
OK,我想上面一段话应该能够解释第一和第二个误区了,那么第三个误区呢?
再看代码spa
@Test public void get和load一级缓存测试() throws Exception { // 启动 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); // 验证load在缓存为空的状况下是否会使得加载的对象过一级缓存 User user1 = (User)session.load(User.class, 1L); // 存在的ID,此时虽然没有解开Proxy但已经进入缓存 Assert.assertTrue(user1 instanceof HibernateProxy); Hibernate.initialize(user1); // 解开Proxy,会触发命中数据库操做 User user3 = (User)session.get(User.class, 1L); Assert.assertTrue(user3 instanceof HibernateProxy); // 即便使用get,但因为缓存中存储的是一个Proxy,因此这里获得的也是Proxy Hibernate.initialize(user3); // 解开Proxy,但不会命中数据库 // 验证在load一个不存在的ID后,不解开而后get User user4 = (User)session.load(User.class, 100L); // 不存在的ID,仍然将Proxy进入缓存 Assert.assertTrue(user4 instanceof HibernateProxy); //Hibernate.initialize(user3); // 不解开Proxy try { session.get(User.class, 100L); // 获得Proxy,命中数据库尝试解开Proxy,因为ID不存在所以抛出异常 Assert.fail("ID不存在因此会出错,不会执行本条"); } catch (ObjectNotFoundException ex) { } // 清空缓存 session.clear(); // 验证缓存为空的状况下get是否为Proxy User user6 = (User)session.get(User.class, 1L); // 命中数据库,直接将组装完成的User实体进入缓存 Assert.assertTrue(!(user6 instanceof HibernateProxy)); // 验证get从缓存中取出对象 User user7 = (User)session.get(User.class, 1L); Assert.assertTrue(!(user7 instanceof HibernateProxy)); // 缓存中是真实的User对象,get取出的就是真实的User对象 // 验证load是否从一级缓存取数据 User user8 = (User)session.load(User.class, 1L); Assert.assertTrue(!(user8 instanceof HibernateProxy)); // 缓存中是真实的User对象,load取出的也是真实的User对象 tx.commit(); session.close(); }
相信注释已经足够详细了,打开hibernate.show_sql,总共命中三次数据库(执行SQL),分别在高亮的三行处,其他的全是从缓存中取数据。
并且值得注意的一点是,若是对象是从load加载到缓存中的,那么不论get仍是load获取出来的都是一个Proxy,若是没有被解开过,那么get会尝试解开它;若是对象是从get加载到缓存中的,那么load和get取出来都会是真实的实体对象。也就是说,get和load都会从缓存中取出对象,且取出的对象老是保持其第一次加载时的状态(load为Proxy,get为真实对象)hibernate
以上代码是一级缓存的验证,想验证二级缓存只须要从Hibernate中开启二级缓存再次运行代码便可