记一次关于jdbcTemplate.queryForList快速Debug及感悟

问题

开发环境报了一个错,如下:

Caused by: java.sql.SQLException: Invalid column index
	at oracle.jdbc.driver.OraclePreparedStatement.setIntInternal(OraclePreparedStatement.java:6201)
	at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:10252)
	at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:9974)
	at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:10799)
	at oracle.jdbc.driver.OraclePreparedStatement.setObject(OraclePreparedStatement.java:10776)
	at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:241)
	at org.apache.commons.dbcp2.DelegatingPreparedStatement.setObject(DelegatingPreparedStatement.java:189)
	at org.apache.commons.dbcp2.DelegatingPreparedStatement.setObject(DelegatingPreparedStatement.java:189)
	at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:402)
	at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:232)
	at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:163)
	at org.springframework.jdbc.core.ArgumentPreparedStatementSetter.doSetValue(ArgumentPreparedStatementSetter.java:69)
	at org.springframework.jdbc.core.ArgumentPreparedStatementSetter.setValues(ArgumentPreparedStatementSetter.java:50)
	at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:662)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:603)
	... 17 more

继续分析,是由于执行某个SQL时报错:

org.springframework.jdbc.InvalidResultSetAccessException: PreparedStatementCallback; invalid ResultSet access for SQL [...]; nested exception is java.sql.SQLException: Invalid column index

定位到代码中,是第3行的jdbcTemplate.queryForList产生的问题。

1	try {
2		String sql = "SELECT SOME_THING FROM SOME_TABLE WHERE CONDITION = " + CONDITION_A;
3		List<String> results = jdbcTemplate.queryForList(sql, new Object[]{CONDITION_A}, String.class);
4		String value = results.get(0);
5		doSomeThing(value);
6	} catch (Exception e) {
7		printError(e);
8	}

分析1

首先,我怀疑这个SQL本身是不是有问题,导致跑不出结果。

于是我把这个SQL手动在DB里跑了一遍,发现没有问题,会返回给我一个String ABC。

SELECT SOME_THING FROM SOME_TABLE WHERE CONDITION = CONDITION_A;
-- result: ABC

这个原因排除,继续分析。

分析2

当我看到第五行,results.get(0),心中不禁有个疑问,这里会不会抛NPE?但是这和上文的问题不想关,所以暂且把这个点记录下来,待会再做改进。

接着,我上网搜了一下关于java.sql.SQLException: Invalid column index的常见原因。

-> https://javarevisited.blogspot.com/2012/01/javasqlsqlexception-invalid-column.html

issue_1

它说,主要是在set prepared statement的时候,值没配好,例子如下:

public static void main(String args[]) throws SQLException {
        Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1526:TESTID", "root", "root");
        PreparedStatement preStatement = conn.prepareStatement("select distinct item from Order where order_id=?");    
        preStatement.setString(0, "123456"); //this will throw "java.sql.SQLException: Invalid column index" because "0" is not valid colum index
        ResultSet result = preStatement.executeQuery();      
        while(result.next()){
            System.out.println("Item: " + result.getString(2)); //this will also throw "java.sql.SQLException: Invalid column index" because resultset has only one column
        }
    }

但是这里我们是用spring jdbcTemplate做query,值是由spring框架自动填进去的,应该不会有问题呀?

分析3

然后,我怀疑这里关于jdbcTemplate.queryForList的用法是不是有问题?于是,我在项目中其它working的代码中找有没有类似的用法。

发现,其它的地方也是类似的用法。

找到问题

深呼吸,一定有什么地方漏掉了。

这句话List<String> results = jdbcTemplate.queryForList(sql, new Object[]{CONDITION_A}, String.class);有问题,
但是,List<String> results没问题,
jdbcTemplate.queryForList也没问题,
那么,唯一有问题的,只能是传入的参数,sql, new Object[]{CONDITION_A}, String.class

结合queryForList的源码如下,分析传参。

@Override
public <T> List<T> queryForList(String sql, Object[] args, Class<T> elementType) throws DataAccessException {
	return query(sql, args, getSingleColumnRowMapper(elementType));
}

方法定义:

  • sql:需要执行的sql语句
  • args:sql内置的参数
  • elementType:返回值类型

实际传入参数:

  • sql:SELECT SOME_THING FROM SOME_TABLE WHERE CONDITION = CONDITION_A
  • args:CONDITION_A
  • elementType:String.class

我发现,实际传入的sql和args有重叠的地方。既然sql已经是完整的了,已经有CONDITION_A了,为什么还需要一个CONDITION_A作为参数传入呢?

问题原来在这!

所以后来Spring在parse参数的时候报错了,因为没有找到?。(回头想一想,这个问题确实挺隐蔽的,乍一看方法签名一点问题没有)

解决

解决也很简单,有两种。

  1. 使用不包含args的queryForListAPI:
this.jdbcTemplate.queryForList(sql, String.class);
  1. 更改sql,使其留出placeholders的位置:
String sql = "SELECT SOME_THING FROM SOME_TABLE WHERE CONDITION = ?";

10分钟改完了重新测试,果然解决了问题。

总结

Debug方法千千万,有三点很重要。

  • 一是发散思维,将问题和固有经验结合,看能不能找到类似的案例。
  • 二是逻辑思维,一步一步,按图索骥,要把问题一步步narrow down到最小的粒度。
  • 三是检索能力,google出来前10个结果,可能只有1-2个能提供到一些实际帮助,需要仔细甄别。
posted @ 2020-07-22 15:16  MaxStack  阅读(768)  评论(0编辑  收藏  举报