该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址Mybatis-Spring 源码分析 GitHub 地址Spring-Boot-Starter 源码分析 GitHub 地址)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

MyBatis中SQL执行的整体过程如下图所示:

SQLExecuteProcess

在 SqlSession 中,会将执行 SQL 的过程交由Executor执行器去执行,过程大致如下:

  1. 通过DefaultSqlSessionFactory创建与数据库交互的 SqlSession “会话”,其内部会创建一个Executor执行器对象
  2. 然后Executor执行器通过StatementHandler创建对应的java.sql.Statement对象,并通过ParameterHandler设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过KeyGenerator先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的ResultSet结果集对象包装成ResultSetWrapper,然后通过DefaultResultSetHandler对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(四)之延迟加载

在前面SQL执行过程一系列的文档中,已经详细地分析了在 MyBatis 的SQL执行过程中,SqlSession 会话将数据库相关操作交由 Executor 执行器去完成,通过 StatementHandler 去执行数据库的操作,并获取到数据库的执行结果,如果是查询结果则通过 DefaultResultSetHandler 对结果集进行映射,转换成 Java 对象

其中 MyBatis 也提供了延迟加载的功能,当调用实体类需要延迟加载的属性的 getter 方法时,才会触发其对应的子查询,获取到查询结果,设置该对象的属性值

在上一篇《SQL执行过程(三)之ResultSetHandler》文档中讲到

  1. 如果存在嵌套子查询且需要延迟加载,则会通过ProxyFactory动态代理工厂,为返回结果的实例对象创建一个动态代理对象(Javassist),也就是说返回结果实际上是一个动态代理对象

    可以回到上一篇文档的4.2.1createResultObject方法小节第4步看看

    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, 
                                                               objectFactory, constructorArgTypes, constructorArgs;
    
  2. 后续属性映射的过程中,如果该属性是嵌套子查询并且需要延迟加载,则会创建一个ResultLoader对象添加到上面的ResultLoaderMap对象lazyLoader

    可以回到上一篇文档的4.2.4.2getNestedQueryMappingValue方法小节第6步看看

    final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, 
                                                       nestedQueryParameterObject, targetType, key, nestedBoundSql);
    if (propertyMapping.isLazy()) { // <6.2> 如果要求延迟加载,则延迟加载
        // <6.2.1> 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象
        lazyLoader.addLoader(property, metaResultObject, resultLoader);
        // <6.2.2> 返回延迟加载占位符
        value = DEFERRED;
    } else { // <6.3> 如果不要求延迟加载,则直接执行加载对应的值
        value = resultLoader.loadResult();
    }
    

那么接下来我们来看看 MyBatis 中的延迟加载是如何实现的

ResultLoader

org.apache.ibatis.executor.loader.ResultLoader:延迟加载的加载器,在上面你可以看到需要延迟加载的属性会被封装成该对象

构造方法

public class ResultLoader {

    /**
     * 全局配置对象
     */
	protected final Configuration configuration;
    /**
     * 执行器
     */
	protected final Executor executor;
    /**
     * MappedStatement 查询对象
     */
	protected final MappedStatement mappedStatement;
	/**
	 * 查询的参数对象
	 */
	protected final Object parameterObject;
	/**
	 * 目标的类型,返回结果的 Java Type
	 */
	protected final Class<?> targetType;
    /**
     * 实例工厂
     */
	protected final ObjectFactory objectFactory;
	protected final CacheKey cacheKey;
    /**
     * SQL 相关信息
    */
	protected final BoundSql boundSql;
	/**
	 * 结果抽取器
	 */
	protected final ResultExtractor resultExtractor;
	/**
	 * 创建 ResultLoader 对象时,所在的线程的 id
	 */
	protected final long creatorThreadId;
	/**
	 * 是否已经加载
	 */
	protected boolean loaded;
	/**
	 * 查询的结果对象
	 */
	protected Object resultObject;

	public ResultLoader(Configuration config, Executor executor, MappedStatement mappedStatement,
			Object parameterObject, Class<?> targetType, CacheKey cacheKey, BoundSql boundSql) {
		this.configuration = config;
		this.executor = executor;
		this.mappedStatement = mappedStatement;
		this.parameterObject = parameterObject;
		this.targetType = targetType;
		this.objectFactory = configuration.getObjectFactory();
		this.cacheKey = cacheKey;
		this.boundSql = boundSql;
		this.resultExtractor = new ResultExtractor(configuration, objectFactory);
		this.creatorThreadId = Thread.currentThread().getId();
	}
}

主要包含以下信息:

  • executor:执行器
  • mappedStatement:查询语句的MappedStatement对象
  • parameterObject:子查询的入参
  • targetType:返回结果的Java Type
  • boundSql:SQL相关信息
  • resultExtractor:查询结果的抽取器
  • loaded:是否已经加载

loadResult方法

loadResult()方法,延迟加载的执行器的执行方法,获取到查询结果,并提取出结果,方法如下:

public Object loadResult() throws SQLException {
    // <1> 查询结果
    List<Object> list = selectList();
    // <2> 提取结果
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    // <3> 返回结果
    return resultObject;
}

selectList方法

selectList()方法,执行延迟加载对应的子查询,获取到查询结果,方法如下:

	private <E> List<E> selectList() throws SQLException {
		// <1> 获得 Executor 对象
		Executor localExecutor = executor;
		if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
      		// 创建一个的 Executor 对象,保证线程安全
			localExecutor = newExecutor();
		}
		try {
			// <2> 执行查询
			return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT,
                                       Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
		} finally {
			// <3> 关闭 Executor 对象
			if (localExecutor != executor) {
				localExecutor.close(false);
			}
		}
	}
  1. 获得 Executor 执行器,如果当前线程不是创建 ResultLoader 对象时所在的线程的,或者这个执行器被关闭了,那么需要调用newExecutor()方法创建一个新的执行器
  2. 通过该执行器进行数据的查询,并返回查询结果
  3. 如果这个执行器是新创建的,则需要关闭它

newExecutor方法

newExecutor()方法,创建一个新的Executor执行器用于执行延迟加载的子查询,执行完后需要关闭,方法如下:

private Executor newExecutor() {
    // 校验 environment
    final Environment environment = configuration.getEnvironment();
    if (environment == null) {
        throw new ExecutorException("ResultLoader could not load lazily.  Environment was not configured.");
    }
    // 校验 DataSource
    final DataSource ds = environment.getDataSource();
    if (ds == null) {
        throw new ExecutorException("ResultLoader could not load lazily.  DataSource was not configured.");
    }
    // 创建 Transaction 对象
    final TransactionFactory transactionFactory = environment.getTransactionFactory();
    final Transaction tx = transactionFactory.newTransaction(ds, null, false);
    // 创建 Executor 对象
    return configuration.newExecutor(tx, ExecutorType.SIMPLE);
}

ResultExtractor

org.apache.ibatis.executor.ResultExtractor:结果提取器,用于提取延迟加载对应的子查询的查询结果,转换成Java对象,代码如下:

public class ResultExtractor {
    /**
     * 全局配置对象
     */
	private final Configuration configuration;
    /**
     * 实例工厂
     */
	private final ObjectFactory objectFactory;

	public ResultExtractor(Configuration configuration, ObjectFactory objectFactory) {
		this.configuration = configuration;
		this.objectFactory = objectFactory;
	}

	/**
     * 从 list 中,提取结果
     *
     * @param list list
     * @param targetType 结果类型
     * @return 结果
     */
	public Object extractObjectFromList(List<Object> list, Class<?> targetType) {
		Object value = null;
		/*
		 * 从查询结果中抽取数据转换成目标类型
		 */
		if (targetType != null && targetType.isAssignableFrom(list.getClass())) { // <1> 场景1,List 类型
		  // 直接返回
			value = list;
		} else if (targetType != null && objectFactory.isCollection(targetType)) { // <2> 场景2,集合类型
			// <2.1> 创建集合的实例对象
			value = objectFactory.create(targetType);
			// <2.2> 将结果添加到其中
			MetaObject metaObject = configuration.newMetaObject(value);
			// <2.3> 将查询结果全部添加到集合对象中
			metaObject.addAll(list);
		} else if (targetType != null && targetType.isArray()) { // <3> 场景3,数组类型
			// <3.1> 获取数组的成员类型
			Class<?> arrayComponentType = targetType.getComponentType();
			// <3.2> 创建数组对象,并设置大小
			Object array = Array.newInstance(arrayComponentType, list.size());
			if (arrayComponentType.isPrimitive()) { // <3.3> 如果是基本类型
				for (int i = 0; i < list.size(); i++) {
				  	// 一个一个添加到数组中
					Array.set(array, i, list.get(i));
				}
				value = array;
			} else {
			 	 // <3.4> 将 List 转换成 Array
				value = list.toArray((Object[]) array);
			}
		} else { // <4> 场景4
			if (list != null && list.size() > 1) {
				throw new ExecutorException("Statement returned more than one row, where no more than one was expected.");
			} else if (list != null && list.size() == 1) {
			  // 取首个结果
				value = list.get(0);
			}
		}
		return value;
	}
}

List<Object> list查询结果提取数据,转换成目标类型,有以下四种场景:

  1. List类型,则直接返回

  2. 集合类型,则为该集合类型创建一个实例对象,并把list全部添加到该对象中,然后返回

  3. 数组类型

    1. 获取数组的成员类型
    2. 创建数组对象,并设置大小
    3. 如果是基本类型则一个一个添加到数组中,否则直接将list转换成数组,然后返回
  4. 其他类型,也就是一个实体类了,直接获取list中的第一个元素返回(如果list集合的个数大于1则抛出异常)

ResultLoaderMap

org.apache.ibatis.executor.loader.ResultLoaderMap:用于保存某个对象中所有的延迟加载

构造方法

public class ResultLoaderMap {
    /**
   	 * 用于延迟加载的加载器
   	 * key:属性名称
   	 * value:ResultLoader 加载器的封装对象 LoadPair
   	 */
	private final Map<String, LoadPair> loaderMap = new HashMap<>();
}

addLoader方法

addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader)方法,用于添加一个需要延迟加载属性

入参分别表示:需要延迟加载的属性名称、该属性所在的Java对象(也就是查询返回的结果对象)、延迟加载对应的加载器,方法如下:

public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
  	// 获取第一个属性名称
    String upperFirst = getUppercaseFirstProperty(property);
    if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
        throw new ExecutorException("省略...");
    }
    loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
}
  1. 如果property属性名称包含.点,且最前面一部分已经有对应的延迟加载对象了,则出现重复添加,需要抛出异常

  2. 将入参信息封装成LoadPair对象,并放入loaderMap

    关于LoadPair,是ResultLoaderMap的一个内部类,里面有对序列化进行处理,最后还是调用ResultLoaderload()方法,这里就不列出来了

load方法

load(String property)方法,用于触发该属性的延迟加载,方法如下:

public boolean load(String property) throws SQLException {
    LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
    if (pair != null) {
        pair.load();
        return true;
    }
    return false;
}
  1. 先将该属性对应的延迟加载从loaderMap集合中删除
  2. 然后调用LoadPairload()方法,触发延迟加载,并设置查询结果设置到对象的属性中

loadAll方法

loadAll() 方法,用于触发所有还没加载的延迟加载,方法如下:

public void loadAll() throws SQLException {
    final Set<String> methodNameSet = loaderMap.keySet();
    String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]);
    for (String methodName : methodNames) {
        load(methodName);
    }
}

ProxyFactory

org.apache.ibatis.executor.loader.ProxyFactory:动态代理工厂接口

public interface ProxyFactory {

  default void setProperties(Properties properties) {
    // NOP
  }

  Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, 
                     ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, 
                     List<Object> constructorArgs);
}
  • 就定义了一个createProxy创建动态代理对象的方法,交由不同的子类去实现

实现类如下图所示:

ProxyFactory

回到Configuration全局配置对象中,你会发现默认使用的是JavassistProxyFactory实现类

// Configuration.java
protected ProxyFactory proxyFactory = new JavassistProxyFactory();

JavassistProxyFactory

org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory:实现ProxyFactory接口,基于javassist(一个开源的分析、编辑和创建Java字节码的类库)创建动态代理对象

构造方法

public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {

	private static final String FINALIZE_METHOD = "finalize";
	private static final String WRITE_REPLACE_METHOD = "writeReplace";

	public JavassistProxyFactory() {
		try {
			// 加载 javassist.util.proxy.ProxyFactory 类
			Resources.classForName("javassist.util.proxy.ProxyFactory");
		} catch (Throwable e) {
			throw new IllegalStateException(
					"Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.",
					e);
		}
	}
}
  • 加载 javassist.util.proxy.ProxyFactory

createProxy方法

createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法

创建动态代理对象的入口,方法如下:

@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
        ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
  	// <1> 创建动态代实例对象
    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, 
                                                     objectFactory, constructorArgTypes, constructorArgs);
}

内部直接调用EnhancedResultObjectProxyImplcreateProxy方法

crateProxy静态方法

crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 方法

用于创建一个动态代理的实例对象,并设置MethodHandler方法增强器,方法如下:

static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes,
        List<Object> constructorArgs) {

    // <3.1> 创建 ProxyFactory 动态代理对象工厂
    ProxyFactory enhancer = new ProxyFactory();
    // <3.2> 设置父类,需要代理的类对象
    enhancer.setSuperclass(type);

    // <3.3> 和序列化相关
    try {
      // 获取需要代理的类对象中的 writeReplace 方法
        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) {
      // 如果没有 writeReplace 方法,则设置接口为 WriteReplaceInterface
        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 {
  		// <3.4> 创建动态代理实例对象
        enhanced = enhancer.create(typesArray, valuesArray);
    } catch (Exception e) {
        throw new ExecutorException("Error creating lazy proxy.  Cause: " + e, e);
    }
    // <3.5> 设置动态代理实例对象的 MethodHandler 方法增强器
    ((Proxy) enhanced).setHandler(callback);
    return enhanced;
}
  1. 创建 ProxyFactory 动态代理对象工厂
  2. 设置父类,需要代理的类对象
  3. 设置和序列化相关配置
  4. 创建动态代理实例对象,传入代理类对象的构造方法的入参类型数组和入参数组
  5. 设置动态代理实例对象的MethodHandler方法增强器

EnhancedResultObjectProxyImpl

JavassistProxyFactory的内部类,动态代理对象的MethodHandler方法增强器

构造方法
private static class EnhancedResultObjectProxyImpl implements MethodHandler {

    private final Class<?> type;
    private final ResultLoaderMap lazyLoader;
    /**
     * 开启时,任一方法的调用都会加载该对象的所有延迟加载属性,默认false
     */
    private final boolean aggressive;
    private final Set<String> lazyLoadTriggerMethods;
    private final ObjectFactory objectFactory;
    private final List<Class<?>> constructorArgTypes;
    private final List<Object> constructorArgs;

    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration,
            ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        this.type = type;
        this.lazyLoader = lazyLoader;
        this.aggressive = configuration.isAggressiveLazyLoading();
        this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
        this.objectFactory = objectFactory;
        this.constructorArgTypes = constructorArgTypes;
        this.constructorArgs = constructorArgs;
    }
}
  • 我们主要看到ResultLoaderMap lazyLoader属性,里面保存了需要延迟加载的属性和加载器
createProxy方法

createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法

创建动态代理实例对象,方法如下:

public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
        ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    final Class<?> type = target.getClass();
    // <2> 创建方法的增强器
    EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, 
                                                                               objectFactory, constructorArgTypes, constructorArgs);
    // <3> 创建动态代理实例对象,设置方法的增强器
    Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
    // <4> 将 target 的属性值复制到 enhanced 动态代实例对象中
    PropertyCopier.copyBeanProperties(type, target, enhanced);
    return enhanced;
}

这个方法在JavassistProxyFactorycreateProxy方法被调用,然后自己内部又调用JavassistProxyFactory的静态createProxy方法,这里我已经按序号标明了步骤

  1. 创建EnhancedResultObjectProxyImpl方法的增强器callback
  2. 创建动态代理实例对象,并设置方法的增强器为callback,调用的是上面的静态createProxy方法
  3. target的属性值复制到enhanced动态代实例对象中
invoke方法

javassist.util.proxy.MethodHandler方法增强器的而实现方法,代理对象的方法都会进入这个方法

@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
    final String methodName = method.getName();
    try {
        synchronized (lazyLoader) {
            // <1> 如果方法名为 writeReplace,和序列化相关
            if (WRITE_REPLACE_METHOD.equals(methodName)) {
                Object original;
                if (constructorArgTypes.isEmpty()) {
                    original = objectFactory.create(type);
                } else {
                    original = objectFactory.create(type, constructorArgTypes, constructorArgs);
                }
                // 从动态代理实例对象中复制属性值到 original 中
                PropertyCopier.copyBeanProperties(type, enhanced, original);
                if (lazyLoader.size() > 0) {
                    return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), 
                                                          objectFactory,constructorArgTypes, constructorArgs);
                } else {
                    return original;
                }
            } else { // <2> 加载延迟加载的属性
                if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                    // <2.1> 如果开启了任一方法的调用都会加载该对象的所有延迟加载属性,或者是 "equals", "clone", "hashCode", "toString" 其中的某个方法
                    if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                      	// 加载所有延迟加载的属性
                        lazyLoader.loadAll();
                    } else if (PropertyNamer.isSetter(methodName)) {
                      	// <2.2> 如果为 setter 方法,从需要延迟加载属性列表中移除
                        final String property = PropertyNamer.methodToProperty(methodName);
                        lazyLoader.remove(property);
                    } else if (PropertyNamer.isGetter(methodName)) {
                      	// <2.3> 如果调用了 getter 方法,则执行延迟加载,从需要延迟加载属性列表中移除
                        final String property = PropertyNamer.methodToProperty(methodName);
                        if (lazyLoader.hasLoader(property)) {
                          // 加载该属性值
                            lazyLoader.load(property);
                        }
                    }
                }
            }
        }
        // <3> 继续执行原方法
        return methodProxy.invoke(enhanced, args);
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

先给ResultLoaderMap lazyLoader添加synchronized关键字,保证线程安全

  1. 如果加强的方法是writeReplace,则进行一些序列化相关的操作,暂不分析,其实是没看懂~

  2. 如果lazyLoader中有延迟加载的属性,并且加强的方法不是finalize

    1. 如果开启了任一方法的调用都会加载该对象的所有延迟加载属性,或者是equals clone hashCode toString其中的某个方法,则触发所有的延迟加载
    2. 否则,如果是属性的setter方法,则从lazyLoader中将该属性的延迟加载删除(如果存在),因为主动设置了这个属性值,则需要取消该属性的延迟加载
    3. 否则,如果是属性的getter方法,则执行延迟加载(会将结果设置到该对象的这个属性中),里面也会从lazyLoader中将该属性的延迟加载删除
  3. 继续执行原方法

到这里,延迟加载已经实现了

CglibProxyFactory

org.apache.ibatis.executor.loader.cglib.CglibProxyFactory:实现ProxyFactory接口,基于cglib(一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口)创建动态代理对象

实现方式和JavassistProxyFactory类似,这里就不进行分析了,感兴趣的可以看一下

总结

本文分析了 MyBatis 中延迟加载的实现方法,在 DefaultResultSetHandler 映射结果集的过程中,如果返回对象有属性是嵌套子查询,且需要延迟加载,则通过JavassistProxyFactory为返回结果创建一个动态代理对象,并设置MethodHandler方法增强器为EnhancedResultObjectProxyImpl对象

其中传入ResultLoaderMap对象,该对象保存了这个结果对象中所有的ResultLoader延迟加载

EnhancedResultObjectProxyImpl中拦截结果对象的方法,进行增强处理,通过ResultLoader延迟加载器获取到该属性值,然后从ResultLoaderMap中删除,在你调用该属性的getter方法时才加载数据,这样就实现了延迟加载

好了,对于 MyBatis 的整个 SQL 执行过程我们已经全部分析完了,其中肯定有不对或者迷惑的地方,欢迎指正!!!感谢大家的阅读!!!

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/lifullmoon/p/14015223.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!

相关课程

3794 178.2元 198元 9折