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

Spring 5+H2内存库实现原子性插入:仅新增不存在的数据

实现H2数据库原子性"不存在则插入"操作(类似ConcurrentHashMap.putIfAbsent)

你遇到的H2不支持PL风格代码块的问题很常见,不过我们可以通过H2原生支持的SQL语法或者Spring事务特性来实现你要的原子操作。下面给你两种可行的方案,都能配合@Transactional注解保证操作的原子性:

方案一:使用H2的MERGE语句(推荐)

H2支持标准的MERGE语法,这个语句本身就是原子性的,能直接实现"不存在则插入"的逻辑,不需要额外的PL块。结合NamedParameterJdbcTemplate(比普通JdbcTemplate更适合带命名参数的场景),代码如下:

import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;

@Transactional
public void create(String city, Double temperature) {
    // H2的MERGE语法:当city不存在时执行插入,存在则不做任何操作
    String mergeSql = """
        MERGE INTO weather (city, temperature, time_stamp)
        KEY (city)
        VALUES (:city, :temperature, :time_stamp)
        """;

    MapSqlParameterSource params = new MapSqlParameterSource()
            .addValue("city", city)
            .addValue("temperature", temperature)
            .addValue("time_stamp", new Date());

    namedParameterJdbcTemplate.update(mergeSql, params);
    System.out.println("操作完成:若城市不存在则已插入记录");
}

说明:

  • KEY (city)指定了判断记录是否存在的唯一键(这里假设city是表的唯一约束字段,最好给weather表的city列加UNIQUE约束,这样MERGE的判断更可靠)。
  • @Transactional注解在这里可以保证如果操作过程中出现异常,会自动回滚,同时也能确保整个MERGE操作在事务上下文里执行(虽然MERGE本身是原子的,但事务能和其他操作保持一致性)。

方案二:先查询再插入(需注意事务隔离级别)

如果你更倾向于用Java逻辑控制,也可以先查询是否存在记录,不存在再插入,但必须通过事务隔离级别避免并发场景下的竞态条件:

import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;

@Transactional(isolation = Isolation.SERIALIZABLE) // 用最高隔离级别防止并发问题
public void create(String city, Double temperature) {
    // 先查询是否存在该城市的记录
    String checkSql = "SELECT COUNT(*) FROM weather WHERE city = :city";
    Long count = namedParameterJdbcTemplate.queryForObject(
            checkSql,
            new MapSqlParameterSource("city", city),
            Long.class
    );

    if (count == 0) {
        String insertSql = """
            INSERT INTO weather (city, temperature, time_stamp)
            VALUES (:city, :temperature, :time_stamp)
            """;
        MapSqlParameterSource params = new MapSqlParameterSource()
                .addValue("city", city)
                .addValue("temperature", temperature)
                .addValue("time_stamp", new Date());
        namedParameterJdbcTemplate.update(insertSql, params);
        System.out.println("成功插入新的天气记录");
    } else {
        System.out.println("该城市的记录已存在,跳过插入");
    }
}

说明:

  • 必须设置isolation = Isolation.SERIALIZABLE,否则在高并发场景下,可能出现两个线程同时查询到记录不存在,然后都执行插入的情况,破坏原子性。
  • 这种方案比MERGE多了一次数据库查询,性能略差,所以优先推荐方案一。

关键注意点

  • 确保weather表的city字段有UNIQUE约束,这是保证"不存在则插入"逻辑可靠的基础,即使出现极端并发情况,数据库层面也会阻止重复插入。
  • 使用NamedParameterJdbcTemplate比普通JdbcTemplate更适合处理命名参数,代码可读性更高,也避免了索引参数的错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 03:50:02