深入理解执行器的设计理念

243 阅读6分钟

深入理解执行器的设计理念

JDBC执行过程
  1. 获取连接:Connection

    从配置文件读取连接所需的配置参数:url,driver,username,password

  2. 预编译SQL:PrepareStatement

    设置参数

  3. 执行SQL

    获的ResultSet对象

  4. 读取结果

    结果封装为Java Bean

三种Statement(依次继承)
1.简单Statement
  • 基本功能:执行静态SQL
  • 传输相关:发送与接收
    • 批处理:addBatch(),一次性向数据库发送多条SQL或参数,批处理操作,将多个SQL合并在一起,最后调用executeBatch 一起发送至数据库执行
    • 设置从数据库每次读取的数量单位:setFetchSize() 该举措是为了防止一次性从数据库加载数据过多,导致内存溢出。
2.预处理PrepareStatement
  • 扩展了Statement,提供SQL参数预编译处理,防止SQL注入,针对不同的参数类型提供不同的setxxx()方法
  • SQL防注入:与Statement发送多条静态SQL(参数已写入SQL,不会转义)不同,PrepareStatement是向数据库发送SQL语句加参数组,参数会在数据库进行转义
3.与存储过程相关的CallableStatement
  • 设置出参,读取入参
Mybatis执行过程(SqlSession-->Executor-->StatementHandler-->DB)
1.SqlSession接口
  • 基本API:增删改查 insert()、delete()、update()、select();select()方法有多个重载,分别有不同的参数,但总共只有4种参数,返回的结果类型有Object,Map,List,Cursor游标,void

    • String statement: @param statement Unique identifier matching the statement to use. //statement唯一标识符
      
    • Object parameter //sql参数
      
    • RowBounds rowBounds: @param rowBounds RowBound instance to limit the query results.//限制返回的结果条数,用于分页
      
    • ResultHandler handler: @param handler ResultHandler that will handle each retrieved row. //结果处理器
      
    • SqlSession的select()方法运用了门面模式,为不同需求提供多种选择,而不需要关心实现细节.

  • 辅助API:提交、回滚、关闭会话连接以及清除会话缓存。(只会回滚更新、删除和新增操作)

    • void clearCache();//清除本地会话缓存
      void commit();
      void commit(boolean force);//是否强制提交
      void rollback();
      void rollback(boolean force);//是否强制回滚
      List<BatchResult> flushStatements();//刷新批处理的Statement。进行批处理时,加载了所有Statement后需要调用此方法刷新后批处理才会生效。类似于io流的flush方法。
      
2.Executor执行器根接口
  • 基本API:改、查、缓存维护

    • <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;//查询方法,其中的BoundSql对象是用于预编译的动态sql对象,动态处理参数映射关系
      
      int update(MappedStatement ms, Object parameter) throws SQLException;//修改,包括了更新,删除和新增
      
      CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);//创建缓存,CachingExecutor重写了该方法,实际为二级缓存
      
      void setExecutorWrapper(Executor executor);//CachingExecutor重写了该方法,执行完二级缓存的逻辑后委托(delegate)给其它执行器
      
  • 辅助API:提交、关闭执行器,批处理刷新

  • Executor的实现类:

    • BaseExecutor:
      • BaseExecutor是抽象类,除了继承实现Executor的基础方法外,还另外定义了一些doXXX()的模板方法供子类实现,do开头的方法一般都是真正干活的,所以可以猜测,在BatchExecutor,ReuseExecutor和SimpleExecutor中的doXX方法应该是真正类执行具体操作的。
      • 有一个localCache属性,这个属性其实就是一级缓存所在。这个属性是PerpetualCache(永久缓存)类型的,而PerpetualCache类内部维护了一个HashMap类型的属性cache,所以这就是最终二级缓存存在的形式。
      • 在构造方法中会new一个id为LocalCache的PerpetualCache对象,赋值给localCache,因此一级缓存的作用域是SqlSession,而且是默认开启的,因为每一次创建sqlSession时都会创建Executor。
      • 当执行update、commit、rollback方法时会调用clearLocalCache()方法, 清除缓存。
    • BatchExecutor
      • 批处理执行器,批量修改操作等。不一定会用到JDBC Statement的addBatch功能,只有当请求的sql完全相同时才会重用一个Statement。否则是按照请求的顺序去创建新的Statement,保证sql执行的顺序。
    • ReuseExecutor
      • 可重用执行器,在这个类中有一个属性叫statementMap,这个map用sql为key,statement为value 。这样下次执行相同的SQL时就不用再创建新的statement。
    • SimpleExecutor
      • 简单执行器,每次执行都会创建一个新的PreparedStatement。
3.StatementHandler声明处理器接口
  • 参数处理与结果处理,真正执行query和update的地方

  • 五种实现类

    • BaseStatementHandler 实现了一些公共方法,其它4种StatementHandler继承于BaseStatementHandler
    • SimpleStatementHandler 简单的StatementHandler,用于处理Statement
    • PrepareStatementHandler 预编译StatementHandler,用于处理PrepareStatement
    • CallableStatementHandler 存储过程StatementHandler,用于处理CallableStatement
    • RoutingStatementHandler 路由处理 ,根据Mapper文件或注解配置的 StatementType 路由到对应的其它3种StatementHandler,可以看成工厂模式的实现(传入statementType,返回对应的StatementHandler)
    Executor执行器体系
  • 顶层接口Executor

    • 直接实现类CachingExecutor,在这个地方处理完二级缓存的逻辑,根据缓存配置,选择使用或不使用二级缓存,该类的构造方法传入的是从SqlSession传过来的Executor,赋值给delegate(Executor),然后调用setExecutorWrapper方法设置下一个Executor,执行下一环节,引用BaseExecutor。

    • 直接实现类BaseExecutor,公共方法的实现,设置一级缓存,获取数据库连接,执行查询或更新方法。上层调用的query方法实际调用的是BaseExecutor的doQuery方法,update调用的实际是doUpdate方法。

      • 子类SimpleExecutor:简单执行器,每次执行都会调用自身私有的prepareStatement方法创建新的预处理器PrepareStatement

        • @Override
            public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
              Statement stmt = null;
              try {
                Configuration configuration = ms.getConfiguration();
                StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                stmt = prepareStatement(handler, ms.getStatementLog());
                return handler.query(stmt, resultHandler);
              } finally {
                closeStatement(stmt);
              }
            }
          
      • 子类ReuseExecutor:可重用执行器,会在一个Map中存放prepareStatement,相同的sql使用Map中的同一个prepareStatement

        • private final Map<String, Statement> statementMap = new HashMap<>();
          
          private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
              Statement stmt;
              BoundSql boundSql = handler.getBoundSql();
              String sql = boundSql.getSql();
              //相同sql则使用同一个prepareStatement
              if (hasStatementFor(sql)) {
                stmt = getStatement(sql);
                applyTransactionTimeout(stmt);
              } else {
                Connection connection = getConnection(statementLog);
                stmt = handler.prepare(connection, transaction.getTimeout());
                putStatement(sql, stmt);
              }
              handler.parameterize(stmt);
              return stmt;
            }
          
          //判断是否存在相同sql
          private boolean hasStatementFor(String sql) {
              try {
                return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
              } catch (SQLException e) {
                return false;
              }
            }
          
            //get操作
            private Statement getStatement(String s) {
              return statementMap.get(s);
            }
           
            //put操作
            private void putStatement(String sql, Statement stmt) {
              statementMap.put(sql, stmt);
            }
          
          }
          
      • BatchExecutor:批处理执行器,批量提交修改,必须先执行flushStatements()方法,刷新Statements才会生效

总结

在Mybatis整个执行过程中,最开始的SqlSession建立数据库连接,提供相关的API,但是它本身并没有实现这些功能,而是在生成session的时候根据配置创建相应的Executor,然后将请求转交给Executor执行器,执行器再调用StatementHandler去真正处理请求。

可以将SqlSession看成是星级餐厅中的服务员角色,Executor相当于大堂经理,StatementHandler相当于制作菜品的主厨,服务员给客户提供菜单,然后将菜单交给大堂经理,由大堂经理告诉主厨客户需要的菜品,最后是由主厨来制作菜品。