大概一年半前,我开始了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还有很多很多值得学习和借鉴的特性,下次有时间我再整理整理。
分享到:
相关推荐
主要介绍了Hibernate延迟加载原理与实现方法,较为详细的分析了Hibernate延迟加载的概念,原理与相关实现技巧,需要的朋友可以参考下
性能------------缓存 延迟加载 事务 悲观 乐观锁
对所有j2ee框架的原理做了解析,dwr的实现原理 hibernate缓存的配置 ioc实现原理 javascript原型 javacript中继承 jboss ibatis 解决延迟加载等等本人近段时间的呕心之做
13.1.6 通过拦截延迟加载 13.2 选择抓取策略 13.2.1 批量预抓取数据 13.2.2 通过子查询预抓取集合 13.2.3 通过联结即时抓取 13.2.4 给二级表优化抓取 13.2.5 优化指导方针 13.3 高速...
基于Hibernate的查询效率的探讨,张文英,安伟莲,本文首先介绍了Hibernate的原理及查询机制,然后从制定合理的缓存策略和应用延迟加载两方面来提高Hibernate的查询效率。
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是什么? ORM是什么 为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里? 传统JDBC开发存在的问题 JDBC编程有哪些不足之处...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 映射文件...
22、Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么? 23、Mybatis 的一级、二级缓存 24、什么是 MyBatis 的接口绑定?有哪些实现方式? 25、使用 MyBatis 的 mapper 接口调用时有哪些要求? 26、...
71.7. Hibernate是如何延迟加载? 60 71.8. Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 60 71.9. 说下Hibernate的缓存机制 60 71.10. Hibernate的查询方式 60 71.11. 如何优化Hibernate? 61 71.12...
延迟加载的原理是什么? 130.说一下 mybatis 的一级缓存和二级缓存? 131.mybatis 和 hibernate 的区别有哪些? 132.mybatis 有哪些执行器(Executor)? 133.mybatis 分页插件的实现原理是什么? 134.mybatis 如何...
17、Hibernate是如何延迟加载的? 93 18、如果优化Hibernate? 93 19、什么是ORM? 94 20、Hibernate的主键生成策略? 94 21、Hibernate的级联操作 94 22、Hibernate有哪5个核心接口? 95 23、什么是重量级?什么是...
当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的...
②延迟加载、 性能优化 ③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工作...
97、Hibernate是如何延迟加载? 22 98、Hibernate中怎样实现类之间的关系?(如:一对多、多对多的关系) 22 99、说下Hibernate的缓存机制 22 100、Hibernate的查询方式 23 101、如何优化Hibernate? 23 102、Struts工作...
Java实现的FTP连接与数据浏览程序,实现实例化可操作的窗口。 部分源代码摘录: ftpClient = new FtpClient(); //实例化FtpClient对象 String serverAddr=jtfServer.getText(); //得到服务器地址 ...
百度云盘分享 ... Java实现的FTP连接与数据浏览程序,实现实例化可操作的窗口。 部分源代码摘录: ftpClient = new FtpClient(); //实例化FtpClient对象 String serverAddr=jtfServer.getText();...
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 分页...