一、异常出现的场景
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
二、异常信息分析
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)通过上面的源码分析,我们可以知道,使用#{}时,使用单引号会导致#{}失效