MyBatis+SpringMVC+SpringBootspring+springmvc+mybatis(SSM)

Mybatis解析

2018-06-20  本文已影响130人  spilledyear

概述

可以直接从github上下载Mybatis源码,这个源码特别好,因为里面包含很多测试代码,下载下来安装依赖之后就可以直接运行。在学习的过程中,结合网上各路资料,debug源码,对学习很有帮助。

之前刚好写了一篇有关于 Java代理 的总结,发现这这里刚好就用上了。

1、介绍了Mybatis部分工作流程:加载配置、代理生成、SQL解析、SQL执行、返回包装结果集。并不涉及到于缓存、插件、日志等一些其它特性,有机会可以补充。
2、介绍了Mybatis-Spring的工作流程。Mybatis可以作为一个单独的框架使用,但在实际应用中,一般是配合Spring一起使用。但是在Spring应用中,怎么可以更方便的使用Mybatis这是一个问题。通过社区提供的Mybatis-Spring插件,可以通过注入的方式使用Mapper,大大的提高了开发效率。
3、结合Hap源码,分析通用Mapper的实现。了解通用Mapper的实现,有利于对Hap中一些API的掌握,提高问题的排查效率。

网上流程的一张Mybatis工作原理图:

image.png

启动

从xml配置文件中读取配置,然后通过SqlSessionFactoryBuilder构建SqlSessionFactory实例(建造者模式)

String resource = "mybatis-config.xml"; 
InputStream is = Resources.getResourceAsStream(resource); 
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

SqlSessionFactory是Mybatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory是创建SqlSession的工厂,每一个Mybatis的应用程序都以一个SqlSessionFactory对象的实例为核心,同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,在应用执行期间都存在。

当Mybatis成功加载配置文件之后,即执行了 new SqlSessionFactoryBuilder().build(configuration)语句之后(其实调用的 return new DefaultSqlSessionFactory(config)),就已经配置了 Configuration 对象中的一部分内容。 此时的 sqlSessionFactory 对象如下

image.png

SqlSessionFactory提供的方法如下:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}
image.png

建立sqlSession

在Mybatis中通过sqlSession提供的API与数据库进行交互。通过sqlSessionFactory获取sqlSession:

// 其实是新建了一个 DefaultSqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();

SqlSession是Mybatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection。SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句.每个线程都应该有它自己的SqlSession实例.SqlSession的实例不能被共享,同时SqlSession也是线程不安全的,绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中.也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Servlet当中的HttpSession对象中.使用完SqlSeesion之后关闭Session很重要,应该确保使用finally块来关闭它。

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

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

从上可知,SqlSession对象 关联了Executor 对象和 Configuration 对象。SqlSession 中提供了一系列的方法:

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  /**
   * Clears local session cache
   */
  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}

getMapper

此方法用于获取一个mapper代理对象。我们知道,我们在应用中定义的都是mapper接口,但是却可以直接注入,这其实都是因为Mybatis为这些接口创建了代理对象。

  BoundAuthorMapper mapper = session.getMapper(BoundAuthorMapper.class);

// DefaultSqlSession
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

// Configuration
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

// MapperRegistry
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory一看名字我们就知道肯定是一个工厂类,就是为了生成MapperProxy。mapperInterface就是Mapper接口,methodCache就是对Mapper接口中的方法和方法的封装类(MapperMethod)的映射,MapperMethod处理的事情主要就是:处理Mapper接口中方法的注解,参数,和返回值。然后就是2个newInstance,看名字就知道是工厂方法,一个是protected,一个是public,public方法有一个参数SqlSession,SqlSession处理的其实就是执行一次SQL的过程。其实public的newInstance就是new了一个MapperProxy,然后调用了protected的newInstance。

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @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<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

看到木有,标准的JDK动态代理

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

MapperRegistry中的knownMappers 缓存了所有Mapper接口与MapperProxyFactory的关系。

那么,当我们调用代理对象方法的时候,其内部是怎么执行的呢?这里需要关注一下MapperProxy这个类,它实现了InvocationHandler接口,也就是当我们mapper接口方法的时候,实际上是执行了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);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

发现其内部调用了 MapperMethod 的 execute 方法,而 execute 方法内部则是调用了sqlSession的方法。

  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 = OptionalUtil.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的getMapper就是通过JDK动态代理创建了一个代理类对象,而调用接口方法的时候,实际上执行的是代理对象的方法。即都会执行MapperProxy的invoke方法,然后MapperMethod中的execute方法调用sqlSession中的方法执行真正的sql。所以如果想知道sql的具体执行流程,需要研究sqlSession中的各个方法。

Executor

通过sqlSession调用select、insert等方法的时候,其内部是怎么执行的?

  sqlSession.selectOne(String statement, Object parameter);

// 以下三个方法都在 DefaultSqlSession 中
  private final Executor executor;

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>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;
    }
  }

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

// BaseExecutor 的 query 方法。可以看到query方法先从缓存中获取结果,如果缓存中没有值,就从数据库获取,这里主要关注从数据库获取结果:queryFromDatabase
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  @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 ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    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);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }


// SimpleExecutor类 是Mybatis执行Mapper语句时默认使用的Executor。以下是 SimpleExecutor类 的doQuery方法实现,发现最后其实是通过 JDBC Statement 来执行SQL的。

  @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.<E>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;
  }

// SimpleStatementHandler 类的 query 方法
  protected final ResultSetHandler resultSetHandler;

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }

// DefaultResultSetHandler 的 handleResultSets 方法。
  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);
  }

所以,以selectOne为例子,其执行流程如下:

image.png

当然其中还有很多细节:比如通过 BoundSql获取动态执行SQL;通过 handleResultSets 包装结果集等,不在本文解读的范围。

观察了sqlSession中的其它几个方法,可以发现,Mybatis中都是通过 Executor 去执行SQL的,在Executor执行SQL的流程中,先看缓存中是否有值,如果没有则从数据库中获取结果。在从数据库中获取结果的过程中,通过BoundSql获取到动态执行的SQL,然后交给JDBC 的 Statement 对象执行execute方法,最后通过DefaultResultSetHandler的handleResultSets方法包装结果集返回。

有关于Executor 接口的 UML图如下:


image.png

StatementHandler 接口的UML图如下:


image.png
ResultSetHandler 接口的UML图如下:
image.png

加载Mapper

目前可以知道的是:Mapper接口中的方法和configuration中的mappedStatements元素是一一关联的,那么这一部分内容在Mybatis中的执行流程是什么样的?构建器。在Mybatis中可以通过两种构造器将Mapper接口中的方法转化为MappedStatement对象。一个是MapperAnnotationBuilder,通过注解的方式构建MappedStatement;还有一个是XMLMapperBuilder,将xml中定义的内容转化为MappedStatement。这里主要介绍MapperAnnotationBuilder。
MapperAnnotationBuilder的作用就是解析指定的mapper接口对应的Class对象中,包含的所有mybatis框架中定义的注解,并生成Cache、ResultMap、MappedStatement三种类型对象。在创建一个的时候,会关联一个MapperBuilderAssistant对象,这是一个构建助理,实际上,真正将接口中的方法转换成MappedStatement对象就是通过MapperBuilderAssistant对象完成的。

configuration.addMapper(BoundAuthorMapper.class);

  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          // 是否谈桥接方法,反射生成的方法是桥接方法,Java的泛型时创建的方法不是桥接方法
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

通过调用MapperAnnotationBuilder的parse方法进行构建。该方法内部会先通过loadXmlResource方法对XML文件构建,然后再通过注解的方式进行构建,主要包括以下注解:

  private static final Set<Class<? extends Annotation>> SQL_ANNOTATION_TYPES = new HashSet<>();
  private static final Set<Class<? extends Annotation>> SQL_PROVIDER_ANNOTATION_TYPES = new HashSet<>();

  static {
    SQL_ANNOTATION_TYPES.add(Select.class);
    SQL_ANNOTATION_TYPES.add(Insert.class);
    SQL_ANNOTATION_TYPES.add(Update.class);
    SQL_ANNOTATION_TYPES.add(Delete.class);

    SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
  }

@Select、@Insert、@Update、@Delete、@SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider

  void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }


  private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
    try {
      Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
      Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
      if (sqlAnnotationType != null) {
        if (sqlProviderAnnotationType != null) {
          throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
        }
        Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
        final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
        return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
      } else if (sqlProviderAnnotationType != null) {
        Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
        return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
      }
      return null;
    } catch (Exception e) {
      throw new BuilderException("Could not find value method on SQL annotation.  Cause: " + e, e);
    }
  }

parseStatement方法才是真正通过注解的方式构建。从代码中可以发现,如果再接口方法中没有使用以上注解,Mybatis将不会执行构建操作。

SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
......
}

至此,大体上可以知道Mybatis工作的流程,但在我们实际使用过程中,单独使用Mybatis的机会很少,一般都是和Spring配合使用。但是不难发现,在使用Spring+Mybatis中的应用中,我们根本不需要单独配置一个个Mapper接口,并且还可以通过注入的方式使用Mapper接口,这样大大的简化了我们的开发工作。那么,这是如何是是实现的?

Mybatis-Spring

Mybatis只是一个独立的框架,并没有提供与Spring整合的方法,为了在Spring中方便的使用Mybatis,社区提供了一个Mybatis-Spring这个工具。
上面已经说过了:在Mybatis的所有操作都是基于一个SqlSession的,而SqlSession是由SqlSessionFactory来产生的,SqlSessionFactory又是由SqlSessionFactoryBuilder来生成的
但是Mybatis-Spring是基于SqlSessionFactoryBean这个类的,这是Mybatis-Spring为我们封装的一个类,不过在这个类里面还是通过SqlSessionFactoryBuilder来建立对应的SqlSessionFactory,进而获取到对应的SqlSession,我们可以通过对SqlSessionFactoryBean指定一些属性来提供Mybatis的一些配置信息。
所以在SSM项目中经常可以在配置文件中看到以下的配置信息:

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jdbc/hap35"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:/**/*Mapper.xml"/>
    <property name="plugins">
        <array>
            <bean class="com.hand.hap.core.interceptor.RequestContextInterceptor"/>
            <bean class="com.hand.hap.core.interceptor.MultiLanguageInterceptor"/>
            <bean class="com.hand.hap.core.interceptor.SecurityTokenInterceptor"/>
            <bean class="com.hand.hap.core.interceptor.OvnInterceptor"/>
            <bean class="com.hand.hap.core.interceptor.AuditInterceptor"/>
            <bean class="com.github.pagehelper.PageHelper"/>
            <bean class="com.hand.hap.core.interceptor.CacheJoinInterceptor">
                <property name="cacheJoinType" ref="cacheJoinType"></property>
            </bean>
        </array>
    </property>
    <property name="configLocation" value="classpath:mybatis-configuration.xml"/>
</bean>

在定义SqlSessionFactoryBean的时候,dataSource属性是必须指定的,它表示用于连接数据库的数据源。当然,我们也可以指定一些其他的属性,下面简单列举几个:

MapperFactoryBean

接下来就是在Spring的applicationContext文件中定义我们想要的Mapper对象对应的MapperFactoryBean了。通过MapperFactoryBean可以获取到我们想要的Mapper对象。MapperFactoryBean实现了Spring的FactoryBean接口,所以MapperFactoryBean是通过FactoryBean接口中定义的getObject方法来获取对应的Mapper对象的。在定义一个MapperFactoryBean的时候有两个属性需要我们注入,一个是Mybatis-Spring用来生成实现了SqlSession接口的SqlSessionTemplate对象的sqlSessionFactory;另一个就是我们所要返回的对应的Mapper接口了。
定义好相应Mapper接口对应的MapperFactoryBean之后,我们就可以把我们对应的Mapper接口注入到由Spring管理的bean对象中了,比如Service bean对象。这样当我们需要使用到相应的Mapper接口时,MapperFactoryBean会从它的getObject方法中获取对应的Mapper接口,而getObject内部还是通过我们注入的属性调用SqlSession接口的getMapper(Mapper接口)方法来返回对应的Mapper接口的。这样就通过把SqlSessionFactory和相应的Mapper接口交给Spring管理实现了Mybatis跟Spring的整合。

// MapperFactoryBean
    <bean id="blogMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">  
       <property name="mapperInterface"  
           value="com.tiantian.mybatis.mapper.BlogMapper" />  
       <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
    </bean> 

MapperScannerConfigurer

利用上面的方法进行整合的时候,我们有一个Mapper就需要定义一个对应的MapperFactoryBean,当我们的Mapper比较少的时候,这样做也还可以,但是当我们的Mapper相当多时我们再这样定义一个个Mapper对应的MapperFactoryBean就显得速度比较慢了。为此Mybatis-Spring为我们提供了一个叫做MapperScannerConfigurer的类,通过这个类Mybatis-Spring会自动为我们注册Mapper对应的MapperFactoryBean对象。
如果我们需要使用MapperScannerConfigurer来帮我们自动扫描和注册Mapper接口的话我们需要在Spring的applicationContext配置文件中定义一个MapperScannerConfigurer对应的bean。对于MapperScannerConfigurer而言有一个属性是我们必须指定的,那就是basePackage。basePackage是用来指定Mapper接口文件所在的基包的,在这个基包或其所有子包下面的Mapper接口都将被搜索到。多个基包之间可以使用逗号或者分号进行分隔。最简单的MapperScannerConfigurer定义就是只指定一个basePackage属性。

<bean id="mapperScannerConfigurer" class="com.hand.hap.mybatis.spring.MapperScannerConfigurer">
    <property name="basePackage" value="*.**.mapper"/>
    <property name="processPropertyPlaceHolders" value="true"/>
    <property name="propertiesMap">
        <map>
            <entry key="mappers" value="com.hand.hap.mybatis.common.Mapper"/>
            <entry key="IDENTITY" value="${mybatis.identity}"/>
            <entry key="dataBaseType" value="${db.type}"/>
            <entry key="seqFormat" value="{3}_s.nextVal"/>
            <entry key="enableMethodAnnotation" value="true"/>
        </map>
    </property>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

这样MapperScannerConfigurer就会扫描指定基包下面的所有接口,并把它们注册为一个个MapperFactoryBean对象。

SqlSessionTemplate

除了上述整合之后直接使用Mapper接口之外,Mybatis-Spring还为我们提供了一种直接使用SqlSession的方式。Mybatis-Spring为我们提供了一个实现了SqlSession接口的SqlSessionTemplate类,它是线程安全的,可以被多个Dao同时使用。同时它还跟Spring的事务进行了关联,确保当前被使用的SqlSession是一个已经和Spring的事务进行绑定了的。而且它还可以自己管理Session的提交和关闭。当使用了Spring的事务管理机制后,SqlSession还可以跟着Spring的事务一起提交和回滚。
使用SqlSessionTemplate时我们可以在Spring的applicationContext配置文件中如下定义:

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
       <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

这样我们就可以通过Spring的依赖注入在Dao中直接使用SqlSessionTemplate来编程了,这个时候我们的Dao可能是这个样子:

package com.tiantian.mybatis.dao;  
   
import java.util.List;  
import javax.annotation.Resource;  
import org.mybatis.spring.SqlSessionTemplate;  
import org.springframework.stereotype.Repository;  
import com.tiantian.mybatis.model.Blog;  
   
@Repository  
publicclass BlogDaoImpl implements BlogDao {  
   
    private SqlSessionTemplate sqlSessionTemplate;  
   
    publicvoid deleteBlog(int id) {  
       sqlSessionTemplate.delete("com.tiantian.mybatis.mapper.BlogMapper.deleteBlog", id);  
    }  
   
    public Blog find(int id) {  
      returnsqlSessionTemplate.selectOne("com.tiantian.mybatis.mapper.BlogMapper.selectBlog", id);  
    }  
   
    public List<Blog> find() {  
       returnthis.sqlSessionTemplate.selectList("com.tiantian.mybatis.mapper.BlogMapper.selectAll");  
    }  
   
    publicvoid insertBlog(Blog blog) {  
       this.sqlSessionTemplate.insert("com.tiantian.mybatis.mapper.BlogMapper.insertBlog", blog);  
    }  
   
    publicvoid updateBlog(Blog blog) {  
       this.sqlSessionTemplate.update("com.tiantian.mybatis.mapper.BlogMapper.updateBlog", blog);  
    }  
     
    public SqlSessionTemplate getSqlSessionTemplate() {  
       returnsqlSessionTemplate;  
    }  
     
    @Resource  
    publicvoid setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {  
       this.sqlSessionTemplate = sqlSessionTemplate;  
    }  
} 

通用Mapper

这一小节主要通过结合Hap中的源码来分析通用Mapper的实现。首先可以明确需要用到MyBatis中用到的几个注解,其实在上面也已经提到过了:@DeleteProvider、@InsertProvider、@UpdateProvider、@SelectProvider。有关于这几个注解的具体作用,可以去网上查一下资料,其实在上面也已经介绍过了,Mybatis的MapperAnnotationBuilder组件会使用这几个注解构建MappedStatement对象。这里举一个小例子,看看Hap是怎么巧妙的通过这几个注解实现通用Mapper的。

/**
 * 通用Mapper接口,查询
 *
 * @param <T> 不能为空
 * @author liuzh
 */
public interface SelectCountMapper<T> {

    /**
     * 根据实体中的属性查询总数,查询条件使用等号
     *
     * @param record
     * @return
     */
    @SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
    int selectCount(T record);

}
public class BaseSelectProvider extends MapperTemplate {

    public BaseSelectProvider() {}

    public BaseSelectProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
        super(mapperClass, mapperHelper);
    }
    ......

    public String selectCount(MappedStatement ms) {
        Class<?> entityClass = getEntityClass(ms);
        StringBuilder sql = new StringBuilder();
        sql.append(SqlHelper.selectCount(entityClass));
        sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
        sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
        return sql.toString();
    }
}

首先看看在Hap中重写的MapperScannerConfigurer类,可以把这个当作是入口:

public class MapperScannerConfigurer extends org.mybatis.spring.mapper.MapperScannerConfigurer {
    private MapperHelper mapperHelper = new MapperHelper();


    private Map<String,String> propertiesMap;


    public void setMarkerInterface(Class<?> superClass) {
        super.setMarkerInterface(superClass);
        if (Marker.class.isAssignableFrom(superClass)) {
            mapperHelper.registerMapper(superClass);
        }
    }

    public MapperHelper getMapperHelper() {
        return mapperHelper;
    }

    public void setMapperHelper(MapperHelper mapperHelper) {
        this.mapperHelper = mapperHelper;
    }

    /**
     * 属性注入
     *
     * @param properties
     */
    public void setProperties(Properties properties) {
        mapperHelper.setProperties(properties);
    }

    public void setPropertiesMap(Map<String, String> propertiesMap) {
        if (propertiesMap.get("ORDER") == null) {
            if ("JDBC".equalsIgnoreCase(propertiesMap.get("IDENTITY"))) {
                propertiesMap.put("ORDER", "AFTER");
            } else {
                propertiesMap.put("ORDER", "BEFORE");
            }
        }
        this.propertiesMap = propertiesMap;
        //Properties properties = new Properties();
        //propertiesMap.forEach((k, v) -> {
        //    properties.put(k, v);
        //});
        //setProperties(properties);
    }

    /**
     * 注册完成后,对MapperFactoryBean的类进行特殊处理
     *
     * @param registry
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

        Properties config = new Properties();
        Properties p = new Properties();
        try {
            config.load(getClass().getResourceAsStream("/config.properties"));
            if (propertiesMap.get("ORDER") == null) {
                if ("JDBC".equalsIgnoreCase(propertiesMap.get("IDENTITY"))) {
                    p.put("ORDER", "AFTER");
                } else {
                    p.put("ORDER", "BEFORE");
                }
            }
            propertiesMap.forEach((k,v)->{
                if (v.startsWith("${") && v.endsWith("}")) {
                    p.put(k, config.getProperty(v.substring(2, v.length() - 1), v));
                } else {
                    p.put(k, v);
                }
            });
            setProperties(p);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        super.postProcessBeanDefinitionRegistry(registry);
        //如果没有注册过接口,就注册默认的Mapper接口
        this.mapperHelper.ifEmptyRegisterDefaultInterface();
        String[] names = registry.getBeanDefinitionNames();
        GenericBeanDefinition definition;
        for (String name : names) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(name);
            if (beanDefinition instanceof GenericBeanDefinition) {
                definition = (GenericBeanDefinition) beanDefinition;
                if (StringUtil.isNotEmpty(definition.getBeanClassName())
                        && definition.getBeanClassName().equals("org.mybatis.spring.mapper.MapperFactoryBean")) {
                    definition.setBeanClass(MapperFactoryBean.class);
                    definition.getPropertyValues().add("mapperHelper", this.mapperHelper);
                }
            }
        }
    }
}

org.mybatis.spring.mapper.MapperScannerConfigurer类实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor接口是在bean定义之后,实例化之前执行的接口,Spring 框架会根据配置,过滤出 BeanDefinitionRegistryPostProcessor 类型的 Bean 定义,并通过 Spring 框架生成其对应的 Bean 对象,Spring 容器会在实例化开发人员所定义的 Bean 前先调用该 processor 的 postProcessBeanDefinitionRegistry(...) 方法。所以在项目启动的时候,会执行Hap中重写的postProcessBeanDefinitionRegistry方法。
首先加载config.properties配置文件,用于构建一个Properties对象,然后调用setProperties方法,这个方法很关键,因为里面涉及到Mapper的加载过程。

    /**
     * 属性注入
     *
     * @param properties
     */
    public void setProperties(Properties properties) {
        mapperHelper.setProperties(properties);
    }

    /**
     * 配置属性
     *
     * @param properties
     */
    public void setProperties(Properties properties) {
        config.setProperties(properties);
        //注册通用接口
        String mapper = null;
        if (properties != null) {
            mapper = properties.getProperty("mappers");
        }
        if (StringUtil.isNotEmpty(mapper)) {
            String[] mappers = mapper.split(",");
            for (String mapperClass : mappers) {
                if (mapperClass.length() > 0) {
                    registerMapper(mapperClass);
                }
            }
        }
    }

    /**
     * 注册通用Mapper接口
     *
     * @param mapperClass
     */
    public void registerMapper(String mapperClass) {
        try {
            registerMapper(Class.forName(mapperClass));
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("注册通用Mapper[" + mapperClass + "]失败,找不到该通用Mapper!");
        }
    }

    /**
     * 注册通用Mapper接口
     *
     * spilledyear
     * 递归 注册 Mapper 的所有父类,registerClass 和 registerMapper 的作用是什么
     *
     * @param mapperClass
     */
    public void registerMapper(Class<?> mapperClass) {
        if (!registerMapper.containsKey(mapperClass)) {
            registerClass.add(mapperClass);
             // fromMapperClass 的作用是通用Mapper接口获取对应的MapperTemplate
            registerMapper.put(mapperClass, fromMapperClass(mapperClass));
        }
        //自动注册继承的接口
        Class<?>[] interfaces = mapperClass.getInterfaces();
        if (interfaces != null && interfaces.length > 0) {
            for (Class<?> anInterface : interfaces) {
                registerMapper(anInterface);
            }
        }
    }

// com.hand.hap.mybatis.common.Mapper
public interface Mapper<T> extends
        BaseMapper<T>,
        ExampleMapper<T>,
        RowBoundsMapper<T>,
        Marker {

}

MapperFactoryBean的继承关系如下:

image.png
public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public DaoSupport() {
    }

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        this.checkDaoConfig();

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }
......
}

所以,在初始化实例之后,会调用MapperFactoryBean的checkDaoConfig方法:

public class MapperFactoryBean<T> extends org.mybatis.spring.mapper.MapperFactoryBean<T> {

    private MapperHelper mapperHelper;

    public MapperFactoryBean() {
    }

    public MapperFactoryBean(Class<T> mapperInterface) {
        super(mapperInterface);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void checkDaoConfig() {
        super.checkDaoConfig();
        //通用Mapper
        if (mapperHelper.isExtendCommonMapper(getObjectType())) {
            mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
        }
    }

    public void setMapperHelper(MapperHelper mapperHelper) {
        this.mapperHelper = mapperHelper;
    }
}

如果是通用Mappper,就会调用mapperHelper的processConfiguration方法重新设置SqlSource。也就是根据各个provider动态生成SQL,然后创建一个SqlSource,然后根据Mybatis中的MappedStatement重新设置SqlSource。这样就达到了通用SQl的效果。

    /**
     * 配置指定的接口
     *
     * @param configuration
     * @param mapperInterface
     */
    public void processConfiguration(Configuration configuration, Class<?> mapperInterface) {
        String prefix;
        if (mapperInterface != null) {
            prefix = mapperInterface.getCanonicalName();
        } else {
            prefix = "";
        }
        for (Object object : new ArrayList<Object>(configuration.getMappedStatements())) {
            if (object instanceof MappedStatement) {
                MappedStatement ms = (MappedStatement) object;
                if (ms.getId().startsWith(prefix) && isMapperMethod(ms.getId())) {
                    if (ms.getSqlSource() instanceof ProviderSqlSource) {
                        setSqlSource(ms);
                    }
                }
            }
        }
    }

configuration.getMappedStatements()的作用是获取所有的MappedStatement对象,一个MappedStatement对象对应Mapper中的一个接口。上面代码的意思就是当找到复核要求的MappedStatement对象时,就重新设置SqlSource。

    /**
     * 重新设置SqlSource
     * <p/>
     * 执行该方法前必须使用isMapperMethod判断,否则msIdCache会空
     * msIdCache 里面缓存了 MappedStatement对象的 key
     *
     * @param ms
     */
    public void setSqlSource(MappedStatement ms) {
        MapperTemplate mapperTemplate = msIdCache.get(ms.getId());
        try {
            if (mapperTemplate != null) {
                mapperTemplate.setSqlSource(ms);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

而重新设置SqlSource的具体逻辑则是交给MapperTemplate的setSqlSource方法

    /**
     * 重新设置SqlSource
     *
     * @param ms
     * @throws java.lang.reflect.InvocationTargetException
     * @throws IllegalAccessException
     */
    public void setSqlSource(MappedStatement ms) throws Exception {
        if (this.mapperClass == getMapperClass(ms.getId())) {
            throw new RuntimeException("请不要配置或扫描通用Mapper接口类:" + this.mapperClass);
        }
        Method method = methodMap.get(getMethodName(ms));
        try {
            //第一种,直接操作ms,不需要返回值
            if (method.getReturnType() == Void.TYPE) {
                method.invoke(this, ms);
            }
            //第二种,返回SqlNode
            else if (SqlNode.class.isAssignableFrom(method.getReturnType())) {
                SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
                DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
                setSqlSource(ms, dynamicSqlSource);
            }
            //第三种,返回xml形式的sql字符串
            else if (String.class.equals(method.getReturnType())) {
                String xmlSql = (String) method.invoke(this, ms);
                SqlSource sqlSource = createSqlSource(ms, xmlSql);
                //替换原有的SqlSource
                setSqlSource(ms, sqlSource);
            } else {
                throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void,SqlNode,String三种!");
            }
            //cache
            checkCache(ms);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getTargetException() != null ? e.getTargetException() : e);
        }
    }

    /**
     * 重新设置SqlSource,同时判断如果是Jdbc3KeyGenerator,就设置为MultipleJdbc3KeyGenerator
     *
     * @param ms
     * @param sqlSource
     */
    protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) {
        MetaObject msObject = SystemMetaObject.forObject(ms);
        msObject.setValue("sqlSource", sqlSource);
        //如果是Jdbc3KeyGenerator,就设置为MultipleJdbc3KeyGenerator
        KeyGenerator keyGenerator = ms.getKeyGenerator();
        if (keyGenerator instanceof Jdbc3KeyGenerator) {
            msObject.setValue("keyGenerator", new MultipleJdbc3KeyGenerator());
        }
    }

这里根据各个Provider中方法的返回类型执行不同的逻辑,这里以 BaseSelectProvider 中的 selectCount 方法为例:

public class BaseSelectProvider extends MapperTemplate {
    /**
     * 查询总数
     *
     * @param ms
     * @return
     */
    public String selectCount(MappedStatement ms) {
        Class<?> entityClass = getEntityClass(ms);
        StringBuilder sql = new StringBuilder();
        sql.append(SqlHelper.selectCount(entityClass));
        sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
        sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
        return sql.toString();
    }
}

其实就是返回一个字符串SQL,至于SQL是怎么拼接出来的,这里不做过多介绍,有兴趣的可以追一下代码,并不复杂。返回字符串之后就重新创建一个SqlSource,然后将新的SqlSource设置给MappedStatement对象。

在我们的Hap应用中,我们自定义的Mapper接口一般都会继承com.hand.hap.mybatis.common.Mapper接口,所以就间接拥有了com.hand.hap.mybatis.common.Mapper接口中的所有方法。但是这些继承的方法在一开始的时候其实是没有任何意义的,只有在执行了MapperFactoryBean的checkDaoConfig方法之后,才会动态生成SQL,然后创建新的SqlSource对象,并一次覆盖MappedStatement对象中的SqlSource对象,这时候这些继承过来的方法来变得有意义。

可能这样一遍看下来,大家会觉得有点模糊。这里还有很多可以值得学些的东西,比如:EntityHelper、MapperHelper、SqlHelper、MapperTemplate 以及Hap中定义的一些注解,在动态生成SQL的时候,其实主要就是应用到了这几个对象。阅读这些代码其实很有用:可以在开发Hap应用的时候更加的熟练;在出现 问题的时候更好的排查;对于非常常用的框架,很有必要更多的了解一些其底层实现,这样在阅读其它应用源码的时候,其实很有帮助。

上一篇下一篇

猜你喜欢

热点阅读