美文网首页
Mybatis源码简析

Mybatis源码简析

作者: 零下城池 | 来源:发表于2017-07-05 17:14 被阅读159次

参加了GitChat一个关于Mybatis活动:《一步步学习 Mybatis:缓存的使用及源码分析》,文中着重讲解了Mybatis缓存配置及使用,对Mybatis源码也稍有讲解。虽自工作以来MyBatis一直有在使用,然源码很少有阅读,随借此机会把部分源码大概看了下,主要从系统启动mapper实例的创建到一条语句的执行。

示例代码:

public interface UserMapper {
    User findUserById(long id);
}

此处UserMapper为一个接口,其中定义了我们要对实体bean User的数据库持久化操作,此处仅为一个根据id查询实例的方法。接口UserMapper 的实例是通过MapperFactoryBean 来创建的, MapperFactoryBean 实现了Spring的FactoryBean接口:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport 
implements FactoryBean<T> 

其实例是通过Java的动态代理来实现的,实现代码见下:

public class MapperProxyFactory<T> {
...
...
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

从上面代码可以看到 userMapper实例是通过Java动态代理Proxy来实现的,其InvocationHandler 为MapperProxy 实例,而在MapperProxy实例中包含了一个SqlSession实例:SqlsessionTemplate,而在SqlSessionTemplate实例中则包含了初始化创建的SqlSessionFactory:DefaultSqlSessionFactory, 在通过userMapper调用findUserById时,也即是执行mapperProxy的invoke方法:

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @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);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

从代码mapperMethod.execute(sqlSession, args)追踪下去看到:

  Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);

这里的sqlSession 其实是SqlSession的实例:SqlSessionTemplate,下面是SqlSessionTemplate中的selectOne方法:

@Override
  public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.<T> selectOne(statement, parameter);
  }

这里的sqlSessionProxy是 SqlSession的一个代理实现:SqlSessionInterceptor(SqlSessionTemplate的内部类)

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;
        ...

跟踪上面代码,查看getSqlSession,最终在DefaultSqlSessionFactory类中看到创建的是一个DefaultSqlSession:

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);
      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();
    }
  }

从上面两段代码可以看到mybatis为mapper里的每一次执行创建了独立的sqlSession实例。
继续追踪代码往下看,在DefaultSqlSession类中看到他的查询方法:

 @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      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();
    }
  }

其中:
MappedStatement: 对发往数据库执行的指令的抽象表示。
Executor: 用来和数据库交互的执行器,其接受MappedStatement作为参数。
如果继续往下看就可以在BaseExecutor看到其在查询之前先从缓存中查询,如果未命中则前往数据库查询:

List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }

相关文章

  • mybatis-spring解析

    1、概述 原生Mybatis源码简析(上)原生Mybatis源码简析(下)在介绍原生Mybatis源码简析文章中,...

  • Mybatis源码简析

    本文将按mybatis中主要数据库查询操作的流程对其源码进行简单分析。目录如下: 1、mybatis的主要工作流程...

  • Mybatis源码简析

    参加了GitChat一个关于Mybatis活动:《一步步学习 Mybatis:缓存的使用及源码分析》,文中着重讲解...

  • MyBatis源码简析

    本人目前水平有限,后期深入学习后会补上不足的地方,感谢阅览。 分析代码: InputStream is = Res...

  • 原生Mybatis源码简析(下)

    在上一篇文章中(原生Mybatis源码简析(上)),我们介绍了原生Mybatis的初始化,以及Mapper接口的运...

  • 原生Mybatis源码简析(上)

    1、概述 目前工作中,直接使用mybatis原生API开发的场景很少,基本都是结合spring一起使用。但对于分析...

  • Mybatis源码分析

    【MyBatis源码分析】环境准备【MyBatis源码分析】Configuration加载(上篇)【MyBatis...

  • 源码系列——mybatis源码刨析总结

    初始化 1.创建git仓库 1.新建一个目录 然后点击右键 git base here 创建git (会弹出一个窗...

  • Flink自定义StreamOperator

    在上一篇StreamOperator源码简析从源码角度分析了StreamOperator以及其实现类,此篇幅主要分...

  • 源码学习之Mybatis

    Mybatis源码解读 1 源码下载 学习源码之前需要先将源码下载下来,这里需要下载mybatis源码和mybat...

网友评论

      本文标题:Mybatis源码简析

      本文链接:https://www.haomeiwen.com/subject/yytqhxtx.html