Mybatis源码解析之执行SQL语句

时间:2022-12-13 12:06:41

作者:郑志杰

mybatis操作数据库的过程

// 第一步:读取mybatis-config.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 第二步:构建SqlSessionFactory(框架初始化)
SqlSessionFactory sqlSessionFactory = new  SqlSessionFactoryBuilder().bulid();
// 第三步:打开sqlSession
SqlSession session = sqlSessionFactory.openSession();
// 第四步:获取Mapper接口对象(底层是动态代理)
AccountMapper accountMapper = session.getMapper(AccountMapper.class);
// 第五步:调用Mapper接口对象的方法操作数据库;
Account account = accountMapper.selectByPrimaryKey(1);

通过调用session.getMapper(AccountMapper.class)所得到的AccountMapper 是一个动态代理对象,所以执行
accountMapper.selectByPrimaryKey(1)方法前,都会被invoke()拦截,先执行invoke()中的逻辑。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //  要执行的方法所在的类如果是Object,直接调用,不做拦截处理
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
            //如果是默认方法,也就是java8中的default方法
        } else if (isDefaultMethod(method)) {
            // 直接执行default方法
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    } 
    // 从缓存中获取MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}

从methodCache获取对应DAO方法的MapperMethod

MapperMethod的主要功能是执行SQL语句的相关操作,在初始化的时候会实例化两个对象:SqlCommand(Sql命令)和 MethodSignature(方法签名)。

  /**
   * 根据Mapper接口类型、接口方法、核心配置对象 构造MapperMethod对象
   * @param mapperInterface
   * @param method
   * @param config
   */
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    // 将Mapper接口中的数据库操作方法(如Account selectById(Integer id);)封装成方法签名MethodSignature
    this.method = new MethodSignature(config, mapperInterface, method);
  }

new SqlCommand()调用SqlCommand类构造方法:

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 获取Mapper接口中要执行的某个方法的方法名
      // 如accountMapper.selectByPrimaryKey(1)
      final String methodName = method.getName();
      // 获取方法所在的类
      final Class<?> declaringClass = method.getDeclaringClass();
      // 解析得到Mapper语句对象(对配置文件中的<mapper></mapper>中的sql语句进行封装)
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        // 如com.bjpowernode.mapper.AccountMapper.selectByPrimaryKey
        name = ms.getId();
        // SQL类型:增 删 改 查
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
  private MapperMethod cachedMapperMethod(Method method) {
     MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
     if (mapperMethod == null) {
         mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
         this.methodCache.put(method, mapperMethod);
     }
     return mapperMethod;
 }

调用mapperMethod.execute(sqlSession, args)

在mapperMethod.execute()方法中,我们可以看到:mybatis定义了5种SQL操作类型:
insert/update/delete/select/flush。其中,select操作类型又可以分为五类,这五类的返回结果都不同,分别对应:

•返回参数为空:executeWithResultHandler();

•查询多条记录:executeForMany(),返回对象为JavaBean

•返参对象为map:executeForMap(), 通过该方法查询数据库,最终的返回结果不是JavaBean,而是Map

•游标查询:executeForCursor();关于什么是游标查询,自行百度哈;

•查询单条记录: sqlSession.selectOne(),通过该查询方法,最终只会返回一条结果;

通过源码追踪我们可以不难发现:当调用mapperMethod.execute() 执行SQL语句的时候,无论是
insert/update/delete/flush,还是select(包括5种不同的select), 本质上时通过sqlSession调用的。在SELECT操作中,虽然调用了MapperMethod中的方法,但本质上仍是通过Sqlsession下的select(), selectList(), selectCursor(), selectMap()等方法实现的。

而SqlSession的内部实现,最终是调用执行器Executor(后面会细说)。这里,我们可以先大概看一下mybatis在执行SQL语句的时候的调用过程:

Mybatis源码解析之执行SQL语句