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返回給調用方
resultsethandler一行一行解析處理(行),解析一行將結果發送給resultcontext(一個一個的對象),context主要可以判斷是否繼續往下解析,可以作為分頁使用。最后將結果發送給resulthandle,存放到list中
結果集轉換中99%的邏輯DefaultResultSetHandler 中實現。整個流程可大致分為以下階段:
-
讀取結果集
-
遍歷結果集當中的行
-
創建對象
-
填充屬性