SimpleJpaRepository saveAll遇重复数据抛异常,求优雅处理方案
解决方案:Spring Batch批量保存时忽略重复实体
方法1:自定义JPA插入语句(推荐,性能最优)
针对不同数据库,通过@SQLInsert注解指定带忽略重复逻辑的原生插入语句,让数据库层面直接处理重复项,无需JPA抛出异常。
MySQL示例
@Entity @Table(name = "your_table") @SQLInsert(sql = "INSERT INTO your_table (unique_col, col1, col2) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE id = id") public class YourEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) private String uniqueCol; // 其他字段、getter/setter }
当唯一列重复时,SQL会执行无实际意义的主键更新操作,不会触发异常,直接跳过重复实体。
PostgreSQL示例
@SQLInsert(sql = "INSERT INTO your_table (unique_col, col1, col2) VALUES (?, ?, ?) ON CONFLICT (unique_col) DO NOTHING")
注意:需保证实体字段顺序与SQL参数顺序完全匹配,且数据库连接已开启批量支持(如MySQL需加rewriteBatchedStatements=true参数)。
方法2:捕获异常拆分重试
如果不想依赖数据库原生语法,可在ItemWriter中捕获唯一约束异常,将批次拆分为单条实体逐个处理,跳过重复项。
@Component public class YourItemWriter implements ItemWriter<YourEntity> { private static final Logger log = LoggerFactory.getLogger(YourItemWriter.class); private final YourRepository repository; public YourItemWriter(YourRepository repository) { this.repository = repository; } @Override public void write(List<? extends YourEntity> items) throws Exception { try { repository.saveAll(items); } catch (DataIntegrityViolationException e) { // 仅在出现重复时退化为单条处理 for (YourEntity item : items) { try { repository.save(item); } catch (DataIntegrityViolationException ignore) { log.warn("跳过重复实体,唯一列值: {}", item.getUniqueCol()); } } } } }
同时可在Spring Batch Step配置中添加跳过规则,避免整个Step因异常重试:
@Bean public Step yourStep(StepBuilderFactory stepBuilderFactory, YourItemWriter writer) { return stepBuilderFactory.get("yourStep") .<YourEntity, YourEntity>chunk(100) .writer(writer) .skip(DataIntegrityViolationException.class) .skipLimit(1000) .build(); }
方法3:自定义Repository批量插入逻辑
直接在Repository中实现原生批量插入方法,完全绕过JPA的saveAll()逻辑,性能最优但需手动维护字段映射。
public interface YourRepository extends JpaRepository<YourEntity, Long> { @Modifying @Query(value = "INSERT INTO your_table (unique_col, col1, col2) VALUES (:uniqueCols, :col1s, :col2s) ON DUPLICATE KEY UPDATE id = id", nativeQuery = true) void batchInsert(@Param("uniqueCols") List<String> uniqueCols, @Param("col1s") List<String> col1s, @Param("col2s") List<Integer> col2s); }
在Writer中调用该方法时,需将实体列表拆分为对应字段的参数列表传入。
内容的提问来源于stack exchange,提问作者user1436883
相关产品推荐
相关产品推荐

