Mybatis 懒加载使用及源码分析
Mybatis 懒加载的使用
什么是懒加载?懒加载的意思就是在使用的时候才去加载,不使用不去加载,相反的就叫饥饿加载或者立即加载。懒加载在Mybatis中一般是存在与联合查询的情况,比如查询一个对象的同时连带查询相关的表对应的数据。在Mybatis中查询可以通过ResultMap设置查询对象返回一个集合属性,也就是说像这样的:
@Data
public class User implements Serializable {
private int id;
private int age;
private String name;
private List<Order> orderList;
}
这里的orderList就是一个集合,在mapper.xml中配置如下:
<resultMap id="userMap" type="mybatis.model.User">
<id column="id" property="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
<collection property="orderList" ofType="mybatis.model.Order" column="id" select="findByUid"/>
</resultMap>
<select id="findByUid" resultType="mybatis.model.Order">
select * from `order` where uid = #{id}
</select>
<select id="selectById" resultMap="userMap">
select * from user where id = #{id}
</select>
可以看到这里查询User对象的时候还查询了Order列表,这个用户关联的订单信息。如果只是这样查询那么结果是饥饿加载:
@Test
public void testLazyLoad(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectById(1);
System.out.println(user.getName());
}
输出结果,执行了两个sql语句查询,说明查询User的同时也查询了Order
09:52:56.575 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, age, name
<== Row: 1, 18, 灵犀
Cache Hit Ratio [mybatis.mapper.UserMapper]: 0.0
09:52:56.613 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
====> Preparing: select * from `order` where uid = ?
====> Parameters: 1(Integer)
<==== Columns: id, uid, order_name, price
<==== Row: 1, 1, 苹果, 8.00
<==== Row: 3, 1, 笔记本电脑, 8000.00
<==== Total: 2
<== Total: 1
灵犀
Process finished with exit code 0
配置懒加载:
<resultMap id="userMap" type="mybatis.model.User">
<id column="id" property="id"/>
<result property="age" column="age"/>
<result property="name" column="name"/>
<collection property="orderList" ofType="mybatis.model.Order" column="id" select="findByUid" fetchType="lazy"/>
</resultMap>
这里的collection标签中的fetchType属性可以设置为lazy或者eager,默认就是eager饥饿加载,配置完之后执行:
09:56:22.649 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, age, name
<== Row: 1, 18, 灵犀
<== Total: 1
灵犀
可以看到只执行了查询user的sql语句,而查询订单order的sql语句没有执行,只有在使用orderList这个属性的时候才会去执行sql查询:
@Test
public void testLazyLoad(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectById(1);
System.out.println(user.getName());
// 懒加载
System.out.println(user.getOrderList());
}
输出结果:
09:58:02.681 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, age, name
<== Row: 1, 18, 灵犀
<== Total: 1
灵犀
Cache Hit Ratio [mybatis.mapper.UserMapper]: 0.0
09:58:02.746 [main] INFO mybatis.plugins.MyPlugin - 对方法进行增强....
==> Preparing: select * from `order` where uid = ?
==> Parameters: 1(Integer)
<== Columns: id, uid, order_name, price
<== Row: 1, 1, 苹果, 8.00
<== Row: 3, 1, 笔记本电脑, 8000.00
<== Total: 2
[Order(id=1, uid=1, orderName=苹果, price=8.00), Order(id=3, uid=1, orderName=笔记本电脑, price=8000.00)]
Process finished with exit code 0
可以看到执行查询订单的sql语句并且打印了订单信息
Mybatis 懒加载原理及源码解析
Mybatis懒加载的原理要搞清楚的话,就需要去找到返回结果的时候看看Mybatis是如何封装的,找到ResultSetHandler,因为这个接口就是专门用于结果集封装的,默认实现为DefaultResultSetHandler,根据查询数据流程不难发现封装结果集的时候调用的是handleResultSets方法:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获取ResultSet的包装器
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 验证结果数量
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理结果集
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
点击处理结果集的方法:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// 处理每行的数据
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
点击处理每行的数据方法:
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 如果存在嵌套的结果集
if (resultMap.hasNestedResultMaps()) {
// 安全行约束检查,如果是嵌套查询需要关闭安全行约束条件
ensureNoRowBounds();
// 检查结果处理器是否符合嵌套查询约束
checkResultHandler();
// 执行嵌套查询结果集处理
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 简单的结果集分装处理
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
由于我们写的这个结果是简单结果集,所以进入handleRowValuesForSimpleResultMap:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 获取每行的值
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
挑重点,直接进入获取每行值方法中:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建结果值
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
继续进入获取每行结果值的方法,createResultObject:
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 创建结果对象 ,使用ObjectFactory 反射进行创建
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 检查属性是否是懒加载的属性
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
// 使用动态代理创建一个代理对象作为结果对象返回出去,默认使用javassist 进行创建
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
这里就先是通过反射创建出这个对象resultObject,然后遍历去检查这些属性是否是懒加载的,如果是那么就通过代理工厂去创建一个代理对象,由于这里创建的是一个返回对象,不是一个接口因此动态代理实现是通过cglib实现的,Mybatis这里使用javassist包下的代理进行创建代理对象,代理工厂默认就是JavassistProxyFactory:
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
ProxyFactory enhancer = new ProxyFactory();
enhancer.setSuperclass(type);
try {
type.getDeclaredMethod(WRITE_REPLACE_METHOD);
// ObjectOutputStream will call writeReplace of objects returned by writeReplace
if (LogHolder.log.isDebugEnabled()) {
LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
}
} catch (NoSuchMethodException e) {
enhancer.setInterfaces(new Class[] { WriteReplaceInterface.class });
} catch (SecurityException e) {
// nothing to do here
}
Object enhanced;
Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
try {
// 创建代理对象
enhanced = enhancer.create(typesArray, valuesArray);
} catch (Exception e) {
throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e);
}
((Proxy) enhanced).setHandler(callback);
return enhanced;
}
实际上这里也是通过反射进行创建,只是在外面封装成了ProxyFactory这个对象,当我们调用getOrderList方法的时候就会执行到invoke方法中,并且判断是否是延迟加载的,如果是那么就会执行lazyLoader.load方法执行延迟加载,也就是执行sql查询数据:
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
if (WRITE_REPLACE_METHOD.equals(methodName)) {
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
return original;
}
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
//判断方法是否是get方法
} else if (PropertyNamer.isGetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
// 判断属性是否是延迟加载的。如果是那么执行加载
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
// 执行原方法
return methodProxy.invoke(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
load方法就会执行真正的查询sql语句,将数据赋值给User对象,这样就完成了真正的懒加载操作,所以Mybatis的懒加载实际上就是利用动态代理将对象的参数封装进行了延迟加载,当需要时再去调用真正的查询操作并返回数据。
Mybatis 懒加载使用及源码分析的更多相关文章
- 【JavaScript】使用纯JS实现多张图片的懒加载(附源码)
一.效果图如下 上面的效果图,效果需求如下 1.还没加载图片的时候,默认显示加载图片背景图 2.刚开始进入页面,自动加载第一屏幕的图片 3.下拉界面,当一张图片容器完全显露出屏幕,即刻加载图片,替换背 ...
- 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】
目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...
- jetty加载spring-context容器源码分析
带着疑问开始 web.xml的顺序问题 先拿一个最简单的spring mvc web.xml来说问题,如下图:如果我将三者的顺序倒置或是乱置,会产生什么结果呢? 启动报错?还是加载未知结果?还是毫无影 ...
- 第一次源码分析: 图片加载框架Picasso源码分析
使用: Picasso.with(this) .load("http://imgstore.cdn.sogou.com/app/a/100540002/467502.jpg") . ...
- 源码分析: 图片加载框架Picasso源码分析
使用: Picasso.with(this) .load("http://imgstore.cdn.sogou.com/app/a/100540002/467502.jpg") . ...
- 【 js 模块加载 】【源码学习】深入学习模块化加载(node.js 模块源码)
文章提纲: 第一部分:介绍模块规范及之间区别 第二部分:以 node.js 实现模块化规范 源码,深入学习. 一.模块规范 说到模块化加载,就不得先说一说模块规范.模块规范是用来约束每个模块,让其必须 ...
- 2款不同样式的CSS3 Loading加载动画 附源码
原文:2款不同样式的CSS3 Loading加载动画 附源码 我们经常看到的Loading加载很多都是转圈圈的那种,今天我们来换一种有创意的CSS3 Loading加载动画,一种是声波形状的动画,另一 ...
- MyBatis 懒加载
懒加载的概念 MyBatis中的延迟加载,也称为懒加载,是指进行关联查询时,按需执行子查询. 当程序需要获取|使用关联对象时,mybatis再执行子查询,这样可以减轻数据库的压力,在一定程度上可以降低 ...
- Servlet在启动时加载的tomcat源码(原创)
tomcat 8.0.36 知识点: 通过配置loadOnStartup可以设置Servlet是否在Tomcat启动时加载,以及按值大小进行有序加载,其最小有效值为0,最大有效值为Integer.MA ...
随机推荐
- Redis - 持久化 AOF 和 RDB
Redis - 持久化 AOF 和 RDB AOF AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集. AOF 文件中的命令全部以 Redis 协议的格 ...
- numpy中shape的部分解释
转载自:https://blog.csdn.net/qq_28618765/article/details/78081959和https://www.jianshu.com/p/e083512e4f4 ...
- VTK 截图
vtk的vtkRenderWindowInteractor中的Initialize函数初始化了可交互的窗口,但是实际工程中,往往需要把窗口拿出来在别的页面上显示,如存为png图片等等.本文主要介绍如何 ...
- redis持久化之RDB (七)
一:什么是redis的持久化 Redis 持久化 Redis 提供了不同级别的持久化方式: RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储. AOF持久化方式记录每次对服务器写的操作,当 ...
- [pwn基础] Linux安全机制
目录 [pwn基础] Linux安全机制 Canary(栈溢出保护) 开启关闭Cannary Canary的种类 Terminator canaries(终结者金丝雀) Random cannarie ...
- 【Java面试】介绍下Spring IoC的工作流程
Hi,我是Mic 一个工作了4年的粉丝,在面试的时候遇到一个这样的问题. "介绍一下Spring IOC的工作流程" 他说回答得不是很好,希望我能帮他梳理一下. 关于这个问题,我们 ...
- 使用c++爬取股市数据,获取最新行情
最近自己动手写个小软件(界面原生态,还没来得及加样式哈).每天看看潜力股懒人做法,不介意推荐.资源有限,只能观察一下低价股,分析一下运动规律,什么时候拉升,惯性如何 主要功能:读取网络数据:保存本地数 ...
- Vue动态组件的实践与原理探究
我司有一个工作台搭建产品,允许通过拖拽小部件的方式来搭建一个工作台页面,平台内置了一些常用小部件,另外也允许自行开发小部件上传使用,本文会从实践的角度来介绍其实现原理. ps.本文项目使用Vue CL ...
- golang的超时处理使用技巧
原文链接:https://www.zhoubotong.site/post/57.html golang的超时处理 2天前Go实例技巧25 大家知道Select 是 Go 中的一个控制结构,每个 ...
- PaddleOCR系列(二)--hubserving & pdserving & hub install
一.各种部署方式特点及注意事项 简称 hubserving=PaddleHub Serving pdserving=PaddleHub Serving hub install =指通过paddlehu ...