• 记一次关于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个能提供到一些实际帮助,需要仔细甄别。
  • 相关阅读:
    面向对象定义
    xml与面上对象初识
    模块configparser/subprocess/表格处理模块
    python模块
    python-判断语句
    python了解
    Qt Qwdget 汽车仪表知识点拆解7 图像绘制,旋转
    Qt Qwdget 汽车仪表知识点拆解6 自定义控件
    Qt Qwdget 汽车仪表知识点拆解5 标题栏图标闪烁
    Qt Qwdget 汽车仪表知识点拆解4 另类进度条实现
  • 原文地址:https://www.cnblogs.com/maxstack/p/13360852.html
Copyright © 2020-2023  润新知