手撸Spring系列12:MyBatis(源码篇)

说在前头: 笔者本人为大三在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。

手撸Spring系列是笔者本人首次尝试的、较为规范的系列博客,将会围绕Spring框架分为 IOC/DI 思想Spring MVCAOP 思想Spring JDBC 四个模块,并且每个模块都会分为 理论篇源码篇实战篇 三个篇章进行讲解(大约12篇文章左右的篇幅)。从原理出发,深入浅出,一步步接触Spring源码并手把手带领大家一起写一个 迷你版的Spring框架 ,促进大家进一步了解Spring的本质!

由于源码篇涉及到源码的阅读,可能有小伙伴没有成功构建好Spring源码的阅读环境,笔者强烈建议:想要真正了解Spring,一定要构建好源码的阅读环境再进行研究,具体构建过程可查看笔者此前的博客:《如何构建Spring5源码阅读环境》

1.从Mybatis的入口开始

在上一篇理论篇手撸Spring系列11:MyBatis(理论篇)中,我们了解到了,MyBatis的配置文件是由SqlSessionFactoryBuilder开始读取的,那么我们就从这个类开始,展开我们对MyBatis源码的研究。


1.1.SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {
  /** 第一个build */
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  /** 第二个build */
  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  /** 第三个build */
  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  /** 第四个build */
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  /** 第五个build */
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  /** 第六个build */
  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  /** 第七个build */
  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  /** 第八个build */
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  /** 第九个build */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

在该类中,一共有九个build方法,但过半的方法都只是调用第四、八或九个build方法而已,自身并没有处理实际的逻辑操作。因此,我们将目光的重点移到第四、八和九中,其中,第四个build与第八个build相比,区别其实就只是前者使用了字符流的方式读取配置信息,而后者使用字节流读取罢了。我们从中选择一个方法研究即可。

那么我们的重心就主要放在 第八个build第九个build 中。

  • 第八个build:实例化了XMLConfigBuilder对象并调用其parse()方法
  • 第九个build:实例化了DefaultSqlSessionFactory工厂对象

1.2.XMLConfigBuilder#parse

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

在这个方法中,会调用方法parseConfiguration来解析配置文件的信息,除了最基本的数据库连接信息和环境信息的解析外,还会解析映射文件的信息,我们看到mapperElement(root.evalNode("mappers"));方法中来,调用的这一个方法就是用来解析映射信息的。


1.3.XMLConfigBuilder#mapperElement

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {

      // 1.以package的方式配置
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");

        // 2.以resource的方式配置
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }
          // 3.以url的方式配置
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          }
          // 4.以class的方式配置
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

进入到mapperElement方法的源码,我们可以发现在该方法中有着四个if判断语句,其实这四个代码块分别是定位映射文件的四种不同的形式。

  1. package:扫描该包下的所有mapper
  2. resourse:使用类路径定位资源
  3. url:使用url路径定位资源
  4. class:使用类型定位资源

这里我们只探讨第一种形式,package,也就是第一个if语句代码块

if ("package".equals(child.getName())) {
  String mapperPackage = child.getStringAttribute("name");
  configuration.addMappers(mapperPackage);
}

该代码块中,获取到对应的包名后,以包名为参数传入ConfigurationaddMappers方法中,我们进入到该方法中。

public void addMappers(String packageName) {
  mapperRegistry.addMappers(packageName);
}

进入该方法后发现,最终的任务会交给MapperRegistryaddMappers方法去完成,那么我们继续进入该方法的源码进行探究。


1.4.Configuration#addMappers

public void addMappers(String packageName) {
  addMappers(packageName, Object.class);
}

public void addMappers(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    addMapper(mapperClass);
  }
}

public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    // 判断mapper是否已在此前注册了
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      // 注册映射接口
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // 解析映射接口
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

该方法下主要任务是注册和解析映射接口。在finally代码块中,会判断loadCompleted是否为真,从上述代码中其实我们就可以知道,只要MapperAnnotationBuilder的实例化过程和parse()执行成功,没有抛出异常的情况下,就会进入到该判断语句中。

即:当映射接口解析成功后,将会在注册表中被remove掉。

接下来我们进入到MapperAnnotationBuilderparse方法中。


1.5.MapperAnnotationBuilder#parse

public void parse() {
  String resource = type.toString();
  if (!configuration.isResourceLoaded(resource)) {
    // 解析xml文件
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析缓存对象
    parseCache();
    // 解析缓存引用,会覆盖之前解析的缓存对象
    parseCacheRef();
    for (Method method : type.getMethods()) {
      if (!canHaveStatement(method)) {
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        // 解析方法并生成MappedStatement对象
        parseStatement(method);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

该方法中主要完成xml文件,注解的解析调用,以及为方法生成对应的MappedStatenment对象。

我们先来看到解析xml文件的loadXmlResource方法。

1.6.loadXmlResource

private void loadXmlResource() {
  if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    // 获取对应的xml配置文件
    String xmlResource = type.getName().replace('.', '/') + ".xml";
    InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
    if (inputStream == null) {
      try {
        // 将配置文件转为字节流
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e2) {
        // ignore, resource is not required
      }
    }
    if (inputStream != null) {
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      // 解析xml文件,解析过程不做详细分析
      xmlParser.parse();
    }
  }
}

在这里其实我们可以发现,获取与接口相对应的xml配置文件时,是按照全类名加.xml后缀获取的(如:接口全类名为com.bosen.mapper.TestMapper,那么它对应的配置文件的路径应该为com/bosen/mapper/TestMapper.xml)。

接下来我们回到parse方法中,该方法下还调用了一个核心方法parseStatement(method);,我们到该方法的源码进行探究。


1.7.parseStatement(Method method)

void parseStatement(Method method) {
  // 获取参数类型
  final Class<?> parameterTypeClass = getParameterType(method);
  // 获取语言驱动对象
  final LanguageDriver languageDriver = getLanguageDriver(method);

  getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
    // 用于封装sql语句的SqlSource
    final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
    // 获取sql语句的类型
    final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
    // 获取注解Option
    final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
    // statement的id由全类名加方法名组成
    final String mappedStatementId = type.getName() + "." + method.getName();
    // 主键生成对象
    final KeyGenerator keyGenerator;
    String keyProperty = null;
    String keyColumn = null;
    // 如果sql语句属于插入或修改语句进入该代码块
    if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
      // 首先检查SelectKey注释,用于生成主键
      SelectKey selectKey = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);
      if (selectKey != null) {
        // 初始化keyGenerator
        keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
        // 设置主键属性
        keyProperty = selectKey.keyProperty();
      } else if (options == null) {
        // 如果设置了主键,则使用Jdbc3KeyGenerator,否则使用NoKeyGenerator
        keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      } else {
        // 如果option设置了生成主键,则使用Jdbc3KeyGenerator,否则使用NoKeyGenerator
        keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        // 获取主键属性
        keyProperty = options.keyProperty();
        // 获取数据库列
        keyColumn = options.keyColumn();
      }
    } else {
      // 既不是插入语句也不是修改语句,使用NoKeyGenerator
      keyGenerator = NoKeyGenerator.INSTANCE;
    }

    Integer fetchSize = null;
    Integer timeout = null;
    StatementType statementType = StatementType.PREPARED;
    ResultSetType resultSetType = configuration.getDefaultResultSetType();
    // 是否为查询语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 不是查询语句,刷新
    boolean flushCache = !isSelect;
    // 是查询语句,使用缓存
    boolean useCache = isSelect;
    // 如果使用了options注解,则读取其中的信息
    if (options != null) {
      if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
        flushCache = true;
      } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
        flushCache = false;
      }
      useCache = options.useCache();
      fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
      timeout = options.timeout() > -1 ? options.timeout() : null;
      statementType = options.statementType();
      if (options.resultSetType() != ResultSetType.DEFAULT) {
        resultSetType = options.resultSetType();
      }
    }

    String resultMapId = null;
    // sql属于查询语句
    if (isSelect) {
      // 获取返回值类型
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      // 如果存在ResultMap注解,则为返ResultMap生成id
      if (resultMapAnnotation != null) {
        resultMapId = String.join(",", resultMapAnnotation.value());
      } else {
        resultMapId = generateResultMapName(method);
      }
    }

    // 创建MappedStatement对象,并存入到Configuration名为mappedStatements的map中。
    assistant.addMappedStatement(
        mappedStatementId,
        sqlSource,
        statementType,
        sqlCommandType,
        fetchSize,
        timeout,
        // ParameterMapID
        null,
        parameterTypeClass,
        resultMapId,
        getReturnType(method),
        resultSetType,
        flushCache,
        useCache,
        // TODO gcode issue #577
        false,
        keyGenerator,
        keyProperty,
        keyColumn,
        statementAnnotation.getDatabaseId(),
        languageDriver,
        // ResultSets
        options != null ? nullOrEmpty(options.resultSets()) : null);
  });
}

这个方法代码非常多,相应的它也完成了不少的工作,总结如下:

  1. 获取参数类型
  2. 获取语言驱动对象LanguageDriver
  3. 创建sql封装对象SqlSource
  4. 获取主键生成对象KeyGenerator
  5. 创建MappedStatement对象,并存入到Configuration名为mappedStatements的map中。

以下是一个MappedStatement对象所包含的信息

到了这里,我们可以知道,SqlSessionFactoryBuilder第八个build方法的作用其实是解析xml配置文件(或者注解配置),将配置中的sql语句进行封装并生成与之对应的MappedStatement对象。


2.开始创建会话工厂

/** 第九个build */
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

第九个build方法实例化了一个DefaultSqlSessionFactory工厂对象,其中,该类的核心方法openSessionFromDataSource的作用就是用于创建SqlSession会话对象的。我们进入该方法中。

2.1.DefaultSqlSessionFactory

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);
    // 创建SqlSession会话对象
    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();
  }
}

在即将创建SqlSession对象前,都会为其创建一个执行器Executor。接下来我们进入到DefaultSqlSession源码中。

2.2.DefaultSqlSession

public class DefaultSqlSession implements SqlSession {

  // 省略了很多行代码……

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

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

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  // 省略了很多行代码……
}

在该方法中,有关数据库操作的方法,会调用到上面三个方法:updateselectListselectCursor,并且最终交给执行器Executor执行具体操作。

由于类Executor是一个接口,因此如果我们想要查看执行器是如何执行sql语句的,应该进入到其实现类中查看。笔者这里选用其中的一个实现类SimpleExecutor来探究。


2.3.SimpleExecutor

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
  // 这里省略了多行代码……
}

在执行器的修改方法doUpdate中,我们发现,该方法实例化了一个助手类StatementHandler,而修改操作则是交给这个助手类来完成的。

StatementHandler同样也只是一个接口,为了探讨该类是如何进行具体的数据库操作的,我们需要进入到其子类的源码中。笔者这里选择其中一个实现类SimpleStatementHandler来探究。


2.4.SimpleStatementHandler

public class SimpleStatementHandler extends BaseStatementHandler {
  
  // 这里省略了多行代码……
  
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }
  
  // 这里省略了多行代码……
}

在助手类SimpleStatementHandler中也存在多个方法,这里我们只关注其中的查找方法query。此方法中,其实就是调用了JDBC中Statement对象的execute方法来运行sql语句


3.获取映射器Mapper

经过上面的操作,我们的Mybatis已经完成了初始化,映射器Mapper也已经准备完成,就等着我们去使用它啦。但,此时问题又来了,我们要如何才可以获取到映射器Mapper?

MyBatis与我们用户之间交互的操作都会通过会话对象SqlSeesion来完成,此时我们回看到DefaultSqlSession的源码。

3.1.DefaultSqlSession

其中有个获取mapper的方法getMapper,看到这个方法中,发现其中调用了ConfigurationgetMapper方法。

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

继续进入到该方法中,又发现其调用了mapperRegistrygetMaper,我们再继续进入到该方法中。

3.2.MapperRegistry

public class MapperRegistry {

  // 这里省略了多行代码……

  @SuppressWarnings("unchecked")
  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);
    }
  }
  // 这里省略了多行代码……
}

来到这里,发现方法最终会创建一个mapper代理工厂

3.3.MapperProxyFactory

public class MapperProxyFactory<T> {

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

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

该工厂中的实例化代理的方法newInstance中,创建了代理类MapperProxy,我们进入到改代理类源码中。

3.4.MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private final SqlSession sqlSession;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
}

进入到代理类MapperProxy,发现该类实现了接口InvocationHandler,表明该代理类使用JDK动态代理来创建的。

我们继续顺着cachedInvoker探讨MyBatis的代理类最终是如何进行操作的。

3.5.cachedInvoker

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        // 这里判断方法是否是default方法,java8的新特性,
        // 这里我们不做分析,直接看else代码块
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          // PlainMethodInvoker用于封装MapperMethod
          // 而MapperMethod用于保存保存对应接口方法的SQL以及入参和出参的数据类型等信息
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

该方法下,我们主要关注else代码亏块的代码即可,在该代码块中,实例化了MapperMethod对象,和PlainMethodInvoker对象。

  • PlainMethodInvoker用于封装MapperMethod

  • MapperMethod用于保存保存对应接口方法的SQL以及入参和出参的数据类型等信息

最终PlainMethodInvoker对象中的invoke方法会得到调用

3.6.PlainMethodInvoker

private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;

  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return mapperMethod.execute(sqlSession, args);
  }
}

在该类invoke方法中,调用了mapperMethodexecute方法,进入到该对象的方法源码中。

3.7.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);
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      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;
}

来到这里,我们终于发现,MyBatis的代理类“绕了多个弯子”后,最后是通过调用会话对象SqlSession的方法实现增、删、改、查操作的,而SqlSession则将具体的sql执行任务交给JDBC中的Statement来完成。

至此,MyBatis的源码执行流程我们就分析到这里,下一篇实战篇中,我们将来尝试实现迷你版的MyBatis,并和我们之前实现的迷你版Spring进行整合,使得我们的迷你版Spring功能更加完善~!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring整合Mybatis源码分析可以分为以下几个步骤: 1. 创建Spring容器并加载配置文件。在Spring Boot中,可以通过@SpringBootApplication注解来创建Spring容器,并在配置文件中配置Mybatis相关的属性。 2. 创建Mybatis的SqlSessionFactory。Spring Boot会自动配置Mybatis的SqlSessionFactory,通过读取配置文件中的数据源信息和Mybatis的配置信息,创建SqlSessionFactory对象。 3. 注册MybatisMapper接口。Spring Boot会自动扫描项目中的Mapper接口,并将其注册到Spring容器中。 4. 创建Mapper代理对象。Spring Boot使用MybatisMapperFactoryBean来创建Mapper接口的代理对象。在创建代理对象时,会使用SqlSessionFactory来创建SqlSession,并将SqlSession注入到Mapper接口中。 5. 使用Mapper代理对象进行数据库操作。通过调用Mapper接口的方法,可以实现对数据库的增删改查操作。 整个过程中,Spring Boot通过自动配置和注解扫描的方式,简化了Spring整合Mybatis的配置和使用过程,使得开发者可以更方便地使用Mybatis进行数据库操作。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [SpringBoot整合Mybatis源码解析](https://blog.csdn.net/u013521882/article/details/120624374)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Spring-Mybatis整合源码分析](https://blog.csdn.net/qq_42651904/article/details/111059652)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Spring源码解析之整合Mybatis](https://blog.csdn.net/heroqiang/article/details/79135500)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云丶言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值