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

使用JPA/Hibernate插入CityHistory时如何复用已存在的城市记录?

解决方案

你当前遇到重复尝试插入City记录的核心原因是CityHistory实体中cityId字段的@ManyToOne注解配置了cascade = CascadeType.ALL,保存CityHistory时会默认级联持久化关联的City对象。如果关联的City是未被JPA上下文管理的临时对象,就算数据库中已经存在对应identification的记录,JPA也会尝试执行INSERT语句,触发唯一键冲突,可按以下步骤改造:


1. 调整实体类级联配置

首先移除CityHistory中不必要的级联持久化配置,避免无差别级联插入City:

@ManyToOne(fetch = FetchType.LAZY)
// 移除 cascade = CascadeType.ALL 配置,不需要级联操作City实体
@JoinColumn(name = "CITY_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_CITY_ID"))
private City cityId;

2. 新增City查询接口

给City对应的Repository新增按唯一键identification查询的方法:

public interface CityRepository extends JpaRepository<City, Long> {
    // 按城市唯一标识查询城市记录
    Optional<City> findByIdentification(String identification);
    
    // 批量查询用(可选,适配大数据量场景)
    List<City> findByIdentificationIn(Collection<String> identifications);
}

3. 修改ItemWriter写入逻辑

调整InfoItemWriter的写入逻辑,写入前先判断对应城市是否存在,存在则复用,不存在再新增:

@Component
public class InfoItemWriter implements ItemWriter<Object> {

    @Autowired
    private CityHistoryRepository cityHistoryRepository;
    @Autowired
    private CityRepository cityRepository;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void write(List<? extends Object> items) throws Exception {
        if (items.isEmpty() || !(items.get(0) instanceof CityHistory)) {
            return;
        }
        List<CityHistory> cityHistories = (List<? extends CityHistory>) items;
        
        // 大数据量场景优化:先批量查询所有已存在的城市,减少DB查询次数
        Set<String> identifications = cityHistories.stream()
                .map(h -> h.getCityId().getIdentification())
                .collect(Collectors.toSet());
        Map<String, City> identToCityMap = cityRepository.findByIdentificationIn(identifications)
                .stream()
                .collect(Collectors.toMap(City::getIdentification, Function.identity()));

        for (CityHistory history : cityHistories) {
            City city = history.getCityId();
            City existCity = identToCityMap.get(city.getIdentification());
            if (existCity != null) {
                // 存在则复用已有记录
                history.setCityId(existCity);
            } else {
                // 不存在则先保存新城市记录
                city = cityRepository.save(city);
                history.setCityId(city);
                // 同批次新增的城市存入map,避免同批次重复插入
                identToCityMap.put(city.getIdentification(), city);
            }
        }
        // 批量保存所有城市历史记录
        cityHistoryRepository.saveAll(cityHistories);
    }
}

4. 并发场景优化(可选)

如果存在并发写入相同城市的场景,可给查询方法加悲观锁避免并发插入冲突:

@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<City> findByIdentification(String identification);

也可以直接捕获唯一键冲突异常,二次查询复用已有记录做兜底。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 11:51:02