2023-06-06  阅读(6)
原文作者:惑边 原文地址:https://blog.csdn.net/my_momo_csdn

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子类如下:

202306062340391281.png

202306062340396962.png

实现类 描述
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
    }

202306062340402943.png

3.3 PreparedStatementHandler

  • StatementType默认是PREPARED,下面通过一次查询的调试步骤看看获取Statement的过程。
  • 第一步:SimpleExecutor的doQuery方法中:configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);创建StatementHandler创建的就是RoutingStatementHandler。

202306062340408404.png

  • 第二步:在SimpleExecutor#prepareStatement方法中,调用RoutingStatementHandler的prepare方法。

202306062340414805.png

  • 第三步:在RoutingStatementHandler的prepare方法中,调用的是PreparedStatementHandler的prepare方法。(默认情况下使用的就是PreparedStatementHandler,此时RoutingStatementHandler内部的代理是PreparedStatementHandler

202306062340421096.png

  • 第四步:走到抽象父类BaseStatementHandler的prepare方法,这是创建Statement的骨架方法,但是instantiateStatement实例化的过程会走到子类PreparedStatementHandler

202306062340428657.png

  • 第五步:走到PreparedStatementHandler的instantiateStatement方法实例化Statement成功。

202306062340435168.png

  • 第六步:最后一步一步返回到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小结的调试过程,下面使用流程图展示过程

202306062340443999.png

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的访问。

五、参考


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] ,回复【面试题】 即可免费领取。

阅读全文