JDBC 提供了三种 Statement 接口,分别是 Statement 、 PreparedStatement 和
CallableStatement。他们的关系如下:
image.png

  • Statement 接口提供了执行 SQL,获取执行结果等基本功能。
  • PreparedStatement 在此基础上,对IN类型的参数提供了支持。使得我们可以使用运行时参数替换 SQL 中的问号?占位符,而不用手动拼接 SQL。
  • CallableStatement 则是在PreparedStatement基础上,对OUT类型的参数提供了支持,该种类型的参数用于保存存储过程输出的结果。
1
2
3
4
5
6
7
8
9
10
11
12
// -☆- SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log
statementLog) throws SQLException {
Statement stmt;
// 获取数据库连接
Connection connection = getConnection(statementLog);
// 创建 Statement,
stmt = handler.prepare(connection, transaction.getTimeout());
// 为 Statement 设置 IN 参数
handler.parameterize(stmt);
return stmt;
}

上面代码的逻辑比较简单,总共包含三个步骤。如下:

  1. 获取数据库连接
  2. 创建 Statement
  3. 为 Statement 设置 IN 参数

MyBatis 并未没有在 getConnection 方法中直接调用 JDBC DriverManager的getConnection 方法获取获取连接,而是通过数据源获取连接。MyBatis提供了两种基于JDBC接口的数据源,
分别为 PooledDataSource和UnpooledDataSource。创建或获取数据库连接的操作最终是由这
两个数据源执行。

PreparedStatement 的创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// -☆- PreparedStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException {
Statement statement = null;
try {
// 创建 Statement
statement = instantiateStatement(connection);
// 设置超时和 FetchSize
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("……");
}
}


protected Statement instantiateStatement(Connection connection)
throws SQLException {
String sql = boundSql.getSql();
// 根据条件调用不同的 prepareStatement 方法创建 PreparedStatement
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() != null) {
return connection.prepareStatement(sql,
mappedStatement.getResultSetType().getValue(),
ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}

运行时参数被设置到 SQL 中的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// -☆- PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
// 通过参数处理器 ParameterHandler 设置运行时参数到 PreparedStatement 中
parameterHandler.setParameters((PreparedStatement) statement);
}

public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;

public void setParameters(PreparedStatement ps) {
// 从 BoundSql 中获取 ParameterMapping 列表,每个 ParameterMapping
// 与原始 SQL 中的 #{xxx} 占位符一一对应
List<ParameterMapping> parameterMappings =
boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping=parameterMappings.get(i);
// 检测参数类型,排除掉 mode 为 OUT 类型的 parameterMapping
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 获取属性名
String propertyName = parameterMapping.getProperty();
// 检测 BoundSql 的 additionalParameters 是否包含 propertyName
if (boundSql.hasAdditionalParameter(propertyName)) {
value=boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
// 检测运行时参数是否有相应的类型解析器
} else if (typeHandlerRegistry.hasTypeHandler(
parameterObject.getClass())) {
// 若运行时参数的类型有相应的类型处理器 TypeHandler,则将
// parameterObject 设为当前属性的值。
value = parameterObject;
} else {
// 为用户传入的参数 parameterObject 创建元信息对象
MetaObject metaObject =
configuration.newMetaObject(parameterObject);
// 从用户传入的参数中获取 propertyName 对应的值
value = metaObject.getValue(propertyName);
}
// ---------------------分割线---------------------
TypeHandler typeHandler =
parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
// 此处 jdbcType = JdbcType.OTHER
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 由类型处理器 typeHandler 向 ParameterHandler 设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException(...);
} catch (SQLException e) {
throw new TypeException(...);
}
}
}
}
}
}
  • 分割线以上的大段代码用于获取#{xxx}占位符属性所对应的运行时参数。
  • 分割线以下的代码则是获取#{xxx}占位符属性对应的TypeHandler,并在最后通过TypeHandler将运行时参数值设置到 PreparedStatement 中