使用原生的 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 实例,然后通过该实例操作数据库。