内容更新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. poj3225(线段树区间更新&hash)

    题目链接: http://poj.org/problem?id=3225 题意: 初始给一个全局为 0~65536 的区间  s, 然后不断地对区间 s 进行 并, 交, 差, 相对差等运算, 输出最 ...

  2. AcEdCommandStack 输出所有命令

    ; AcEdCommandIterator* iter = nullptr; for (iter = acedRegCmds->iterator(); !iter->done(); ite ...

  3. PJzhang:安全小课堂-安全软件为什么很重要,看这里!

    猫宁!!! 参考链接: http://www.360.cn/webzhuanti/mianyigongju.html https://www.freebuf.com/fevents/204100.ht ...

  4. transition动画最简使用方式

    HTML <a href="#" title="">test</a> CSS a {display:block; width:200px ...

  5. [TCP/IP]ARP与RARP的总结

    一. 总述 简单的说,ARP协议就是将IP地址转换为MAC物理地址:而RARP,就是ARP的逆向,也就是将MAC物理地址转换为IP地址.看起来这两个协议是完全对称的,但发明这两个协议的初衷基本上没有什 ...

  6. 小技巧(updating)

    小技巧 我们要算一个点集中所有点到另一个点集中所有点的一些量的时候,可以建立一个超级源点和超级汇点,从多->多变成单->单 整体二分的时候,操作要可以撤销,才能保证复杂度,每一层到左边区间 ...

  7. POJ-3177-RedundantPaths(边联通分量,缩点)

    链接:https://vjudge.net/problem/POJ-3177#author=Dillydally 题意: 有n个牧场,Bessie 要从一个牧场到另一个牧场,要求至少要有2条独立的路可 ...

  8. HDU-2767-ProvingEquivalences

    链接:https://vjudge.net/problem/HDU-2767 题意: 给一个图,求最少需要几条边将其连成一个强连通图 思路: tarjan,缩点,考虑缩点后的图,出度为0的点和入度为0 ...

  9. codechef FIBTREE 码农题 线段树 树剖 标记永久化

    好烦啊,调了半天 线段树部分标记比较多,手抖打错了一个 剩下的都是取模的问题 我自己瞎jb推的公式里保留了abs,但是在模意义下是gg的,所以必须把正负区分开 调试的时候一定要注意构造各种形状的树,不 ...

  10. 线程池(3)Executors.newCachedThreadPool

    例子: ExecutorService es = Executors.newCachedThreadPool(); try { for (int i = 0; i < 20; i++) { Ru ...