You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch删除操作报EmptyResultDataAccessException如何忽略不存在记录

问题背景

现有Spring Batch批处理作业逻辑为:通过带多表连接的复杂SELECT查询从源数据源读取数据,通过INSERT语句写入目标数据源的目标表,初始读写组件配置如下:

@Bean
public JdbcCursorItemReader<Employee> myReader2() {
    JdbcCursorItemReader<Employee> reader = new JdbcCursorItemReader<>();
    reader.setSql(COMPLICATED_QUERY_WITH_MANY_JOINS);
    reader.setDataSource(dataSourceOne);
    reader.setPreparedStatementSetter(new MyPrepStSetterOne());
    reader.setRowMapper(new EmployeeRowMapper());
    return reader;
}

@Bean
public JdbcBatchItemWriter<Employee> myWriter2(DataSource dataSource) {
    JdbcBatchItemWriter<Employee> writer = new JdbcBatchItemWriter<>();
    writer.setSql(INSERT_QUERY);
    writer.setPreparedStatementSetter(new MyPrepStSetterTwo());
    writer.setDataSource(dataSourceTwo);
    return writer;
}

上述读写器配置在同一个Step中执行。为避免当日数据和前一日作业插入的员工记录重复,需要在插入前先删除前一日作业插入的、可能和当日数据重复的员工记录(无需删除全量历史数据)。

因此在插入Step前新增了一个前置删除Step:该Step的读取器复用和插入Step完全一致的复杂关联查询逻辑,写入器配置为执行删除语句,配置代码如下:

@Bean
public JdbcCursorItemReader<Employee> myReader1() {
    JdbcCursorItemReader<Employee> reader = new JdbcCursorItemReader<>();
    reader.setSql(COMPLICATED_QUERY_WITH_MANY_JOINS);
    reader.setDataSource(dataSourceOne);
    reader.setPreparedStatementSetter(new MyPrepStSetterOne());
    reader.setRowMapper(new EmployeeRowMapper());
    return reader;
}

@Bean
public JdbcBatchItemWriter<Employee> myWriter1(DataSource dataSource) {
    JdbcBatchItemWriter<Employee> writer = new JdbcBatchItemWriter<>();
    writer.setSql(DELETE_QUERY_WHERE_EMPLOYEE_NAME_IS);
    writer.setPreparedStatementSetter(new MyPrepStSetterZero());
    writer.setDataSource(dataSourceTwo);
    return writer;
}

运行作业时抛出异常:EmptyResultDataAccessException: Item 3 of 10 did not update any rows。异常触发原因:当日待处理的员工记录并非全部在前一日完成插入,部分待删除的记录在目标表中实际不存在。

需要实现的效果:配置myWriter1,待删除记录不存在时跳过该场景,继续处理后续记录。

解决方案

JdbcBatchItemWriter默认开启了更新行数校验:每条SQL执行完成后,若返回的影响行数为0,就会抛出上述EmptyResultDataAccessException。针对删除场景不需要该校验,直接调用setAssertUpdates(false)关闭校验即可:

  • 该参数默认值为true,设置为false后,删除操作匹配不到记录、影响行数为0时不会抛出异常,会直接继续处理后续条目,是当前场景下最简单无额外开销的方案。

修改后的myWriter1配置如下:

@Bean
public JdbcBatchItemWriter<Employee> myWriter1(DataSource dataSource) {
    JdbcBatchItemWriter<Employee> writer = new JdbcBatchItemWriter<>();
    writer.setSql(DELETE_QUERY_WHERE_EMPLOYEE_NAME_IS);
    writer.setPreparedStatementSetter(new MyPrepStSetterZero());
    writer.setDataSource(dataSourceTwo);
    // 关闭更新行数校验,删除不存在记录时不抛异常
    writer.setAssertUpdates(false);
    return writer;
}

如果需要做更细粒度的控制(比如仅删除操作允许0影响行,其他更新操作仍保留校验),可以自定义ItemWriter实现,在执行逻辑中捕获EmptyResultDataAccessException后直接跳过即可,不过对于当前场景没必要做这种额外封装。

额外优化提示:当前两个Step复用了完全相同的复杂查询逻辑,相当于对源数据源执行了两次相同的重查询,数据量大时会产生不必要的性能开销,后续可以考虑在删除Step中将查询到的待处理员工主键存入作业执行上下文,插入Step直接复用该数据,避免重复查询源表。

内容的提问来源于stack exchange,提问作者Rey Rajesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 01:39:18