使用原生的 JDBC API 来执行 SQL,需要经历加、连、语、执、释
步骤。如下:
private static void query() throws Exception {
// 加载驱动(可以不显示加载,因为引入 MySQL 驱动之后会通过 SPI自动加载)
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///test", "root", "admin");
// 预编译语句
PreparedStatement statement = connection.prepareStatement("SELECT * FROM user WHERE id = ?");
// 设置参数
statement.setLong(1, 1);
// 执行 SQL
ResultSet resultSet = statement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
String columnValue = resultSet.getString(columnName);
System.out.println(columnName + " : " + columnValue);
}
}
// 关闭
statement.close();
connection.close();
}
在使用 MyBatis 框架的时候我们无感知,并没有如上一系列操作。只需要编写 Mapper.xml 中的 SQL 语句即可。在前面我们已经学习了 SQL 语句抽象 和 根据实参获取 SQL 语句 ,现在我们学习在获取 SQL 之后用 StatementHandler 来处理 。
MyBatis 框架内部使用此类来处理 Statement, 内部通过创建 SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler 对象进行正真的处理
// 通过构造 RoutingStatementHandler 时候根据 StatementType 来创建对应的 StatementHandler
// 在 Mapper.xml 中 insert/update/delete/select 标签中有 statementType 属性进行设置,默认是 PREPARED
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
StatementHandler 通用实现,如 getBoundSql、getParameterHandler 以及 prepare 方法,通过定义 instantiateStatement(Connection connection) 模板方法来获取不同类型的 Statment 实例。
public abstract class BaseStatementHandler implements StatementHandler {
// MyBatis 全局配置对象
protected final Configuration configuration;
// 对象工厂
protected final ObjectFactory objectFactory;
// 类型处理器注册表
protected final TypeHandlerRegistry typeHandlerRegistry;
// 结果集处理器
protected final ResultSetHandler resultSetHandler;
// 参数处理器
protected final ParameterHandler parameterHandler;
// 执行器
protected final Executor executor;
// XML insert/update/select/delete 标签所有元信息对象
protected final MappedStatement mappedStatement;
// 分页设置
protected final RowBounds rowBounds;
// SQL 语句和参数映射等元信息
protected BoundSql boundSql;
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) {
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
}
简单语句处理,对应 JDBC Statement(静态 SQL),所有涉及数据库操作则通过调用 Statement API
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
// 通过 BoundSql 获取 SQL 语句,然后调用 JDBC Statement API 执行操作
// 根据主键生成策略不同回填主键值
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.addBatch(sql);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 通过 BoundSql 获取 SQL 语句,然后调用 JDBC Statement API 执行操作并对结果集进行处理
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
// 通过 BoundSql 获取 SQL 语句,然后调用 JDBC Statement API 执行操作并对结果集进行处理
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleCursorResultSets(statement);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 通过原生 JDBC Connectio API 创建 Statement 对象设置相应的结果集类型
// select 标签可以设置 resultSetType 属性为 FORWARD_ONLY、SCROLL_INSENSITIVE 、SCROLL_SENSITIVE
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
// 设置 select 标签里面的 resultSetType,并把结果集的并发模型设置为只读
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) {
// N/A
// 静态 SQL 不存在设置参数空实现
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
// 调用 JDBC PreparedStatement API 执行操作
// 根据主键生成策略不同回填主键值
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 调用 JDBC PreparedStatement API 执行操作
// 使用结果处理器对结果集进行处理
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
// 调用 JDBC PreparedStatement API 执行操作
// 使用结果处理器对结果集进行处理
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleCursorResultSets(ps);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 通过 BoundSql 获取 SQL 语句,通过 MappedStatement 对象获取主键生成策略以及主键列,
// 然后调用 JDBC Connection API 获得 PreparedStatement 实例
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);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过参数处理器对预编译中的 ? 占位符进行值设置
parameterHandler.setParameters((PreparedStatement) statement);
}
}
与上面的 PreparedStatementHandler 源码差不多,只是要设置存储过程输出参数。通过 JDBC 原生 Connection API 获取 CallableStatement 实例,然后通过该实例操作数据库。