Spring Boot JPA如何对带唯一约束的实体列表执行插入或更新操作
方案1:基于PostgreSQL原生UPSERT实现(效率最优)
你当前使用的数据库从schema = "public"判断为PostgreSQL,可直接用其原生支持的ON CONFLICT语法实现批量插入更新,全程走单SQL逻辑,性能最高。
你可以通过NamedParameterJdbcTemplate实现,无Spring Data JPA版本兼容问题:
@Service @Transactional public class BusinessApiTokenService { @Autowired private NamedParameterJdbcTemplate jdbcTemplate; public void batchUpsert(List<BusinessApiToken> tokens) { String sql = "INSERT INTO public.business_api_token (connection_id, token_type, token_value) " + "VALUES (:connectionId, :tokenType, :tokenValue) " + "ON CONFLICT (connection_id, token_type) DO UPDATE SET token_value = :tokenValue"; // 构造批量参数 MapSqlParameterSource[] params = tokens.stream() .map(token -> new MapSqlParameterSource() .addValue("connectionId", token.getConnection().getId()) .addValue("tokenType", token.getTokenType().name()) .addValue("tokenValue", token.getTokenValue())) .toArray(MapSqlParameterSource[]::new); jdbcTemplate.batchUpdate(sql, params); } }
说明:
ON CONFLICT后紧跟你设置的联合唯一约束字段,EXCLUDED指代本次插入冲突的新数据,若还有其他需要更新的字段,直接在DO UPDATE SET后追加对应赋值逻辑即可。
方案2:兼容多数据库的优化批量处理方案
如果你需要兼容MySQL、Oracle等其他数据库,不想绑定特定数据库语法,可以用批量查询+拆分处理的方案,比逐个查询判断效率高至少一个量级:
- 先批量提取所有待处理实体的联合唯一键,一次查询查出所有已存在的记录
- 拆分待处理列表为「待更新列表」和「待插入列表」
- 分别调用
saveAll批量处理两类数据
代码示例:
@Service @Transactional public class BusinessApiTokenService { @Autowired private BusinessApiTokenRepository tokenRepository; public void batchSaveOrUpdate(List<BusinessApiToken> toProcessList) { // 提取所有待处理的联合唯一键 List<Long> connectionIds = toProcessList.stream() .map(token -> token.getConnection().getId()) .toList(); List<ApiTokenType> tokenTypes = toProcessList.stream() .map(BusinessApiToken::getTokenType) .toList(); // 一次查询查出所有已存在的记录,构造唯一键映射 List<BusinessApiToken> existedList = tokenRepository.findByConnectionIdInAndTokenTypeIn(connectionIds, tokenTypes); Map<String, BusinessApiToken> existedMap = existedList.stream() .collect(Collectors.toMap( token -> token.getConnection().getId() + "_" + token.getTokenType(), Function.identity() )); List<BusinessApiToken> toUpdate = new ArrayList<>(); List<BusinessApiToken> toInsert = new ArrayList<>(); // 拆分待处理列表 for (BusinessApiToken token : toProcessList) { String uniqueKey = token.getConnection().getId() + "_" + token.getTokenType(); if (existedMap.containsKey(uniqueKey)) { // 已有记录赋值ID后加入更新列表 token.setId(existedMap.get(uniqueKey).getId()); toUpdate.add(token); } else { toInsert.add(token); } } // 批量执行 tokenRepository.saveAll(toInsert); tokenRepository.saveAll(toUpdate); } }
对应的Repository新增查询方法即可:
public interface BusinessApiTokenRepository extends JpaRepository<BusinessApiToken, Long> { List<BusinessApiToken> findByConnectionIdInAndTokenTypeIn(List<Long> connectionIds, List<ApiTokenType> tokenTypes); }
优化提示:如果待处理数据量超过1000条,可以按每批500-1000条的粒度拆分处理,避免单次查询参数过多。
批量性能优化配置
不管选用哪种方案,都建议在配置文件中开启JPA批量操作优化,进一步提升执行效率:
spring: jpa: properties: hibernate: jdbc: batch_size: 500 # 批量操作单批大小 order_inserts: true # 优化插入语句顺序 order_updates: true # 优化更新语句顺序
内容的提问来源于stack exchange,提问作者Mankind1023
相关产品推荐
相关产品推荐

