【异常及源码分析】org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping

一、异常出现的场景

1)异常出现的SQL

@Select("SELECT\n" +
            " id,discount_type ,min_charge, ${cardFee} AS actualDiscountPrice , discount_price AS discountPrice ,status ,name \n" +
            "FROM\n" +
            "\tuser_coupon \n" +
            "WHERE\n" +
            "\tstore_id = #{storeId}\n" +
            "\tAND uid = #{uid}\n" +
            "\tAND STATUS = 0 \n" +
            "\tAND expired_date >= now( ) \n" +
            "\tAND (card_coupon_type = 1 OR ( card_coupon_type = 2 AND JSON_CONTAINS ( card_coupon -> '$[*]','#{cardId}', '$' ) )\n" +
            "\tAND (discount_type = 2  OR ( discount_type = 1 AND min_charge <= #{cardFee} ) ) )")
    List<UserCouponRuleResult> findUserCouponCard(@Param("storeId") Integer storeId, @Param("uid") Integer uid, @Param("cardFee") BigDecimal cardFee, @Param("cardId") Integer cardId);

2)异常信息

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='cardFee', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Parameter index out of range (4 > number of parameters, which is 3).

    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
    at com.sun.proxy.$Proxy185.selectList(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
    at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:139)
    at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:76)
    at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
    at com.sun.proxy.$Proxy250.findUserCouponCard(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:197)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy251.findUserCouponCard(Unknown Source)
    at com.xinchan.xcauto.merchants.saas.CommonTest.run13(CommonTest.java:79)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='cardFee', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Parameter index out of range (4 > number of parameters, which is 3).
    at org.apache.ibatis.scripting.defaults.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:89)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:93)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:64)
    at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86)
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:326)
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:143)
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
    at com.sun.proxy.$Proxy414.query(Unknown Source)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
    ... 48 more
Caused by: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Parameter index out of range (4 > number of parameters, which is 3).
    at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:55)
    at org.apache.ibatis.scripting.defaults.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:87)
    ... 65 more
Caused by: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . Try setting a different JdbcType for this parameter or a different configuration property. Cause: java.sql.SQLException: Parameter index out of range (4 > number of parameters, which is 3).
    at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:55)
    at org.apache.ibatis.type.UnknownTypeHandler.setNonNullParameter(UnknownTypeHandler.java:45)
    at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:53)
    ... 66 more
Caused by: java.sql.SQLException: Parameter index out of range (4 > number of parameters, which is 3).
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:898)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:887)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:861)
    at com.mysql.jdbc.PreparedStatement.checkBounds(PreparedStatement.java:3327)
    at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3312)
    at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3351)
    at com.mysql.jdbc.PreparedStatement.setBigDecimal(PreparedStatement.java:2790)
    at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setBigDecimal(HikariProxyPreparedStatement.java)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:67)
    at com.sun.proxy.$Proxy415.setBigDecimal(Unknown Source)
    at org.apache.ibatis.type.BigDecimalTypeHandler.setNonNullParameter(BigDecimalTypeHandler.java:32)
    at org.apache.ibatis.type.BigDecimalTypeHandler.setNonNullParameter(BigDecimalTypeHandler.java:27)
    at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:53)
    ... 68 more
View Code

二、异常信息分析

1)从第一个异常信息来看,无法设置cardFee属性,但是具体原因需要看下个异常信息

Could not set parameters for mapping: ParameterMapping{property='cardFee', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}. Cause: org.apache.ibatis.type.TypeException: Error setting non null for parameter #4 with JdbcType null . 
 

2)从第二个异常信息来看,需要设置的参数值个数超过了范围,这就奇怪了,我们的SQL中明明有4个#{},也有对应的4个参数,那为什么会超过呢?

Caused by: java.sql.SQLException: Parameter index out of range (4 > number of parameters, which is 3).

3)我们仔细看写的SQL,会发现这个:'#{}',#{}被单引号引用了,那会不会是这个原因呢?结果就是这个原因,去掉就可以了

 JSON_CONTAINS ( card_coupon -> '$[*]','#{cardId}', '$' ) )

三、解决方案及源码分析

使用#{}时,使用单引号会导致#{}失效

解决方案:

  如异常分析第三点,去掉单引号就可以了

源码分析:

1)我们直接定位到Mybatis设置参数的代码类和方法
  DefaultParameterHandler类的setParameters方法

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

2)BaseTypeHandler类的setParameter方法

@Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
                "Cause: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType); //从这里进
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
                "Try setting a different JdbcType for this parameter or a different configuration property. " +
                "Cause: " + e, e);
      }
    }
  }

3)BigDecimalTypeHandler类的setNonNullParameter方法

@Override
  public void setNonNullParameter(PreparedStatement ps, int i, BigDecimal parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setBigDecimal(i, parameter); //从从里继续
  }

4)PreparedStatement类的setBigDecimal方法

public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
        if (x == null) {
            setNull(parameterIndex, java.sql.Types.DECIMAL);
        } else {
            setInternal(parameterIndex, StringUtils.fixDecimalExponent(StringUtils.consistentToString(x))); //从这里进

            this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DECIMAL;
        }
    }
protected final void setInternal(int paramIndex, String val) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {

            byte[] parameterAsBytes = null;

            if (this.charConverter != null) {
                parameterAsBytes = this.charConverter.toBytes(val);
            } else {
                parameterAsBytes = StringUtils.getBytes(val, this.charConverter, this.charEncoding, this.connection.getServerCharset(),
                        this.connection.parserKnowsUnicode(), getExceptionInterceptor());
            }

            setInternal(paramIndex, parameterAsBytes); //从这里进
        }
    }
protected final void setInternal(int paramIndex, byte[] val) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {

            int parameterIndexOffset = getParameterIndexOffset();

            checkBounds(paramIndex, parameterIndexOffset); //看这里

            this.isStream[paramIndex - 1 + parameterIndexOffset] = false;
            this.isNull[paramIndex - 1 + parameterIndexOffset] = false;
            this.parameterStreams[paramIndex - 1 + parameterIndexOffset] = null;
            this.parameterValues[paramIndex - 1 + parameterIndexOffset] = val;
        }
    }
protected void checkBounds(int paramIndex, int parameterIndexOffset) throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            if ((paramIndex < 1)) {
                throw SQLError.createSQLException(Messages.getString("PreparedStatement.49") + paramIndex + Messages.getString("PreparedStatement.50"),
                        SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            } else if (paramIndex > this.parameterCount) {  //这个If不满足即 4 > 3,抛出异常
                throw SQLError
                        .createSQLException(
                                Messages.getString("PreparedStatement.51") + paramIndex + Messages.getString("PreparedStatement.52")
                                        + (this.parameterValues.length) + Messages.getString("PreparedStatement.53"),
                                SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
            } else if (parameterIndexOffset == -1 && paramIndex == 1) {
                throw SQLError.createSQLException("Can't set IN parameter for return value of stored function call.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
                        getExceptionInterceptor());
            }
        }
    }

5)paramIndex代表我的待赋值的参数的索引从1开始,那么上面的this.parameterCount应该为4才对为啥是3呢?那么我们就需要看看this.parameterCount值怎么来的

5.1、PreparedStatement类initializeFromParseInfo方法

private void initializeFromParseInfo() throws SQLException {
        synchronized (checkClosed().getConnectionMutex()) {
            this.staticSqlStrings = this.parseInfo.staticSql;//看这里 (2)
            this.isLoadDataQuery = this.parseInfo.foundLoadData;
            this.firstCharOfStmt = this.parseInfo.firstStmtChar;

            this.parameterCount = this.staticSqlStrings.length - 1; //看这里 (1)

            this.parameterValues = new byte[this.parameterCount][];
            this.parameterStreams = new InputStream[this.parameterCount];
            this.isStream = new boolean[this.parameterCount];
            this.streamLengths = new int[this.parameterCount];
            this.isNull = new boolean[this.parameterCount];
            this.parameterTypes = new int[this.parameterCount];

            clearParameters();

            for (int j = 0; j < this.parameterCount; j++) {
                this.isStream[j] = false;
            }
        }
    }

5.2、在看this.parseInfo值怎么来的,是从PreparedStatement的构造函数

public PreparedStatement(MySQLConnection conn, String sql, String catalog) throws SQLException {
        super(conn, catalog);

        if (sql == null) {
            throw SQLError.createSQLException(Messages.getString("PreparedStatement.0"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
        }

        detectFractionalSecondsSupport();
        this.originalSql = sql;

        this.doPingInstead = this.originalSql.startsWith(PING_MARKER);

        this.dbmd = this.connection.getMetaData();

        this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23);

        this.parseInfo = new ParseInfo(sql, this.connection, this.dbmd, this.charEncoding, this.charConverter); //看这里

        initializeFromParseInfo();

        this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts();

        if (conn.getRequiresEscapingEncoder()) {
            this.charsetEncoder = Charset.forName(conn.getEncoding()).newEncoder();
        }
    }

ParseInfo是PreparedStatement的一个内部类

ParseInfo(String sql, MySQLConnection conn, java.sql.DatabaseMetaData dbmd, String encoding, SingleByteCharsetConverter converter) throws SQLException {
            this(sql, conn, dbmd, encoding, converter, true); //进去
        }
 public ParseInfo(String sql, MySQLConnection conn, java.sql.DatabaseMetaData dbmd, String encoding, SingleByteCharsetConverter converter,
                boolean buildRewriteInfo) throws SQLException {
            try {
                if (sql == null) {
                    throw SQLError.createSQLException(Messages.getString("PreparedStatement.61"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT,
                            conn.getExceptionInterceptor());
                }

                this.charEncoding = encoding;
                this.lastUsed = System.currentTimeMillis();

                String quotedIdentifierString = dbmd.getIdentifierQuoteString();

                char quotedIdentifierChar = 0;

                if ((quotedIdentifierString != null) && !quotedIdentifierString.equals(" ") && (quotedIdentifierString.length() > 0)) {
                    quotedIdentifierChar = quotedIdentifierString.charAt(0);
                }

                this.statementLength = sql.length();

                ArrayList<int[]> endpointList = new ArrayList<int[]>();
                boolean inQuotes = false;
                char quoteChar = 0;
                boolean inQuotedId = false;
                int lastParmEnd = 0;
                int i;

                boolean noBackslashEscapes = conn.isNoBackslashEscapesSet();

                // we're not trying to be real pedantic here, but we'd like to  skip comments at the beginning of statements, as frameworks such as Hibernate
                // use them to aid in debugging

                this.statementStartPos = findStartOfStatement(sql);

                for (i = this.statementStartPos; i < this.statementLength; ++i) {
                    char c = sql.charAt(i);

                    if ((this.firstStmtChar == 0) && Character.isLetter(c)) {
                        // Determine what kind of statement we're doing (_S_elect, _I_nsert, etc.)
                        this.firstStmtChar = Character.toUpperCase(c);

                        // no need to search for "ON DUPLICATE KEY UPDATE" if not an INSERT statement
                        if (this.firstStmtChar == 'I') {
                            this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(sql, conn.getDontCheckOnDuplicateKeyUpdateInSQL(),
                                    conn.getRewriteBatchedStatements(), conn.isNoBackslashEscapesSet());
                            this.isOnDuplicateKeyUpdate = this.locationOfOnDuplicateKeyUpdate != -1;
                        }
                    }

                    if (!noBackslashEscapes && c == '\\' && i < (this.statementLength - 1)) {
                        i++;
                        continue; // next character is escaped
                    }

                    // are we in a quoted identifier? (only valid when the id is not inside a 'string')
                    if (!inQuotes && (quotedIdentifierChar != 0) && (c == quotedIdentifierChar)) {
                        inQuotedId = !inQuotedId;
                    } else if (!inQuotedId) {
                        //    only respect quotes when not in a quoted identifier

                        if (inQuotes) {
                            if (((c == '\'') || (c == '"')) && c == quoteChar) { //(5)当sql中存在单引号和双引号时,会进入改方法
                                if (i < (this.statementLength - 1) && sql.charAt(i + 1) == quoteChar) {
                                    i++;
                                    continue; // inline quote escape
                                }

                                inQuotes = !inQuotes; //(4)这里会改变inQuotes的值
                                quoteChar = 0;
                            } else if (((c == '\'') || (c == '"')) && c == quoteChar) {
                                inQuotes = !inQuotes;
                                quoteChar = 0;
                            }
                        } else {
                            if (c == '#' || (c == '-' && (i + 1) < this.statementLength && sql.charAt(i + 1) == '-')) {
                                // run out to end of statement, or newline, whichever comes first
                                int endOfStmt = this.statementLength - 1;

                                for (; i < endOfStmt; i++) {
                                    c = sql.charAt(i);

                                    if (c == '\r' || c == '\n') {
                                        break;
                                    }
                                }

                                continue;
                            } else if (c == '/' && (i + 1) < this.statementLength) {
                                // Comment?
                                char cNext = sql.charAt(i + 1);

                                if (cNext == '*') {
                                    i += 2;

                                    for (int j = i; j < this.statementLength; j++) {
                                        i++;
                                        cNext = sql.charAt(j);

                                        if (cNext == '*' && (j + 1) < this.statementLength) {
                                            if (sql.charAt(j + 1) == '/') {
                                                i++;

                                                if (i < this.statementLength) {
                                                    c = sql.charAt(i);
                                                }

                                                break; // comment done
                                            }
                                        }
                                    }
                                }
                            } else if ((c == '\'') || (c == '"')) {
                                inQuotes = true;
                                quoteChar = c;
                            }
                        }
                    }

                    if ((c == '?') && !inQuotes && !inQuotedId) {  //(3) 不仅是要?,而且还要满足 inQuotes == false
                        endpointList.add(new int[] { lastParmEnd, i }); //(2)
                        lastParmEnd = i + 1;

                        if (this.isOnDuplicateKeyUpdate && i > this.locationOfOnDuplicateKeyUpdate) {
                            this.parametersInDuplicateKeyClause = true;
                        }
                    }
                }

                if (this.firstStmtChar == 'L') {
                    if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) {
                        this.foundLoadData = true;
                    } else {
                        this.foundLoadData = false;
                    }
                } else {
                    this.foundLoadData = false;
                }

                endpointList.add(new int[] { lastParmEnd, this.statementLength });
                this.staticSql = new byte[endpointList.size()][]; //(1)

                for (i = 0; i < this.staticSql.length; i++) {
                    int[] ep = endpointList.get(i);
                    int end = ep[1];
                    int begin = ep[0];
                    int len = end - begin;

                    if (this.foundLoadData) {
                        this.staticSql[i] = StringUtils.getBytes(sql, begin, len);
                    } else if (encoding == null) {
                        byte[] buf = new byte[len];

                        for (int j = 0; j < len; j++) {
                            buf[j] = (byte) sql.charAt(begin + j);
                        }

                        this.staticSql[i] = buf;
                    } else {
                        if (converter != null) {
                            this.staticSql[i] = StringUtils.getBytes(sql, converter, encoding, conn.getServerCharset(), begin, len, conn.parserKnowsUnicode(),
                                    conn.getExceptionInterceptor());
                        } else {
                            this.staticSql[i] = StringUtils.getBytes(sql, encoding, conn.getServerCharset(), begin, len, conn.parserKnowsUnicode(), conn,
                                    conn.getExceptionInterceptor());
                        }
                    }
                }
            } catch (StringIndexOutOfBoundsException oobEx) {
                SQLException sqlEx = new SQLException("Parse error for " + sql);
                sqlEx.initCause(oobEx);

                throw sqlEx;
            }

            if (buildRewriteInfo) {
                this.canRewriteAsMultiValueInsert = PreparedStatement.canRewrite(sql, this.isOnDuplicateKeyUpdate, this.locationOfOnDuplicateKeyUpdate,
                        this.statementStartPos) && !this.parametersInDuplicateKeyClause;

                if (this.canRewriteAsMultiValueInsert && conn.getRewriteBatchedStatements()) {
                    buildRewriteBatchedParams(sql, conn, dbmd, encoding, converter);
                }
            }
        }

6)通过上面的源码分析,我们可以知道,使用#{}时,使用单引号会导致#{}失效

猜你喜欢

转载自www.cnblogs.com/756623607-zhang/p/10569048.html
今日推荐