• MyBatis 源码分析——生成Statement接口实例


    JDBC的知识对于JAVA开发人员来讲在简单不过的知识了。PreparedStatement的作用更是胸有成竹。我们最常见用到有俩个方法:executeQuery方法和executeUpdate方法。这俩个方法之外还有一个execute方法。只是这个方法我们很少用。但是mybatis框架就是却用这个方法来实现的。不管mybatis用是哪一个方法来实现。有一点可以肯定——那就是必须得到Statement接口实例。你可以这样子理解mybatis把如何获得Statement接口实例做了一个完美的封装。而这一个封装就是上一章出现的StatementHandler接口。

    mybatis里面实现StatementHandler接口有四个类。

    RoutingStatementHandler类:笔者把它理解为下面三个类的代理类。

    CallableStatementHandler类:对应处理JDBC里面的CallableStatement类。

    PreparedStatementHandler类:对应处理JDBC里面的PreparedStatement类。

    SimpleStatementHandler类:对应处理JDBC里面的一般Statement接口实例(笔者也不知道JDBC是需叫他什么)。

    正如上面所讲的笔者把RoutingStatementHandler类理解为三个类的代理类。mybatis并没有直接去引用后面三个类。而是通过RoutingStatementHandler类来判断当前到底要调用哪个类。再去执行相关的Statement接口实例。

     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;
      }

    这一段源码就是前一章尾部源码的后继执行。源码的意图就是新建一个RoutingStatementHandler类实例。关键的点是在RoutingStatementHandle类的构造函数里面。

     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());
        }
    
      }

    从这里就可以看出笔者为什么说RoutingStatementHandler类可以理解为三个类的代理类。事实上所有的工作都是内部成员delegate来做的。而delegate又是在构造函数里面进行判断生成的。看样子在这里JDBC的三种操作方式完美的体现出来。通过MappedStatement的getStatementType方法得到相应返回值,判断当前SQL语句是要用哪一种操作方式来进行。默认情况下是用Prepared方式。当前笔者不是瞎说的。在MappedStatement的Builder方法里就已经设置了。请读者们自行查看。

     1  public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
     2       mappedStatement.configuration = configuration;
     3       mappedStatement.id = id;
     4       mappedStatement.sqlSource = sqlSource;
     5       mappedStatement.statementType = StatementType.PREPARED;
     6       mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
     7       mappedStatement.resultMaps = new ArrayList<ResultMap>();
     8       mappedStatement.sqlCommandType = sqlCommandType;
     9       mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    10       String logId = id;
    11       if (configuration.getLogPrefix() != null) {
    12         logId = configuration.getLogPrefix() + id;
    13       }
    14       mappedStatement.statementLog = LogFactory.getLog(logId);
    15       mappedStatement.lang = configuration.getDefaultScriptingLanuageInstance();
    16     }

    如果实在不想用默认的方式进行处理的话,可以在相关每一个XML节点的statementType属性进行设置。如下

    <select id="SelectProducts" resultMap="result" statementType="STATEMENT" >
            select * from Products where #{0} > ProductID and ProductName like #{1}
        </select>

    生成Statement接口实例要用到StatementHandler接口的俩个方法:prepare方法和parameterize方法。prepare方法用于完成构建Statement接口实例。parameterize方法用于处理Statement接口实例对应的参数。理解这一过程需要调头查看SimpleExecutor类的doQuery方法。

     1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     2     Statement stmt = null;
     3     try {
     4       Configuration configuration = ms.getConfiguration();
     5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     6       stmt = prepareStatement(handler, ms.getStatementLog());
     7       return handler.<E>query(stmt, resultHandler) ; 
     8     } finally {
     9       closeStatement(stmt);
    10     }
    11   }

    源码的prepareStatement方法里面可以体现prepare方法和parameterize方法的作用。通过prepareStatement方法就可以得到一个完整Statement接口实例。最后在通过StatementHandler接口实例的query方法来获得对应的结果。笔者暂且跳过这一个过程(query方法处理结果)。让我们来看看关于prepare方法和parameterize方法。

     private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
      }

    上面说到prepare方法就是用于构建Statement接口实例。默认情况是PreparedStatementHandler类。那么笔者就拿PreparedStatementHandler类来切入吧。当笔者点开PreparedStatementHandler类的源码,试着去查看一下prepare方法。发现找不到。原来他在PreparedStatementHandler类的父类(BaseStatementHandler类)里面。

     1  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
     2     ErrorContext.instance().sql(boundSql.getSql());
     3     Statement statement = null;
     4     try {
     5       statement = instantiateStatement(connection);
     6       setStatementTimeout(statement, transactionTimeout);
     7       setFetchSize(statement);
     8       return statement;
     9     } catch (SQLException e) {
    10       closeStatement(statement);
    11       throw e;
    12     } catch (Exception e) {
    13       closeStatement(statement);
    14       throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    15     }
    16   }

    每一个框架都有一个共同的特点——方法调来调去的。prepare方法里面通过instantiateStatement方法来返回相关的Statement实例。而这个方法却是一个抽象方法。

     protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

    他的实例就是在各自的子类里面。完美的利用了继承的好处。

     1  protected Statement instantiateStatement(Connection connection) throws SQLException {
     2     String sql = boundSql.getSql();
     3     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
     4       String[] keyColumnNames = mappedStatement.getKeyColumns();
     5       if (keyColumnNames == null) {
     6         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
     7       } else {
     8         return connection.prepareStatement(sql, keyColumnNames);
     9       }
    10     } else if (mappedStatement.getResultSetType() != null) {
    11       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    12     } else {
    13       return connection.prepareStatement(sql);
    14     }
    15   }

    上面的源码是PreparedStatementHandler类的。所以不用笔者多讲——就是生成PreparedStatement实例。

    有了PreparedStatement实例,当然就要对他进行设置相应的参数。这也是parameterize方法的作用。但是如何是简单的设置那显然没有什么可说的。主要还是因为mybatis对于设置参数方面做精心的设计。好话不多说。还是看一下源码最实在。

    public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
      }

    ParameterHandler接口的作用显然不用笔者多讲。DefaultParameterHandler类便是他的实例类。DefaultParameterHandler类的代码不多,可是他包含的内容却很多。进去看一下就知道了。

     1  public void setParameters(PreparedStatement ps) {
     2     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
     3     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
     4     if (parameterMappings != null) {
     5       for (int i = 0; i < parameterMappings.size(); i++) {
     6         ParameterMapping parameterMapping = parameterMappings.get(i);
     7         if (parameterMapping.getMode() != ParameterMode.OUT) {
     8           Object value;
     9           String propertyName = parameterMapping.getProperty();
    10           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
    11             value = boundSql.getAdditionalParameter(propertyName);
    12           } else if (parameterObject == null) {
    13             value = null;
    14           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    15             value = parameterObject;
    16           } else {
    17             MetaObject metaObject = configuration.newMetaObject(parameterObject);
    18             value = metaObject.getValue(propertyName);
    19           }
    20           TypeHandler typeHandler = parameterMapping.getTypeHandler();
    21           JdbcType jdbcType = parameterMapping.getJdbcType();
    22           if (value == null && jdbcType == null) {
    23             jdbcType = configuration.getJdbcTypeForNull();
    24           }
    25           try {
    26             typeHandler.setParameter(ps, i + 1, value, jdbcType);
    27           } catch (TypeException e) {
    28             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    29           } catch (SQLException e) {
    30             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    31           }
    32         }
    33       }
    34     }

    BoundSql类又一次出现在我们的面前,前面笔者也没有提过关于BoundSql类的作用。因为如果没有一个上下文的作用是很难推断出BoundSql类。笔者也只是从源码来看的,也不一定是对的。前面部分的源码里面有出现过使用MappedStatement类。他可以说是一个包含节点(select节点,update节点等)信息的类。但是对的具体的SQL语句用到的信息却很少。那么BoundSql类就是存放于的组装SQL句语信息。从源码里面我们可以看到BoundSql类处理返回结果的信息却没有。有的只是SQL语句的参数之类的信息。如下他的内部成员。

     private String sql;
      private List<ParameterMapping> parameterMappings;
      private Object parameterObject;
      private Map<String, Object> additionalParameters;
      private MetaObject metaParameters;

    有了对BoundSql类的概念认识,我们接着谈谈上面源码(setParameters方法部分)里面发生的事情吧。如果想要一下就明白他是做什么的怎么样子做。那笔者只能说自己功力不行。笔者只能大概的看出他在做什么。通过BoundSql类获得相应的ParameterMapping类。找到对应的属性名(如:#{id})。接着通过传入的参数信息获得对应的MetaObject类。在通过MetaObject类和属性名获得相应属性名的值。最后一步就是通过TypeHandler接口实例设置值了。

    到了这里面StatementHandler接口的工作算是结束了。对于MetaObject类是如何获得的,他又是什么。笔者这里就不多加言论。笔者留意的点还是TypeHandler接口。这部分的知识点官网也讲到过——typeHanlders。了解TypeHandler接口的源码也就是成了下一个目标了。

  • 相关阅读:
    tomcat常见错误
    tomcat——大致简介和执行过程
    JSON的基本操作
    VS Code 终端显示问题
    VS Code在本地进行调试和打开本地服务器
    重做了一下我的音乐播放器
    Treimu更新记录1.2.9.0
    Android开发中Eclipse里的智能提示设置
    一次面试经历
    正确处理WPF中Slider值改变事件的方式
  • 原文地址:https://www.cnblogs.com/hayasi/p/6371900.html
Copyright © 2020-2023  润新知