1. 程式人生 > >mybatis關於ORM的使用以及設計(三)[參數對象轉換為SQL語言]

mybatis關於ORM的使用以及設計(三)[參數對象轉換為SQL語言]

nonnull builder base cau fig amp ould erro per

上節分析了Mapper對象的創建。

在ORM的定義中可以理解為Object->SQLMapper抽象層(這一層並不負責具體的SQL執行。這一層可以理解為SQL代理層)

本節分析以下內容:

①SqlSession在具體執行SQL時,如果通過namespace+sqlid定位到具體的MappedStatement(sql的對象化表現形式)

②參數(Object) 如何填充到具體的SQL

③SQL是如何執行的

  • 獲取StateMentMapper.前面講到初始化時,會緩存MappedStatement,MappedStatement被保存在StrictMap中.

   StrictMap是Mybatis實現HashMap子類。Key重復放入的時候會報錯。

技術分享圖片

  

  • 參數(Object) 如何填充到具體的SQL(Param-SQL的Orm轉換)

  1、通過Executor執行SQL

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

 2、Executor是如何獲取的?

  SqlSessionFactory工廠中代碼如下:

   從Configuration中獲得Excecutor,默認的執行器類型為configuration.getDefaultExecutorType()在configuration類中定義為

  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;


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

  ExecutorType.SIMPLE會創建何種執行器?

  來看Configuraiton的獲得執行器的方法

  默認的執行器為:SimpleExecutor,而cacheEnabled默認值為true.所以實際是CachingExecutor,使用了裝飾器模式。

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);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  •   Excecutor是如何將參數成功映射到具體SQL的參數?

   先看下MappedStatement中的類成員構成。sqlSource是具體獲取待執行SQL的對象。

      技術分享圖片

  •   sqlSource接口定義:
    BoundSql getBoundSql(Object parameterObject)的方法,改方調用實際的SqlSource的實現類,來獲取真正執行的SQL

    技術分享圖片

  先說說幾個處理類的區別:

  DynamicSqlSource:sql中包含<where><if><choose>等條件是,會被定義為.通過具體的Node處理對象,拼接SQL。

  看一段代碼,我們關註rootSqlNode變量,以及getBoundSql()方法的執行。

  

public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

  解析過程描述:

  ①rootSqlNode為從XML解析的具體SQL節點,每一行作為一個node。對於<if><choose>等。每一個節點是一個node

  ②<if></if>的判斷是在具體的node中執行的。SQLNode有以下幾種類型。

  技術分享圖片

  看一段IfSqlNode的代碼:apply方法中的evaluateBoolean方法,將對<if>語句進行判斷,返回結果。如果結果為真,則把條件添加到contents 

/**
 * @author Clinton Begin
 */
public class IfSqlNode implements SqlNode {
  private final ExpressionEvaluator evaluator;
  private final String test;
  private final SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

}

  mybatis中定義的SQL節點如下。

  技術分享圖片

  RawSqlSource:對於不包含<if>等條件判斷,替換#{}變為? 在創建RawSqlSource對象時執行這項操作

  DynamicSqlSource: 對sql中包含${}參數的會轉換為該對象

  •   SqlSource的轉換是在初始化加載時完成。那真正的參數是何時轉換為SQL?

  SimpleExecutor中,創建PrepareStateMent的過程。

  

  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);
   //創建、初始化PreparedStatement stmt
= handler.prepare(connection, transaction.getTimeout());
  //設置參數 handler.parameterize(stmt);
return stmt; }

  離我們想知道的真相越來越近了,來看具體的參數化過程

  StateMentHandler.parameterize

技術分享圖片

  RoutingStatementHandler用來做路由器:根據實際的StatementType做路由

  我們來看PreparedStatementHadler

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

  

                    
public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());     
  //獲得所有的參數
List
<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();

  
if (parameterMappings != null) {

    for(int i = 0; i < parameterMappings.size(); ++i) {

      ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);

      if (parameterMapping.getMode() != ParameterMode.OUT) {

        String propertyName
= parameterMapping.getProperty(); Object value; if (this.boundSql.hasAdditionalParameter(propertyName)) { value = this.boundSql.getAdditionalParameter(propertyName); } else if (this.parameterObject == null) { value = null;
            //如果有typeHandler則用TypeHandler處理參數
            //一些基礎類型是有typeHandler的
}
else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) { value = this.parameterObject; } else {
            //如果沒有typeHandler,通過反射,獲得Bean參數中的值 MetaObject metaObject
= this.configuration.newMetaObject(this.parameterObject); value = metaObject.getValue(propertyName); }             //根據參數的JDBCtype找到TypeHandler,設置到PrePareStatement中 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = this.configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException var10) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10); } catch (SQLException var11) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11); } } } } }

  TypeHandler的繼承關系如下:

  技術分享圖片

  技術分享圖片

  我們查看其中的BigDecimalTypeHandler的源碼

  

public class BigDecimalTypeHandler extends BaseTypeHandler<BigDecimal> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, BigDecimal parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setBigDecimal(i, parameter);
  }

  @Override
  public BigDecimal getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getBigDecimal(columnName);
  }

  @Override
  public BigDecimal getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getBigDecimal(columnIndex);
  }

  @Override
  public BigDecimal getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getBigDecimal(columnIndex);
  }
}

  會調用JDBCAPI中的相應方法獲得正確的值

  

mybatis關於ORM的使用以及設計(三)[參數對象轉換為SQL語言]