这里主要分析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对象的呢?第三篇分析