源碼學(xué)習(xí)之Mybatis

Mybatis源碼解讀

1 源碼下載

學(xué)習(xí)源碼之前需要先將源碼下載下來,這里需要下載mybatis源碼和mybatis-parent源碼,下載地址如下:

下載mybatis源碼時選擇對應(yīng)的Release版本:



下載完mybatis源碼之后,將其導(dǎo)入到IDEA中,注意pom.xml中的依賴版本

<parent>  
    <groupId>org.mybatis</groupId>  
    <artifactId>mybatis-parent</artifactId>  
    <version>32</version>  
    <relativePath>../parent-mybatis-parent-32/pom.xml</relativePath>
</parent>

其中關(guān)注version為32,relativePath為編譯后的mybatis-parent中pom.xml文件的相對路徑。從https://github.com/mybatis/parent下載對應(yīng)版本的依賴

下載之后,解壓先編譯mybatis-parent-32在編譯mybatis

編譯mybatis-parent-32:進(jìn)入目錄執(zhí)行

mvn clean install -Dmaven.test.skip

編譯mybatis:進(jìn)入目錄執(zhí)行

mvn clean install -Dmaven.test.skip

顯示BUILD SUCCESS表示導(dǎo)入成功,就可以閱讀源碼了。

2 源碼解讀

我們知道m(xù)ybatis是一款在持久層使用的框架,其內(nèi)部封裝了JDBC的操作,首先看看JDBC操作數(shù)據(jù)庫的過程

package cn.jdbc;

import java.sql.*;

public class jdbcTest { //定義測試類
  public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    try {
      String driver = "com.mysql.jdbc.Driver";//1.定義driverClass
      String url = "jdbc:mysql://localhost:3306/test"; //2.定義url
      String username = "root";                  //3.定義用戶名,寫你想要連接到的用戶。
      String password = "123456";                 //4.用戶密碼。
      String sql = "select * from user";     //5.你想要查找的表名。
      Class.forName(driver);//6.注冊驅(qū)動程序
      conn = DriverManager.getConnection(url, username, password);//7.獲取數(shù)據(jù)庫連接
      //Statement stmt=conn.createStatement(); //8.構(gòu)造一個statement對象來執(zhí)行sql語句
      pstmt = conn.prepareStatement(sql);
      //CallableStatement cstmt = conn.prepareCall("{CALL demoSp(? , ?)}") ;
      rs = pstmt.executeQuery();//9.執(zhí)行sql
      while (rs.next()) {  //10.遍歷結(jié)果集
        //do something...
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (rs != null) {//11.關(guān)閉記錄集
        try {
          rs.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (pstmt != null) {//12.關(guān)閉聲明的對象
        try {
          pstmt.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
      if (conn != null) {//13.關(guān)閉連接
        try {
          conn.close();
        } catch (SQLException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

以上流程大致分為四步分別是獲取連接對象、執(zhí)行sql語句、處理結(jié)果集、關(guān)閉連接

mybatis源碼也為我們封裝了這四步

2.1 獲取連接對象

首先看看mybatis官方入門文檔,需要創(chuàng)建一個xml文件,并且定義數(shù)據(jù)源DataSource以及事務(wù)管理器TransactionFactory,然后通過加載解析xml文件生成Configuration,下面給出xml文件的簡單示例:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

其中environment包含數(shù)據(jù)源和事務(wù)管理器,mapper有三個屬性分別是resource、class、url

resource表示Mapper.xml文件的資源路徑

class表示Mapper接口的路徑,這里搜索Mapper.xml文件默認(rèn)從Mapper接口下查找

url路徑對應(yīng)的是網(wǎng)絡(luò)上了某個文件,注意file:// 前綴 +路徑+文件名

當(dāng)然也可以使用package表示指定包下的所有Mapper

mybatis主要通過SqlSession操作數(shù)據(jù)的,對于SqlSession的獲取首先需要構(gòu)建一個SqlSessionFactory工廠,其代碼如下:

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

跟蹤build方法發(fā)現(xiàn)最終執(zhí)行

//最終執(zhí)行此方法 reader輸入流,environment環(huán)境,properties配置文件
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {  
  try {    
    XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);         return build(parser.parse());  
  } catch (Exception e) {    
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
  } finally {
    ErrorContext.instance().reset();
    try {
      reader.close();
    } catch (IOException e) { 
      // Intentionally ignore. Prefer previous error.
    }  
  }
}

其中XMLConfigBuilder中的parser方法跟蹤如下:

//解析Configuration文件
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      //配置文件
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      //配置別名
      typeAliasesElement(root.evalNode("typeAliases"));
      //插件
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //環(huán)境 1.DataSource  2.TransactionFactory
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

這里重點注意的是解析mapper,在Configuration中的MapperRegistry中添加mapper具體如下:

//在MapperRegistry中添加一個Mapper
  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<>(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);
        }
      }
    }
  }

其中knownMappers的定義如下:

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

是一個map,map的key是Mapper的類型,value是Mapper的代理工廠MapperProxyFactory,使用該工廠動態(tài)代理出Mapper,MapperProxyFactory定義一個map:

private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

key是Method,value是MapperMethodInvoker,它是MapperProxy類的內(nèi)部接口。

使用MapperAnnotationBuilder解析Mapper

//解析mapper
  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      //加載xml文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
          continue;
        }
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {
          //構(gòu)建resultMap
          parseResultMap(method);
        }
        try {
          //構(gòu)建sql
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

這里主要關(guān)心構(gòu)建sql過程,跟蹤parseStatement方法

void parseStatement(Method method) {
    final Class<?> parameterTypeClass = getParameterType(method);
    final LanguageDriver languageDriver = getLanguageDriver(method);
    getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
      //構(gòu)建SqlSource
      final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
      final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
      final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
      final String mappedStatementId = type.getName() + "." + method.getName();
      final 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 = getAnnotationWrapper(method, false, SelectKey.class).map(x -> (SelectKey)x.getAnnotation()).orElse(null);
        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;
      }
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = configuration.getDefaultResultSetType();
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;
      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();
        if (options.resultSetType() != ResultSetType.DEFAULT) {
          resultSetType = options.resultSetType();
        }
      }
      String resultMapId = null;
      if (isSelect) {
        ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
        if (resultMapAnnotation != null) {
          resultMapId = String.join(",", resultMapAnnotation.value());
        } else {
          resultMapId = generateResultMapName(method);
        }
      }
      //構(gòu)建MappedStatement
      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,
          statementAnnotation.getDatabaseId(),
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    });
  }

進(jìn)入buildSqlSource方法查看解析sql的過程

   //主要解析sql的方法
  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token
    int start = text.indexOf(openToken);
    if (start == -1) {
      //找不到說明沒有參數(shù)
      return text;
    }
    //有參數(shù),將字符串轉(zhuǎn)化為數(shù)組進(jìn)行操作
    char[] src = text.toCharArray();
    int offset = 0;
    //保留字符串構(gòu)建器
    final StringBuilder builder = new StringBuilder();
    //表達(dá)式這里存儲#{}或者${}括號中間的
    StringBuilder expression = null;
    do {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        //   \\${}或者\\#{}這時表示字符串"\${}"或者"\#{}"
        //  select * from user where id=\\#{id} => select * from user where id=#{id}
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        //將openToken之間的拷貝到builder中
        builder.append(src, offset, start - offset);
        //偏移openToken.length個長度
        offset = start + openToken.length();
        //從偏移開始找closeToken位置
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            // 若是\\}情況則跳過,繼續(xù)找下一個}
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            //expression存儲openToken和closeToken之間的字符
            //找到了closeToken直接跳出循環(huán)
            expression.append(src, offset, end - offset);
            break;
          }
        }
        //走到這里 1.找不到closeToken 這里end=-1 后面的全部填上
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          //有openToken和closeToken 將其替換成? 并且將參數(shù)設(shè)置到Handler中
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    } while (start > -1);
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }

執(zhí)行完畢后Configuration對象構(gòu)造成功,返回DefaultSqlSessionFactory,通過openSession方法獲得SqlSession對象。

2.2 執(zhí)行sql語句

生成Mapper代理對象過程如下

DefaultSqlSession--->Configuration--->MapperRegistry--->MapperProxyFactory

獲取代理對象之后執(zhí)行方法時會自動調(diào)用MapperProxy中的invoke方法,跟蹤

//執(zhí)行主入口,根據(jù)sql類型執(zhí)行相應(yīng)的分支
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      //新增邏輯
      case INSERT: {
        //將args對象數(shù)組,轉(zhuǎn)換成sql參數(shù)
        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()) {
          //返回值是list
          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());
    }
    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;
 }


其中Insert、Update、Delete的返回結(jié)果都是int或者long 其表示操作影響行數(shù),而Select的返回結(jié)果有

Void、many、one、map、cursor,下面分析Select過程

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()) {
      //queryStack緩存棧指針為0  且可以清空緩存 則清空緩存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //從數(shù)據(jù)庫查數(shù)據(jù)
        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;
  }

其中有緩存直接從緩存中拿,沒有緩存則從數(shù)據(jù)庫查

執(zhí)行器的繼承結(jié)構(gòu)如下:

最終在相應(yīng)的Executor執(zhí)行相應(yīng)的邏輯,以query為例

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

下面是StatementHandler的繼承結(jié)構(gòu)

其中PreparedStatementHandler、SimpleStatementHandler、CallableStatementhandler分別對應(yīng)Statement接口中的PreparedStatement、Statement、CallableStatement

2.3 處理結(jié)果集

進(jìn)入query方法處理結(jié)果集

//處理結(jié)果集
  @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;
    //獲取結(jié)果集,并將其包裝成ResultSetWrapper對象
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //獲取定義的resultMap
    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);
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容