diff --git a/flow-engine-example/src/main/resources/application-dm.properties b/flow-engine-example/src/main/resources/application-dm.properties index b801958a..94de4f29 100644 --- a/flow-engine-example/src/main/resources/application-dm.properties +++ b/flow-engine-example/src/main/resources/application-dm.properties @@ -6,6 +6,23 @@ spring.jpa.properties.hibernate.dialect=com.codingapi.example.dialect.HydDmDiale # 开启 JDBC 批次插入,配合 SEQUENCE 主键(t_flow_record/t_flow_todo_record/t_flow_todo_marge/t_flow_sub_process_record) # 将批量创建流程记录/待办/合并/子流程时的多次串行 INSERT 合并为批次往返,显著降低远程库耗时 +# +# ⚠️ 达梦主键序列约定: +# 这四个实体显式声明了 @SequenceGenerator,序列名固定为 t_flow_record_seq / +# t_flow_todo_record_seq / t_flow_todo_marge_seq / t_flow_sub_process_record_seq, +# allocationSize = 50。 +# Hibernate 的 ddl-auto=update 在达梦上自动建序列并不可靠,请【首次部署前手工预建】这四个序列, +# 且 INCREMENT BY 必须与 allocationSize(50) 严格一致(否则 pooled 优化器可能主键重复)。 +# 起始值 start with 按表是否有历史数据决定(pooled 发号从 start 起连续递增,绝不下探到 start 以下): +# ⚪ 表为空: start with 1 increment by 50 +# 🟠 表已存在数据(id 到 N): start with (N+1) increment by 50 ← 务必为已占用的最大 id + 1,否则新号撞历史主键 +# 示例(空表): +# create sequence t_flow_record_seq increment by 50 start with 1; +# create sequence t_flow_todo_record_seq increment by 50 start with 1; +# create sequence t_flow_todo_marge_seq increment by 50 start with 1; +# create sequence t_flow_sub_process_record_seq increment by 50 start with 1; +# 注意:INCREMENT BY(序列步长)与下方的 jdbc.batch_size(JDBC 批插行数)是两个独立概念, +# 两者无需一致;真正必须一致的是 序列步长 == allocationSize。 spring.jpa.properties.hibernate.jdbc.batch_size=100 spring.jpa.properties.hibernate.order_inserts=true spring.jpa.properties.hibernate.order_updates=true diff --git a/flow-engine-example/src/main/resources/sql/dm-sequences.sql b/flow-engine-example/src/main/resources/sql/dm-sequences.sql new file mode 100644 index 00000000..0c4b20bd --- /dev/null +++ b/flow-engine-example/src/main/resources/sql/dm-sequences.sql @@ -0,0 +1,74 @@ +-- ===================================================================== +-- 达梦( DM ) 主键序列预建脚本 +-- ===================================================================== +-- 适用表( SEQUENCE 主键,配合 hibernate 批插 ): +-- t_flow_record -> t_flow_record_seq +-- t_flow_todo_record -> t_flow_todo_record_seq +-- t_flow_todo_marge -> t_flow_todo_marge_seq +-- t_flow_sub_process_record-> t_flow_sub_process_record_seq +-- +-- 序列名 / 步长 与实体 @SequenceGenerator(sequenceName=..., allocationSize=50) 严格一致, +-- 请勿修改,否则 hibernate 会在 insert 时报“无法解析的成员访问表达式[...NEXTVAL]”或出现主键重复。 +-- +-- 特性: +-- * INCREMENT BY 固定 50(必须 == allocationSize,paired/pooled 优化器要求,否则可能主键重复) +-- * START WITH 自动取 「表 max(id) + 1」:表为空则 1,表有历史数据则最大 id+1(绝不撞已有主键) +-- * 幂等:序列已存在则跳过,可反复执行 +-- * 基于当前登录用户(USER)所在模式,与表和序列归属一致 +-- +-- 执行方式:DBA 工具(达梦管理工具/disql) 以应用同款账号(默认 SYSDBA) 执行本脚本即可。 +-- ===================================================================== + +DECLARE + v_cnt INT; + v_start BIGINT; +BEGIN + -- ---------- 1. t_flow_record_seq ---------- + SELECT COUNT(*) INTO v_cnt FROM all_sequences + WHERE sequence_name = 'T_FLOW_RECORD_SEQ' AND sequence_owner = USER; + IF v_cnt = 0 THEN + BEGIN + SELECT NVL(MAX(id), 0) + 1 INTO v_start FROM t_flow_record; + EXCEPTION WHEN OTHERS THEN + v_start := 1; -- 表不存在或为空 + END; + EXECUTE IMMEDIATE 'CREATE SEQUENCE t_flow_record_seq INCREMENT BY 50 START WITH ' || v_start; + END IF; + + -- ---------- 2. t_flow_todo_record_seq ---------- + SELECT COUNT(*) INTO v_cnt FROM all_sequences + WHERE sequence_name = 'T_FLOW_TODO_RECORD_SEQ' AND sequence_owner = USER; + IF v_cnt = 0 THEN + BEGIN + SELECT NVL(MAX(id), 0) + 1 INTO v_start FROM t_flow_todo_record; + EXCEPTION WHEN OTHERS THEN + v_start := 1; + END; + EXECUTE IMMEDIATE 'CREATE SEQUENCE t_flow_todo_record_seq INCREMENT BY 50 START WITH ' || v_start; + END IF; + + -- ---------- 3. t_flow_todo_marge_seq ---------- + SELECT COUNT(*) INTO v_cnt FROM all_sequences + WHERE sequence_name = 'T_FLOW_TODO_MARGE_SEQ' AND sequence_owner = USER; + IF v_cnt = 0 THEN + BEGIN + SELECT NVL(MAX(id), 0) + 1 INTO v_start FROM t_flow_todo_marge; + EXCEPTION WHEN OTHERS THEN + v_start := 1; + END; + EXECUTE IMMEDIATE 'CREATE SEQUENCE t_flow_todo_marge_seq INCREMENT BY 50 START WITH ' || v_start; + END IF; + + -- ---------- 4. t_flow_sub_process_record_seq ---------- + SELECT COUNT(*) INTO v_cnt FROM all_sequences + WHERE sequence_name = 'T_FLOW_SUB_PROCESS_RECORD_SEQ' AND sequence_owner = USER; + IF v_cnt = 0 THEN + BEGIN + SELECT NVL(MAX(id), 0) + 1 INTO v_start FROM t_flow_sub_process_record; + EXCEPTION WHEN OTHERS THEN + v_start := 1; + END; + EXECUTE IMMEDIATE 'CREATE SEQUENCE t_flow_sub_process_record_seq INCREMENT BY 50 START WITH ' || v_start; + END IF; +END; +/ \ No newline at end of file diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java index 75716479..9e8c8764 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java @@ -11,7 +11,8 @@ public class FlowRecordEntity { * 记录id */ @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "flow_record_seq") + @SequenceGenerator(name = "flow_record_seq", sequenceName = "t_flow_record_seq", allocationSize = 50) private Long id; /** * 工作id diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoMargeEntity.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoMargeEntity.java index 5d699e8c..62f9d3a3 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoMargeEntity.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoMargeEntity.java @@ -9,7 +9,8 @@ public class FlowTodoMargeEntity { @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "flow_todo_marge_seq") + @SequenceGenerator(name = "flow_todo_marge_seq", sequenceName = "t_flow_todo_marge_seq", allocationSize = 50) private Long id; /** * 待办id diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java index 88555dda..69387f30 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java @@ -14,7 +14,8 @@ public class FlowTodoRecordEntity { *

使用序列生成(而非数据库自增),配合 hibernate 批插,降低高频批量写表的 DB 往返。 */ @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "flow_todo_record_seq") + @SequenceGenerator(name = "flow_todo_record_seq", sequenceName = "t_flow_todo_record_seq", allocationSize = 50) private Long id; /** diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/SubProcessRecordEntity.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/SubProcessRecordEntity.java index ef0da7c6..b9de6b28 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/SubProcessRecordEntity.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/SubProcessRecordEntity.java @@ -7,6 +7,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Lob; +import jakarta.persistence.SequenceGenerator; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import jakarta.persistence.Version; @@ -28,7 +29,8 @@ public class SubProcessRecordEntity { * 主键,由序列生成(替代数据库自增,配合 hibernate 批插降低批量往返) */ @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "flow_sub_process_record_seq") + @SequenceGenerator(name = "flow_sub_process_record_seq", sequenceName = "t_flow_sub_process_record_seq", allocationSize = 50) private Long id; /**