内容更新github地址:我飞

StatementHandler接口

StatementHandler封装了Mybatis连接数据库操作最基础的部分。因为,无论怎么封装,最终我们都是要使用JDBC和数据库打交道的。

最早我们学习java连接数据库时的代码就像下面写的那样::

import java.sql.*;

public class FirstExample {
// JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/emp"; // Database credentials
static final String USER = "root";
static final String PASS = "123456"; public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
//STEP 2: Register JDBC driver
Class.forName("com.mysql.jdbc.Driver"); //STEP 3: Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS); //STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql); //STEP 5: Extract data from result set
while(rs.next()){
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last"); //Display values
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
//STEP 6: Clean-up environment
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
System.out.println("There are so thing wrong!");
}//end main
}//end FirstExample

而对于StatementHandler来说就是将下面的代码进行了封装和抽象,将和数据库交互的能力提供给Mybatis上层应用

StatementHandler接口:

public interface StatementHandler {
// 从connection中获取statement
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 对sql进行设置参数
void parameterize(Statement statement)
throws SQLException;
// 批量执行
void batch(Statement statement)
throws SQLException;
// 执行预编译后的sql语句(update,delete,insert)
int update(Statement statement)
throws SQLException;
// 执行查询sql
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
// 使用游标执行查询sql
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
// 获取执行SQL语句的封装类BoundSql
BoundSql getBoundSql();
// 参数处理器
ParameterHandler getParameterHandler();
}

先看一下接口下面的实现类关系:

BaseStatementHandler

BaseStatementHandler作为继承StatementHandler接口的抽象类存在。

public abstract class BaseStatementHandler implements StatementHandler {

  protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler; protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds; 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) { // issue #435, get the key before calculating the statement
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);
} @Override
public BoundSql getBoundSql() {
return boundSql;
} @Override
public ParameterHandler getParameterHandler() {
return parameterHandler;
} @Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
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; protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
Integer queryTimeout = null;
if (mappedStatement.getTimeout() != null) {
queryTimeout = mappedStatement.getTimeout();
} else if (configuration.getDefaultStatementTimeout() != null) {
queryTimeout = configuration.getDefaultStatementTimeout();
}
if (queryTimeout != null) {
stmt.setQueryTimeout(queryTimeout);
}
StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
} protected void setFetchSize(Statement stmt) throws SQLException {
Integer fetchSize = mappedStatement.getFetchSize();
if (fetchSize != null) {
stmt.setFetchSize(fetchSize);
return;
}
Integer defaultFetchSize = configuration.getDefaultFetchSize();
if (defaultFetchSize != null) {
stmt.setFetchSize(defaultFetchSize);
}
} protected void closeStatement(Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
//ignore
}
} protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
} }

它内部维护了核心字段:

  • parameterHandler 作用就是将参数替换sql中的占位符的功能
  • resultSetHandler 将sql结果集映射成结果对象
  • executor 执行sql的抽象,后续详细看

只实现了三个接口方法:

  • getBoundSql
  • getParameterHandler
  • prepare 其中prepare的实现使用了模版模式,子类必须实现instantiateStatement方法来完成父类prepare方法的模版。

在BaseStatementHandler的构造函数中我们可以看到一段调用generateKeys的代码,它和生成主键key有关。

关于获得主键key和插入数据时放入主键key牵涉到以下几个配置:

  • selectKey
  • useGeneratedKeys 设置true使用自动生成的主键
  • keyProperty 指定主键是(javaBean的)哪个属性。

对于支持自动生成记录主键的数据库,如:MySQL,SQL Server,此时设置useGeneratedKeys参数值为true,在执行添加记录之后可以获取到数据库自动生成的主键ID,合keyProperty指定主键。

例子:

  <insert id="saveMsg" parameterType="cn.com.tt.e.nano.Notice"
useGeneratedKeys="true" keyProperty="msgId">
insert into notice(msg_type,title,content,rec_time,send_time,user_id,deleted,viewed)
values(#{msgType,jdbcType=INTEGER},#{title,jdbcType=VARCHAR},#{content,jdbcType=VARCHAR},
#{recTime,jdbcType=BIGINT},#{sendTime,jdbcType=BIGINT},#{userId,jdbcType=VARCHAR},
#{deleted,jdbcType=TINYINT},#{viewed,jdbcType=INTEGER})
</insert>

如果使用selectKey,可以设置order属性为AFTER。

例子:

<insert id="insertAndgetkey" parameterType="com.soft.mybatis.model.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into t_user (username,password,create_date) values(#{username},#{password},#{createDate})
</insert>

对于Oracle数据库,当要用到自增字段时,需要用到Sequence或者使用外界传入的唯一值比如uuid。则也使用selectKey,设置order为before。

例子:

<insert id="insert"  parameterType="com.lzumetal.mybatis.entity.Employee">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
SELECT SEQ_ADMIN.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO tbl_employee(id, name, age, create_time)
VALUES(#{id}, #{name}, #{age}, #{createTime})
</insert>
KeyGenerator

KeyGenerator接口的实现有:Jdbc3KeyGeneratorSelectKeyGeneratorNoKeyGenerator

useGeneratedKeys设置在settings配置文件中的相关源码在MappedStatementorg.apache.ibatis.mapping.MappedStatement.Builder#Builder中:

mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;

默认是NoKeyGenerator,如果配置了useGeneratedKeys=true并且是insert操作则使用Jdbc3KeyGenerator

useGeneratedKeys设置在mapper文件中的相关源码:

if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

如果设置了selectKey则使用SelectKeyGenerator(这部分可以更下去看到),useGeneratedKeys逻辑和前面一样。

selectKey标签中的order分别对应着KeyGenerator中的processBefore方法和processAfter方法。

而前面提到的BaseStatementHandler中generateKeys方法就是触发processBefore的地方,也是唯一一个地方。

  protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}

org.apache.ibatis.executor.statement.StatementHandler#update中的各个实现中都可以看到执行processAfter的代码,比如SimpleStatementHandler的代码:

  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 {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
ParameterHandler

再来看一下前面提到的ParameterHandler,功能就是将动态的sql中的占位符替换成实参。

它的实现是DefaultParameterHandler

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 DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
} @Override
public Object getParameterObject() {
return parameterObject;
} @Override
public void setParameters(PreparedStatement ps) {
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) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
} }

SimpleStatementHandler类

SimpleStatementHandler是BaseStatementHandler的子类,使用Statement来完成数据库的操作,所以Sql中不会有占位符,parameterize就是空实现。

query方法:

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}

最后一步将数据库数据转换成java对象,这个后续展开。

update方法:

  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 {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}

其中对主键处理的代码前面已经作了解释,从上面两个代码片段来看已经挖到最深了,最终都是调用java.sql.*api

PreparedStatementHandler

PreparedStatementHandler使用PreparedStatement实现。

  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleCursorResultSets(ps);
}
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}

RoutingStatementHandler

看名字就可以猜到了这个是路由用的,看它的构造函数:

  private final 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是由MappedStatement.getStatementType()决定的

CallableStatementHandler

依赖CallableStatement,用于调用存储过程。

StatementHandler-Mybatis源码系列的更多相关文章

  1. Mybatis源码系列 执行流程(一)

    1.Mybatis的使用 public static void main(String[] args) throws IOException { //1.获取配置文件流 InputStream is ...

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

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

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

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

  4. 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ...

  5. 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ...

  6. 深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)

    上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)>我们通过对mybatis源码的简单分析,可看出,在mybatis配置文件中,在configuration根 ...

  7. Mybatis源码分析-StatementHandler

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

  8. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  9. 深入浅出Mybatis系列五-TypeHandler简介及配置(mybatis源码篇)

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliase ...

  10. 深入浅出Mybatis系列四-配置详解之typeAliases别名(mybatis源码篇)

    注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(三)---配置详解之properties ...

随机推荐

  1. 938. Range Sum of BST

    Given the root node of a binary search tree, return the sum of values of all nodes with value betwee ...

  2. unity_pojie

    http://www.ceeger.com/forum/read.php?tid=23396&desc=1

  3. AI决策算法 之 GOAP (二)

    http://blog.csdn.net/lovethRain/article/details/67634803 GOAP 的主要逻辑: 1.Agent的状态机初始化Idle状态 2.Idel状态根据 ...

  4. SpringBoot2.0 基础案例(07):集成Druid连接池,配置监控界面

    一.Druid连接池 1.druid简介 Druid连接池是阿里巴巴开源的数据库连接池项目.Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能.功能强大,能防SQL注入,内置Login ...

  5. 源码构建Apollo以及改造

    1. 下载release版本 本次构建的是1.4.0的版本 2. 初始化数据库信息 数据库表信息 2.1 修改注册中心配置 初始化数据库表后,需要修改 ApolloConfigDB.ServerCon ...

  6. HTTP2 Sampler for JMeter

    今天开发大大说能不能帮忙压一下HTTP2的链接,便去查了一下相关的东西. HTTP 2.0 的出现,相比于 HTTP 1.x ,大幅度的提升了 web 性能.在与 HTTP/1.1 完全语义兼容的基础 ...

  7. Python面向对象之魔法方法/双下方法

    1.__new__ and __init__ 这两个方法都是在实例化的时候执行,__new__ 在 __init__ 之前执行,并且 如果实例化的时候封装属性,__new__也是必须要传的,而且__n ...

  8. Java中List,Set和Map详解及其区别和使用场景(转)

    https://www.cnblogs.com/EasonJim/p/7967138.html

  9. High waits on control file sequential read

    High waits on control file sequential read (文档 ID 2277867.1) In case we run into an issue where cont ...

  10. 079 Word Search 单词搜索

    给定一个二维面板和一个单词,找出该单词是否存在于网格中.这个词可由顺序相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格.同一个单元格内的字母不允许被重复使用.例如,给定 二 ...