该系列文档是本人在学习 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执行的整体过程如下图所示:

在 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创建动态代理对象的方法,交由不同的子类去实现

实现类如下图所示:

回到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 执行过程我们已经全部分析完了,其中肯定有不对或者迷惑的地方,欢迎指正!!!感谢大家的阅读!!!

参考文章:芋道源码《精尽 MyBatis 源码分析》

精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载的更多相关文章

  1. 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler

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

  2. 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler

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

  3. 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口

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

  4. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

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

  5. 精尽 MyBatis 源码分析 - 基础支持层

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

  6. 精尽MyBatis源码分析 - 插件机制

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

  7. 精尽MyBatis源码分析 - 文章导读

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

  8. MyBatis 源码篇-SQL 执行的流程

    本章通过一个简单的例子,来了解 MyBatis 执行一条 SQL 语句的大致过程是怎样的. 案例代码如下所示: public class MybatisTest { @Test public void ...

  9. 精尽MyBatis源码分析 - MyBatis-Spring 源码分析

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

随机推荐

  1. Java基础之类型转换总结篇

    Java中,经常可以遇到类型转换的场景,从变量的定义到复制.数值变量的计算到方法的参数传递.基类与派生类间的造型等,随处可见类型转换的身影.Java中的类型转换在Java编码中具有重要的作用.    ...

  2. 一起学Vue:入门

    Why-为什么需要Vue? 前端开发存在的问题? 其一,需求变化频率更高 产品功能迭代前端肯定需要跟着调整. 提意见的人多,前端嘛谁都能看得见,所以,谁都可以指手画脚提一点意见.产品经理.项目经理.老 ...

  3. Libevent库基础(2)

    带缓冲区的事件 bufferevent #include <event2/bufferevent.h> read/write 两个缓冲. 借助 队列. 创建.销毁bufferevent: ...

  4. [Luogu P2261] [CQOI2007]余数求和 (取模计算)

    题面 传送门:https://www.luogu.org/problemnew/show/P2261 Solution 这题显然有一个O(n)的直接计算法,60分到手. 接下来我们就可以拿出草稿纸推一 ...

  5. 浅谈 Tarjan 算法

    目录 简述 作用 Tarjan 算法 原理 出场人物 图示 代码实现 例题 例题一 例题二 例题三 例题四 例题五 总结 简述 对于初学 Tarjan 的你来说,肯定和我一开始学 Tarjan 一样无 ...

  6. Codeforces Round #677 (Div. 3) 题解

    Codeforces Round #677 (Div. 3) 题解 A. Boring Apartments 题目 题解 简单签到题,直接数,小于这个数的\(+10\). 代码 #include &l ...

  7. Elasticsearch原理解析与性能调优

    基本概念 定义 一个分布式的实时文档存储,每个字段 可以被索引与搜索 一个分布式实时分析搜索引擎 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据 用途 全文检索 结构化搜索 分 ...

  8. 剑指offer之顺序打印数组

    算法的要求为: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打 ...

  9. C# 时间格式处理

    C#的常用时间格式意义: 1字符"y"---year,年,yy显示13,yyyy显示2013 2字符"M"---Month,月份,M显示5,MM显示05 3字符 ...

  10. VirtualBox 6 安装 CentOS 7

    1 安装环境 windows7 Oracle VM VirtualBox 6.0.24 CentOS 7 2 VirtualBox 6 - 虚拟机软件 2.1 下载 Oracle VM Virtual ...