MyBatis核心源码深度剖析SQL执行过程

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis核心源码深度剖析SQL执行过程相关的知识,希望对你有一定的参考价值。

目录


1 SQL语句的执行过程介绍

MyBatis核心执行组件:

2 SQL执行的入口分析

2.1 为Mapper接口创建代理对象

// 方式1:
User user = session.selectOne("com.oldlu.dao.UserMapper.findUserById", 101);
// 方式2:
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();

2.2 执行代理逻辑

方式1入口分析:
session是DefaultSqlSession类型的,因为sqlSessionFactory默认生成的SqlSession是
DefaultSqlSession类型。
selectOne()会调用selectList()。

// DefaultSqlSession类
public <E> List<E> selectList(String statement, Object parameter, RowBounds
rowBounds) 
  try 
    MappedStatement ms = configuration.getMappedStatement(statement);
    // CURD操作是交给Excetor去处理的
    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();
 

方式2入口分析:
获取代理对象:

//DefaultSqlSession类 ====================>
@Override
public <T> T getMapper(Class<T> type) 
  return configuration.getMapper(type, this);
  
// Configuration类 ====================>
public <T> T getMapper(Class<T> type, SqlSession sqlSession) 
  return mapperRegistry.getMapper(type, sqlSession);

//MapperRegistry ----> apperProxyFactory.newInstance ====================>
public <T> T getMapper(Class<T> type, SqlSession sqlSession) 
  //从缓存中获取该Mapper接口的代理工厂对象
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)
knownMappers.get(type);
  //如果该Mapper接口没有注册过,则抛异常
  if (mapperProxyFactory == null) 
    throw new BindingException("Type " + type + " is not known to the
MapperRegistry.");
 
  try 
    //【使用代理工厂创建Mapper接口的代理对象】
    return mapperProxyFactory.newInstance(sqlSession);
  catch (Exception e) 
    throw new BindingException("Error getting mapper instance. Cause: " + e,
e);
 

//MapperProxyFactory  --->此时生成代理对象 ====================>
protected T newInstance(MapperProxy<T> mapperProxy) 
  //Mybatis底层是调用JDK的Proxy类来创建代理实例
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
Class[]  mapperInterface , mapperProxy);

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

代理对象执行逻辑:

//MapperProxy   ====================>
/**代理对象执行的方法,代理以后,所有Mapper的方法调用时,都会调用这个invoke方法*/
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable 
 try 
  if (Object.class.equals(method.getDeclaringClass())) 
   //如果是Object方法,则调用方法本身
   return method.invoke(this, args);
  else 
   //调用接口方法:根据被调用接口的Method对象,从缓存中获取MapperMethodInvoker对象
   //apper接口中的每一个方法都对应一个MapperMethodInvoker对象,而MapperMethodInvoker
对象里面的MapperMethod保存着对应的SQL信息和返回类型以完成SQL调用 ...
   return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
 
 catch (Throwable t) 
  throw ExceptionUtil.unwrapThrowable(t);


/**
获取缓存中MapperMethodInvoker,如果没有则创建一个,而MapperMethodInvoker内部封装这一
个MethodHandler
*/
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable 
  try 
    return methodCache.computeIfAbsent(method, m -> 
      if (m.isDefault()) 
        //如果调用接口的是默认方法(default方法)
        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 
        //如果调用的普通方法(非default方法),则创建一个PlainMethodInvoker并放
入缓存,其中MapperMethod保存对应接口方法的SQL以及入参和出参的数据类型等信息
        return new PlainMethodInvoker(new MapperMethod(mapperInterface,
method, sqlSession.getConfiguration()));
     
   );
  catch (RuntimeException re) 
    Throwable cause = re.getCause();
throw cause == null ? re : cause;
 

// MapperProxy内部类: PainMethodInvoker  ====================>
// 当cacheInvoker返回了PalinMethodInvoker实例之后,紧接着调用了这个实例的
PlainMethodInvoker:invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession
sqlSession) throws Throwable 
 //Mybatis实现接口方法的核心: MapperMethod::execute方法:
 return mapperMethod.execute(sqlSession, args);

// MapperMethod  ====================>
public Object execute(SqlSession sqlSession, Object[] args) 
  Object result;
  switch (command.getType()) 
    case INSERT: 
      // 将args进行解析,如果是多个参数则,则根据@Param注解指定名称将参数转换为Map,
如果是封装实体则不转换
      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 
        //解析参数,因为SqlSession::selectOne方法参数只能传入一个,但是我们
Mapper中可能传入多个参数,
        //有可能是通过@Param注解指定参数名,所以这里需要将Mapper接口方法中的多个参
数转化为一个ParamMap,
        //也就是说如果是传入的单个封装实体,那么直接返回出来;如果传入的是多个参数,
实际上都转换成了Map
        Object param = method.convertArgsToSqlCommandParam(args);
        //可以看到动态代理最后还是使用SqlSession操作数据库的
        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;

// 此时我们发现: 回到了sqlsession中
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) 
  List<E> result;
  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);
 
 // ...
  return result;

3 查询语句的执行过程分析

3.1 selectOne方法分析

// DefaultSqlSession类  ===============>
// selectOne
@Override
public <T> T selectOne(String statement, Object parameter) 
  // //selectOne()会调用selectList()。
  List<T> list = this.selectList(statement, parameter);
  if (list.size() == 1) 
    return list.get(0);
  else if (list.size() > 1) 
    throw new TooManyResultsException("Expected one result (or null) to be
returned by selectOne(), but found: " + list.size());
  else 
    return null;
 

// selectList
public <E> List<E> selectList(String statement, Object parameter, RowBounds
rowBounds) 
  try 
    MappedStatement ms = configuration.getMappedStatement(statement);
    // CURD操作是交给Excetor去处理的
    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();
 

3.2 sql获取

// CachingExecutor ===============>
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException 
  // 获取绑定的sql命令,比如"SELECT * FROM xxx"
  BoundSql boundSql = ms.getBoundSql(parameterObject); 
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  throws SQLException 
  Cache cache = ms.getCache();
  if (cache != null) 
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) 
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) 
        list = delegate.query(ms, parameterObject, rowBounds,
resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
    
      return list;
   
 
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key,
boundSql);

//真正执行query操作的是SimplyExecutor代理来完成的,SimplyExecutor的父类BaseExecutor的
query方法中:
// BaseExecutor类:SimplyExecutor的父类 =================>
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws
SQLException 
  ErrorContext.instance().resource(ms.getResource()).activity("executing a
query").object(ms.getId());
  if (closed) 
    throw new以上是关于MyBatis核心源码深度剖析SQL执行过程的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis核心源码深度剖析工作机制和实现原理

[原创]深度剖析Golang map源码

Mybatis源码剖析-黑马程序员

mybatis源码级别深度剖析

Mybatis拦截器源码深度解析

Mybatis拦截器源码深度解析