Mybatis 懒加载使用及源码分析()

  本篇文章为你整理了Mybatis 懒加载使用及源码分析()的详细内容,包含有 Mybatis 懒加载使用及源码分析,希望能帮助你了解 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 懒加载使用及源码分析()的详细内容,想要了解更多 Mybatis 懒加载使用及源码分析的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: