這裏主要分析Mybatis是如何把我們的參數和SQL拼接成一起的?上一篇文章說到,insert操作最關鍵的步驟是SimpleExecutor.doUpdate()方法裏的兩行代碼,return 那裏和return上面的一行代碼。以下代碼均爲Mybatis源碼
@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);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
通過configuration實例的newStatementHandler獲得一個StatementHandler的實例,debug進去如下
進入new RoutingHandler方法如下
可以看到這裏的這裏的handler被實例成PrepareHandler對象的實例,實例化PrepareHandler對象的時候,會附加實例化BoundSql這個對象,如下圖:
可以看到這個對象又是從mapperStatement對象中拿的,在深入一點呢?
可以看到boundSql來自於sqlSource,sqlSource又來自於mapperStatement對象。
第一篇裏我們也說過這個重要的東西,這個對象什麼時候出現的呢的,以後會解釋。
那實例化prepareStatement這個對象有什麼用呢?
接下來進入prepareStatement方法裏,找到倒數第三行代碼,handler.prepare(),看方法名字,類似於對handler進行預處理,那到底預處理了什麼?
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
debug進去,後面的方法就是設置超時時間和FetchSize,重點關注instantiateStatement這個方法,instantiate--實例化的意思,那這個方法就是實例化Statement,究竟實例化幹了哪些東西呢?
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
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);
}
}
debug的時候發現,有三個類型供我們選擇,從上面知道,獲得的是PrepareHandler對象的實例,所以肯定是進入這個類的方法,細心的童鞋肯定看到了當前的SQL不是我們原始的,並且還有boundSql對象,那什麼時候Mybatis裝換的呢?後面詳細解釋。
具體代碼如下,可以看到所有的return語句都是connection.prepareStatement(sql,...);到這裏特別熟悉,這裏就是傳統JDBC的
PreparedStatement prepare = conn.prepareStatement(sql);這一句代碼,實例化爲PreparedStatement對象
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
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);
}
}
OK,至此爲止SimpleExecutor.prepareStatement()方法中第一個重要的方法執行完畢,已經實例化Statement爲PrepareStatement對象了。那接下來套用傳統JDBC的路子就是給參數賦值。
進入handler.parameterize()方法,方法名字就是確定handler的參數,可以看出這裏就是給PrepareStatement對象賦值的對象的地方了。debug進去
這裏Mybatis做的工作基本上就是爲參數賦值,在boundSql對象中拿到parametermappings。上面已經說了,boundSql和parametermappings是怎麼來的
@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 (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
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 {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
這裏就是根據每個語句中的字段找到其中對應的值,拿第一個字段id賦值過程進行舉例,
這裏可以看出獲取到id的值爲7,並且將知道了JavaType爲Integer,至於parameterMappings是什麼時候生成的,後面會詳細解釋。
並且根據當前parameterMappings獲得其具體的TypeHandler,這個TypeHandler有很多實例化的類,什麼int,long基本上和數據庫類型對應,這個東西是後面給SQL語句中賦值用的,
進入IntegerTypeHandler會看到,如果當前字段沒有設置jdbcType,就是在XML文件裏沒有這樣寫#{id,jdbcType=int}這種
則會根據當前屬性的類型設置值,這裏id屬性的類型爲Integer,所以就是setInt(i,parameter);這裏是不是想起了傳統的JDBC的部分代碼?
就這樣循環把該SQL語句中的字段和參數一一設置。
這裏Mybatis完成的工作就是傳統JDBC中,
prepare.setString(1,user.getId);
prepare.setString(2,user.getName);
prepare.setInt(3,user.getAge);
上面這三步。
到此爲止,文章開頭那裏的doUpdate()方法裏的prepareStatement就執行完了。到這裏爲止傳統的JDBC中的prepareStatement.executeUpdate()之前的步驟都已經執行完畢了,接下來肯定就是這一步了,那麼handler.update()就是這一步,在上一篇裏已經說了,ps.execute()。
至此,Mybatis如何獲取PrepareStatement對象以及獲取參數,並給參數設置正確的jdbcType,最終執行SQL。這些就一步一步的真真切切的看到了。
接下來研究Mybatis是如何從哪裏開始就拿到了我們XML文件裏的SQL語句並把它實例化成一個MapperStatement對象的呢?第三篇分析