设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间13 现在时间13 缓存数据 风骨神仙籍里人,诗狂酒圣且平生。开元一遇成何事,留得千秋万古名。

风骨神仙籍里人,诗狂酒圣且平生。开元一遇成何事,留得千秋万古名。 -- 杨花落尽子规啼

查看: 1203|回复: 1

一文了解mybatis的延迟加载

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:231
  • 打卡月天数:0
  • 打卡总奖励:3582
  • 最近打卡:2025-03-30 03:26:09
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
435
主题
407
精华
0
金钱
4874
积分
904
注册时间
2023-1-5
最后登录
2025-3-30

发表于 2023-2-17 11:29:48 | 显示全部楼层 |阅读模式
本文主要介绍下mybatis的延迟加载,从原理上介绍下怎么使用、有什么好处能规避什么问题。延迟加载一般用于级联查询(级联查询可以将主表不能直接查询的数据使用自定义映射规则调用字表来查,主查询查完之后通过某个column列或多个列将查询结果传递给子查询,子查询再根据主查询传递的参数进行查询,最后将子查询结果进行映射)。mybatis的懒加载是通过创建代理对象来实现的,只有当调用getter等方法的时候才会去查询子查询,查询后完成设值再获取值。

1. 什么时候会创建代理对象
  1.   private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
  2.     this.useConstructorMappings = false; // reset previous mapping result
  3.     final List<Class<?>> constructorArgTypes = new ArrayList<>();
  4.     final List<Object> constructorArgs = new ArrayList<>();
  5.     // 创建result接收对象
  6.     Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
  7.     if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  8.       // 处理其他属性properties
  9.       final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  10.       for (ResultMapping propertyMapping : propertyMappings) {
  11.         // issue gcode #109 && issue #149 创建代理
  12.         if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
  13.           resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  14.           break;
  15.         }
  16.       }
  17.     }
  18.     // 使用有参构造函数创建了对象
  19.     this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
  20.     return resultObject;
  21.   }
复制代码
通过mybatis代码
  1. propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()
复制代码
发现只有当存在嵌套查询select子句和isLazy=true的时候才会创建代理,那么isLazy=true是什么条件,从创建ResultMapping的代码中可以看到
  1. boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
复制代码
只有手动设置fetchType=lazy或者全局设置configuration的lazyLoadingEnabled=true,两者缺一不可。
关于代理是通过Javassist创建的,下面有一个简单的例子
  1. public class HelloMethodHandler implements MethodHandler {

  2.   private Object target;

  3.   public HelloMethodHandler(Object o) {
  4.     this.target = o;
  5.   }

  6.   @Override
  7.   public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
  8.     String methodName = thisMethod.getName();
  9.     if (methodName.startsWith("get")) {
  10.       System.out.println("select database....");
  11.       // 进行sql查询到结果并set设置值
  12.       ((Student)self).setName("monian");
  13.     }
  14.     return proceed.invoke(self, args);
  15.   }

  16.   public static void main(String[] args) throws Exception {
  17.     Student student = new Student();
  18.     ProxyFactory proxyFactory = new ProxyFactory();
  19.     proxyFactory.setSuperclass(Student.class);

  20.     Constructor<Student> declaredConstructor = Student.class.getDeclaredConstructor();
  21.     Object o = proxyFactory.create(declaredConstructor.getParameterTypes(), new Object[]{});
  22.     ((Proxy)o).setHandler(new HelloMethodHandler(student));

  23.     Student proxy = (Student)o;
  24.     System.out.println(proxy.getName());
  25.   }
  26. }
复制代码
mybatis的原理就是通过创建一个代理对象,当通过这个代理对象调用getter、is、equals、clone、toString、hashCode等方法时会调用select子查询,然后完成设置,最后取值就像早就获取到一样。

2. 如何使用
  1. public class UserDO {

  2.   private Integer userId;

  3.   private String username;

  4.   private String password;

  5.   private String nickname;

  6.   private List<PermitDO> permitDOList;

  7.   public UserDO() {}
  8. }
复制代码
  1. <resultMap id="BaseMap" type="org.apache.ibatis.study.entity.UserDO">
  2.     <id column="user_id" jdbcType="INTEGER" property="userId" />
  3.     <result column="username" jdbcType="VARCHAR" property="username" />
  4.     <result column="password" jdbcType="VARCHAR" property="password" />
  5.     <result column="nickname" jdbcType="VARCHAR" property="nickname"/>

  6.     <collection property="permitDOList" column="user_id" select="getPermitsByUserId"
  7.      fetchType="lazy">

  8.     </collection>
  9. </resultMap>

  10.   <resultMap id="PermitBaseMap" type="org.apache.ibatis.study.entity.PermitDO">
  11.     <id column="id" jdbcType="INTEGER" property="id"/>
  12.     <result column="code" jdbcType="VARCHAR" property="code"/>
  13.     <result column="name" jdbcType="VARCHAR" property="name"/>
  14.     <result column="type" jdbcType="TINYINT" property="type"/>
  15.     <result column="pid" jdbcType="INTEGER" property="pid"/>
  16.   </resultMap>
  17.   
  18.       
  19.    <select id="getByUserId2" resultMap="BaseMap">
  20.     select * from user
  21.     where user_id = #{userId}
  22.   </select>

  23.   <select id="getPermitsByUserId" resultMap="PermitBaseMap">
  24.     select p.*
  25.     from user_permit up
  26.     inner join permit p on up.permit_id = p.id
  27.     where up.user_id = #{userId}
  28.   </select>
复制代码
通过fetchType=lazy指定子查询getPermitsByUserId使用懒加载,这样的话就不用管全局配置lazyLoadingEnabled是true还是false了。当然这里可以直接用多表关联查询不使用子查询,使用方法在上一篇文章
测试代码
  1. public class Test {

  2.   public static void main(String[] args) throws IOException {

  3.     try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
  4.       // 构建session工厂 DefaultSqlSessionFactory
  5.       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  6.       SqlSession sqlSession = sqlSessionFactory.openSession();
  7.       UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
  8.       UserDO userDO = userMapper.getByUserId2(1);
  9.       System.out.println(userDO);
  10.     }
  11.   }

  12. }
复制代码
结果如下,打了断点可以看到原userDO对象已被代理并且permitDOList是null需要调用get方法才会去查询拿到值,咳咳这边之前直接运行显示是已经把permitDOList查询出来了,想了半天啥原因后来才发现println会调用userDO对象的toString方法,而toString方法也会走代理方法直接去调用子查询的
123005ibq9w9pl9mw1enur.png

123006ymx8m7lpy4pmm7r4.jpeg


3.延迟加载的好处

延迟加载主要能解决mybatis的N+1问题,什么是N+1问题其实叫1+N更为合理,以上面的业务例子来说就是假设一次查询出来10000个用户,那么还需要针对这10000个用户使用子查询getPermitsByUserId获取每个用户的权限列表,需要10000次查询,总共10001次,真实情况下你可能并不需要每个子查询的结果,这样就浪费数据库连接资源了。如果使用延迟加载的话就相当于不用进行这10000次查询,因为它是等到你真正使用的时候才会调用子查询获取结果。
以上就是一文了解mybatis的延迟加载的详细内容,更多关于mybatis延迟加载的资料请关注晓枫资讯其它相关文章!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
19
积分
18
注册时间
2022-12-29
最后登录
2022-12-29

发表于 前天 18:51 | 显示全部楼层
感谢楼主,顶。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表