zoukankan      html  css  js  c++  java
  • 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接口的源码也就是成了下一个目标了。

  • 相关阅读:
    windows的80端口被占用时的处理方法
    Ansible自动化运维工具安装与使用实例
    Tomcat的测试网页换成自己项目首页
    LeetCode 219. Contains Duplicate II
    LeetCode Contest 177
    LeetCode 217. Contains Duplicate
    LeetCode 216. Combination Sum III(DFS)
    LeetCode 215. Kth Largest Element in an Array(排序)
    Contest 176 LeetCode 1354. Construct Target Array With Multiple Sums(优先队列,递推)
    Contest 176
  • 原文地址:https://www.cnblogs.com/hayasi/p/6371900.html
Copyright © 2011-2022 走看看