写点什么

源码学习之 MyBatis 的底层查询原理

  • 2022 年 9 月 30 日
    北京
  • 本文字数:11276 字

    阅读完需:约 37 分钟

源码学习之MyBatis的底层查询原理

导读

本文通过 MyBatis 一个低版本的 bug(3.4.5 之前的版本)入手,分析 MyBatis 的一次完整的查询流程,从配置文件的解析到一个查询的完整执行过程详细解读 MyBatis 的一次查询流程,通过本文可以详细了解 MyBatis 的一次查询过程。在平时的代码编写中,发现了 MyBatis 一个低版本的 bug(3.4.5 之前的版本),由于现在很多工程中的版本都是低于 3.4.5 的,因此在这里用一个简单的例子复现问题,并且从源码角度分析 MyBatis 一次查询的流程,让大家了解 MyBatis 的查询原理。

1 问题现象

1.1 场景问题复现


如下图所示,在示例 Mapper 中,下面提供了一个方法 queryStudents,从 student 表中查询出符合查询条件的数据,入参可以为 student_name 或者 student_name 的集合,示例中参数只传入的是 studentName 的 List 集合


 List<String> studentNames = new LinkedList<>(); studentNames.add("lct"); studentNames.add("lct2"); condition.setStudentNames(studentNames);
复制代码


  <select id="queryStudents" parameterType="mybatis.StudentCondition" resultMap="resultMap">

select * from student <where> <if test="studentNames != null and studentNames.size > 0 "> AND student_name IN <foreach collection="studentNames" item="studentName" open="(" separator="," close=")"> #{studentName, jdbcType=VARCHAR} </foreach> </if>

<if test="studentName != null and studentName != '' "> AND student_name = #{studentName, jdbcType=VARCHAR} </if> </where> </select>
复制代码


期望运行的结果是


select * from student WHERE student_name IN ( 'lct' , 'lct2' )
复制代码


但是实际上运行的结果是


==> Preparing: select * from student WHERE student_name IN ( ? , ? ) AND student_name = ?


==> Parameters: lct(String), lct2(String), lct2(String)


<== Columns: id, student_name, age


<== Row: 2, lct2, 2


<== Total: 1


通过运行结果可以看到,没有给 student_name 单独赋值,但是经过 MyBatis 解析以后,单独给 student_name 赋值了一个值,可以推断出 MyBatis 在解析 SQL 并对变量赋值的时候是有问题的,初步猜测是 foreach 循环中的变量的值带到了 foreach 外边,导致 SQL 解析出现异常,下面通过源码进行分析验证

2 MyBatis 查询原理

2.1 MyBatis 架构


2.1.1 架构图


先简单来看看 MyBatis 整体上的架构模型,从整体上看 MyBatis 主要分为四大模块:


接口层:主要作用就是和数据库打交道


数据处理层:数据处理层可以说是 MyBatis 的核心,它要完成两个功能:


  • 通过传入参数构建动态 SQL 语句;

  • SQL 语句的执行以及封装查询结果集成 List<E>


框架支撑层:主要有事务管理、连接池管理、缓存机制和 SQL 语句的配置方式


引导层:引导层是配置和启动 MyBatis 配置信息的方式。MyBatis 提供两种方式来引导 MyBatis :基于 XML 配置文件的方式和基于 Java API 的方式


2.1.2 MyBatis 四大对象


贯穿 MyBatis 整个框架的有四大核心对象,ParameterHandler、ResultSetHandler、StatementHandler 和 Executor,四大对象贯穿了整个框架的执行过程,四大对象的主要作用为:


  • ParameterHandler:设置预编译参数

  • ResultSetHandler:处理 SQL 的返回结果集

  • StatementHandler:处理 sql 语句预编译,设置参数等相关工作

  • Executor:MyBatis 的执行器,用于执行增删改查操作


2.2 从源码解读 MyBatis 的一次查询过程


首先给出复现问题的代码以及相应的准备过程


2.2.1 数据准备


CREATE TABLE `student`  (  `id` bigint(20) NOT NULL AUTO_INCREMENT,  `student_name` varchar(255) NULL DEFAULT NULL,  `age` int(11) NULL DEFAULT NULL,  PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1;

-- ------------------------------ Records of student-- ----------------------------INSERT INTO `student` VALUES (1, 'lct', 1);INSERT INTO `student` VALUES (2, 'lct2', 2);
复制代码


2.2.2 代码准备


1.mapper 配置文件


<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="mybatis.StudentDao"> <!-- 映射关系 --> <resultMap id="resultMap" type="mybatis.Student"> <id column="id" property="id" jdbcType="BIGINT" /> <result column="student_name" property="studentName" jdbcType="VARCHAR" /> <result column="age" property="age" jdbcType="INTEGER" />

</resultMap>

<select id="queryStudents" parameterType="mybatis.StudentCondition" resultMap="resultMap">

select * from student <where> <if test="studentNames != null and studentNames.size > 0 "> AND student_name IN <foreach collection="studentNames" item="studentName" open="(" separator="," close=")"> #{studentName, jdbcType=VARCHAR} </foreach> </if>

<if test="studentName != null and studentName != '' "> AND student_name = #{studentName, jdbcType=VARCHAR} </if> </where> </select>

</mapper>
复制代码


2.示例代码


public static void main(String[] args) throws IOException {        String resource = "mybatis-config.xml";        InputStream inputStream = Resources.getResourceAsStream(resource);        //1.获取SqlSessionFactory对象        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);        //2.获取对象        SqlSession sqlSession = sqlSessionFactory.openSession();        //3.获取接口的代理类对象        StudentDao mapper = sqlSession.getMapper(StudentDao.class);        StudentCondition condition = new StudentCondition();        List<String> studentNames = new LinkedList<>();        studentNames.add("lct");        studentNames.add("lct2");        condition.setStudentNames(studentNames);        //执行方法        List<Student> students = mapper.queryStudents(condition);    }
复制代码


2.2.3 查询过程分析


1.SqlSessionFactory 的构建


先看 SqlSessionFactory 的对象的创建过程


//1.获取SqlSessionFactory对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
复制代码


代码中首先通过调用 SqlSessionFactoryBuilder 中的 build 方法来获取对象,进入 build 方法


 public SqlSessionFactory build(InputStream inputStream) {    return build(inputStream, null, null);  }
复制代码


调用自身的 build 方法



图 1 build 方法自身调用调试图例


在这个方法里会创建一个 XMLConfigBuilder 的对象,用来解析传入的 MyBatis 的配置文件,然后调用 parse 方法进行解析



图 2 parse 解析入参调试图例


在这个方法中,会从 MyBatis 的配置文件的根目录中获取 xml 的内容,其中 parser 这个对象是一个 XPathParser 的对象,这个是专门用来解析 xml 文件的,具体怎么从 xml 文件中获取到各个节点这里不再进行讲解。这里可以看到解析配置文件是从 configuration 这个节点开始的,在 MyBatis 的配置文件中这个节点也是根节点


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"        "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>

<properties> <property name="dialect" value="MYSQL" /> <!-- SQL方言 --> </properties>
复制代码


然后将解析好的 xml 文件传入 parseConfiguration 方法中,在这个方法中会获取在配置文件中的各个节点的配置



图 3 解析配置调试图例


以获取 mappers 节点的配置来看具体的解析过程


 <mappers>        <mapper resource="mappers/StudentMapper.xml"/>    </mappers>
复制代码


进入 mapperElement 方法


mapperElement(root.evalNode("mappers"));
复制代码



图 4 mapperElement 方法调试图例


看到 MyBatis 还是通过创建一个 XMLMapperBuilder 对象来对 mappers 节点进行解析,在 parse 方法中


public void parse() {  if (!configuration.isResourceLoaded(resource)) {    configurationElement(parser.evalNode("/mapper"));    configuration.addLoadedResource(resource);    bindMapperForNamespace();  }

parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();}
复制代码


通过调用 configurationElement 方法来解析配置的每一个 mapper 文件


private void configurationElement(XNode context) {  try {    String namespace = context.getStringAttribute("namespace");    if (namespace == null || namespace.equals("")) {      throw new BuilderException("Mapper's namespace cannot be empty");    }    builderAssistant.setCurrentNamespace(namespace);    cacheRefElement(context.evalNode("cache-ref"));    cacheElement(context.evalNode("cache"));    parameterMapElement(context.evalNodes("/mapper/parameterMap"));    resultMapElements(context.evalNodes("/mapper/resultMap"));    sqlElement(context.evalNodes("/mapper/sql"));    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));  } catch (Exception e) {    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);  }}
复制代码


以解析 mapper 中的增删改查的标签来看看是如何解析一个 mapper 文件的


进入 buildStatementFromContext 方法


private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {  for (XNode context : list) {    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);    try {      statementParser.parseStatementNode();    } catch (IncompleteElementException e) {      configuration.addIncompleteStatement(statementParser);    }  }}
复制代码


可以看到 MyBatis 还是通过创建一个 XMLStatementBuilder 对象来对增删改查节点进行解析,通过调用这个对象的 parseStatementNode 方法,在这个方法里会获取到配置在这个标签下的所有配置信息,然后进行设置



图 5 parseStatementNode 方法调试图例


解析完成以后,通过方法 addMappedStatement 将所有的配置都添加到一个 MappedStatement 中去,然后再将 mappedstatement 添加到 configuration 中去


builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,    resultSetTypeEnum, flushCache, useCache, resultOrdered,     keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
复制代码



可以看到一个 mappedstatement 中包含了一个增删改查标签的详细信息



图 7 mappedstatement 对象方法调试图例


而一个 configuration 就包含了所有的配置信息,其中 mapperRegistertry 和 mappedStatements



图 8 config 对象方法调试图例


具体的流程



图 9 SqlSessionFactory 对象的构建过程 图 9 SqlSessionFactory 对象的构建过程


2.SqlSession 的创建过程


SqlSessionFactory 创建完成以后,接下来看看 SqlSession 的创建过程


SqlSession sqlSession = sqlSessionFactory.openSession();
复制代码


首先会调用 DefaultSqlSessionFactory 的 openSessionFromDataSource 方法


@Overridepublic SqlSession openSession() {  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}
复制代码


在这个方法中,首先会从 configuration 中获取 DataSource 等属性组成对象 Environment,利用 Environment 内的属性构建一个事务对象 TransactionFactory


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {  Transaction tx = null;  try {    final Environment environment = configuration.getEnvironment();    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);    final Executor executor = configuration.newExecutor(tx, execType);    return new DefaultSqlSession(configuration, executor, autoCommit);  } catch (Exception e) {    closeTransaction(tx); // may have fetched a connection so lets call close()    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);  } finally {    ErrorContext.instance().reset();  }}
复制代码


事务创建完成以后开始创建 Executor 对象,Executor 对象的创建是根据 executorType 创建的,默认是 SIMPLE 类型的,没有配置的情况下创建了 SimpleExecutor,如果开启二级缓存的话,则会创建 CachingExecutor


public Executor newExecutor(Transaction transaction, ExecutorType executorType) {  executorType = executorType == null ? defaultExecutorType : executorType;  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;  Executor executor;  if (ExecutorType.BATCH == executorType) {    executor = new BatchExecutor(this, transaction);  } else if (ExecutorType.REUSE == executorType) {    executor = new ReuseExecutor(this, transaction);  } else {    executor = new SimpleExecutor(this, transaction);  }  if (cacheEnabled) {    executor = new CachingExecutor(executor);  }  executor = (Executor) interceptorChain.pluginAll(executor);  return executor;}
复制代码


创建 executor 以后,会执行 executor = (Executor)


interceptorChain.pluginAll(executor)方法,这个方法对应的含义是使用每一个拦截器包装并返回 executor,最后调用 DefaultSqlSession 方法创建 SqlSession



图 10 SqlSession 对象的创建过程


3.Mapper 的获取过程


有了 SqlSessionFactory 和 SqlSession 以后,就需要获取对应的 Mapper,并执行 mapper 中的方法


StudentDao mapper = sqlSession.getMapper(StudentDao.class);
复制代码


在第一步中知道所有的 mapper 都放在 MapperRegistry 这个对象中,因此通过调用


org.apache.ibatis.binding.MapperRegistry#getMapper 方法来获取对应的 mapper


public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);  if (mapperProxyFactory == null) {    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");  }  try {    return mapperProxyFactory.newInstance(sqlSession);  } catch (Exception e) {    throw new BindingException("Error getting mapper instance. Cause: " + e, e);  }}
复制代码


在 MyBatis 中,所有的 mapper 对应的都是一个代理类,获取到 mapper 对应的代理类以后执行 newInstance 方法,获取到对应的实例,这样就可以通过这个实例进行方法的调用


public class MapperProxyFactory<T> {

private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; }

public Class<T> getMapperInterface() { return mapperInterface; }

public Map<Method, MapperMethod> getMethodCache() { return methodCache; }

@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }

public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }

}
复制代码


获取 mapper 的流程为



图 11 Mapper 的获取过程


4.查询过程


获取到 mapper 以后,就可以调用具体的方法


//执行方法List<Student> students = mapper.queryStudents(condition);
复制代码


首先会调用


org.apache.ibatis.binding.MapperProxy#invoke 的方法,在这个方法中,会调用 org.apache.ibatis.binding.MapperMethod#execute


public Object execute(SqlSession sqlSession, Object[] args) {  Object result;  switch (command.getType()) {    case INSERT: {   Object param = method.convertArgsToSqlCommandParam(args);      result = rowCountResult(sqlSession.insert(command.getName(), param));      break;    }    case UPDATE: {      Object param = method.convertArgsToSqlCommandParam(args);      result = rowCountResult(sqlSession.update(command.getName(), param));      break;    }    case DELETE: {      Object param = method.convertArgsToSqlCommandParam(args);      result = rowCountResult(sqlSession.delete(command.getName(), param));      break;    }    case SELECT:      if (method.returnsVoid() && method.hasResultHandler()) {        executeWithResultHandler(sqlSession, args);        result = null;      } else if (method.returnsMany()) {        result = executeForMany(sqlSession, args);      } else if (method.returnsMap()) {        result = executeForMap(sqlSession, args);      } else if (method.returnsCursor()) {        result = executeForCursor(sqlSession, args);      } else {        Object param = method.convertArgsToSqlCommandParam(args);        result = sqlSession.selectOne(command.getName(), param);      }      break;    case FLUSH:      result = sqlSession.flushStatements();      break;    default:      throw new BindingException("Unknown execution method for: " + command.getName());  }  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {    throw new BindingException("Mapper method '" + command.getName()         + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");  }  return result;}
复制代码


首先根据 SQL 的类型增删改查决定执行哪个方法,在此执行的是 SELECT 方法,在 SELECT 中根据方法的返回值类型决定执行哪个方法,可以看到在 select 中没有 selectone 单独方法,都是通过 selectList 方法,通过调用


org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)方法来获取到数据


@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {  try {    MappedStatement ms = configuration.getMappedStatement(statement);    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  } catch (Exception e) {    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);  } finally {    ErrorContext.instance().reset();  }}
复制代码


在 selectList 中,首先从 configuration 对象中获取 MappedStatement,在 statement 中包含了 Mapper 的相关信息,然后调用


org.apache.ibatis.executor.CachingExecutor#query()方法



图 12 query()方法调试图示


在这个方法中,首先对 SQL 进行解析根据入参和原始 SQL,对 SQL 进行拼接



图 13 SQL 拼接过程代码图示


调用 MapperedStatement 里的 getBoundSql 最终解析出来的 SQL 为



图 14 SQL 拼接过程结果图示


接下来调用


org.apache.ibatis.parsing.GenericTokenParser#parse 对解析出来的 SQL 进行解析



图 15 SQL 解析过程图示


最终解析的结果为



图 16 SQL 解析结果图示


最后会调用 SimpleExecutor 中的 doQuery 方法,在这个方法中,会获取 StatementHandler,然后调用


org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize 这个方法进行参数和 SQL 的处理,最后调用 statement 的 execute 方法获取到结果集,然后 利用 resultHandler 对结进行处理



图 17 SQL 处理结果图示


查询的主要流程为




图 18 查询流程处理图示


5.查询流程总结


总结整个查询流程如下



图 19 查询流程抽象


2.3 场景问题原因及解决方案


2.3.1 个人排查


这个问 bug 出现的地方在于绑定 SQL 参数的时候再源码中位置为


 @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {   BoundSql boundSql = ms.getBoundSql(parameter);   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}
复制代码


由于所写的 SQL 是一个动态绑定参数的 SQL,因此最终会走到


org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql 这个方法中去


public BoundSql getBoundSql(Object parameterObject) {  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();  if (parameterMappings == null || parameterMappings.isEmpty()) {    boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);  }

// check for nested result maps in parameter mappings (issue #30) for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); } } }

return boundSql;}
复制代码


在这个方法中,会调用 rootSqlNode.apply(context)方法,由于这个标签是一个 foreach 标签,因此这个 apply 方法会调用到


org.apache.ibatis.scripting.xmltags.ForEachSqlNode#apply 这个方法中去


@Overridepublic boolean apply(DynamicContext context) {  Map<String, Object> bindings = context.getBindings();  final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);  if (!iterable.iterator().hasNext()) {    return true;  }  boolean first = true;  applyOpen(context);  int i = 0;  for (Object o : iterable) {    DynamicContext oldContext = context;    if (first) {      context = new PrefixedContext(context, "");    } else if (separator != null) {      context = new PrefixedContext(context, separator);    } else {        context = new PrefixedContext(context, "");    }    int uniqueNumber = context.getUniqueNumber();    // Issue #709     if (o instanceof Map.Entry) {      @SuppressWarnings("unchecked")       Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;      applyIndex(context, mapEntry.getKey(), uniqueNumber);      applyItem(context, mapEntry.getValue(), uniqueNumber);    } else {      applyIndex(context, i, uniqueNumber);      applyItem(context, o, uniqueNumber);    }    contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));    if (first) {      first = !((PrefixedContext) context).isPrefixApplied();    }    context = oldContext;    i++;  }  applyClose(context);  return true;}
复制代码


当调用 appItm 方法的时候将参数进行绑定,参数的变量问题都会存在 bindings 这个参数中区


private void applyItem(DynamicContext context, Object o, int i) {  if (item != null) {    context.bind(item, o);    context.bind(itemizeItem(item, i), o);  }}
复制代码


进行绑定参数的时候,绑定完成 foreach 的方法的时候,可以看到 bindings 中不止绑定了 foreach 中的两个参数还额外有一个参数名字 studentName->lct2,也就是说最后一个参数也是会出现在 bindings 这个参数中的,


private void applyItem(DynamicContext context, Object o, int i) {  if (item != null) {    context.bind(item, o);    context.bind(itemizeItem(item, i), o);  }}
复制代码



图 20 参数绑定过程


最后判定


org.apache.ibatis.scripting.xmltags.IfSqlNode#apply


@Overridepublic boolean apply(DynamicContext context) {  if (evaluator.evaluateBoolean(test, context.getBindings())) {    contents.apply(context);    return true;  }  return false;}
复制代码


可以看到在调用 evaluateBoolean 方法的时候会把 context.getBindings()就是前边提到的 bindings 参数传入进去,因为现在这个参数中有一个 studentName,因此在使用 Ognl 表达式的时候,判定为这个 if 标签是有值的因此将这个标签进行了解析



图 21 单个参数绑定过程


最终绑定的结果为



图 22 全部参数绑定过程


因此这个地方绑定参数的地方是有问题的,至此找出了问题的所在。


2.3.2 官方解释


翻阅 MyBatis 官方文档进行求证,发现在 3.4.5 版本发行中 bug fixes 中有这样一句



图 23 此问题官方修复 github 记录 图 23 此问题官方修复 github 记录


修复了 foreach 版本中对于全局变量 context 的修改的 bug


issue 地址为https://github.com/mybatis/mybatis-3/pull/966


修复方案为https://github.com/mybatis/mybatis-3/pull/966/commits/84513f915a9dcb97fc1d602e0c06e11a1eef4d6a


可以看到官方给出的修改方案,重新定义了一个对象,分别存储全局变量和局部变量,这样就会解决 foreach 会改变全局变量的问题。



图 24 此问题官方修复代码示例


2.3.3 修复方案


  • 升级 MyBatis 版本至 3.4.5 以上

  • 如果保持版本不变的话,在 foreach 中定义的变量名不要和外部的一致

3 源码阅读过程总结

MyBatis 源代码的目录是比较清晰的,基本上每个相同功能的模块都在一起,但是如果直接去阅读源码的话,可能还是有一定的难度,没法理解它的运行过程,本次通过一个简单的查询流程从头到尾跟下来,可以看到 MyBatis 的设计以及处理流程,例如其中用到的设计模式:



图 25 MyBatis 代码结构图


  • 组合模式:如 ChooseSqlNode,IfSqlNode 等

  • 模板方法模式:例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的子类例如 IntegerTypeHandler

  • Builder 模式:例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder

  • 工厂模式:例如 SqlSessionFactory、ObjectFactory、MapperProxyFactory

  • 代理模式:MyBatis 实现的核心,比如 MapperProxy、ConnectionLogger

4 文档参考

https://mybatis.org/mybatis-3/zh/index.html


作者:李春廷

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018.11.20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
源码学习之MyBatis的底层查询原理_Java_京东科技开发者_InfoQ写作社区