mybatis關於ORM的使用以及設計(三)[參數對象轉換為SQL語言]
上節分析了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語言]