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
灵犀

可以看到只执行了查询usersql语句,而查询订单ordersql语句没有执行,只有在使用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 懒加载使用及源码分析的更多相关文章

  1. 【JavaScript】使用纯JS实现多张图片的懒加载(附源码)

    一.效果图如下 上面的效果图,效果需求如下 1.还没加载图片的时候,默认显示加载图片背景图 2.刚开始进入页面,自动加载第一屏幕的图片 3.下拉界面,当一张图片容器完全显露出屏幕,即刻加载图片,替换背 ...

  2. 别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

    目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.C ...

  3. jetty加载spring-context容器源码分析

    带着疑问开始 web.xml的顺序问题 先拿一个最简单的spring mvc web.xml来说问题,如下图:如果我将三者的顺序倒置或是乱置,会产生什么结果呢? 启动报错?还是加载未知结果?还是毫无影 ...

  4. 第一次源码分析: 图片加载框架Picasso源码分析

    使用: Picasso.with(this) .load("http://imgstore.cdn.sogou.com/app/a/100540002/467502.jpg") . ...

  5. 源码分析: 图片加载框架Picasso源码分析

    使用: Picasso.with(this) .load("http://imgstore.cdn.sogou.com/app/a/100540002/467502.jpg") . ...

  6. 【 js 模块加载 】【源码学习】深入学习模块化加载(node.js 模块源码)

    文章提纲: 第一部分:介绍模块规范及之间区别 第二部分:以 node.js 实现模块化规范 源码,深入学习. 一.模块规范 说到模块化加载,就不得先说一说模块规范.模块规范是用来约束每个模块,让其必须 ...

  7. 2款不同样式的CSS3 Loading加载动画 附源码

    原文:2款不同样式的CSS3 Loading加载动画 附源码 我们经常看到的Loading加载很多都是转圈圈的那种,今天我们来换一种有创意的CSS3 Loading加载动画,一种是声波形状的动画,另一 ...

  8. MyBatis 懒加载

    懒加载的概念 MyBatis中的延迟加载,也称为懒加载,是指进行关联查询时,按需执行子查询. 当程序需要获取|使用关联对象时,mybatis再执行子查询,这样可以减轻数据库的压力,在一定程度上可以降低 ...

  9. Servlet在启动时加载的tomcat源码(原创)

    tomcat 8.0.36 知识点: 通过配置loadOnStartup可以设置Servlet是否在Tomcat启动时加载,以及按值大小进行有序加载,其最小有效值为0,最大有效值为Integer.MA ...

随机推荐

  1. python求最大公约数和最小公倍数

    1 def gcd(x,y): 2 while(y): 3 t=x%y 4 x=y 5 y=t 6 #print("最小公倍数是:",x*y/x)#最小公倍数是两数之积除以最大公约 ...

  2. 【生成对抗网络学习 其一】经典GAN与其存在的问题和相关改进

    参考资料: 1.https://github.com/dragen1860/TensorFlow-2.x-Tutorials 2.<Generative Adversarial Net> ...

  3. SAP HTLM Control

    HTML 事件 效果 代码 *&---------------------------------------------------------------------* *& Re ...

  4. Maven-打包jar指定main函数所在类的一个例子

    问题描述:maven打包jar时,由于带main方法的类没有被加入manifest中,导致执行java -jar mvn-jar-1.0-SNAPSHOT.jar时,会提示没有主清单属性. 解决办法: ...

  5. 详解SQL中Groupings Sets 语句的功能和底层实现逻辑

    摘要:本文首先简单介绍 Grouping Sets 的用法,然后以 Spark SQL 作为切入点,深入解析 Grouping Sets 的实现机制. 本文分享自华为云社区<深入理解 SQL 中 ...

  6. JDBCTools 第一个版本

    JDBCToolV1: package com.dgd.test; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax ...

  7. JAVA学习的第一周

    这是发表的第一篇博客,关于Java编程的学习体会如下 1.了解Java的产生与发展时机:1995左右出现Java语言,然后Java的最主要的特点是"跨平台".对于跨平台我不太理解, ...

  8. AlterNats是如何做到高性能的发布订阅的?

    前言 在过去的一些文章里面,我们聊了一些.NET平台上高性能编程的技巧,今天带大家了解一下AlterNats这个库是如何做到远超同类SDK性能的. NATS:NATS是一个开源.轻量级.高性能的分布式 ...

  9. GET 和 POST 请求的区别与安全性

    超文本传输协议( HTTP )是用于启用客户端与服务器之间的通信,其中 GET 请求和 POST 请求是则是 HTTP 方法中最为常用的两种.那么这 GET 和 POST 的区别到底是什么呢?两者是否 ...

  10. ooday01类_对象_访问成员

    笔记: 什么是类?什么是对象? 现实生活中是由很多很多对象组成的,基于对象抽出了类 对象:软件中真实存在的单个个体/东西 类:类别/类型,代表一类个体 类是对象的模子,对象是类的具体的实例 类中可以包 ...