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

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等其他数据库,不想绑定特定数据库语法,可以用批量查询+拆分处理的方案,比逐个查询判断效率高至少一个量级:

  1. 先批量提取所有待处理实体的联合唯一键,一次查询查出所有已存在的记录
  2. 拆分待处理列表为「待更新列表」和「待插入列表」
  3. 分别调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.15 06:24:04