public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
public class DefaultParameterHandler implements ParameterHandler {
//1.类型处理器注册中心
private final TypeHandlerRegistry typeHandlerRegistry;
//2.MappedStatement是保存sql语句的数据结构
private final MappedStatement mappedStatement;
//3.参数对象
private final Object parameterObject;
//4.BoundSql对象是sql语句和相关信息的封装
private BoundSql boundSql;
//5.全局配置对象
private Configuration configuration;
//构造方法
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
/**
* 将占位符替换为参数值
* */
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//1.获取sql语句的参数,ParameterMapping里面包含参数的名称类型等详细信息,还包括类型处理器
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//2.遍历依次处理
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//3.OUT类型参数不处理
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//4.获取参数名称
String propertyName = parameterMapping.getProperty();
//5.如果propertyName是动态参数,就会从动态参数中取值。(当使用的时候,MyBatis会自动生成额外的动态参数)
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//6.如果参数是null,不管属性名是什么,都会返回null。
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//7.判断类型处理器是否有参数类型,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。
value = parameterObject;
} else {
//8.这种情况下是复杂对象或者Map类型,通过反射方便的取值。通过MetaObject操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//9.获取对应的数据库类型
JdbcType jdbcType = parameterMapping.getJdbcType();
//空类型
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//10.对PreparedStatement的占位符设置值(类型处理器可以给PreparedStatement设值)
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
1.sql语句中的占位符?都对应了BoundSql#parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称和参数的相关属性。
在SimpleExecutor的doQuery方法中调用prepareStatement方法处理参数占位符,方法里面调用的就是PreparedStatementHandler的parameterize
方法,最终调用的是DefaultParameterHandler#setParameters,在PreparedStatementHandler内部持有ParameterHandler对象。
PS:简单来说就是SimpleExecutor#doQuery方法内部通过配置对象创建StatementHandler -> 调用PreparedStatementHandler#parameterize方
法(内部持有ParameterHandler) ->DefaultParameterHandler#setParameters来完成sql语句执行之前的参数替换占位符
mapper.findMemberById(1);
/**
* 查询的实现
* */
@Override
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();
//1.创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//2.用StatementHandler对象创建stmt,并使用StatementHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//3.通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
/**
* 创建Statement
* */
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//1.获取connection对象的动态代理,添加日志能力;(这里参考日志模块的代理模式)
Connection connection = getConnection(statementLog);
//2.使用StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//3.使用StatementHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
//BaseTypeHandler
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
//省略...
try {
setNonNullParameter(ps, i, parameter, jdbcType);
//省略...
}
}
//IntegerTypeHandler
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
Executor是sql语句的执行器,Executor通过配置对象创建StatementHandler,继而得到了StatementHandler,StatementHandler是整个数据库访问过程的控制关键,它的内部持有ParameterHandler,因此StatementHandler可以通过后者来处理参数。在StatementHandler处理参数的过程中会通过参数类型来找到对应的typeHandler来处理参数,整个过程中Statement对象都作为参数在传递,到了typeHandler他会调用Statement的setInt来设置值,其实整个过程中Statement对象都在传递,Mybatis通过封装,但是还是在使用JDBC的API。
关于Mybatis中数据访问的整体流程,可以阅读参考文章[3]