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

时间:2022-09-03 19:32:03

一、异常出现的场景

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)通过上面的源码分析,我们可以知道,使用#{}时,使用单引号会导致#{}失效