`
kyo100900
  • 浏览: 633830 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

hibernate延迟加载的原理与实现

阅读更多

大概一年半前,我开始了Java之旅。上来就是spring MVC + hibernate3 + spring的架构让我最头痛的就是hiberante3。后来项目因为数据量大,被迫改成了JDBC。现在回想当初那个hibernate3使用的相当菜了(现在似乎也算刚刚入门),而且对很多hibernate的概念原理懵懵懂懂,用的不好,招来老板对hibernate的一顿质疑。一年半后的今天。当我再次看待hibernate时,除了当年隐隐约约的“委屈”涌上心头,更希望让大家了解hibernate,不要仅仅从应用角度出发。好了,咱们今天来看看hibernate关于延迟加载的原理与实现。主要使用的就是CGLib。

 

 

首先看一段熟悉的代码:

 

	public void testLazy() {
		// 自己弄了一个丑陋的sessionFactory和session,主要是因为自己写的,比较容易控制。
		SessionFactory<User, String> sessionFactory = new SessionFactoryImpl<User, String>(
				User.class);
		Session<User, String> session = sessionFactory.openSession();
		User u = session.load("1");
		// 这一句不会触发数据库查询操作,请看图1
		assertEquals("1", u.getId());
		// 访问的是非主键属性,开始查询数据库,请看图2
		assertNotSame("11", u.getName());
		session.close();
	}

 

 图1:通过断点,我们可以看到User对象只是一个代理,并且只有主键id有值

 

 

图2:通过断点,我们可以看到原本属于代理对象的User,其targetObject一项已经有值了,表示已经发出select语句从数据库取值了。

 

 

好,有了这点感性认识,咱们继续前进。

 

原理:在hibernate中,如果使用了延迟加载(比如常见的load方法),那么除访问主键以外的其它属性时,就会去访问数据库(假设不考虑hibernate的一级缓存),此时session是不允许被关闭。 

 

先简单看看要操作的对象User

 

@Entity
public class User{
	@Id
	private String id;

	@Column
	private String name;

	........set,get省略
}

 

 这些@Entity,@Id,@Column也是我写的一些标注,让大家感觉更贴近hibernate(或jpa)些所做的一些模拟。所有的标注都是空实现,比如说@Id

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Id {

}

 

这些标注在后面的反射操作中会用到。

 

 

好现在我们从session.load方法慢慢深入

 

public T load(PK id) {
		// annotationParas利用反射解析被标注为@Entity的type类型(比如说上文提到的User.class),
		// 然后将标注为@Id和@Column的属性存入FieldClass对象,供下面进一步使用
		final FieldClass fieldClass = annotationParas.generatorSQL(type);
		T obj = null;
		// 因为是load方法,默认给它加一个基于CGLib的拦截器,该拦截器是实现延迟加载的关键,稍后我们再详细看看
		LazyInitializer<T, PK> interceptor = new LazyInitializerImpl<T, PK>();
		// 将当前的session对象设置给该拦截器,以便在取非主键属性时,能够正常查询数据库
		// 从而将对象初始化
		interceptor.setSession(this);

		// 默认生成的是一个基于CGLib的代理,并非真实的对象,通过图1,图2,大家应该可以看到
		// User=User$$EnhancerByCGLib$$... 我就不多说了
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(type);
		// 注意别忘记将刚才生成的拦截器注入到代理中去
		enhancer.setCallback(interceptor);
		obj = (T) enhancer.create();

		try {
			// 因为通过CGLib生成的User对象,主键属性id=null
			// 所以我们还得执行主键的set方法(比如说setId),这样就可以像图1显示那样,id="1"是有值的
			// 到此,load方法执行完毕,始终没有查询数据库
			Method method = type.getMethod(getMethodFromField(fieldClass
					.getKey()),
					new Class<?>[] { fieldClass.getKey().getType() });
			method.invoke(obj, new Object[] { id });
			return obj;
		} catch (Exception e) {
			e.printStackTrace();
		}

		throw new RuntimeException("找不到主键为:[" + id + "]的实体");
	}

 

 

annotationParas其实就是一个工具类,完成实体类与数据库表之间的映射。里面无非就是反射,判断,组装,最后组成一个我们想要的数据信息装进一个载体里——在这里是一个叫FieldClass 的JavaBean。对hibernate来说,将对象映射工作是在程序启动之初就完成了。

 

接下来是LazyInitializer,咱们先看它的实现:

 

 

public class LazyInitializerImpl<T, PK extends Serializable> implements
		LazyInitializer<T, PK>, MethodInterceptor {

	private Session<T, PK> session; // 绑定的session对象
	private boolean isAlreadyInit = false; // 是否已经查询过数据库
	private T targetObject; // 目标对象

	// 通CGLib生成的对象,如果设置了此拦截器,那么其方法每次调用时,都会触发此方法
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		// 继续利用反射得到代理对象的标有@Id的主键属性
		Class<?> clas = obj.getClass();
		Field field = getPrimaryKey(clas);

		assert (field != null);
		// 如果当前调用的方法是标注为@Id的话,那么就不从数据库里取,直接返回代理
		// 即如果是getId()的话,直接用代理调用;如果是getName()的话,那就必须查询数据库,取出实际对象,并进行相应的调用了
		if (method.getName().toLowerCase().indexOf(field.getName()) > -1) {
			return proxy.invokeSuper(obj, args);
		} else {
			if (!isAlreadyInit) {
				field.setAccessible(true);
				// session.get方法直接查询数据库,并将ResultSet结果组将成User对象
				targetObject = session.get((PK) field.get(obj));
				isAlreadyInit = true;
			}
			return method.invoke(targetObject, args);

		}

	}

        ..............省略其它辅助方法

}

 

 

当我们User u = session.load("1")对象后,

 

  • 调用u.getId()时,会立即转入LazyInitializer的intercept()方法,然后按照上面的逻辑,自然是直接返回getId()的值,根本不会与数据库打交道。
  • 当调用u.getName()时,也会先立即转入LazyInitializer的intercept()方法,然后发现"getName()".indexOf("id")>-1==false,于是立即利用已经绑定的session对象去用主键ID往数据库里查询。这也是为什么在hibernate中,如果使用了延迟加载使得一个代理没有被初始化,而你又关闭了session,再次去取除主键外的其它属性时,常常出现session close异常。

看到这里,大家是不是觉得所谓的延迟加载并不是那么神秘,而且从数据库I/O操作上来说,会觉得这种设计确实是比较优雅。当然hibernate还有很多很多值得学习和借鉴的特性,下次有时间我再整理整理。

59
8
分享到:
评论
29 楼 2047699523 2015-03-24  
java hibernate demo使用实例教程源代码下载:http://www.zuidaima.com/share/khibernate-p1-s1.htm
28 楼 在世界的中心呼喚愛 2014-01-09  
hantsy 写道
这种lazy的解释只对了一半,按java persistence with hibernate的讲解,
u.getId()同样有可能引发数据查询。
在Hibernate 实现lazy的方式有两种,一种是动态代理(运行时产生),另一种是编译时二进制代码增强。
你说的只是第一种情况。
在第二情况下,可以对类的各个属性设置lazy=true,编译时处理的话(hibernate tools 提供了这样的工具),在u.getId()时同样会访问数据库。

属性懒加载,在调用属性的时候才会查询库
27 楼 saint.deng 2009-02-19  
能不能把你写的示例的源码上传上去我们下载一下。我们可以亲身体验一下!!!
26 楼 quiii 2009-01-12  
25 楼 天空之城 2008-12-16  
好文章啊,楼主 
24 楼 bloodrate 2008-09-23  
很好的方法,有几个问题:
1、由一个地方有疑惑:
LazyInitializer<T, PK> interceptor = new LazyInitializerImpl<T, PK>();  
enhancer.setCallback(interceptor);  

我没试过这段代码,也没用过Cglib,但是猜想LazyInitializer为楼主自定义接口,而真正cglib拦截器需要实现的接口是MethodInterceptor
public class LazyInitializerImpl<T, PK extends Serializable> implements  
        LazyInitializer<T, PK>, MethodInterceptor {   

那么用LazyInitializer引用的对象无法体现MethodInterceptor接口特性能传到enhancer.setCallback(interceptor);   里么?

2、一个设计问题,如果一个用户包含很多信息,名字只是其中之一,你是在第一次读取非ID任何一个信息的时候全部加载其他信息?还是读哪个加载哪个?第二种方式可能会有更多的数据库访问次数。

3、另一个设计问题,延迟加载是不是在所有业务中都能用,要读取用户信息的时候确实好用,但是用户信息才能有多少,延迟了效果增加也不明显,主要大头是在业务数据上,我认为至少要一个能很好的把relation 映射到 object 的持久曾(不一定是Hibernate)才好拦截,因为业务人员直接操作SQL的话神仙也没办法。而业务人员确实很多是在写SQL,写SQL比构建ORMapping容易的多。同时系统业务需要很好的Domain模型才可能把拦截用于真正业务上。
23 楼 icewubin 2008-09-22  
引用
如果id为1的根本不存在的话.


直到触发sql的时候才会发生异常。

但是,请问你这个不存在的id是哪来的呢?
从应用需求上来说,id不可能是用户输入的,所以id的来源一般根本上来说只有程序生成如UUID,或者数据库生成策略,反过来说,如果程序中出现不存在的id,一定是获取此id的代码有问题,这种情况不用管的,就抛异常好了。

如果你一定需要id有效,那就手动触发初始化(多种方式)这个实体对象。
22 楼 i2u112233 2008-09-22  
如果id为1的根本不存在的话.
21 楼 lkjust08 2008-09-22  
受益匪浅,LZ看对这很有研究呀。
20 楼 raymond2006k 2008-09-21  
楼主研究精神值得学习啊, 对原理的分析也比较细致且精炼。


楼主使用Hibernate遇到的问题可以把具体情况写出来,大家一起分析分析。我的经验也可以分享分享。

根据个人的经验, 在使用 hibernate 时,到了具体应用或需求,可能会冒出很多问题。例如楼主说的:" 在关联很多,查询统计", 以及大数据量的问题. 既可以看作 Hibernate 本身存在一定缺陷, 也可以看作对它的使用不是 最佳方案。这些都可以继续探讨学习。
19 楼 seasar 2008-09-19  
kyo100900 写道

icewubin 写道
引用java persistence with hibernate第二版 也不错。对hibernate,jpa都有比较详细与深入的讲解。而且本文也是在看书的时候,在理解的基础上写出来的。考虑的仅仅只是单一实体的延迟加载,与hibernate源码肯定有差距和区别。不过,好在很简单,比较适合和大家分享交流。翻译太垃圾,单论质量不如深入浅出,如果没有基础看这本书,我估计很多人会看得一头雾水,不知道那人在翻译什么东西。:D&nbsp; 我买的早,是英文版的。 中文版的不清楚。英文版还是很不错的。

第2版是 je上某个很有名的 XX司令翻译的,翻译质量简直和 金山词霸 一个德性,这么好的书被糟蹋了,真实暴殓天物。而且价钱还不便宜99块。。。。。
18 楼 kyo100900 2008-09-19  
icewubin 写道

引用java persistence with hibernate第二版 也不错。对hibernate,jpa都有比较详细与深入的讲解。而且本文也是在看书的时候,在理解的基础上写出来的。考虑的仅仅只是单一实体的延迟加载,与hibernate源码肯定有差距和区别。不过,好在很简单,比较适合和大家分享交流。

翻译太垃圾,单论质量不如深入浅出,如果没有基础看这本书,我估计很多人会看得一头雾水,不知道那人在翻译什么东西。


  我买的早,是英文版的。 中文版的不清楚。英文版还是很不错的。
17 楼 icewubin 2008-09-19  
引用
java persistence with hibernate第二版 也不错。对hibernate,jpa都有比较详细与深入的讲解。而且本文也是在看书的时候,在理解的基础上写出来的。考虑的仅仅只是单一实体的延迟加载,与hibernate源码肯定有差距和区别。不过,好在很简单,比较适合和大家分享交流。


翻译太垃圾,单论质量不如深入浅出,如果没有基础看这本书,我估计很多人会看得一头雾水,不知道那人在翻译什么东西。
16 楼 kyo100900 2008-09-19  
chenzengpeng 写道

我建议大家看看···《深入浅出Hibernate》
对Hibernate的基本原理和高级技术有比较详细的介绍··
只可惜这书是2004年的··到现在没出新的··
不过原理的东西都是相通的··
自己最近在研读··


java persistence with hibernate第二版 也不错。对hibernate,jpa都有比较详细与深入的讲解。而且本文也是在看书的时候,在理解的基础上写出来的。考虑的仅仅只是单一实体的延迟加载,与hibernate源码肯定有差距和区别。不过,好在很简单,比较适合和大家分享交流。
15 楼 icewubin 2008-09-19  
引用
《深入浅出Hibernate》

是好书,但是书中不会有博主这种从源代码来分析的内容。

而且老的书中有关annotation的深入都太少。
14 楼 china8jie 2008-09-19  
是否有更好的解读?
13 楼 favor 2008-09-19  
谢了!收藏了!
12 楼 chenzengpeng 2008-09-19  
我建议大家看看···《深入浅出Hibernate》
对Hibernate的基本原理和高级技术有比较详细的介绍··
只可惜这书是2004年的··到现在没出新的··
不过原理的东西都是相通的··
自己最近在研读··
11 楼 kyo100900 2008-09-19  
引用

darchen 2 小时前
好文章啊,也在实践中学习应用中.

想听听楼对,为什么数据量大,经常出问题,能分析一下么?


1.当时没有使用延迟加载,关联很多,查询统计都非常慢,最慢的时候分页查询竟然要几分钟,无法忍受。
2.因为要频繁的插入更新数据,几乎每天都做这些事,mysql老自动挂掉,或出错死锁。后来这一部分改成用JDBC后,速度又快不少。

总之hibernate那套理论都设计的很好,
比如说最大限度的延迟插入更新操作,以便最小限度的锁定表;一级缓存,二级缓存;延迟加载;自动脏数据查检;事务处理自由切换;预编译SQL语句;查询自动优化。。。 很多很多。但很多都需要通过实战才能整理出符合自己项目的最佳实践。
10 楼 icewubin 2008-09-19  
延迟加载在某一个属性是多对一的时候请大家参考我的文章:

http://www.iteye.com/topic/212236

相关推荐

    Hibernate延迟加载原理与实现方法

    主要介绍了Hibernate延迟加载原理与实现方法,较为详细的分析了Hibernate延迟加载的概念,原理与相关实现技巧,需要的朋友可以参考下

    Hibernate性能(缓存 延迟加载 事务 悲观 乐观锁).ppt

    性能------------缓存 延迟加载 事务 悲观 乐观锁

    所有j2ee框架方面的原理全集

    对所有j2ee框架的原理做了解析,dwr的实现原理 hibernate缓存的配置 ioc实现原理 javascript原型 javacript中继承 jboss ibatis 解决延迟加载等等本人近段时间的呕心之做

    Hibernate实战(第2版 中文高清版)

     13.1.6 通过拦截延迟加载   13.2 选择抓取策略   13.2.1 批量预抓取数据   13.2.2 通过子查询预抓取集合   13.2.3 通过联结即时抓取   13.2.4 给二级表优化抓取   13.2.5 优化指导方针   13.3 高速...

    论文研究-基于Hibernate的查询效率的探讨 .pdf

    基于Hibernate的查询效率的探讨,张文英,安伟莲,本文首先介绍了Hibernate的原理及查询机制,然后从制定合理的缓存策略和应用延迟加载两方面来提高Hibernate的查询效率。

    Java求职面试宝典各大公司常考知识点

    1.11. Hibernate是如何延迟加载? 6 1.12. Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 6 1.13. Hibernate的查询方式 7 1.14. 如何优化Hibernate? 7 2 Struts部分 7 4.1. Struts1.x工作机制? 7 ...

    MyBatis经典面试题详细

    MyBatis简介 MyBatis是什么? ORM是什么 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? 传统JDBC开发存在的问题 JDBC编程有哪些不足之处...Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

    mybatis学习笔记

    1.6.9 与hibernate不同 16 2 Dao开发方法 16 2.1 需求 16 2.2 SqlSession的使用范围 17 2.2.1 SqlSessionFactoryBuilder 17 2.2.2 SqlSessionFactory 17 2.2.3 SqlSession 17 2.3 原始Dao开发方式 18 2.3.1 映射文件...

    1000道 互联网Java架构师面试题.pdf和JAVA核心知识整理.zip

    22、Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么? 23、Mybatis 的一级、二级缓存 24、什么是 MyBatis 的接口绑定?有哪些实现方式? 25、使用 MyBatis 的 mapper 接口调用时有哪些要求? 26、...

    java面试题

    71.7. Hibernate是如何延迟加载? 60 71.8. Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 60 71.9. 说下Hibernate的缓存机制 60 71.10. Hibernate的查询方式 60 71.11. 如何优化Hibernate? 61 71.12...

    Java常见面试题208道.docx

    延迟加载的原理是什么? 130.说一下 mybatis 的一级缓存和二级缓存? 131.mybatis 和 hibernate 的区别有哪些? 132.mybatis 有哪些执行器(Executor)? 133.mybatis 分页插件的实现原理是什么? 134.mybatis 如何...

    Java面试宝典2020修订版V1.0.1.doc

    17、Hibernate是如何延迟加载的? 93 18、如果优化Hibernate? 93 19、什么是ORM? 94 20、Hibernate的主键生成策略? 94 21、Hibernate的级联操作 94 22、Hibernate有哪5个核心接口? 95 23、什么是重量级?什么是...

    Spring面试题

    当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的...

    java面试题库2021.pdf

    ②延迟加载、 性能优化 ③HQL 查询、 条件查询、 SQL 查询 ④二级缓存与查询缓存 3、 Struts ①MVC 模式与 Struts 体系 4、 mybatis 5、 MVC 框架 6、 各框架对比与项目优化 7、 JPA ①EJB 三、 Java web 开发核心...

    千方百计笔试题大全

    97、Hibernate是如何延迟加载? 22 98、Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 22 99、说下Hibernate的缓存机制 22 100、Hibernate的查询方式 23 101、如何优化Hibernate? 23 102、Struts工作...

    java面试宝典

    97、Hibernate是如何延迟加载? 22 98、Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 22 99、说下Hibernate的缓存机制 22 100、Hibernate的查询方式 23 101、如何优化Hibernate? 23 102、Struts工作...

    JAVA上百实例源码以及开源项目源代码

     Java实现的FTP连接与数据浏览程序,实现实例化可操作的窗口。  部分源代码摘录:  ftpClient = new FtpClient(); //实例化FtpClient对象  String serverAddr=jtfServer.getText(); //得到服务器地址  ...

    JAVA上百实例源码以及开源项目

    百度云盘分享 ... Java实现的FTP连接与数据浏览程序,实现实例化可操作的窗口。  部分源代码摘录:  ftpClient = new FtpClient(); //实例化FtpClient对象  String serverAddr=jtfServer.getText();...

    Spring.3.x企业应用开发实战(完整版).part2

    12.2.8 延迟加载的问题 12.3 在Spring中使用myBatis 12.3.1 配置SqlMapClient 12.3.2 在Spring配置myBatis 12.3.3 编写myBatis的DAO 12.5 DAO层设计 12.5.1 DAO基类的设计 12.5.2 查询接口方法的设计 12.5.3 分页...

Global site tag (gtag.js) - Google Analytics