Mybatis-SpringBoot源码解析之三:执行sql流程

Mybatis-SpringBoot源码解析之三:执行sql流程


1、前言

前面已经介绍了SqlSessionFactory以及SqlSessionTemplate的构建流程,本章内容就介绍sql的具体解析流程,不过在此之前需要先浅分析一个重要注解@MapperScan,在使用中我们都知道他会扫描所有的xxxdao.java,也就是xxx.xml的namespace的对应的dao的位置,在构建SqlSessionFactory中已经解析了会将namespace封装到MapperRegistryknownMappers 属性上,在这儿就发挥着巨大的作用。

PS:这儿不对@MapperScan做详细分析,以后有时间的话在写一下,这儿只做浅析。

2、@MapperScanner流程简析

@Import(MapperScannerRegistrar.class)
public @interface MapperScan {}

可以看到@Import导入了MapperScannerRegistrar类。

流程如下:

  1. 通过registerBeanDefinitions方法解析@MapperScanner的所有属性以及注册了MapperScannerConfigurer类,后续通过MapperScannerConfigurer类进行进一步的解析操作。

  2. MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor所以会进一步的进入到postProcessBeanDefinitionRegistry方法中,然后调用scan方法。

  3. doScan方法简要代码如下所示:

      public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        1、扫描该包下面所有的的符合条件的对象也就是获取我们的dao类
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        2、在这儿会将dao进一步的封装成MapperFactoryBean
        processBeanDefinitions(beanDefinitions);
        return beanDefinitions;
      }
    

    这儿比较重要,会将dao封装成MapperFactoryBean,所以他是个FactoryBean实现了getObject方法。

3、getObject方法具体调用逻辑

  1、位于MapperFactoryBean类中
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  2、位于DefaultSqlSession类中
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
  3、位于Configuration类中,老朋友了,保存在sqlSessionFactory中的重要属性
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    3.1、mapperRegistry类中内部有knownMappers属性保存的就是namespace指向的dao也就是当前解析的dao
       是不是有种恍然大悟流程穿起来的感觉。
    return mapperRegistry.getMapper(type, sqlSession);
  }
  4、创建代理对象MapperProxy
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    return mapperProxyFactory.newInstance(sqlSession);
  }

4、newInstance创建代理对象

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

  protected T newInstance(MapperProxy<T> mapperProxy) {
    1、创建代理对象,最终返回的是该代理对象MapperProxy,保存到了spring的factoryBeanObjectCache中。
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

5、小总结

在这儿做一个小总结吧

  1. @MapperScanner会扫描所有的dao接口,然后将他们封装成MapperFactoryBean
  2. MapperFactoryBeangetObect方法去Configuration中获取MapperProxyFactory属性。
  3. MapperProxyFactory最终通过反射获取具体的代理类MapperProxy,保存到spring容器中。

6、sql执行流程

前面已经做好了所有的准备工作下面就开始正式进入执行sql吧。

例子:UserDao.java接口

public interface UserDao {
     List<User> getListById(@Param(value = "id") Long id);

     List<User> getList();
}

UserMapper.xml文件

<?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="com.zgf.study.mybatis.mapper.UserDao">

    <select id="getListById" resultType="com.zgf.study.mybatis.entity.User">
        select uid,my_name myName,age,email,sex from t_user where uid =#{id}
    </select>
    <select id="getList" resultType="com.zgf.study.mybatis.entity.User">
        select uid,my_name myName,age,email,sex from t_user
    </select>
</mapper>

6.1、首先进入MapperProxy的invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  1、上面的代码就省略了,只看重点。
  return mapperMethod.execute(sqlSession, args);
}

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    1、获取sql类型 (select update delete 还是 insert)
    switch (command.getType()) {
      2、insert语句
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      3、update语句
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      4、delete语句
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
	  5、select语句
      case SELECT:
        这儿根据返回值,调用不同的方法解析。
        5.1、如果返回结果为void 并且 有结果处理程序 处理结构 之后 返回null
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        5.2、返回多条数据
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        5.3、返回map
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        5.4、返回的是Cursor
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          5.5、单条查询
          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() + ").");
    }
    6、返回最终结果
    return result;
  }

至此,走到了真正解析sql的地方,这儿还可以再往下稍微探索一点。

6.2、executeForMany查询返回多条

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    1、这儿就是获取入参
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }


  public <E> List<E> selectList(String statement, Object parameter) {
   	1、sqlSessionProxy就是SqlSessionTemplate前文提到的执行sql的代理类,所以会再次执行invoke方法。
    return this.sqlSessionProxy.selectList(statement, parameter);
  }

已经很深入了,就解析到这儿吧。

7、总结

在这儿总结一下mybatis与springBoot整合的整个流程步骤如下所示:

  • 首先mybatis是通过MybatisAutoConfiguration自动配置的方式进行注入到spring中去的,在该类中同样注册了SqlSessionFactory以及SqlSessionTemplate

  • 构建SqlSessionFactory,主要是构建内部的Configuration属性,在这儿得到两个重要的参数。

    • MappedStatement: 解析mapper.xml后封装成的对象。
    • MapperRegistry类内的knownMappers缓存: key为namespace对应的dao的class,value为MapperProxyFactory
  • 通过@MapperScanner注解扫描所有的dao,将dao封装成MapperFactoryBean,后续通过getObject方法获取对应的MapperProxyFactory,以此创建对应的dao的代理类MapperProxy,在该代理类中封装了mapperInterface (是被代理类的class)和SqlSessionTemplate 两个重要属性。

  • 执行sql,调用MapperProxyinvoke方法

  • 最终根据sql类型如select、insert、update、delete进入SqlSessionTemplate执行真正的sql。

  • SqlSessionTemplate内部通过mapperInterfaceSqlSessionFactory内的mappedStatements 属性缓存中获取对应的MappedStatement。进而执行相应的操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值