Spring Batch ItemReader执行JOIN查询报错及关联字段映射方案
Spring Batch JdbcCursorItemReader 多表JOIN查询列找不到问题解决
问题描述
开发从数据库读取数据写入CSV文件的Spring Batch程序时,调用jdbcCursorItemReader.setSql()传入多表JOIN查询语句后抛出异常:Column 'id' not found。该SQL语句在数据库控制台可正常执行,需要实现RowMapper对JOIN关联的其他表字段做正确映射。
涉及的JOIN查询语句
SELECT iad.order_id, tb.mch_order_id, iad.transaction_time, iad.pay_amount, iad.fee_rate, iad.net_amount FROM ibg_account_detail iad INNER JOIN ibg_account ia ON iad.ibg_account_id = ia.id INNER JOIN ibg_settlement is2 ON ia.ibg_settlement_id = is2.id INNER JOIN tb_bill tb ON iad.order_id = tb.order_id WHERE is2.created_date >= '2022-05-20 00:00:00' AND is2.created_date <= '2022-05-20 23:59:59' AND ia.account = '15088888888';
JdbcCursorItemReader 实现代码
public JdbcCursorItemReader<IbgAccountDetailEntity> jdbcCursorItemReader(){ JdbcCursorItemReader<IbgAccountDetailEntity> jdbcCursorItemReader = new JdbcCursorItemReader<>(); jdbcCursorItemReader.setDataSource(dataSource); jdbcCursorItemReader.setSql(GetEntityFromQuery.SELECT_FILTER_QUERY); jdbcCursorItemReader.setRowMapper(mapper); return jdbcCursorItemReader; }
当前RowMapper实现代码
public IbgAccountDetailEntity mapRow(ResultSet resultSet, int rowNum) throws SQLException { IbgAccountDetailEntity ibgAccountDetail = new IbgAccountDetailEntity(); ibgAccountDetail.setId(resultSet.getLong("id")); ibgAccountDetail.setOrderId(resultSet.getString("order_id")); ibgAccountDetail.setTransactionTime(resultSet.getTimestamp("transaction_time")); ibgAccountDetail.setPayAmount(resultSet.getBigDecimal("pay_amount")); ibgAccountDetail.setFeeRate(resultSet.getBigDecimal("fee_rate")); ibgAccountDetail.setNetAmount(resultSet.getBigDecimal("net_amount")); return ibgAccountDetail; }
问题原因
抛出Column 'id' not found错误的直接原因是:上述SELECT语句没有查询id字段,但RowMapper中却调用resultSet.getLong("id")读取该列,无法匹配到对应列。
多表JOIN场景下如果不做特殊处理,多张表的同名字段(比如各表普遍存在的id、create_time字段)会出现歧义,直接读取会导致映射结果错乱。
解决步骤
- 调整SQL查询语句,显式声明所有需要映射的字段
必须把RowMapper中要读取的所有字段都写到SELECT子句中,包括之前缺失的主表id字段;如果不同表存在同名字段,必须给字段加唯一别名避免冲突。修正后的SQL示例如下:SELECT iad.id, -- 补充之前缺失的主表id字段,直接解决当前报错 iad.order_id, tb.mch_order_id, iad.transaction_time, iad.pay_amount, iad.fee_rate, iad.net_amount -- 如需映射其他关联表字段,在此处继续追加,同名字段加别名,例如:ia.id as account_id, is2.id as settlement_id FROM ibg_account_detail iad INNER JOIN ibg_account ia ON iad.ibg_account_id = ia.id INNER JOIN ibg_settlement is2 ON ia.ibg_settlement_id = is2.id INNER JOIN tb_bill tb ON iad.order_id = tb.order_id WHERE is2.created_date >= '2022-05-20 00:00:00' AND is2.created_date <= '2022-05-20 23:59:59' AND ia.account = '15088888888'; - 调整映射逻辑,和SQL返回字段一一对应
如果需要映射多张关联表的字段,不要直接使用单表生成的IbgAccountDetailEntity接收结果,建议新建专门的DTO类承载所有关联字段;RowMapper中读取字段时,名称要和SQL中写的字段名/别名完全一致。修正后的RowMapper示例如下:public AccountDetailBillDTO mapRow(ResultSet resultSet, int rowNum) throws SQLException { AccountDetailBillDTO detail = new AccountDetailBillDTO(); // 对应SQL中查询的iad.id字段 detail.setId(resultSet.getLong("id")); detail.setOrderId(resultSet.getString("order_id")); // 映射关联tb_bill表的mch_order_id字段 detail.setMchOrderId(resultSet.getString("mch_order_id")); detail.setTransactionTime(resultSet.getTimestamp("transaction_time")); detail.setPayAmount(resultSet.getBigDecimal("pay_amount")); detail.setFeeRate(resultSet.getBigDecimal("fee_rate")); detail.setNetAmount(resultSet.getBigDecimal("net_amount")); // 读取别名字段时直接传入别名即可,例如读取account_id:detail.setAccountId(resultSet.getLong("account_id")); return detail; } - 避坑提示:绝大多数JDBC驱动不会返回带表别名前缀的列名,不要尝试用
resultSet.getLong("iad.id")这种格式读取字段,所有需要的字段必须显式写在SELECT子句中。
内容的提问来源于stack exchange,提问作者Fardad Yadegare
相关产品推荐
相关产品推荐

