ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Activiti 7 建表 SQL 藏在 jar 包里?提取、手动建库与避坑实战

Activiti 7 建表 SQL 藏在 jar 包里?提取、手动建库与避坑实战 直接把 Activiti 引擎的 jar 包反编译拿出来看建表 SQL我劝你别这么干但看完这篇再决定。不是因为不能反编译而是因为你会踩到一堆莫名其妙的坑。就拿activiti-engine-7.0.0.Beta2.jar里org/activiti/db/create/*.sql这批文件来说不少人在做 Activiti 7 项目初始化时拿着资料去找create.activiti6_database.sql翻遍整个 maven 仓库都找不到。还有人在 Linux 服务器上用jar命令替换包内文件后服务直接起不来。这些我都遇到过。最初接手一个基于 Activiti 7.0.0.Beta2 的老项目数据库初始化脚本找了三层目录都没找到。后来发现不只 Activiti 7很多流程引擎中间件都会把建表脚本“藏”在引擎 jar 里默认不单独发布 SQL 文件。想在离线环境或者内网里手动初始化一套带流程引擎表的库第一步就是搞清楚这些 SQL 文件到底放在哪、里面是什么、怎么才能安全地拿出来用。这篇就把我从 jar 里翻出 create 目录 SQL、手动建库、再改回 jar 全过程的实际操作拆开讲清楚顺便把我踩过的几个坑也一并说透。1. Activiti 建表脚本为什么“藏”在 jar 包里而不是单独发个 SQL1.1 先搞清楚 jar 包内部的结构逻辑之前用过 Activiti 5 和 6 的老同事应该有印象那时下载的安装包解压后有database目录里面有 create、drop 一类脚本找个activiti.mysql.create.engine.sql相当直接。但到了 Activiti 7 时代这种独立的数据库脚本分发方式变了。它的引擎核心逻辑和建表脚本一起被打进了activiti-engine-7.0.0.Beta2.jar目录就在org/activiti/db/create/只看这个路径就能猜到这是引擎启动时自动执行建表脚本的默认读取位置。Activiti 7 更倾向于让你在应用启动时由ProcessEngine自动检测数据库并创建表结构。所以默认情况下引擎跑起来后你会看到ACT_RE_*、ACT_RU_*、ACT_ID_*、ACT_HI_*、ACT_GE_*这些表在数据库里一份份冒出来整个过程不需要你手动去执行任何 SQL 文件。内部脚本按数据库类型拆成了不同文件夹create/activiti.mysql.create.engine.sqlcreate/activiti.mysql.create.history.sqlcreate/activiti.mysql.create.identity.sqlcreate/activiti.h2.create.engine.sqlcreate/activiti.h2.create.history.sqlcreate/activiti.h2.create.identity.sql还有db2、oracle、postgres、mssql等各数据库的对应版本。从命名能看出它不是把整库表都塞进一个 SQL而是按 Engine、History、Identity、General 几个模块拆开。因为 Activiti 7 的引擎代码里会按ProcessEngineConfiguration中配置的数据库类型定位到对应 SQL 文件并用 DB schema 工具逐条解析执行。如果你试图手动“合并执行”极可能出现表依赖顺序错误。提醒一下如果你用的是更老的 Activiti 5/6 的数据脚本里面很多表结构在 7 版本中已经变了。7 的ACT_RU_EXECUTION、ACT_RU_TASK、ACT_HI_PROCINST这些主表字段都和以前有差异别直接拿老库的表结构当 7 来用更不能拿老 SQL 覆盖新库。1.2 引擎运行时到底怎么用这些 SQLActiviti 7 的引擎启动时会做一次 schema 检查逻辑大致是读取配置的数据库类型比如mysql、postgres、h2。根据databaseSchemaUpdate配置值决定要不要执行脚本false不执行、true每次都执行、drop-create先删后建。从 classpath 定位到org/activiti/db/create/下对应的 SQL 文件。用内置的脚本解析器拆分 SQL逐条执行建表。所以jar 中 create 目录里的 SQL 并不是摆设——如果你用processEngineConfiguration.setDatabaseSchemaUpdate(true)它会真的去读这些文件。我们可以直接把从 jar 包中抽取的 SQL 文件用于手动建库但要小心引擎自动建表与手写建表脚本重复导致的冲突。1.3 手动建表最常见的翻车现场我在本地验证时遇到过一个比较典型的坑直接把activiti.mysql.create.engine.sql在 MySQL 客户端里打开执行结果报错ERROR 1064 (42000): You have an error in your SQL syntax原因不是文件损坏而是脚本里带有引擎专用的特殊注释标记和换行规则。如果你用普通文本工具打开会看到里面有类似/*!40014 SET ... */这样的 MySQL 条件编译注释还有source类型分隔。直接在 Navicat 里整段粘贴也没问题但要保证当前数据库连接确实支持这些语法。如果自定义 SQL 脚本与自动执行的 con 逻辑冲突或混合执行就会出现莫名其妙的表缺失。我后来核对源码才发现这个 jar 包基于 Activiti 7.0.0.Beta2它采用的 SQL 脚本风格兼容 MySQL 5.7 以上我在 8.0 环境执行没有问题但如果数据库版本过老部分字段类型可能不支持。2. 从 activiti-engine-7.0.0.Beta2.jar 中完整取出 create 目录 SQL2.1 直接用 jar 命令解包如果你只要org/activiti/db/create/下的文件不必把全部 jar 解开。在 Linux 或 Windows 的 cmd 中运行cd /path/to/your/libs jar xf activiti-engine-7.0.0.Beta2.jar org/activiti/db/create这样只会解压这个目录到当前路径。执行完能看到目录结构org/activiti/db/create/ ├── activiti.mysql.create.engine.sql ├── activiti.mysql.create.history.sql ├── activiti.mysql.create.identity.sql ├── activiti.mysql.create.general.sql │ └── ...如果系统没配置jar命令可以先把 jar 后缀改成.zip解压或者在 IDEA 里直接双击打开 jar在树状目录里找到org/activiti/db/create/后右键 Extract。这个简单办法足以拿到指定配置文件。不过这个方法有个小限制jar xf命令解压出来的 SQL 文件可能保留了很多类路径org/activiti/db/create/前缀如果你要放到项目src/main/resources/db/下作为自定义脚本需要自己复制出来处理一下。也可以直接用unzip把文件解压到指定目录unzip activiti-engine-7.0.0.Beta2.jar org/activiti/db/create/* -d extracted_activiti_sql这样所有 SQL 就到了extracted_activiti_sql/org/activiti/db/create/下结构非常清楚。2.2 反编译 class 看执行逻辑比看 SQL 文件本身更有用纯拿 SQL 文件还不够。你可能想知道引擎在启动时究竟先执行哪个 SQL再执行哪个 SQL。这时候要看org/activiti/db/DbSqlSessionFactory、ProcessEngineConfiguration这些关键类。流程是先用javap -p -c DbSqlSessionFactory查看字节码再用 CFR 等反编译工具还原源码。我在做这一步时为了确认脚本加载顺序先用 IDEA 打开 jar 内置的类文件实际就是反编译定位到org/activiti/db/DbSqlSession的schemaCreate方法。这个方法里会按固定顺序执行ACT_GE_PROPERTY与ACT_GE_BYTEARRAY通用表General。引擎相关表ACT_RE_*Repository流程定义等。运行时表ACT_RU_*Runtime执行实例、任务等。历史表ACT_HI_*History。身份相关表ACT_ID_*Identity用户、组等如果你的流程不需要用户体系可通过配置跳过。如果直接手动执行 SQL最好是先创建general再engine再history最后identity。顺序反了不一定报错但容易造成业务代码启动时报“表不存在”。我就在第一次手动建库时只执行了 engine 脚本没执行 identity 脚本。结果运行时执行一个带用户任务的流程查询ACT_ID_USER时直接抛Table activiti.ACT_ID_USER doesnt exist。排查很久才发现是这个原因。经验如果想绕过手动建表的这一大堆脚本顺序也可以在引擎配置中把databaseSchemaUpdate设为true启动一次自动把表全部建好。不过生产环境不推荐长期持有这个配置容易在后续升级时误操作变更字段。3. 拿到 SQL 脚本后手动搭建 Activiti 7 数据库的核心步骤3.1 先决条件与建库字符集我这里以 MySQL 8.0 为例先建库再执行脚本。如果之前有旧库建议先备份再处理CREATE DATABASE activiti DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE activiti;必须用utf8mb4而不是老项目里常见的utf8。Activiti 7 的表里存在VARCHAR(255)类型字段流程变量、备注等可能存入多字节字符用utf8在 MySQL 5.7 下不影响常规使用但涉及 emoji 等内容时utf8mb4是最稳妥的选择。在 MySQL 8.0 中如果建库用了过时的排序规则可能出现字符串比较和索引异常。后续步骤的执行顺序为source或直接运行activiti.mysql.create.general.sqlsource或直接运行activiti.mysql.create.engine.sqlsource或直接运行activiti.mysql.create.history.sqlsource或直接运行activiti.mysql.create.identity.sql用命令行执行其中一个示例mysql -u root -p activiti activiti.mysql.create.general.sql mysql -u root -p activiti activiti.mysql.create.engine.sql也可以直接在 MySQL 客户端里执行USE activiti; SOURCE /path/to/activiti.mysql.create.engine.sql;如果在source时出现Unknown command \的报错通常是脚本里有DELIMITER相关语法或文件为 Linux 换行符导致 Windows 客户端解析异常。建议直接换用命令行重定向方式执行不要反复手工粘贴。3.2 脚本执行完之后如何自检表是否齐全创建完成后查询所有表清单SHOW TABLES;正常情况应能见到以下几个大的分组以表名前缀区分:前缀作用常见表举例ACT_GE_通用数据用于存放字节数组和属性配置ACT_GE_BYTEARRAY、ACT_GE_PROPERTYACT_RE_仓库定义相关流程定义、模型等静态资源ACT_RE_DEPLOYMENT、ACT_RE_PROCDEFACT_RU_运行时数据执行流、任务、变量、作业等ACT_RU_EXECUTION、ACT_RU_TASK、ACT_RU_VARIABLE、ACT_RU_JOBACT_HI_历史数据流程实例归档、任务归档、活动日志ACT_HI_PROCINST、ACT_HI_TASKINST、ACT_HI_ACTINSTACT_ID_身份数据用户、组、关系ACT_ID_USER、ACT_ID_GROUP、ACT_ID_MEMBERSHIP如果发现缺少ACT_ID_*表不影响部署流程定义和创建流程实例但如果你在流程中使用Assignee、Candidate Group、用户组查询或者启动时触发用户同步逻辑就会报错。我在项目中建议默认把 identity 相关表也建上哪怕暂时不用内置用户体系也免得后续做扩展时数据模型不一致。3.3 表结构设计上的几个易忽略点Activiti 7 的表结构沿用了 Activiti 6 的核心设计但有些细节需要注意第一ACT_RU_EXECUTION表里的ID_字段长度通常是 64为什么有时部署乱码Id? 如果业务自定义生成流程实例 ID 时超过 64插入会报超长异常需要等待 Activiti 7 修正组件。尽量用系统默认生成的 ID。第二ACT_GE_BYTEARRAY里存放流程定义 XML、图片资源、流程变量二进制内容。它和ACT_RE_DEPLOYMENT通过DEPLOYMENT_ID_关联如果手动删除部署记录没有级联清理ACT_GE_BYTEARRAY中的资源历史包文件就会成为垃圾数据。第三流程结束后ACT_RU_*表中运行数据默认会被清理由历史任务完成监听触发历史归档到ACT_HI_*。如果想实现完整审计建议开启历史级别为fullProcessEngineConfiguration config processEngine.getProcessEngineConfiguration(); config.setHistoryLevel(HistoryLevel.FULL);如果你从老版本迁移过来可能见不到ACT_HI_DETAIL、ACT_HI_COMMENT这些表里没有数据因为默认历史级别不是 full。4. 修改 create 目录 SQL 后再替换回 jar详细实操与防坑4.1 什么场景需要替换 jar 里的 SQL场景一比较常见公司内部强制要求所有表名统一加业务前缀比如BIZ_ACT_RE_DEPLOYMENT。如果直接改activiti-engine里的 SQL 文件版本升级时改动会被冲掉所以习惯上会自定义一个ProcessEngineConfiguration的 bean在构建引擎前重写脚本。场景二Oracle 数据库里表空间和索引表空间要单独指定默认脚本不带表空间参数DBA 要求加TABLESPACE BIZ_TS_INDEX等。这时需要替换 jar 包中的建表脚本。场景三你改了 SQL 后想快速验证但不想重新编译整个工程。那就直接替换 jar 内的文件。4.2 Linux 下替换 jar 包内文件的完整步骤第一步先备份原始 jarcp activiti-engine-7.0.0.Beta2.jar activiti-engine-7.0.0.Beta2.jar.bak第二步解压出你要修改的文件jar xf activiti-engine-7.0.0.Beta2.jar org/activiti/db/create/activiti.mysql.create.engine.sql第三步编辑文件。例如想给所有表名加上业务前缀BIZ_sed -i s/ACT_/BIZ_ACT_/g org/activiti/db/create/activiti.mysql.create.engine.sql不过这里要谨慎表名前缀如果全部替换要确保脚本中的索引名、外键名与真实表名还有关联关系对得上。sed批量替换虽然简单但可能把ACT_GE_PROPERTY表里的NAME_值也改了导致引擎启动校验版本号失败。第四步将文件替换回 jarjar uf activiti-engine-7.0.0.Beta2.jar org/activiti/db/create/activiti.mysql.create.engine.sql这一步相当于把修改后的文件更新进 jar 包。执行成功后再用解压工具打开 jar 确认文件时间戳已更新。第五步启动时如果引擎配置了自动建表那么修改即可生效。如果只是手动建库则需要把改过的 SQL 在数据库中重新执行一遍。4.3 替换 jar 包后启动失败的几类常见症状症状一报错java.util.zip.ZipException: invalid LOC header (bad signature)。原因是.jar文件在解压和重新压缩过程中没有保持正确的 ZIP 结构比如直接用文本编辑器改 jar 内容后保存jar 包损坏。解决方式是先jar xf解压再修改文件最后用jar cf重新打包而不是直接编辑二进制包。症状二启动后报could not find resource org/activiti/db/create/activiti.mysql.create.engine.sql。一般是因为替换回 jar 时路径不对。比如你解压时一直在org/activiti/db/create/目录下执行jar uf activiti-engine-7.0.0.Beta2.jar ...很可能把文件路径写成了db/create/xxx.sql导致 jar 包中找不到原来的 classpath 路径。重新按 jar 包前缀路径替换即可。症状三旧表数据与新加字段不兼容。替换 SQL 文件后执行自动建表如果是drop-create模式所有旧表会被删除数据全部丢失如果是true模式仅当表不存在才建表已存在的表不会自动加字段。这时候你以为脚本更新了但表结构根本没变运行期查询某些新列会报 unknown column。4.4 替换 jar 是“非法操作”其实只是最后的兜底方案客观说在项目里直接把 create SQL 文件替换回引擎 jar并作为标准交付物不是好实践。原因主要有两个依赖升级时jar 包被本地修改过maven 仓库与构建服务器上的包不一致可能出现本地能跑但 CI 上建表失败这种诡异问题。同一版本的 Activiti jar 被多个服务共用时A 服务修改了表前缀B 服务继续用原表名一库多租户场景下会直接互串。反编译 jar 或解压 jar 修改文件本身是为了排查“为什么引擎自动生成的表比我预期少”而不是把它当作常规手段。正常情况下建表 SQL 的定制应当放在引擎初始化之前Bean public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer() { return config - { config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); config.setDatabaseType(ProcessEngineConfiguration.DATABASE_TYPE_MYSQL); }; }如果你确实要自定义 SQL 脚本内容最规范的操作是从 jar 包中把模板文件导出到自己的资源目录改名后在引擎配置里指定脚本位置不直接动原始 jar。这样既保留 jar 原样也能把建表脚本纳入工程版本管理。5. 在这台机器上折腾三天后我建议你一定要知道这些5.1 使用 H2 内存库时别再去找 MySQL 建表 SQL有一次我排查“为什么同一个流程在开发环境能跑、测试环境就报找不到表”最后发现开发环境用的是 H2 内存库引擎每次启动都会自动创建完整的 Activiti 表测试环境连的是 MySQL但数据库里一张表都没有运维把启动参数里的databaseSchemaUpdatetrue去掉了也没有手动跑建表脚本。如果你的应用使用了 H2 内存库注意以下几点jar 包中的 H2 建表脚本路径是org/activiti/db/create/activiti.h2.create.*.sql。如果开启了 Spring Boot 的spring.datasource.schema自动执行避免重复执行多个create语句产生主键冲突或锁表问题。同样一份 BPMN 流程定义 XML在 H2 与 MySQL 之间并不会有差别但涉及自定义流程变量类型排序时底层 SQL 方言不同结果集顺序可能不一样历史流程查询要注意。5.2 为什么启动日志里能看到 “No MyBatis mapping found ...” 却还能建表Activiti 7 引擎内部大量使用 MyBatis 的 Mapper 映射文件负责 CRUD 操作。启动时如果日志出现类似No MyBatis mapping found for ...的警告先不要急着断开很多是引擎在探测是否有自定义 Mapper。真正建表逻辑则会通过 DbSqlSession 直接执行 SQL和 MyBatis mapping 不一定相关。但如果你把activiti-enginejar 里的org/activiti/db/mapping/entity相关 XML 放在了自己的 classpath 下或者改成同名同路径覆盖一定要保证 XML 中定义的 namespace 与源码中 Mapper 接口一致。反编译后修改实体映射是风险最高的操作之一远高于仅仅动建表 SQL。5.3 表前缀配置不要把ACT_GE_PROPERTY里的值也改了Activiti 建表完成后ACT_GE_PROPERTY表默认会写入几行关键的属性例如NAME_VALUE_schema.version7.0.0.0schema.historycreate(7.0.0.0)next.dbid2501如果你改表前缀时批量替换了 SQL 里的所有ACT_ACT_GE_PROPERTY表内插入语句里纯字符串内容如果也被替换运行时 MyBatis 会强制校验schema.version。比如我实际见到过一个包SQL 里INSERT INTO ACT_GE_PROPERTY VALUES (schema.version, 7.0.0.0, 1)被sed替换成了INSERT INTO BIZ_ACT_GE_PROPERTY VALUES (BIZ_ACT_schema.version, 7.0.0.0, 1)引擎启动直接报 schema 版本不匹配。正确做法是只替换建表语句中的表名不要动INTO之后插入数据的字符串字面量。建议用正则精确匹配sed -i s/into ACT_/into BIZ_ACT_/Ig sed -i s/REFERENCES ACT_/REFERENCES BIZ_ACT_/Ig这样能避开插入值中的字符串。5.4 控制引擎自动建表与 Flyway/Liquibase 的重叠现在很多项目引入了 Flyway 管理数据库版本这时请重点关注 Activiti 的建表机制会不会“双重建表”。推荐方案项目启动时先由 Flyway 执行自己的迁移脚本同时在其中通过spring.flyway.locations指定一个目录存放从 jar 抽取的 Activiti 建表 SQL并执行。引擎配置中将databaseSchemaUpdate设为false不允许多套建表逻辑同时跑。但这样设计有个前提从 jar 抽取的执行脚本不能包含 Flyway 不兼容的多行注释或特殊分隔符。这时候可以先手动执行一次然后用 mysqldump 导出干净的表结构再交给 Flyway 管理。如果项目里已有生产库不建议一开始就切换 Flyway 接管 Activiti 表否则 Flyway 会在首次执行时要求基线baseline。Activiti 7 的表结构和引擎代码绑定紧密它不像普通业务表你可以自由删改字段。手动自定义时有任何偏差都可能造成流程引擎无法正常启动或流程实例状态错乱。以我在实际项目中的经验看能不改 jar 就不改 jar能从外部 SQL 脚本接管建表就从外部接管不到万不得已不要用解压修改 jar 的方式去维护数据库结构。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进