StatementHandler
一、StatementHandler和Statement
1.1 StatementHandler接口
- StatementHandler是Mybatis四大对象之一,它完成Mybatis最核心的工作,它封装了Statement的相关操作来完成数据库的访问,也是Executor组件的基础。包括创建Statement对象,为sql绑定参数,执行sql,结果集映射等。它是一个接口,使用不同的实现类来处理不同的情况,并且采用了模板模式,使用BaseStatementHandler来完成基本骨架,子类继承然后实现具体方法细节。
- 在了解StatementHandler之前,我们先了解一下Statement。
1.2 Statement接口
- Statement是JDK的java.sql包下的接口,JDK源码注释表示Statement是用于执行sql语句并返回结果集的对象,接口定义了很多关于数据访问和处理的方法。
//The object used for executing a static SQL statement and returning the results it produces
public interface Statement extends Wrapper, AutoCloseable {
//执行sql,返回单个结果集
ResultSet executeQuery(String sql) throws SQLException;
//执行增、删或者改的sql语句
int executeUpdate(String sql) throws SQLException;
//关闭Statement
void close() throws SQLException;
//执行sql,可返回多个结果集
boolean execute(String sql) throws SQLException;
//其他方法...省略...
}
- Statement接口的主要实现类是PreparedStatement,另外还有CallableStatemen和其他实现类。
- 我们简单了解Statement的功能即可,知道Statement在访问数据库过程中的作用,后面的StatementHandler实现的相关功能则是围绕Statement的。
二、StatementHandler
2.1 接口
- StatementHandler接口封装了关于Statement的很多操作方法,比如获取Statement的prepare方法,处理参数的parameterize方法(处理参数内部调用的还是Statement自身的方法)、查询或者更新方法query和update等。简而言之StatementHandler的存在让很多原本需要访问Statement的操作现在只需要访问StatementHandler,不过本质上还是访问Statement,只不过做了一些封装和简化。
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
2.2 实现类
- StatementHandler子类如下:
实现类 | 描述 |
---|---|
BaseStatementHandler | 不同子类的抽象父类,模板模式的骨架方法实现,同时也实现了大部分接口方法,简化子类的实现 |
RoutingStatementHandler | 静态代理,本身没有额外功能,Execuotr中实例化的就是它,它会根据具体的环境实例化不同的子类 |
SimpleStatementHandler | 使用statement访问数据库,无需参数化 |
PreparedStatementHandler | 使用预编译的PrepareStatement访问数据库 |
CallableStatementHandler | 存储过程处理 |
- StatementHandler中使用了模板模式。举一个模板模式体现的例子:在StatementHandler中定义了prepare方法获取Statement,在BaseStatementHandler中完成了prepare方法的主体流程,但是真正创建Statement实例在BaseStatementHandler没有实现,而是通过一个abstract的方法instantiateStatement交给子类去实现,因为不同的三个子类创建Statement的逻辑是不一样的,这就是模板模式的体现。另外在BaseStatementHandler中也实现了StatementHandler中的很多方法,这样三个真正的子类只需要实现部分方法即可,大部分方法都在BaseStatementHandler中实现了,通过继承获取,简化了部分代码。
三、实现类分析
3.1 BaseStatementHandler
- BaseStatementHandler是StatementHandler的实现类,也是其他几个实现类的类抽象父类,实现类主要的一些方法,其中包括获取Statement的prepare方法的主流程,但是把实例化Statement的步骤通过抽象方法instantiateStatement交给不同的子类实现。
/**
* StatementHandler创建Statement的主要逻辑都在这里,StatementHandler的其他子类并没有实现这个方法,直接从BaseStatementHandler
* 继承获得到,但是Statement的真正实例化是在抽象方法instantiateStatement中,留给不同的子类实现的
* */
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//创建Statement主流程
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);
}
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
3.2 RoutingStatementHandler
- RoutingStatementHandler没有实现额外功能,它通过静态代理模式起到了一个路由的功能,根据不同的场景选择合适的StatementHandler。它的内部持有一个StatementHandler对象delegate,在构造方法总实例化内部的delegate,然后实现了StatementHandler的全部方法,实际上都是调用delegate来执行,自己就像一个路由角色,代码如下:
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
//构造方法,在构造方法总实例化内部的delegate,然后实现StatementHandler的所有方法,实际上都是调用delegate来执行,自己就像一个路由角色
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的方法实际上都是调用delegate来执行
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
//省略其他....
}
- 在映射文件的sql语句中可以设置StatementType,在RoutingStatementHandler里面到底是要哪一个,取决于select里面配置的是哪一种。MappedStatement是sql语句映射的数据结构,在MappedStatement中保存了StatementType,RoutingStatementHandler实例化真实的子类时会根据MappedStatement.getStatementType()进行判断。
public enum StatementType {
STATEMENT, PREPARED, CALLABLE
}
3.3 PreparedStatementHandler
- StatementType默认是PREPARED,下面通过一次查询的调试步骤看看获取Statement的过程。
- 第一步:SimpleExecutor的doQuery方法中:configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);创建StatementHandler创建的就是RoutingStatementHandler。
- 第二步:在SimpleExecutor#prepareStatement方法中,调用RoutingStatementHandler的prepare方法。
- 第三步:在RoutingStatementHandler的prepare方法中,调用的是PreparedStatementHandler的prepare方法。(默认情况下使用的就是PreparedStatementHandler,此时RoutingStatementHandler内部的代理是PreparedStatementHandler
- 第四步:走到抽象父类BaseStatementHandler的prepare方法,这是创建Statement的骨架方法,但是instantiateStatement实例化的过程会走到子类PreparedStatementHandler
- 第五步:走到PreparedStatementHandler的instantiateStatement方法实例化Statement成功。
- 第六步:最后一步一步返回到SimpleExecutor类的doQuery方法进行数据库后续的操作。流程图可以参考4.1小节
3.4 SimpleStatementHandler
- SimpleStatementHandler和PreparedStatementHandler相比最大的区别是前者不会对sql语句进行预编译,因此在实例化Statement的核心方法instantiateStatement和PreparedStatementHandler有所不同
/**
*
* SimpleStatementHandler不会预编译sql语句,返回Statement
* */
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() != null) {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
3.5 CallableStatementHandler
- CallableStatementHandler主要处理存储过程,不做重点分析。
四、小结
4.1 流程图
- 对于3.3小结的调试过程,下面使用流程图展示过程
4.2 小结
- StatementHandler对Statement的相关操作进行了封装,他有有一个抽象子类BaseStatementHandler实现了大部分方法和主要方法的核心流程,将不同情况下Statement的实例化步骤通过抽象方法交给不同的子类实现
- BaseStatementHandler是三种不同子类SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler的抽象父类,PreparedStatementHandler是默认的情况,会预编译sql语句,SimpleStatementHandler则不会
- StatementType有3中切换模式,在Mybatis的映射文件的sql节点可以配置
- 不管是哪一种Statement,他都是被Executor组件使用,其实Executor实例化的是RoutingStatementHandler,RoutingStatementHandler内部实例化不同的子类对象。Executor组件是执行sql操作的组件,但是实际上Executor还是通过StatementHandler来操作Statement,自上而下来看,本质上还是JDBC的访问。
五、参考
- [1] 带注释源码
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。