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
相关产品推荐
相关产品推荐

