天天看點

Mybatis3.x 源碼閱讀-05 insert執行流程1. 前言2. 正文3. 總結4. 參考

1. 前言

上一篇講了mapper的注冊與擷取,這一節一起看看MapperMethod類

2. 正文

2.1 案例

@Test
    public void test(){
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getSqlSessionFactory();
        SqlSession sqlSession = sqlSessionFactory.openSession();
        PersonDao personDao =  sqlSession.getMapper(PersonDao.class);
        Person p = new Person();
        p.setAddress("廣東省");
        p.setAge(12);
        p.setEmail("[email protected]");
        p.setName("chen");
        p.setPhone("15345634565");
        personDao.insert(p);
        System.out.println(p.toString());
        sqlSession.commit();
        sqlSession.close();
    }
           

2.2 MapperMethod#execute

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {@1
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);@2
        result = rowCountResult(sqlSession.insert(command.getName(), param));@3
        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());
    }
    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;
  }
           

@1 判斷你要執行的語句類型走不同是case

@2 擷取你對象中的參數,上面案例中就是person的對象值

@3 調動sqlSession的insert方法,然後通過rowCountResult封裝insert傳回的對象,最終傳回result,核心的流程就是這一行,進去看下具體的實作

2.3 常見的接口及實作類

在看具體實作之前,先理下幾個核心的接口和實作類

2.3.1 SqlSession

首先是SqlSession,裡面包含了經常操作資料庫用到的一些方法,比如insert/update/selectOne/select/selectList 等等,預設有2個實作類,預設是DefaultSqlSession

Mybatis3.x 源碼閱讀-05 insert執行流程1. 前言2. 正文3. 總結4. 參考

2.3.2 Executor 執行器

BaseExecutor是抽象類,下面有3個要重點關注下:

SimpleExecutor 簡單的執行器,就是執行完Statement會及時關掉,也是mybatis預設的執行器。

ReuseExecutor 複用的執行器,會把用過的Statement 用map存起來,key=sql,value=Statement

BatchExecutor 批量的執行器,為了提高性能,一次性操作多個Statement ,執行完,也會及時關閉。

這三個執行器,可以在mybatis 中配置,一種方式是在mybatis-config.xml 配置,另外一種是在spring xml 中配置sqlSession的時候配置。

還有一個特殊的,就是CachingExecutor ,它是把MappedStatement 緩存起來了。

Mybatis3.x 源碼閱讀-05 insert執行流程1. 前言2. 正文3. 總結4. 參考

注意:他裡面持有了一個特殊的對象,

private final Executor delegate;

還記得之前擷取session的時候,建構的執行器吧,就是下面這個代碼

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);@1
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);@2
    } else {
      executor = new SimpleExecutor(this, transaction);@3
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);@4
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
           

@1、@2、@3 對象建構好後,隻要是開啟了緩存(預設開啟),則會對executor 加一層包裝,進而變成CachingExecutor,到這裡就豁然開朗了吧,也就是上面案例中CachingExecutor 中的delegate 其實就是SimpleExecutor(預設是簡單的執行器);

2.3.2 StatementHandler

這幾個子類其實就是對應jdbc裡面的幾種Statement

SimpleStatementHandler 簡單SQL的處理;

PreparedStatementHandler 預編譯SQL處理;

CallableStatementHandler JDBC中CallableStatement,執行存儲過程相關的接口

RoutingStatementHandler 是核心,也是負責把上面三個handle串起來的關鍵類,主要負責上面三個handle的建立和調用,是以他也和CachingExecutor類似,持有一個

private final StatementHandler delegate;

Mybatis3.x 源碼閱讀-05 insert執行流程1. 前言2. 正文3. 總結4. 參考

2.3.3 KeyGenerator

KeyGenerator的功能是生成資料庫主鍵和将insert 生成的主鍵設定到pojo中

Mybatis3.x 源碼閱讀-05 insert執行流程1. 前言2. 正文3. 總結4. 參考

重點關注2個實作類:Jdbc3KeyGenerator、SelectKeyGenerator

Jdbc3KeyGenerator表示自增的,也就是資料庫自增後如果需要知道值,比如mysql 表主鍵自增。

這個是将自增結果回填到對象中是從傳回的Statement中擷取id值。配置方式:在 insert 标簽中配置了

keyProperty="id" useGeneratedKeys="true"

屬性

<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
        INSERT INTO person (name, age, phone, email, address)
        VALUES(#{name},#{age},#{phone},#{email},#{address})
    </insert>
           

SelectKeyGenerator 是通過insert後,再查詢一次,來擷取id的值,Oracle 需要結合sequence 來設定主鍵,就可以用SelectKeyGenerator方式配置。配置方式:在insert标簽内,配置selectKey标簽

<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
		<selectKey resultType="int" keyProperty="id" order="BEFORE">
     				SELECT LAST_INSERT_ID()  AS id 
  		</selectKey>
        INSERT INTO person (name, age, phone, email, address)
        VALUES(#{name},#{age},#{phone},#{email},#{address})
    </insert>
           

BEFORE 表示在插入之前,先查詢一次擷取到id

2.4 核心流程

2.4.1 繼續回到上面2.2

Object param = method.convertArgsToSqlCommandParam(args);@2
        result = rowCountResult(sqlSession.insert(command.getName(), param));@3
           

進入到insert中

@Override
public int insert(String statement, Object parameter) {
  return update(statement, parameter);
}

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);@1
    return executor.update(ms, wrapCollection(parameter));@2
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
           

@1 從config中擷取mapper的資訊

@2 這裡的executor是CachingExecutor,原因上面解釋過了。進入到CachingExecutor#update

2.4.2 CachingExecutor#update

@Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);@1
    return delegate.update(ms, parameterObject);@2
  }
private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      tcm.clear(cache);
    }
  }
           

@1 剛剛說過的, MappedStatement 是有緩存的,這裡需要進行是否需要clear,isFlushCacheRequired 參數是在建立緩存的時候,就需要設定的。

@2 調用抽象類BaseExecutor的update方法

2.4.3 BaseExecutor#update

@Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());@1
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();@2
    return doUpdate(ms, parameter);@3
  }
           

@1 記錄上下文資訊,用于抛異常的時候,擷取資訊。内部通過ThreadLocal來存儲

@2 清除本地緩存

@3 調用子類的doUpdate方法

2.4.4 SimpleExecutor#doUpdate

@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);@1
    stmt = prepareStatement(handler, ms.getStatementLog());@2
    return handler.update(stmt);@3
  } 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);@4
  return stmt;
}
           

@1 建構StatementHandler 對象,這裡會根據配置的Statement攔截器,執行所有的攔截後,傳回StatementHandler

@2 建構Statement對象,進入到prepareStatement方法,裡面有@4,這裡是設定參數處理handle

最終的執行方法如下:

DefaultParameterHandler#setParameters

@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
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          //判斷SQL中是否包含此屬性 
          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 {
            //給pre 設定值
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
           

進入PreparedStatementHandle update 方法

2.4.5 PreparedStatementHandle#update

@Override
public int update(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();@1
  int rows = ps.getUpdateCount();@2
  Object parameterObject = boundSql.getParameterObject();@3
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();@4
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);@5
  return rows;
}
           

@1 執行jdbc execute

@2 傳回受影響的記錄條數

@3 擷取插入的對象,上述案例中的person對象

@4 擷取主鍵生成器,設定person id字段的值為剛剛插入的主鍵

2.4.6 繼續回到

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

接下來是sqlSession.insert 傳回受影響的記錄條數,然後通過rowCountResult 傳回一個通用的result.

處理邏輯如下:

private Object rowCountResult(int rowCount) {
    final Object result;
    if (method.returnsVoid()) {
      result = null;
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long)rowCount;
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = rowCount > 0;
    } else {
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }
           

根據方法的要求的傳回類型,處理傳回值。

3. 總結

insert 時序圖

Mybatis3.x 源碼閱讀-05 insert執行流程1. 前言2. 正文3. 總結4. 參考
  1. 發起insert 請求,擷取mapper代理對象
  2. 調用代理對象的invoke
  3. 執行MapperMethod的execute,根據sql語句類型,執行不同case
  4. DefaultSqlSession#insert 底層調用的也是DefaultSqlSession#update(可以參考DefaultSqlSession.java 183行),調用執行器的update方法
  5. 擷取預設開啟了緩存,是以是調用CachingExecutor#update
  6. 調用抽象類的BaseExecutor#update
  7. 調用子類的具體實作SimpleExecutor#doUpdate
  8. 執行後,逐漸傳回受影響的記錄條數

4. 參考

[1] https://blog.csdn.net/Roger_CoderLife/article/details/88835765

[2] https://blog.csdn.net/yangliuhbhd/article/details/80982254

繼續閱讀