Spring Data JPA插入Postgres分区表时抛出ObjectOptimisticLockingFailureException
问题原因
出现ObjectOptimisticLockingFailureException的核心原因是:
- 你使用的BEFORE INSERT触发器返回NULL,Postgres会取消对主表
parcel_event的插入操作,因此执行INSERT语句后数据库返回的影响行数是0。 - Hibernate的
save()方法默认会验证插入语句的影响行数是否为1,当实际返回0时,就会抛出这个异常。
解决方案
方案1:禁用Hibernate的插入行数检查(快速解决)
在对应的实体类上添加@SQLInsert注解,指定不检查插入结果的行数:
import org.hibernate.annotations.SQLInsert; import org.hibernate.annotations.ResultCheckStyle; @Entity @Table(name = "parcel_event") @SQLInsert( sql = "INSERT INTO parcel_event (carrier_code, carrier_status, carrier_status_code, carrier_status_time_gmt, carrier_sub_status, carrier_sub_status_code, carrier_sub_status_description, destination_location, extra_info, status, status_code, status_description, sub_status, sub_status_code, sub_status_description, last_updated_at, latitude, location_code, location_name, longitude, order_no, org_id, previous_status, previous_status_time, status_identifier_value, status_received_at, type, value, id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", check = ResultCheckStyle.NONE ) public class ParcelEvent { // 实体字段定义 }
这个配置会让Hibernate不再验证插入语句的影响行数,即使数据库返回0,也不会抛出异常。
方案2:切换到Postgres声明式分区(推荐,长期方案)
Postgres 10及以上支持原生的声明式分区,不需要触发器即可自动路由插入到对应分区,Hibernate可以正常处理,从根源避免问题。
步骤1:清理现有继承式分区和触发器
-- 删除触发器 DROP TRIGGER IF EXISTS parcel_event_insert_or_partition ON public.parcel_event; -- 删除触发器函数 DROP FUNCTION IF EXISTS public.parcel_event_insert_partition(); -- 删除所有子分区(根据实际分区名调整) DROP TABLE IF EXISTS public.parcel_event_2024_01_01_2024_02_01; -- 重新创建主表(如果需要保留结构,先备份数据) CREATE TABLE public.parcel_event ( -- 复制原表的所有字段定义,确保包含carrier_status_time_gmt id BIGINT NOT NULL, carrier_code VARCHAR(255), carrier_status VARCHAR(255), carrier_status_code VARCHAR(255), carrier_status_time_gmt TIMESTAMP NOT NULL, -- 其他字段... PRIMARY KEY (id, carrier_status_time_gmt) -- 分区表的主键必须包含分区键 ) PARTITION BY RANGE (carrier_status_time_gmt);
步骤2:创建分区(按月)
可以手动创建或用Flyway脚本自动创建:
-- 创建2024年1月的分区 CREATE TABLE public.parcel_event_2024_01 PARTITION OF public.parcel_event FOR VALUES FROM ('2024-01-01 00:00:00') TO ('2024-02-01 00:00:00'); -- 给分区创建索引 CREATE INDEX ON public.parcel_event_2024_01 (carrier_status_time_gmt, carrier_code, value);
步骤3:自动管理分区(可选)
可以使用pg_partman扩展自动创建未来的分区,避免手动维护:
-- 安装pg_partman(如果未安装) CREATE EXTENSION IF NOT EXISTS pg_partman; -- 配置自动分区 SELECT partman.create_parent( p_parent_table := 'public.parcel_event', p_control := 'carrier_status_time_gmt', p_type := 'range', p_interval := 'month' );
使用声明式分区后,插入主表的语句会自动路由到对应的分区,Hibernate执行INSERT后会得到正确的影响行数(1),不会再抛出异常。
内容的提问来源于stack exchange,提问作者Awadesh
相关产品推荐
相关产品推荐

