Class JdbcFlowRepository
java.lang.Object
cloud.opencode.base.flow.persistence.jdbc.JdbcFlowRepository
- All Implemented Interfaces:
FlowRepository
JDBC-backed
FlowRepository for MySQL, PostgreSQL and H2.
基于 JDBC 的 FlowRepository 实现,支持 MySQL / PostgreSQL / H2。
SQL-injection posture (design v4 §6, §14):
- The only non-parameterised string permitted in any SQL is the
table name; that string is composed exclusively from the
opencode.base.flow.table-prefixsystem property (defaults to"flow_"), validated againstSqlBuilder.PREFIX_PATTERNat startup. Any mismatch throwsFlowExceptionwithFlowErrorCode.CFG_INVALID_VALUE. - Every dynamic value flows through a
PreparedStatement?marker. initSchema()executes oneCREATE TABLE IF NOT EXISTSper statement (re-runnable); the script is shipped underschema/V1__init_<dialect>.sqlin the JAR.
SQL 注入防护(设计 v4 §6 / §14):
- SQL 中唯一允许的非参数化字符串是表名;该字符串仅由系统属性
opencode.base.flow.table-prefix(默认"flow_") 构成,启动时按SqlBuilder.PREFIX_PATTERN严校验,不匹配抛FlowException+FlowErrorCode.CFG_INVALID_VALUE。 - 其它所有动态值均经
PreparedStatement?占位符传入。 initSchema()逐句执行CREATE TABLE IF NOT EXISTS(可重复执行);脚本随 JAR 打包在schema/V1__init_<dialect>.sql。
- Since:
- JDK 25, opencode-base-flow V1.0.5
- Author:
- Leon Soo
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringDefault table prefix whenSYS_PROP_TABLE_PREFIXis unset.static final StringSystem property used to override the table prefix at startup (default"flow_"). -
Constructor Summary
ConstructorsConstructorDescriptionJdbcFlowRepository(DataSource dataSource) Constructs a repository backed by the suppliedDataSource; the dialect is auto-detected from the connection's JDBC URL and the table prefix is read from theSYS_PROP_TABLE_PREFIXsystem property.JdbcFlowRepository(DataSource dataSource, String tablePrefix) Test-friendly constructor that takes the table prefix explicitly. -
Method Summary
Modifier and TypeMethodDescriptionvoidappendHistory(HistoryRecord record) Appends a new history row.intcancelAllTasksForStep(String instanceId, String stepId) Bulk-transitions every task under(instanceId, stepId)whose current status is notTaskStatus.CANCELEDinto theTaskStatus.CANCELEDstate — including DONE / REJECTED rows from a prior approval round.intcancelOtherPendingTasks(String instanceId, String stepId, String exceptTaskId) Bulk-cancels every otherTaskStatus.PENDINGtask on the same step (excludingexceptTaskId), respecting optimistic-lock version.intcasIncrementAddsignDepth(String instanceId, int expectedVersion, int maxDepth) Compare-and-set:UPDATE addsign_depth = addsign_depth + 1, version = version + 1 WHERE id = ? AND version = ? AND addsign_depth < maxDepth.dialect()Returns the detected dialect (visible for diagnostics and testing).findActiveDefinition(String flowKey) Returns the singleFlowStatus.ACTIVEdefinition for the given business key, if any.Looks up a definition by its primary id.findDefinitionVersions(String flowKey) Lists every persisted version of the given business key, in version ascending order.findHistoryByInstance(String instanceId) Lists every history row for an instance in chronological order (earliest first).findHistoryByTaskId(String taskId) Looks up a history row by its related task id (one-to-one for terminal task actions such as approve / reject).findHistoryByUser(String userId, int page, int size) Paginated history query for the given user (matched onHistoryRecord.operatorId()).Looks up an instance by id.findInstancesByInitiator(String userId, int page, int size) Lists instances started by the given user, paginated.findTaskById(String id) Looks up a task by id.findTasksByInstance(String instanceId) Lists every task (any status) under the given instance.findTasksByInstanceAndStatus(String instanceId, TaskStatus status) Lists tasks that belong to the given instance and currently sit in the given status.findTodoByUser(String userId, int page, int size) Paginated todo-list query for the given user (status =TaskStatus.PENDINGonly).findVotesByTask(String taskId) Lists every vote recorded against the given task, in vote order.voidLoadsschema/V1__init_<dialect>.sqlfrom the classpath, substitutes the validated${TABLE_PREFIX}token, then executes each statement inside a single transaction (R1 N-4).voidrecordVote(String taskId, String voterId, VoteOpinion opinion) Records a single vote at aStepType.VOTEstep.voidrunInTransaction(Runnable action) Runs the supplied action atomically (R1 S-5).Persists a new flow definition.saveInstance(FlowInstance inst) Persists a new flow instance.Persists a new task.Returns the validated table prefix (visible for diagnostics and testing).voidupdateDefinitionStatus(String id, FlowStatus newStatus) Mutates the lifecycle status of a definition (ACTIVE / DEPRECATED).intupdateInstance(FlowInstance inst) Optimistic-lock UPDATE; returns the number of affected rows (0indicates version conflict).intupdateTask(Task task) Optimistic-lock UPDATE; returns affected rows (0= version conflict).
-
Field Details
-
SYS_PROP_TABLE_PREFIX
System property used to override the table prefix at startup (default"flow_"). The value is validated againstSqlBuilder.PREFIX_PATTERN. 启动时覆盖表前缀的系统属性(默认"flow_")。- See Also:
-
DEFAULT_TABLE_PREFIX
Default table prefix whenSYS_PROP_TABLE_PREFIXis unset. 当SYS_PROP_TABLE_PREFIX未设置时使用的默认前缀。- See Also:
-
-
Constructor Details
-
JdbcFlowRepository
Constructs a repository backed by the suppliedDataSource; the dialect is auto-detected from the connection's JDBC URL and the table prefix is read from theSYS_PROP_TABLE_PREFIXsystem property. 使用给定的DataSource构造仓储;方言由 JDBC URL 自动推断, 表前缀读自系统属性SYS_PROP_TABLE_PREFIX。- Parameters:
dataSource- the data source — 数据源- Throws:
FlowException- withFlowErrorCode.CFG_INVALID_VALUEwhen the prefix is illegal or the JDBC URL cannot be probed — 前缀非法或 JDBC URL 无法探测时抛- Since:
- JDK 25, opencode-base-flow V1.0.5
-
JdbcFlowRepository
Test-friendly constructor that takes the table prefix explicitly. 测试友好构造器,显式传入表前缀。- Parameters:
dataSource- the data source — 数据源tablePrefix- table prefix (e.g."flow_") — 表前缀- Throws:
FlowException- withFlowErrorCode.CFG_INVALID_VALUEwhen the prefix is illegal or the JDBC URL cannot be probed- Since:
- JDK 25, opencode-base-flow V1.0.5
-
-
Method Details
-
initSchema
public void initSchema()Loadsschema/V1__init_<dialect>.sqlfrom the classpath, substitutes the validated${TABLE_PREFIX}token, then executes each statement inside a single transaction (R1 N-4). Idempotent — usesCREATE TABLE IF NOT EXISTS. 加载 classpath 中的schema/V1__init_<dialect>.sql,将${TABLE_PREFIX}占位符替换为校验后的前缀,在一个事务内按顺序执行每条 SQL(R1 N-4)。 幂等 —— 使用CREATE TABLE IF NOT EXISTS。MySQL caveat: MySQL implicitly commits each DDL statement (CREATE TABLE / CREATE INDEX), so rollback cannot undo partially executed scripts. Operators must clean up half-applied schemas manually when
initSchemafails mid-way on MySQL. H2 and PostgreSQL respect the transaction boundary as expected.MySQL 注意:MySQL 的 DDL 隐式提交,事务回滚无法 撤销半执行的脚本;运维需手动清理。H2 / PostgreSQL 正常遵守事务边界。
- Throws:
FlowException- when the script cannot be loaded or executed — 脚本加载或执行失败时抛- Since:
- JDK 25, opencode-base-flow V1.0.5
-
dialect
Returns the detected dialect (visible for diagnostics and testing). 返回探测出的方言(用于诊断与测试)。- Returns:
- the dialect — 方言
- Since:
- JDK 25, opencode-base-flow V1.0.5
-
tablePrefix
Returns the validated table prefix (visible for diagnostics and testing). 返回校验后的表前缀(用于诊断与测试)。- Returns:
- the prefix — 前缀
- Since:
- JDK 25, opencode-base-flow V1.0.5
-
saveDefinition
Description copied from interface:FlowRepositoryPersists a new flow definition. The returned record reflects the stored state (including any engine-assigned fields). 持久化一份新的流程定义。返回已存储的视图。- Specified by:
saveDefinitionin interfaceFlowRepository- Parameters:
def- the definition to persist — 待持久化的定义- Returns:
- the persisted definition — 已持久化的定义
-
findDefinitionById
Description copied from interface:FlowRepositoryLooks up a definition by its primary id. 根据主键 ID 查找定义。- Specified by:
findDefinitionByIdin interfaceFlowRepository- Parameters:
id- definition id — 定义 ID- Returns:
- the definition wrapped in an
Optional— 定义(Optional)
-
findActiveDefinition
Description copied from interface:FlowRepositoryReturns the singleFlowStatus.ACTIVEdefinition for the given business key, if any. 返回指定业务 key 当前唯一启用的(FlowStatus.ACTIVE)定义。- Specified by:
findActiveDefinitionin interfaceFlowRepository- Parameters:
flowKey- business flow key — 业务 key- Returns:
- the active definition — 启用中的定义
-
findDefinitionVersions
Description copied from interface:FlowRepositoryLists every persisted version of the given business key, in version ascending order. 返回给定业务 key 所有版本(按版本号升序)。- Specified by:
findDefinitionVersionsin interfaceFlowRepository- Parameters:
flowKey- business flow key — 业务 key- Returns:
- all definition versions — 全部版本
-
updateDefinitionStatus
Description copied from interface:FlowRepositoryMutates the lifecycle status of a definition (ACTIVE / DEPRECATED). 修改某定义的生命周期状态(ACTIVE / DEPRECATED)。- Specified by:
updateDefinitionStatusin interfaceFlowRepository- Parameters:
id- definition id — 定义 IDnewStatus- the new status — 新状态
-
saveInstance
Description copied from interface:FlowRepositoryPersists a new flow instance. 持久化新的流程实例。- Specified by:
saveInstancein interfaceFlowRepository- Parameters:
inst- the instance to persist — 待持久化的实例- Returns:
- the persisted instance — 已持久化的实例
-
findInstanceById
Description copied from interface:FlowRepositoryLooks up an instance by id. 根据 ID 查找实例。- Specified by:
findInstanceByIdin interfaceFlowRepository- Parameters:
id- instance id — 实例 ID- Returns:
- the instance wrapped in an
Optional— 实例(Optional)
-
findInstancesByInitiator
Description copied from interface:FlowRepositoryLists instances started by the given user, paginated. 分页列出指定发起人启动的实例。- Specified by:
findInstancesByInitiatorin interfaceFlowRepository- Parameters:
userId- initiator user id — 发起人 userIdpage- zero-based page index — 页码(0 起)size- page size — 每页大小- Returns:
- instances on the requested page — 该页实例
-
updateInstance
Description copied from interface:FlowRepositoryOptimistic-lock UPDATE; returns the number of affected rows (0indicates version conflict). 乐观锁 UPDATE;返回 affected rows(0表示版本冲突)。- Specified by:
updateInstancein interfaceFlowRepository- Parameters:
inst- the instance to update — 待更新的实例- Returns:
- affected rows — 影响行数
-
casIncrementAddsignDepth
Description copied from interface:FlowRepositoryCompare-and-set:UPDATE addsign_depth = addsign_depth + 1, version = version + 1 WHERE id = ? AND version = ? AND addsign_depth < maxDepth. Returns affected rows (0= conflict or depth exceeded). Callers must re-read the instance to distinguish the two cases. CAS:UPDATE addsign_depth + 1, version + 1 WHERE id = ? AND version = ? AND addsign_depth < maxDepth。返回 affected rows(0表示冲突或超深)。调用方需复读 instance 区分两种情况。- Specified by:
casIncrementAddsignDepthin interfaceFlowRepository- Parameters:
instanceId- instance id — 实例 IDexpectedVersion- expected current version — 期望的当前版本maxDepth- configured max addsign depth — 配置的加签链上限- Returns:
- affected rows — 影响行数
-
saveTask
Description copied from interface:FlowRepositoryPersists a new task. 持久化新任务。- Specified by:
saveTaskin interfaceFlowRepository- Parameters:
task- the task to persist — 待持久化的任务- Returns:
- the persisted task — 已持久化的任务
-
findTaskById
Description copied from interface:FlowRepositoryLooks up a task by id. 根据 ID 查找任务。- Specified by:
findTaskByIdin interfaceFlowRepository- Parameters:
id- task id — 任务 ID- Returns:
- the task wrapped in an
Optional— 任务(Optional)
-
findTasksByInstanceAndStatus
Description copied from interface:FlowRepositoryLists tasks that belong to the given instance and currently sit in the given status. 列出指定实例下、指定状态的任务。- Specified by:
findTasksByInstanceAndStatusin interfaceFlowRepository- Parameters:
instanceId- owning instance id — 所属实例 IDstatus- task status filter — 任务状态- Returns:
- matching tasks — 符合条件的任务
-
findTasksByInstance
Description copied from interface:FlowRepositoryLists every task (any status) under the given instance. 列出指定实例下所有任务(不限状态)。- Specified by:
findTasksByInstancein interfaceFlowRepository- Parameters:
instanceId- owning instance id — 所属实例 ID- Returns:
- all tasks — 所有任务
-
findTodoByUser
Description copied from interface:FlowRepositoryPaginated todo-list query for the given user (status =TaskStatus.PENDINGonly). 分页查询某用户的待办(仅TaskStatus.PENDING)。- Specified by:
findTodoByUserin interfaceFlowRepository- Parameters:
userId- assignee user id — 承接人 userIdpage- zero-based page index — 页码(0 起)size- page size — 每页大小- Returns:
- tasks on the requested page — 该页任务
-
updateTask
Description copied from interface:FlowRepositoryOptimistic-lock UPDATE; returns affected rows (0= version conflict). 乐观锁 UPDATE;返回 affected rows(0表示版本冲突)。- Specified by:
updateTaskin interfaceFlowRepository- Parameters:
task- the task to update — 待更新的任务- Returns:
- affected rows — 影响行数
-
cancelOtherPendingTasks
Description copied from interface:FlowRepositoryBulk-cancels every otherTaskStatus.PENDINGtask on the same step (excludingexceptTaskId), respecting optimistic-lock version. Returns the number of tasks that were successfully canceled. 批量取消同节点其他 PENDING 任务(不含exceptTaskId),遵守乐观锁; 返回成功取消的条数。- Specified by:
cancelOtherPendingTasksin interfaceFlowRepository- Parameters:
instanceId- owning instance id — 所属实例 IDstepId- step id — 节点 IDexceptTaskId- the task id to keep — 保留的任务 ID- Returns:
- number of canceled tasks — 成功取消条数
-
cancelAllTasksForStep
Description copied from interface:FlowRepositoryBulk-transitions every task under(instanceId, stepId)whose current status is notTaskStatus.CANCELEDinto theTaskStatus.CANCELEDstate — including DONE / REJECTED rows from a prior approval round. Used byRejectOperationwhen rolling back to a previously visited step so thatStepEvaluatoronly ever observes tasks from the new batch. 批量将(instanceId, stepId)下所有非 CANCELED 状态的任务(含上一轮 的 DONE / REJECTED)置为TaskStatus.CANCELED。RejectOperation在回退到已访问节点时调用此方法,确保StepEvaluator只看见新一轮批次的任务。Invariant: each step has at most one active batch of tasks at any given time. Reject re-entry must enforce this by calling this method for every affected step before creating fresh PENDING rows.
不变式:任意时刻每个 step 最多有一批活跃 task;reject 重入前必须对所有 受影响 step 调用本方法。
- Specified by:
cancelAllTasksForStepin interfaceFlowRepository- Parameters:
instanceId- owning instance id — 所属实例 IDstepId- step id — 节点 ID- Returns:
- number of affected rows — 影响行数
-
appendHistory
Description copied from interface:FlowRepositoryAppends a new history row. History is append-only — once written, rows are never updated or deleted. 追加一条历史记录。历史表仅追加,写入后不可更新或删除。- Specified by:
appendHistoryin interfaceFlowRepository- Parameters:
record- the history row to append — 待追加的历史
-
findHistoryByInstance
Description copied from interface:FlowRepositoryLists every history row for an instance in chronological order (earliest first). 按时间升序列出指定实例的所有历史记录。- Specified by:
findHistoryByInstancein interfaceFlowRepository- Parameters:
instanceId- owning instance id — 所属实例 ID- Returns:
- chronological history — 时间升序的历史
-
findHistoryByUser
Description copied from interface:FlowRepositoryPaginated history query for the given user (matched onHistoryRecord.operatorId()). 分页查询某用户的历史记录(按HistoryRecord.operatorId()匹配)。- Specified by:
findHistoryByUserin interfaceFlowRepository- Parameters:
userId- operator user id — 操作人 userIdpage- zero-based page index — 页码(0 起)size- page size — 每页大小- Returns:
- history rows on the requested page — 该页历史
-
findHistoryByTaskId
Description copied from interface:FlowRepositoryLooks up a history row by its related task id (one-to-one for terminal task actions such as approve / reject). 按关联任务 ID 查找历史(approve / reject 等终态任务动作一对一)。- Specified by:
findHistoryByTaskIdin interfaceFlowRepository- Parameters:
taskId- related task id — 关联任务 ID- Returns:
- the history row wrapped in an
Optional— 历史(Optional)
-
recordVote
Description copied from interface:FlowRepositoryRecords a single vote at aStepType.VOTEstep. Append-only. 在StepType.VOTE节点记录一票。追加写。- Specified by:
recordVotein interfaceFlowRepository- Parameters:
taskId- related task id — 关联任务 IDvoterId- voter user id — 投票人opinion- vote opinion — 投票意见
-
findVotesByTask
Description copied from interface:FlowRepositoryLists every vote recorded against the given task, in vote order. 列出关联任务的所有投票(按投票顺序)。- Specified by:
findVotesByTaskin interfaceFlowRepository- Parameters:
taskId- related task id — 关联任务 ID- Returns:
- votes in submission order — 投票(按提交顺序)
-
runInTransaction
Runs the supplied action atomically (R1 S-5).- Re-uses an outer transaction when nested.
- Opens a single connection with
autoCommit=false, binds it to this thread, runs the action, then commits on success or rolls back on exception.
autoCommit=false连接,提交或回滚。- Specified by:
runInTransactionin interfaceFlowRepository- Parameters:
action- the action — 待执行动作- Since:
- JDK 25, opencode-base-flow V1.0.5
-