分析到这里的时候,mybatis 初始化、接口、事务、缓存等主要功能都已经讲完了,现在就还剩下 StatementHandler 这个真正干活的家伙没有分析了;所以接下来的博客内容主要和数据库的关系比较密切,而 StatementHandler 的主要流程也基本是和 JDBC 的流程是一一对应的;

一、StatementHandler 执行流程

在 mybatis 系列文章的第一篇,我放了一张 mybatis 整体的执行流程图:

从上面的图中也能比较清楚的看到 StatementHandler 的职责:获取 Statement -> 设置参数 -> 查询数据库 -> 将查询结果映射为 JavaBean,从这里也能看到是和我们使用原生 JDBC 的流程是一样的;而整个过程 StatementHandler 又将其拆分成了部分:

  • KeyGenerator:主键设置
  • ParameterHandler:参数设置
  • ResultSetHandler:结果集设置

这里我们首先介绍 StatementHandler 的类结构:

  • RoutingStatementHandler:路由处理器,这个相当于一个静态代理,根据 MappedStatement.statementType 创建对应的对处理器;
  • SimpleStatementHandler:不需要预编译的简单处理器;
  • PreparedStatementHandler:预编译的 SQL 处理器;
  • CallableStatementHandler:主要用于存储过程的调度;

这里的 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler 同时也和 JDBC 的三个 Statement 一一对应;首先我们还是先看一下接口方法:

public interface StatementHandler {
Statement prepare(Connection connection) throws SQLException; // 获取 Statement
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;
BoundSql getBoundSql(); // 获取绑定sql
ParameterHandler getParameterHandler(); // 得到参数处理器
}

其中公共的方法都封装到了 BaseStatementHandler 中(这里使用的是模版模式);

// 获取 Statement
@Override
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection); // 实例化 Statement
setStatementTimeout(statement); // 设置超时
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;

二、StatementHandler 子类

1. RoutingStatementHandler

静态代理模式,根据 MappedStatement.statementType 创建对应的对处理,默认是 PREPARED,可以使用 XML 配置或者注解的方式指定;

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建路由选择语句处理器
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 插件拦截
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) { // 根据语句类型,委派到不同的语句处理器(STATEMENT|PREPARED|CALLABLE)
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());
}
}

2. SimpleStatementHandler

其功能和 JDBC.Statement 对应,从初始化方法也可以看到使用的是简单 Statement;

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 调用 Connection.createStatement
if (mappedStatement.getResultSetType() != null) {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
@Override
public int update(Statement statement) throws SQLException {
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 {
//如果没有keyGenerator,直接调用Statement.execute和Statement.getUpdateCount
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}

这里在更新的时候需要区分 KeyGenerator,因为使用的是简单 Statement,所以需要在查询的时候指定返回主键 statement.execute(sql, Statement.RETURN_GENERATED_KEYS);

3. PreparedStatementHandler

其功能和 PreparedStatement 对应,这里初始化的时候可以看到比 SimpleStatementHandler 要复杂一些,因为 PreparedStatement 更新返回主键有三个方法,详细分析后面会单独放一篇详细讲解;

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
//调用Connection.prepareStatement
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() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}

还有一点不同的就是 PreparedStatement 需要参数化设置,就是设置预编译 SQL 对应占位符的参数;

// DefaultParameterHandler
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 循环设参数
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 如果不是OUT,才设进去
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
// 若有额外的参数, 设为额外的参数
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
// 若参数为null,直接设null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 若参数有相应的TypeHandler,直接设object
value = parameterObject;
} else {
// 除此以外,MetaObject.getValue反射取得值设进去
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
// 不同类型的set方法不同,所以委派给子类的setParameter方法
jdbcType = configuration.getJdbcTypeForNull();
}
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}

另外还有 CallableStatementHandler ,代码也很简单,这里就不详细分析了;

mybatis 源码分析(六)StatementHandler 主体结构分析的更多相关文章

  1. Mybatis源码分析-StatementHandler

    承接前文Mybatis源码分析-BaseExecutor,本文则对通过StatementHandler接口完成数据库的CRUD操作作简单的分析 StatementHandler#接口列表 //获取St ...

  2. 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  3. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  4. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  5. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

  6. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  7. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  8. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  9. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  10. Mybatis源码分析

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

随机推荐

  1. 线程锁,threadinglocal,线程池,生产者消费者模型

    1.线程锁 1.锁Lock(只能锁一次) import threading import time v = [] lock = threading.Lock() def func(arg): lock ...

  2. File文件类

    目录 File文件类 File类的构造方法 File类的创建功能 File类的重命名 File类的删除功能 File类的判断功能 File类的获取功能 文件名称过滤器 File文件类 File:文件和 ...

  3. ironic+nova详解

    ironic+nova详解 说明: Openstack 的安装步骤省略,按照社区的文档即可搭建出一套相对稳定的使用环境.本文档基于Newton版本. 假设现在已经有一套可用的Newton环境, 以下的 ...

  4. request获取容器过程

    获取容器过程 CoyoteAdapter.postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coy ...

  5. laravel 模型查询总结

    1.Model::find($id);//查找主键为$id的数据 2.Model::find([$key1,$key2]);//使用双主键进行查找 3.Model::findOrFail($id);/ ...

  6. 【IDEA】IntelliJ IDEA Web调试控制台中文乱码问题

    RT,解决方法: Tomcat VM Options 配置参数 -Dfile.encoding=UTF-8,如图所示:

  7. ERROR 临时

    ERROR ITMS-4238: "Redundant Binary Upload. There already exists a binary upload with build vers ...

  8. python 实现爬取网站下所有URL

    python3 实现爬取网站下所有URL 获取首页元素信息: 首页的URL链接获取: 遍历第一次返回的结果: 递归循环遍历: 全部代码如下: 小结: python3.6 requests && ...

  9. 自定义仿 IPhone 开关控件

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...

  10. 客户端埋点实时OLAP指标计算方案

    背景 产品经理想要实时查询一些指标数据,在新版本的APP上线之后,我们APP的一些质量指标,比如课堂连接掉线率,课堂内崩溃率,APP崩溃率等指标,以此来看APP升级之后上课的体验是否有所提升,上课质量 ...