`
kyo100900
  • 浏览: 637862 次
  • 性别: 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延迟加载以及利用Spring

    #### 一、Hibernate延迟加载概念与原理 在理解Hibernate的延迟加载机制之前,我们首先需要了解什么是延迟加载。延迟加载(Lazy Loading)是一种设计模式,其核心思想是在真正需要数据时才加载数据,而不是一开始就...

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

    Hibernate延迟加载是一种优化数据库操作的技术,它旨在减少不必要的数据库访问,从而提高应用程序的性能。当我们在Hibernate中使用延迟加载时,只有在实际需要数据时,才会从数据库中加载相关对象,而不是在初始加载...

    hibernate 延迟加载深入剖析

    ### Hibernate延迟加载深入剖析 #### 一、概述 在现代软件开发中,特别是与数据库交互的应用场景下,Hibernate作为一款流行的Java持久层框架,提供了多种高效处理数据的技术。其中,延迟加载(Lazy Loading)是一...

    hibernate延迟加载解决

    ### Hibernate延迟加载详解 #### 一、什么是延迟加载? 延迟加载是一种优化技术,在软件开发中广泛应用于各种场景,尤其在数据库交互中尤为重要。其核心思想是仅在确实需要某个资源时才加载它,而非一开始就加载...

    Hibernate 延迟加载剖析与代理模式应用

    ### Hibernate延迟加载剖析与代理模式应用 #### 一、引言 在现代软件开发中,尤其是在基于Java的企业级应用开发中,Hibernate作为一种流行的ORM(Object Relational Mapping)框架,极大地简化了开发者对数据库的...

    Hibernate延时加载与lazy机制.doc

    Hibernate的延迟加载(Lazy Loading)和懒加载机制(Lazy Initialization)是优化ORM框架性能的重要策略。这个机制的主要目的是提高程序的效率,减少不必要的数据库交互,只在真正需要数据时才去加载它们。以下是对...

    hibernate 延迟加载.docx

    **延迟加载的实现原理** 当使用Hibernate的`Session.load()`方法或在映射文件中设置了`lazy="true"`时,Hibernate并不会立即从数据库中获取关联对象的所有数据,而是创建一个代理对象。这个代理对象在需要访问其...

    hibernate延迟加载技术详细解

    #### 三、工作原理与注意事项 1. **Hibernate 默认配置**: - 默认情况下,Hibernate 3 采用懒加载策略来处理关联对象的加载。 - 可以通过配置 `hibernate.default_batch_fetch_size` 参数来调整默认的批量加载...

    Hibernate的延迟加载

    集合类型的延迟加载是Hibernate延迟加载机制中最能显著提升性能的部分。在Hibernate中,集合类型的延迟加载通过使用自定义的集合类实现,如`net.sf.hibernate.collection.Set`,而非标准的`java.util.Set`。这样,...

    Hibernate集合属性的延迟加载.doc

    当我们处理与实体相关的集合属性时,如一对多、多对一或多对多关系,延迟加载尤其有用。 在给定的文档中,我们看到一个例子,展示了如何在 Hibernate 中配置一个具有集合属性的实体类 `Person`,以及如何通过映射...

    Hibernate延迟加载

    ### Hibernate延迟加载深入解析 #### 一、概念与原理 **延迟加载**(Lazy Loading)是Hibernate框架中的一个重要特性,主要用于优化数据库操作,减少不必要的数据加载,从而提升应用程序的性能。在传统的Eager ...

    Hibernate lazy延迟加载

    在Java的持久化框架Hibernate中,懒加载(Lazy Loading)是一种重要的优化策略,它的核心思想是“延迟加载”或“按需加载”。默认情况下,当一个实体被加载时,并不会立即加载其关联的对象或集合,而是在真正需要...

    hibernate懒加载策略.doc

    - 针对集合类型的延迟加载,Hibernate做了特别优化,如使用自定义的JDK Collection实现,如`PersistentSet`、`PersistentList`等。这些集合在初始化时不加载元素,只有在迭代或访问集合内的元素时,才触发数据库...

    Hibernate、Spring和Struts工作原理及使用理由

    Hibernate支持实体对象和集合的延迟加载,只有在实际需要时才会加载相关数据,节省内存,提高性能。 **类间关系**: Hibernate通过配置文件支持一对一、一对多、多对一和多对多的关系映射。 **缓存机制**: - **一...

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

    Hibernate延迟加载(Lazy Loading)** 延迟加载是一种优化策略,只在真正需要数据时才执行数据库查询。例如,当实体对象的集合属性未被访问时,Hibernate不会立即加载这些数据,而是等到需要时才加载,减少不必要的...

    延迟加载技术

    例如,在ORM(对象关系映射)框架如Hibernate中,一个实体可能有多个关联的对象,如果这些关联对象在初次加载时不被立即需要,那么可以设置为延迟加载。只有当试图访问这些关联对象时,才会执行SQL查询来获取它们,...

    Spring+Hibernate+Struts工作原理

    Hibernate是一个ORM(Object-Relational Mapping)框架,它将Java对象与关系数据库的数据进行映射,实现了对象与数据库之间的一套映射规则。其主要工作流程包括: 1. 加载并解析配置文件和映射信息,创建...

    Hibernate原理

    ### Hibernate原理深度解析 #### Hibernate为何重要? Hibernate作为一款开源的对象关系映射(ORM)框架,在Java开发领域占据着举足轻重的地位。其重要性体现在以下几个方面: 1. **资源管理**:Hibernate通过...

    Hibernate原理解析

    Hibernate提供了懒加载机制,允许延迟加载关联的对象,直到真正需要时才从数据库获取,从而提高性能。而立即加载则会在加载主体对象的同时加载其关联的对象。 8. **实体状态** Hibernate将实体的状态分为四种:瞬...

    Strut+Spring+Hibernate框架的工作原理

    Hibernate提供了延迟加载机制,可以在真正需要数据时才将其加载到内存中,从而节省服务器的内存开销。Hibernate的延迟加载机制可以分为两种: 1. 实体对象的延迟加载 2. 集合的延迟加载 Hibernate的映射关系 ...

Global site tag (gtag.js) - Google Analytics