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

H2数据库中PreparedStatement与自增ID引发无原生标识值错误

解决H2数据库视图写入触发的“无原生生成的标识值”错误

嘿,我来帮你搞定这个问题!首先得搞清楚为啥会报错:你的JPA实体用了GenerationType.IDENTITY,这意味着它依赖数据库自动生成主键后返回给它,但你通过TriggerAdapter里的PreparedStatement处理视图插入时,没把数据库生成的ID正确返回给JPA,所以JPA就喊“找不到原生生成的标识值”了。

下面给你几个可行的解决方案,按优先级排序:

方案1:修改触发器的PreparedStatement,让它返回生成的ID

这是最直接的修复方式,因为问题本质就是JPA拿不到生成的ID。你需要在触发器的插入语句里加上返回主键的逻辑,并且把ID同步回触发上下文:

@Override
public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException {
    // 初始化插入语句时,加上RETURNING id,同时指定返回生成的键
    prepInsert = conn.prepareStatement(
        "INSERT INTO school (/* 这里填你的其他列,比如name之类的 */) VALUES (/* 对应参数占位符 */) RETURNING id",
        Statement.RETURN_GENERATED_KEYS
    );
    // 初始化delete语句的逻辑保持不变...
}

@Override
public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException {
    // 处理插入场景:newRow不为null,oldRow为null
    if (newRow != null && oldRow == null) {
        // 先给prepInsert设置好所有参数,比如prepInsert.setString(1, newRow[1].toString());
        prepInsert.executeUpdate();
        
        // 拿到数据库生成的ID
        ResultSet generatedKeys = prepInsert.getGeneratedKeys();
        if (generatedKeys.next()) {
            Long generatedId = generatedKeys.getLong(1);
            // 把生成的ID塞回newRow的对应位置(假设id是实体的第一个列,对应newRow[0])
            newRow[0] = generatedId;
        }
    }
    // 处理delete的逻辑...
}

这样JPA就能从触发器的操作结果中拿到生成的ID,错误自然就消失了。

方案2:换用序列生成主键(备选方案)

如果触发器的代码不好修改,你可以调整实体的主键生成策略,用序列代替IDENTITY,这样JPA会先从序列拿到ID再插入,不需要依赖数据库返回:

  1. 修改实体类的主键注解:
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "school_seq")
@SequenceGenerator(name = "school_seq", schema = "vw", sequenceName = "school_sequence", allocationSize = 1)
private Long id;
  1. 在Liquibase里添加序列和表的默认值配置:
CREATE SEQUENCE vw.school_sequence START WITH 1 INCREMENT BY 1;
ALTER TABLE vw.school ALTER COLUMN id SET DEFAULT NEXT VALUE FOR vw.school_sequence;

这种方式绕开了触发器返回ID的问题,也能正常工作。

方案3:确保触发器是AFTER类型

最后检查一下你的触发器创建语句,一定要用AFTER触发器,而不是BEFORE。因为BEFORE触发器在实际插入表之前执行,这时候主键还没生成,JPA根本拿不到:

CREATE TRIGGER school_view_trigger
AFTER INSERT ON vw.your_school_view_name
FOR EACH ROW
CALL "com.yourpackage.UpdatableViewSchool";

AFTER触发器会在数据插入到实际表、主键生成之后再执行,这时候获取ID才是有效的。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:16:15