Mybatis架构解析

目录

整体架构层

SqlSession初始化

SQL请求的执行和分发

 执行器层和缓存


ps:mybatis 有很多不同的用法,不同的用法对应相同或不同类的不同方法,走不同的分支逻辑,但是它们终究都还是走相同的执行流程,因为不同的写法,用法都是依赖相同的接口,所以如果我们使用mybatis用得比较简单,那么它可能就不会走很多的很长的分支逻辑,比如存储过程,association,类型别名这些东西我们在使用过程中几乎不会用到,自然就不会走到它们的分支处理逻辑,所以我们分析代码的时候不关注它们,它们都只是流程里的细节,但流程是不变的。

整体架构层

这是mybatis的架构图,层次很清晰,从中我们可以看到 SqlSession 就是mybatis提供给用户层操作数据库的顶级接口。 SqlSession 会调用它的一下层执行器Executor层,执行器Executor层 会调用它的下一层 StatementHandler 层 ,然后 StatementHandler 层会调用它的下一层 ParameterHandler 层 ,ParameterHandler 层会调用它的下一层 Statement 和mysql等数据库进行交互,完成增删改查的操作,返回执行结果,这个执行结果毫无疑问就是 ResultSet ,然后 ResultSetHandler 层会对这个执行结果进行处理,最终返回数据给用户层 。

下面就是一个最简单的例子,我们应用层确实是操作 SqlSession 层来操作mybatis和数据库进行交互的。

@org.junit.Test
public void testQuery() { 
    try(InputStream inputStream=Resources.getResourceAsStream("mybatis.xml")) {  
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "development");
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        System.out.println(user.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } 
}

在 SqlSession 会话层, SqlSession 会调用它的下一层 Executor 执行器去实际执行查询或更新,DefaultSqlSession 是默认的 SqlSession 接口实现类。Executor 有三种具体类型。

executor.query(ms, wrapCollection(parameter), rowBounds, handler);
executor.update(ms, wrapCollection(parameter));
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  ...
  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

在 Executor 执行器层,SimpleExecutor 通过 StatementHandler 创建了一个 Statement 对象,当Statement 对象被创建出来后, StatementHandler 会通过它的update()或 query() 等方法实际执行 SQL语句

StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);

,并将结果交给 ResultHandler 进行处理 。

handler.update(stmt);
public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
}

在 StatementHandler 层,它会先使用 ParameterHandler 把 Statement中的 "?"占位符替换为实际的参数。

 @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

即在 DefaultParameterHandler 中通过反射的方式或类型处理器的方式替换。

public class DefaultParameterHandler implements ParameterHandler {
  
  private final TypeHandlerRegistry typeHandlerRegistry;
  private final MappedStatement mappedStatement;
  private final Object parameterObject;
  private final BoundSql boundSql;
  private final Configuration configuration;
    
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
}

完成占位符的替换后,StatementHandler 会调用 Statement 执行 sql 命令。

ps.execute();
public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }
}

Statement 执行完成后,ResultSetHandler 会对 ResultSet 进行处理,

resultSetHandler.handleResultSets(ps)

返回最终结果。我们可以看到都是处理 ResultMap ,ResultMapping ,这些都是mybatis的标签,最终返回一个List<Object>。

public class DefaultResultSetHandler implements ResultSetHandler {
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }
}

以上就是mybatis的主要执行流程,中间还有很多细节可以通过看代码,debug跟踪断点的方式去认识mybatis。

SqlSession初始化

XMLConfigBuilder是一个解析mybati xml配置的解析器,xml是一个树形嵌套结构,很好解析,解析完成后生成一个Configuration类,它贯穿了mybatis的整个生命周期。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

SqlSessionFactory生成后,就会获取一个sqlSession对象,它是java代码操作mybatis的顶级接口,可以通过一个DataSource或Connection来获取。

@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
	return openSessionFromDataSource(execType, null, autoCommit);
}

@Override
public SqlSession openSession(Connection connection) {
	return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}

SQL请求的执行和分发

mybatis 是怎么通过 mapper 接口找到对应的具体sql代码执行的呢?

原来在 mybatis 启动的时候会扫描所有 mapper.xml 文件里的每一个 <insert/><update/><delete/><select/> 标签,它们都会被解析为一个个的MappedStatement 对象并保存在Configuration对象里面,它的id就是mapper.xml的命名空间再加上<select/>等标签的id, 它们组成一个Map,key是这个id,value就是MappedStatement,它是怎么解析的我们不管,就是解析一个xml树 JAXBElement 节点而已。当 SqlSession 发送一条请求过来时,它会被 MapperProxy代理接收并处理,它会交给MapperMethod去处理。

public class MapperProxy<T> implements InvocationHandler, Serializable {
	@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 (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
}

在 MapperMethod 中,它会根据请求类型下发给 sqlSession 去执行。

public class MapperMethod {
      public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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);
          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());
    }
    ...
    return result;
  }
}
public class DefaultSqlSession implements SqlSession {
  @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();
    }
  }
}

mappedStatements 确实是一个Map<String, MappedStatement>的对象

public class Configuration {
    ...
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

    ...
    public MappedStatement getMappedStatement(String id) {
        return this.getMappedStatement(id, true);
    }
    
    public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        if (validateIncompleteStatements) {
            buildAllStatements();
        }  
        return mappedStatements.get(id);
    }
}

总结一下:动态代理proxy会根据请求方法的(select,insert等)类型和(类似com.yzp.demo.mapper.UserMapper.queryUserById)名称去找到要执行的Statement并执行,这个过程就是请求命令的路由分发。

这个过程有点像 open feign 的 ReflectiveFeign 类似,它也是在动态代理中去完成路由分发的逻辑的,另外它们都是使用jdk动态代理。

dispatch 是一个Map,保存了方法和方法处理器的映射关系。

public class ReflectiveFeign extends Feign {
    ....
    static class FeignInvocationHandler implements InvocationHandler {

    private final Target target;
    private final Map<Method, MethodHandler> dispatch;

    FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
      this.target = checkNotNull(target, "target");
      this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if ("equals".equals(method.getName())) {
        try {
          Object otherHandler =
              args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
          return equals(otherHandler);
        } catch (IllegalArgumentException e) {
          return false;
        }
      } else if ("hashCode".equals(method.getName())) {
        return hashCode();
      } else if ("toString".equals(method.getName())) {
        return toString();
      }

      return dispatch.get(method).invoke(args);
    }
}
 

 执行器层和缓存

SqlSession会创建一个执行器Executor,分为Simple,Reuse,Batch三种类型,由执行器通过StatementHandler创建Statement对象,Reuse类型的statement可以复用,Simple类型的不可以。其中一级缓存和二级缓存都是在Executor执行器中完成的。一级缓存是一个HashMap,在一个会话里会使用CacheKey来作为键,键的结构如下,可以简单理解为就是一个sql的字符串,最后会生成一个long类型的hash码,debug一下就知道了。

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
}

通常我们会关闭一级缓存,因为一级缓存是Session会话级别的,不同的会话的一级缓存互不干扰,这就会导致脏读导致的数据不一致问题。关闭一级缓存则改为statement级即可。

ps:一级缓存:Mybatis的缓存机制详解_cnmeimei的博客-CSDN博客

未完待续

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值