mybatis中的 JDBC處理器 StatementHandler


1、StatementHandler 組件和其他組件之間的調用關系。

MyBatis一個基於JDBC的Dao框架,MyBatis把所有跟JDBC相關的操作全部都放到了StatementHandler中。

一個SQL請求會經過會話,然后是執行器,最由StatementHandler執行jdbc最終到達數據庫。其關系如下圖:

 

 

 這里要注意這三者之間比例是1:1:n。也就是說一個sqlsession會對應唯一的一個執行器 和N個StatementHandler。這里的N取決於通過會話調用了多少次Sql,命中緩存除外。

2、StatementHandler 的定義:

  JDBC處理器,基於JDBC構建statement,並設置參數,然后執行sql。每次調用會話當中一次sql,都會有與之相對應的且唯一的statement實例。

3、StatementHandler 的結構

 

 

 StatementHandler是接口,BaseStatementHandler是實現StatementHandler的抽象方法,其中主要放一些SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三個子類的共性操作,比如setStatementTimeout()、setFetchSize等操作。三個子類分別對應JDBC中的Statement、PreparedStatement、CallableStatement。

public interface StatementHandler {/*statement的接口*/

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;/*聲明statement*/

  void parameterize(Statement statement)
      throws SQLException;/*設置參數*/

  void batch(Statement statement)
      throws SQLException;/*添加批處理 並非執行*/

  int update(Statement statement)
      throws SQLException;/*執行更新 對用excutor執行器的update*/

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;/*執行查詢 對用excutor執行器的query*/

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;/*查詢游標*/

  BoundSql getBoundSql();/**/

  ParameterHandler getParameterHandler();/*獲取參數處理器*/

}
public abstract class BaseStatementHandler implements StatementHandler {

、、、、、、、
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);/*具體的statement創建交給具體的實現類 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler*/
      setStatementTimeout(statement, transactionTimeout);/*共性操作,在該對象實現*/
      setFetchSize(statement);/*共性操作,在該對象實現*/
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;/*具體的statement創建交給具體的實現類 */
}
public class PreparedStatementHandler extends BaseStatementHandler {

、、、、、、、
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {/*具體實現,產生一個prepareStatement*/
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

、、、、、、

}

4、statementhandler處理流程解析

 

 

 

總共執行過程分為三個階段:
   預處理:這里預處理不僅僅是通過Connection創建Statement,還包括設置參數。
   執行:包含執行SQL和處理結果映射兩部分。
   關閉:直接關閉Statement。

 

public class SimpleExecutor extends BaseExecutor {
、、、、、、

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {/*excutor此處開始進入statementhandler*/
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);/*通過大管家Configuration 獲取statementhandle*/
      stmt = prepareStatement(handler, ms.getStatementLog());/*statementhandle來創建statement,設置參數*/
      return handler.query(stmt, resultHandler);/*statementhandle來執行查詢*/
    } finally {
      closeStatement(stmt);
    }
  }
、、、、、、
}


public class Configuration {
、、、、、、
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);/*通過包裝類RoutingStatementHandler類決定創建哪一種statement處理器 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler  .該類存在的意義不大,僅僅做了一個判斷,完全可以在Configuration 當前類中完成*/
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);/*添加插件*/
    return statementHandler;
  }
、、、、、、
}


public class RoutingStatementHandler implements StatementHandler {
、、、、、
  private final StatementHandler delegate;/包裝StatementHandler /
、、、、、、
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {/*僅僅有簡單的一個功能,后期可能為了拓展。*/

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
、、、、、、、
  }

}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());/*創建statement,首先是basestatementhandler來處理一些共性參數設置,然后是對應的preparestatementhandler等其他處理器來執行具體的創建工作*/
handler.parameterize(stmt);/*設置參數*/
return stmt;
}
 

其中 statementhandle來執行查詢

public class PreparedStatementHandler extends BaseStatementHandler {

、、、、、、
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();/*執行statement*/
    return resultSetHandler.handleResultSets(ps);/最終使用結果集處理器來處理執行結果/
  }

、、、、、、

}

 

其中statementhandler的類型的設置過程。

UserMapper:

@Select({" select * from users where name='${name}'"})
@Options(statementType = StatementType.PREPARED)
List<User> selectByName(User user);

具體背后的代碼邏輯

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Options {
、、、、、、
  boolean useCache() default true;

  FlushCachePolicy flushCache() default FlushCachePolicy.DEFAULT;

  ResultSetType resultSetType() default ResultSetType.DEFAULT;

  StatementType statementType() default StatementType.PREPARED;/*參數*/

  int fetchSize() default -1;

  int timeout() default -1;

  boolean useGeneratedKeys() default false;

  String keyProperty() default "";

  String keyColumn() default "";

  String resultSets() default "";
}


public enum StatementType {
  STATEMENT, PREPARED, CALLABLE  /*三種類型*/
}

5、

參數處理
參數處理即將Java Bean轉換成數據類型。總共要經歷過三個步驟,參數轉換、參數映射、參數賦值。

 

 

 

參數轉換

即將JAVA 方法中的普通參數,封裝轉換成Map,以便map中的key和sql的參數引用相對應。

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@Param("name") String name, @Param("user") User user);
  • 單個參數的情況下且沒有設置@param注解會直接轉換,勿略SQL中的引用名稱。

  • 多個參數情況:優先采用@Param中設置的名稱,如果沒有則用參數預號代替 即"param1、parm2...."

  • 如果javac編譯時設置了 -parameters 編譯參數,也可以直接獲取源碼中的變量名稱作為key

以上所有轉換邏輯均在ParamNameResolver中實現。

 (1)單個參數:

  

測試代碼:

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@String name, @Param("user") User user);
public void test2() {
    mapper.selectByNameOrAge("肥仔", Mock.newUser());
}

 

public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";
    private final SortedMap<Integer, String> names;
  、、、、、、
  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

  、、、、、、
  public Object getNamedParams(Object[] args) {//args就是CRUD函數入參傳入的值[0,肥仔]
    final int paramCount = names.size();//names就是CRUD函數的入參的名字[1,name],如果沒有使用參數注解@param(name),就是[0,arg0;1,user]
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];//單個參數不需要和sql語句中的缺省值對應,直接返回
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);//存了兩遍
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);//
        }
        i++;
      }
      return param;//param 就是解析之后的值[arg0,肥仔;parame1,肥仔]    //最終基於該map去映射到sql的占位符參數中
    }
  }
、、、、、、
}

 

 

 

arg0基於反射來的,因為parameters 編譯參數沒有打開,所以直接映射成arg0,arg1。

user是基於注解來的@param

param1,param2是基於順序來的。

 

mybatis中獲取參數名稱的方式是反射。

獲取參數的其他方法:

如果是函數的直接通過字節碼插裝方式可以獲得。代碼編譯后,使用IDEA---->show bytecode 可以看到字節碼。如下;可以獲取參數名稱 。

public class ParamTest {
    private SqlSession sqlSession;
    private UserMapper mapper;
    public static void main(String[] args1,int args2){

    }
}

編譯后的字節碼:
 public static main([Ljava/lang/String;I)V //編譯入參的參數名稱變了,但是下邊的局部變量表中還是有的
   L0
    LINENUMBER 27 L0
    RETURN
   L1
    LOCALVARIABLE args1 [Ljava/lang/String; L0 L1 0 //參數名稱1
    LOCALVARIABLE args2 I L0 L1 1   //參數名稱2
    MAXSTACK = 0
    MAXLOCALS = 2

參數映射

映射是指Map中的key如何與SQL中綁定的參數相對應。以下這幾種情況

  • 單個原始類型:直接映射,勿略SQL中引用名稱

  • Map類型:基於Map key映射

  • Object:基於屬性名稱映射,支持嵌套對象屬性訪問

在Object類型中,支持通過“.”方式映射屬中的屬性。如:user.age

參數賦值

通過TypeHandler 為PrepareStatement設置值,通常情況下一般的數據類型MyBatis都有與之相對應的TypeHandler

測試代碼:

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@String name, @Param("user") User user);
public void test2() {
    mapper.selectByNameOrAge("肥仔", Mock.newUser());
}

 

 

public class DefaultParameterHandler implements ParameterHandler {
  private final Object parameterObject;//參數解析器,解析的map對象
、、、、、、、
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject; //入參,參數解析器,解析的map對象
    this.boundSql = boundSql;
  }

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//獲取sql中的占位符參數列表
    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();//獲取占位符的名稱 name ; user.age
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);//hasAdditionalParameter主要用於多數據庫的情況,比如mysql數據庫用一個參數,orcal用另一個參數。也就是給參數增加其他的的操作。較少使用
   } else if (parameterObject == null) { value = null; } 
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//因為只有一個參數時,才能找到對應的類型處理器,map類型沒有對應的類型處理器,所以此處能判斷是不是單個。
value
= parameterObject;//單個參數直接賦值,不管sql中參數名是神馬。 } else { MetaObject metaObject = configuration.newMetaObject(parameterObject);//封裝參數解析器解析的參數列表
value = metaObject.getValue(propertyName);//通過metaObject工具類獲取參數的值,其中對象的嵌套查詢(user.age)也是metaobject完成 //使用sql中的參數去 javabean的參數解析列表map中去獲取值。
} TypeHandler typeHandler = parameterMapping.getTypeHandler();//獲取類型處理器 。xml中沒有配置時,默認使用sql中參數類型對應的類型處理器
JdbcType jdbcType = parameterMapping.getJdbcType();//和javabean 也就是CRUD的入參所對應的數據庫中的類型
if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); }
try { typeHandler.setParameter(ps, i + 1, value, jdbcType);//類型處理器通過statement來設置參數。最終就是prestatement的設置操作 }
catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } //結束 }

public class StringTypeHandler extends BaseTypeHandler<String> {

@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);//prestatement設置參數
}
 

 

 

 

 5、結果集封裝

指讀取ResultSet數據,並將每一行轉換成相對應的對象。用戶可在轉換的過程當中可以通過ResultContext來控制是否要繼續轉換。轉換后的對象都會暫存在ResultHandler中最后統一封裝成list返回給調用方

image-20200609173114311

resultsethandler一行一行解析處理(行),解析一行將結果發送給resultcontext(一個一個的對象),context主要可以判斷是否繼續往下解析,可以作為分頁使用。最后將結果發送給resulthandle,存放到list中

結果集轉換中99%的邏輯DefaultResultSetHandler 中實現。整個流程可大致分為以下階段:

  1. 讀取結果集

  2. 遍歷結果集當中的行

  3. 創建對象

  4. 填充屬性


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM