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

jOOQ中如何正确使用.whereNotExists()方法?

我来帮你搞定这个股票收盘数据去重持久化的问题!核心目标就是保证同一个股票(ticker)的同一天(date)只能有一条记录,咱们从数据库约束到代码实现一步步来:

第一步:给数据库加「防重复」的最后防线

首先得在实体类上配置组合唯一约束,让数据库直接拒绝ticker+date重复的记录。修改你的DailyData实体类:

import jakarta.persistence.*;
import java.math.BigDecimal;
import java.sql.Timestamp;

@Entity
@Table(name = "daily_data", 
       uniqueConstraints = {
           // 核心:ticker和date的组合必须唯一
           @UniqueConstraint(columnNames = {"ticker", "date"})
       })
public class DailyData { 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; 
    private String ticker; 
    private BigDecimal open; 
    private BigDecimal high; 
    private BigDecimal low; 
    private BigDecimal close; 
    private Timestamp date; 

    // 别忘了生成getters、setters,还有必要的构造方法
}
第二步:代码层面的两种实现方案

根据你的需求(是只忽略重复,还是要更新已有记录的最新数据),可以选下面两种方案:

方案一:先查询再保存(适合低并发场景)

这种方式逻辑直观:先查数据库里有没有同ticker同date的记录,没有再保存。如果需要更新已有数据,也可以在这里做。

首先定义Repository接口:

import org.springframework.data.jpa.repository.JpaRepository;
import java.sql.Timestamp;
import java.util.Optional;

public interface DailyDataRepository extends JpaRepository<DailyData, Long> {
    // 根据ticker和date查询唯一记录
    Optional<DailyData> findByTickerAndDate(String ticker, Timestamp date);
}

然后在服务层写处理逻辑:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
public class DailyDataService {

    private final DailyDataRepository repository;

    // 构造注入Repository
    public DailyDataService(DailyDataRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public void processDailyData(List<DailyData> dataList) {
        for (DailyData newData : dataList) {
            // 查库判断是否存在重复
            repository.findByTickerAndDate(newData.getTicker(), newData.getDate())
                .ifPresentOrElse(
                    // 如果存在:可以选择更新数据,或者直接忽略
                    existingData -> {
                        // 这里是更新逻辑,比如把新数据的行情字段覆盖旧的
                        existingData.setOpen(newData.getOpen());
                        existingData.setHigh(newData.getHigh());
                        existingData.setLow(newData.getLow());
                        existingData.setClose(newData.getClose());
                        repository.save(existingData);
                        // 如果只是忽略重复,这里什么都不用写就行
                    },
                    // 如果不存在:直接保存新数据
                    () -> repository.save(newData)
                );
        }
    }
}

方案二:用数据库Upsert(高效且防并发)

如果数据量比较大,或者有并发写入的场景,先查再存的效率太低,还可能出现并发冲突(比如两个线程同时查到不存在,然后都插入,触发数据库唯一约束异常)。这时候用数据库的「Upsert」(插入或更新)语法更合适,直接一条SQL搞定,效率拉满。

比如MySQL的语法,在Repository里加自定义方法:

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.sql.Timestamp;

public interface DailyDataRepository extends JpaRepository<DailyData, Long> {

    @Modifying
    @Transactional
    @Query(value = "INSERT INTO daily_data (ticker, open, high, low, close, date) " +
                   "VALUES (:ticker, :open, :high, :low, :close, :date) " +
                   // 遇到重复键时,更新行情字段;如果只想忽略重复,改成UPDATE id=id就行
                   "ON DUPLICATE KEY UPDATE " +
                   "open = VALUES(open), high = VALUES(high), low = VALUES(low), close = VALUES(close)",
           nativeQuery = true)
    void upsertDailyData(@Param("ticker") String ticker,
                         @Param("open") BigDecimal open,
                         @Param("high") BigDecimal high,
                         @Param("low") BigDecimal low,
                         @Param("close") BigDecimal close,
                         @Param("date") Timestamp date);
}

然后服务层直接调用这个方法就行,不用先查库:

@Service
public class DailyDataService {

    private final DailyDataRepository repository;

    public DailyDataService(DailyDataRepository repository) {
        this.repository = repository;
    }

    public void batchUpsertDailyData(List<DailyData> dataList) {
        for (DailyData data : dataList) {
            repository.upsertDailyData(
                data.getTicker(),
                data.getOpen(),
                data.getHigh(),
                data.getLow(),
                data.getClose(),
                data.getDate()
            );
        }
    }
}

如果是PostgreSQL数据库,Upsert语法会有点不一样,把SQL改成:

INSERT INTO daily_data (ticker, open, high, low, close, date)
VALUES (:ticker, :open, :high, :low, :close, :date)
ON CONFLICT (ticker, date) DO UPDATE SET
open = EXCLUDED.open, high = EXCLUDED.high, low = EXCLUDED.low, close = EXCLUDED.close
额外提醒
  • 如果用方案一,高并发场景下可能会触发DataIntegrityViolationException(唯一约束违反),可以捕获这个异常并忽略,或者加分布式锁。
  • 不管用哪种方案,数据库的组合唯一约束一定要加,这是最后一道防线。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:14:42