Mybatis初探——流程走一遍

对于自己每天用的东西,会用不够,并且要知其所以然。这个专题中我会随便写一些关于现在主流框架的源代码研读。
好奇心是最好的老师。
今天从mybatis写起。

我看的是3.4.3版本的mybatis源码,是结合spring-mybatis一起读的。
更重要的是大家自己去一步步跟进代码,研读

1. mapper是如何映射到一个真实可调用的类的

经过一番探索,发现mapper是使用jdk的动态代理实现的。

以前自己写过一个例子如下:

package cn.sxy.SimulateSpring.AOP.DynamicProxy.ProxyPackage;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy implements InvocationHandler {

    private Object object;

    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object object = method.invoke(this.object, args);
        after();
        return null;
    }

    private void before() {
        System.out.println("Method begin! ");
    }

    private void after() {
        System.out.println("Method end! ");
    }

    public static void main(String[] args) {

        SomeManager someManager = new SomeManagerImpl();

        DynamicProxy dynamicProxy = new DynamicProxy(someManager);


        SomeManager someManagerProxy = (SomeManager) Proxy.newProxyInstance(
                someManager.getClass().getClassLoader(),
                someManager.getClass().getInterfaces(),
                dynamicProxy
        );

        System.out.println(someManagerProxy.getClass().toString());

        someManagerProxy.treat();
    }
}

在这段代码逻辑中,before()和after()被增强到方法本身的前后,这里先不探究jdk动态代理是怎么实现的。

我们发现这段方法中的invoke方法,决定了被增强之后的方法和原方法的关系。

经过一番调试,我们发现了MapperProxy这个类,其中invoke方法揭示了增强与被增强的关系。

@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 if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 这两句话是真正执行sql的地方
    // 1
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 2.
    return mapperMethod.execute(sqlSession, args);
  }

1.1 cachedMapperMethod

然后开始看cachedMapperMethod方法。

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

这就是个经典的缓存判断代码。如果缓存里没有,重新构造一个mappermethod对象。重点是这个MapperMethod的构造方法,点进去看。

 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // 1.
    this.command = new SqlCommand(config, mapperInterface, method);
    // 2.
    this.method = new MethodSignature(config, mapperInterface, method);
  }

于是我们发现了两句看上去非常关键的话。

这里有一个SqlCommand对象,似乎这个东西是把我们配置的xml映射过来的重要玄机。

1.1.1 SqlCommand

继续往里走。

// 这两个是成员变量
private final String name;
private final SqlCommandType type;

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      // MappedStatemenet里封装了和这个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 {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

command对象,获得了name和commandType。这个commandType在后面是有用的。

1.1.2 MethodSignature

方法签名,似乎是对方法有什么操作,点进去继续看。

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 解析返回值的类型,把sql的type映射到javaType
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      // 是不是返回空
      this.returnsVoid = void.class.equals(this.returnType);
      // 是不是返回很多数据
      this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
      this.returnsCursor = Cursor.class.equals(this.returnType);
      // https://blog.csdn.net/clementad/article/details/50589459
      // 这个和mapkey注解有关系,具体可以参考上面的链接
      this.mapKey = getMapKey(method);
      // 
      this.returnsMap = (this.mapKey != null);
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      // 这个其实可以猜猜看,就是mapper里写的那些@Param()的映射关系,维护在这个对象里。
      // 这样mybatis才能知道,怎么对应填入后面的sql语句
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

其实根据上面那些我自己写的注释,你会发现,这就是对这个方法的修饰,这些修饰在后面的真正执行sql语句的时候,都是会用上的。

这个resolveReturnType出发,可以引出mybatis的类型映射关系,这个可以以后看。

1.2. execute

经过上面的一轮查看,发现前面的方法,就是生成了方法有关的信息,生成过程是比较费时的,所以mybatis引入了缓存机制,这样下次使用这个方法的时候,就不用重新生成这些信息了。

接下来进入重头戏,execute方法.

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 下面和command以及method有关的信息,都是通过1.1中的过程生成的
    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;
  }

本人在调试的时候,使用的是一个select的,并且使returnsMany的例子,我们点进去看

1.2.1 executeForMany方法
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.<E>selectList(command.getName(), param, rowBounds);
    } else {
      // 这里是真正执行的地方
      result = sqlSession.<E>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;
  }

点入真正执行的地方。我这里是在spring框架中用的mybatis,进入了mybatis-spring的部分。

发现sqlSession被代理了。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }

这里控制了sql的公共流程,比如说获取session,关闭session,提交等操作,所以我们重点关注invoke里的东西即可。说白了这些是spring对mybatis的增强。

点进去会发现,又回到mybatis的模块了。

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  // 上面调用的就是这里
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 又是这个熟悉的东西
      // 大家有兴趣可以点进去看,会发现,在xml里定义的那些ResultMap也被包装在里面了
      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();
    }
  }

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 这里是真正获取sql语句的地方,里面涉及到一个StaticSqlSource对象
    // 这是在读取xml的时候就生成了的,具体初始化相关的流程,要去builder这个包下看
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // boundSql是把参数填入的sql语句对象,有了这个,就是执行sql的问题了
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

从上文我们发现,其实mybatis的真正执行过程,就是借着jdk动态代理,增强了我们的mapper接口,在初始化的时候,帮我们把sql语句,以及一些映射关系填入相关的对象中,然后执行方法的时候,通过这些对象,拿到真正的sql,交给底层和jdbc相关的模块,去执行sql语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值