下文从mybatis(3.2.7)延迟加载样例讲起,逐步深入其实现机制。

下面的例子是Student类关联一个Teacher对象,在访问Student对象时,不立即加载其关联的Teacher对象,而是等到访问Teacher对象的属性时,才加载Teacher对象。

源代码下载:http://download.csdn.net/detail/u014569459/7363097

1.Student.java

package dao.domain;

public class Student {
public int id;
public String name;
public int teacher_id;
public Teacher teacher;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getTeacher_id() {
return teacher_id;
}
public void setTeacher_id(int teacher_id) {
this.teacher_id = teacher_id;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
} }

2.Teacher.java

package dao.domain;

public class Teacher {
public int id;
public String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} }

3.StudentDAO.java

package dao;

import dao.domain.Student;

public interface StudentDAO {
public Student getStudentByID(int id);
}

4.StudentDAOImpl.java

package dao.impl;

import java.io.IOException;
import java.io.Reader; import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder; import dao.StudentDAO;
import dao.domain.Student; public class StudentDaoImpl implements StudentDAO {
private static SqlSession session = null;
private static StudentDAO mapper = null; static{
String resouce = "config/ibatisConfiguration.xml";
Reader reader = null;
try {
reader = Resources.getResourceAsReader(resouce);
} catch (IOException e) {
e.printStackTrace();
} SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
session = factory.openSession();
mapper = session.getMapper(StudentDAO.class); try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public Student getStudentByID(int id) {
return mapper.getStudentByID(id);
} }

5.TeacherMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.TeacherDAO">
<select id="getTeacherByID" parameterType="int" resultType="Teacher">
select id,name
from p_teacher
where
id = #{id}
</select>
</mapper>

6.StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="dao.StudentDAO">
<resultMap id="StudentMap" type="Student">
<id property="id" column="id" />
<result property="name" column="name" />
<association property="teacher" column="teacher_id"
select="dao.TeacherDAO.getTeacherByID" />
</resultMap> <select id="getStudentByID" resultMap="StudentMap"
parameterType="int">
select *
from p_stu
where
id = #{id}
</select>
</mapper>

7.ibatisConfiguration.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration>
<settings>
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="true" />
</settings>
<typeAliases>
<typeAlias alias="Student" type="dao.domain.Student" />
<typeAlias alias="Teacher" type="dao.domain.Teacher" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3307/test?characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="config/StudentMapper.xml" />
<mapper resource="config/TeacherMapper.xml" />
</mappers>
</configuration>

8.测试类

import dao.StudentDAO;
import dao.domain.Student;
import dao.impl.StudentDaoImpl; public class Test {
public static void main(String[] args) {
StudentDAO dao = new StudentDaoImpl();
Student article = dao.getStudentByID(1); System.out.println(article.getTeacher().getName());
}
}

9.mybatis延迟加载说明:

1)在mybatis配置文件中,配置如下两个配置项:

	<settings>
<setting name="lazyLoadingEnabled" value="true" />
<setting name="aggressiveLazyLoading" value="true" />
</settings>

2)查询语句样例,下面通过association将Student对象一个属性与一个查询语句关联起来:

	<resultMap id="StudentMap" type="Student">
<id property="id" column="id" />
<result property="name" column="name" />
<association property="teacher" column="teacher_id"
select="dao.TeacherDAO.getTeacherByID" />
</resultMap>

这样,在查询出StudentMap这个结果集时,对于teacher字段就会采取延迟加载了,等到访问teacher对象属性时,才会去加载关联的teacher对象。

10.逐步深入延迟加载细节

1)在Test类中如下行打上断点,然后进行Debug

System.out.println(article.getTeacher().getName());

可以看到article对象为(每次测试,结果会不同):dao.domain.Student$$EnhancerByCGLIB$$aa39207a@adc40c

这就是一个cglib自动生成的代理对象。

2)那这个代理对象什么时候生成的呢? 经过反复不停的debug跟踪,得到下面这样一个调用栈。

Thread [main] (Suspended (breakpoint at line 61 in CglibProxyFactory))
CglibProxyFactory.createProxy(Object, ResultLoaderMap, Configuration, ObjectFactory, List<Class<?>>, List<Object>) line: 61
DefaultResultSetHandler.createResultObject(ResultSetWrapper, ResultMap, ResultLoaderMap, String) line: 512
DefaultResultSetHandler.getRowValue(ResultSetWrapper, ResultMap) line: 331
DefaultResultSetHandler.handleRowValuesForSimpleResultMap(ResultSetWrapper, ResultMap, ResultHandler, RowBounds, ResultMapping) line: 291
DefaultResultSetHandler.handleRowValues(ResultSetWrapper, ResultMap, ResultHandler, RowBounds, ResultMapping) line: 266
DefaultResultSetHandler.handleResultSet(ResultSetWrapper, ResultMap, List<Object>, ResultMapping) line: 236
DefaultResultSetHandler.handleResultSets(Statement) line: 150
PreparedStatementHandler.query(Statement, ResultHandler) line: 60
RoutingStatementHandler.query(Statement, ResultHandler) line: 73
SimpleExecutor.doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql) line: 60
SimpleExecutor(BaseExecutor).queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) line: 267
SimpleExecutor(BaseExecutor).query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) line: 137
CachingExecutor.query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) line: 96
CachingExecutor.query(MappedStatement, Object, RowBounds, ResultHandler) line: 77
DefaultSqlSession.selectList(String, Object, RowBounds) line: 108
DefaultSqlSession.selectList(String, Object) line: 102
DefaultSqlSession.selectOne(String, Object) line: 66
MapperMethod.execute(SqlSession, Object[]) line: 68
MapperProxy<T>.invoke(Object, Method, Object[]) line: 52
$Proxy0.getStudentByID(int) line: not available
StudentDaoImpl.getStudentByID(int) line: 40
Test.main(String[]) line: 12

查看原图

可以看到在查询Student对象时,DefaultResultSetHandler(org\apache\ibatis\executor\resultset)类会对数据库查询的每一行结果进行封装处理。

通过下面的代码可以看得更清楚,当结果对象中存在嵌套查询时,当前对象就会被替换为一个代理对象。

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
final List<Object> constructorArgs = new ArrayList<Object>();
final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149
return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
}
}
return resultObject;
}

3)那当访问代理对象时,会发生什么事情呢?

如调用System.out.println(article.getTeacher().getName())时,得到如下的调用栈信息

Thread [main] (Suspended (breakpoint at line 143 in CglibProxyFactory$EnhancedResultObjectProxyImpl))
owns: ResultLoaderMap (id=38)
CglibProxyFactory$EnhancedResultObjectProxyImpl.intercept(Object, Method, Object[], MethodProxy) line: 143
Student$$EnhancerByCGLIB$$e1afda28.getTeacher() line: not available
Test.main(String[]) line: 10

代码执行到了如下地方:

            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll();
} else if (PropertyNamer.isProperty(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}

通过调用lazyLoader.loadAll来对需要加载的对象进行加载.

继续顺藤摸瓜,得到如下的调用栈:

Thread [main] (Suspended)
owns: ResultLoaderMap (id=38)
BeanWrapper.set(PropertyTokenizer, Object) line: 57
MetaObject.setValue(String, Object) line: 133
ResultLoaderMap$LoadPair.load(Object) line: 207
ResultLoaderMap$LoadPair.load() line: 172
ResultLoaderMap.load(String) line: 80
ResultLoaderMap.loadAll() line: 90
CglibProxyFactory$EnhancedResultObjectProxyImpl.intercept(Object, Method, Object[], MethodProxy) line: 143
Student$$EnhancerByCGLIB$$e1afda28.getTeacher() line: not available
Test.main(String[]) line: 10

如下是BeanWrapper中setValue方法:

public void set(PropertyTokenizer prop, Object value) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
setCollectionValue(prop, collection, value);
} else {
setBeanProperty(prop, object, value);
}
}

通过setBeanProperty方法,就完成向Student对象设置Teacher属性的过程(上面代码中的object就是Student对象)。

下面通过一张图,展示一下代理对象与实际对象之间的关系,“Student$$EnhancerByCGLIB$$aa39207a@adc40c”为代理对象,是Student类的子类,当访问到Student的getTeacher方法时,其通过关联的EnhancedResultObjectProxyImpl类来加载teacher对象,具体关联见下图,其中BeanWrapper中object就是代理对象本身,通过对object的设置,将teacher属性设置为从数据库查询出来的对象,完成延迟加载。

至此,mybatis的整个延迟加载过程就分析完了。

参考链接:
mybatis下载主页:https://github.com/mybatis/mybatis-3






mybatis源代码分析:深入了解mybatis延迟加载机制的更多相关文章

  1. mybatis源代码分析:mybatis延迟加载机制改进

    在上一篇博客<mybatis源代码分析:深入了解mybatis延迟加载机制>讲诉了mybatis延迟加载的具体机制及实现原理. 可以看出,如果查询结果对象中有一个属性是需要延迟加载的,那整 ...

  2. MyBatis架构设计及源代码分析系列(一):MyBatis架构

    如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...

  3. Mybatis源代码分析之parsing包

    parsing,从字面上理解就是编译解析的意思,那么这个包中的内容就应该和mybatis配置文件的编译解析有关系.本文首先会按照引用层次来分别介绍这个包中各个类的作用,而后再用实际的例子解释它们是如何 ...

  4. 从源代码分析Android-Universal-Image-Loader的缓存处理机制

    讲到缓存,平时流水线上的码农一定觉得这是一个高大上的东西.看过网上各种讲缓存原理的文章,总感觉那些文章讲的就是玩具,能用吗?这次我将带你一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制. ...

  5. MySQL系列:innodb源代码分析之线程并发同步机制

    innodb是一个多线程并发的存储引擎,内部的读写都是用多线程来实现的,所以innodb内部实现了一个比較高效的并发同步机制. innodb并没有直接使用系统提供的锁(latch)同步结构,而是对其进 ...

  6. Mybatis结合Spring注解自己主动扫描源代码分析

    作为一个想做架构师的程序猿,必须是一个优秀的程序猿.在引入某一个框架的时候,必需要研究源代码,将新的开源框架的风险变为可控性. 1.Spring结合Mybatis最经常使用的配置. <!--理论 ...

  7. Mybatis之延迟加载机制

    1.  延迟加载的含义: 用到的时候才会去进行相关操作 2.  延迟加载的例子: 2.1 spring的BeanFactory,在getBean()的时候才创建Bean 2.2 物理分页查询,只有点击 ...

  8. spring整合mybatis步骤分析

    1.spring配置datasource bean的时候,不同的数据库连接方式有有不同的datasource实现类. 比如采用c3p0数据库连接池,要用c3p0的datasource实现类 com.m ...

  9. Mybatis学习系列(六)延迟加载

    延迟加载其实就是将数据加载时机推迟,比如推迟嵌套查询的执行时机.在Mybatis中经常用到关联查询,但是并不是任何时候都需要立即返回关联查询结果.比如查询订单信息,并不一定需要及时返回订单对应的产品信 ...

随机推荐

  1. 玩转指针(Playing with Pointers)

    Question: What is a Pointer? What are its limitations? What are its benefits? How do we use it? What ...

  2. SRM 599 DIV1

    A 首先发现对于2操作,每种素因子可以单独考虑,然后取出步数最多的计入答案,然后分别加上对每种素因子的1操作; 第二步我犯了个错误,以为最优方案是把素因子指数按二进制操作,在1的位置执行1操作,0的位 ...

  3. 原生javascript 改写的tab选项卡

    <!--css部分--> <style> *{ margin: 0; padding: 0; } ul,li{ list-style: none } .tabbox{ widt ...

  4. Windows下模拟Linux开发

    1.背景 Linux环境下开发是大势所趋,也是开发者必须掌握的技能.然windows系统已深入人心,实在不想放弃windows下的成熟应用,因此可以在Windows上模拟一个Linux系统.这样就满足 ...

  5. 2.4 Git 基础 - 撤消操作

    2.4 Git 基础 - 撤消操作 撤消操作 任何时候,你都有可能需要撤消刚才所做的某些操作.接下来,我们会介绍一些基本的撤消操作相关的命令.请注意,有些撤销操作是不可逆的,所以请务必谨慎小心,一旦失 ...

  6. VCS仿真生成vpd文件(verilog)

    VCS仿真生成vpd文件(verilog) 一.环境与文件 Linux平台  csh环境 VCS 64bit 代码文件请参考<一个简单的Verilog计数器模型> 二.开始仿真 1.com ...

  7. VSIM生成fsdb波形文件(VERILOG)

    VSIM生成fsdb波形文件(verilog) 两步主要的设置 testbench加入函数 运行库调用 1.testbench加入函数 initial begin $fsdbDumpfile(&quo ...

  8. (转)单例模式(Singleton)

    首先来明确一个问题,那就是在某些情况下,有些对象,我们只需要一个就可以了, 比如,一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个, 这里就可以通过单例模式来避免两个打印作业同时输 ...

  9. 不安装开发环境,查看logcat日志

    #公司的测试终于想到要看 android 的日志.安装开发环境太麻烦了,这里有个简单的办法. 我把\sdk\platform-tools目录中,文件名以"adb"开头的三个文件打包 ...

  10. Struts2中的get、set方法作用:

    Struts2中的get.set方法作用: 在Struts2中,客户端和服务器之间的数据传输全部要用到get.set方法:用set方法 ,可以将表单中的值存入Action类.通过Struts2.0标签 ...