Commit 8c5e70484476f809ebdd0363254653312db0e920

Authored by YunaiV
1 parent 88fd7b68

feat: 完善 GaussDB、MogDB 等国产数据库可选接入

README.md
... ... @@ -67,12 +67,12 @@
67 67 |----------|-----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
68 68 | 国产操作系统 | 银河麒麟、统信 UOS 等 Linux 系统 | 参考 [《Linux 部署》](https://doc.iocoder.cn/deployment-linux/),选择与 CPU 架构匹配的 JDK |
69 69 | 关系型数据库 | MySQL、MariaDB、Oracle、PostgreSQL、SQL Server | 默认使用 MySQL |
70   -| 国产关系型数据库 | 达梦 DM、人大金仓 KingbaseES、openGauss、瀚高 HighGo、OceanBase、TiDB | 参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/) |
  70 +| 国产关系型数据库 | 达梦 DM、人大金仓 KingbaseES、openGauss、瀚高 HighGo、OceanBase、TiDB、华为 GaussDB、云和恩墨 MogDB、海量 Vastbase G100、PolarDB PostgreSQL 版 | 参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/)。GaussDB、MogDB、Vastbase G100 使用 PG 兼容模式;神通数据库提供实测差异说明,待专门适配 |
71 71 | 国产缓存 | 东方通 TongRDS、宝兰德 BES CacheDB | 替换 Redis,参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/)。BES CacheDB 提供接入说明,待介质验证 |
72 72 | 国产应用服务器 | 东方通 TongWeb、宝兰德 BES AppServer | 替换 Tomcat,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/)、[《宝兰德服务器部署》](https://doc.iocoder.cn/deployment-bes/)。BES AppServer 提供接入说明,待介质验证 |
73 73 | 国产 Web 服务器 | 东方通 TongHttpServer | 替换 Nginx,提供静态资源、反向代理,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/) |
74 74  
75   -* 数据库脚本:[`sql`](sql/) 目录提供 MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、openGauss、瀚高、OceanBase 的项目初始化脚本(含 Quartz),优先使用对应目录;MariaDB、TiDB 直接使用 MySQL 脚本
  75 +* 数据库脚本:[`sql`](sql/) 目录提供 MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、openGauss、瀚高、OceanBase 的项目初始化脚本(含 Quartz),优先使用对应目录;MariaDB、TiDB 直接使用 MySQL 脚本;GaussDB、MogDB、Vastbase G100 基于 openGauss 脚本导入,PolarDB PostgreSQL 版直接使用 PostgreSQL 脚本
76 76 * 其它数据库、后续表结构变更,可参考 [`sql/tools`](sql/tools/README.md) 的转换工具及 [《表结构变更(版本升级)》](https://doc.iocoder.cn/sql-update/#_3-数据库转换),转换后仍需按实际数据库调整、测试
77 77  
78 78 ## 🐳 项目关系
... ...
sql/tools/README.md
... ... @@ -6,7 +6,7 @@
6 6  
7 7 ## 1. 测试数据库的快速启动
8 8  
9   -基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server、OceanBase、TiDB 等数据库。
  9 +基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server、OceanBase、TiDB、MogDB 等数据库。
10 10  
11 11 注意!使用 Docker Compose 启动完测试数据后,因为会自动导入项目的 SQL 脚本,所以可能需要等待 1-2 分钟。
12 12  
... ... @@ -175,6 +175,50 @@ password: 123456
175 175  
176 176 > 注意:端口只绑定本机 `127.0.0.1:4000`。数据保存在 `tidb_pd`、`tidb_tikv` 数据卷中,重启不需要重新导入 SQL。如需重建,执行 `docker compose down tidb tidb-tikv tidb-pd` 后,删除 `ruoyi-vue-pro_tidb_pd`、`ruoyi-vue-pro_tidb_tikv` 数据卷。该环境仅用于开发测试,生产部署见 [TiDB 官方文档](https://docs.pingcap.com/zh/tidb/v8.5/production-deployment-using-tiup/)。
177 177  
  178 +### 1.11 华为 GaussDB
  179 +
  180 +GaussDB 没有官方 Docker 镜像,需要自行安装,例如华为云官网的 [GaussDB 混合云开发者版](https://www.huaweicloud.com/intl/zh-cn/product/gaussdb/download.html)(官方声明仅供个人学习、适配验证或测试,不得用于商业用途)。项目复用 `sql/opengauss` 脚本,导入到 PG 兼容模式的数据库中。
  181 +
  182 +① 初始化数据库:在安装了 GaussDB `gsql` 客户端的机器上(例如以数据库安装用户登录 GaussDB 服务器,并复制项目的 `sql` 目录),使用具有 CREATEDB 权限的账号执行:
  183 +
  184 +```Bash
  185 +GAUSSDB_PORT='数据库端口' GAUSSDB_USER='数据库账号' GAUSSDB_PASSWORD='数据库密码' bash gaussdb/create_schema.sh
  186 +```
  187 +
  188 +> 注意:脚本创建 PG 兼容模式(`DBCOMPATIBILITY = 'PG'`)、UTF8 编码的 `ruoyi-vue-pro` 数据库,依次导入 `sql/opengauss/quartz.sql`、`sql/opengauss/ruoyi-vue-pro.sql`。GaussDB 默认创建的是 A(Oracle)兼容模式的数据库,项目脚本需要使用 PG 兼容模式。GaussDB 已内置 `dual` 视图,原脚本开头的 `DROP TABLE IF EXISTS dual` 会报 `"dual" is not a table` 错误,所以脚本只跳过创建 `dual` 表的一段,不修改内置视图,其余 SQL 保持不变;该段格式与预期不一致,或者未检测到内置 `dual` 视图时,拒绝导入。初始化只允许创建全新的数据库:数据库已存在(包括空库)或重复执行时,脚本拒绝导入并返回非零状态;导入失败时保留数据库便于排查。
  189 +
  190 +> 注意:`GAUSSDB_HOST` 为空时,使用本机 Unix Socket 连接;远程连接时,需要在 `gs_hba.conf`(不是 `pg_hba.conf`)中为该账号、客户端地址配置密码认证(例如 sha256),不要使用 trust。GaussDB 的初始用户不允许远程连接,项目请使用单独创建的账号。
  191 +
  192 +② 项目使用 GaussDB 原生 JDBC 驱动(`com.huawei.gaussdb.jdbc.Driver`,`jdbc:gaussdb://127.0.0.1:端口/ruoyi-vue-pro`),按 PostgreSQL 处理序列主键、Quartz、工作流等差异,驱动需要引入到 `yudao-server/pom.xml` 的 `<dependencies>` 中。详见 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/#_7-华为-gaussdb-数据库) 文档。
  193 +
  194 +### 1.12 MogDB
  195 +
  196 +使用云和恩墨官方 MogDB 5.0.0 容器镜像(官方说明仅用于测试),支持 x86_64 和 MacBook Apple Silicon。`mogdb` 服务属于 `mogdb` profile,执行 `docker compose up -d` 时不会启动。
  197 +
  198 +① 启动数据库:
  199 +
  200 +```Bash
  201 +docker compose up -d mogdb
  202 +```
  203 +
  204 +② 初始化数据库:创建 PG 兼容模式(`DBCOMPATIBILITY = 'PG'`)、UTF8 编码的 `ruoyi-vue-pro` 数据库,所有者为 `mogdb` 用户,并依次导入 `sql/opengauss/quartz.sql`、`sql/opengauss/ruoyi-vue-pro.sql`:
  205 +
  206 +```Bash
  207 +docker compose exec -u omm mogdb bash /tmp/create_schema.sh
  208 +```
  209 +
  210 +> 注意:MogDB 默认创建的是 A(Oracle)兼容模式的数据库,`varchar(n)` 按字节计算长度,导入项目脚本时会在中文数据处报 `value too long for type character varying(32)` 错误,所以需要使用 PG 兼容模式。初始化只允许创建全新的数据库:数据库已存在(包括空库)或重复执行时,脚本拒绝导入并返回非零状态;导入失败时保留数据库便于排查,重新初始化需要执行 `docker compose down mogdb` 后,删除 `ruoyi-vue-pro_mogdb` 数据卷。
  211 +
  212 +③ 项目使用 openGauss JDBC 驱动(`org.opengauss.Driver`),将数据源配置中的地址、账号和密码修改为:
  213 +
  214 +```yaml
  215 +url: jdbc:opengauss://127.0.0.1:15432/ruoyi-vue-pro
  216 +username: mogdb
  217 +password: Yudao@2024
  218 +```
  219 +
  220 +> 注意:端口只绑定本机 `127.0.0.1:15432`;`omm` 初始用户不允许远程连接,项目使用 `mogdb` 用户。`jdbc:opengauss:` 按 PostgreSQL 处理序列主键、Quartz 等差异,驱动需要引入到 `yudao-server/pom.xml` 的 `<dependencies>` 中。详见 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/#_11-云和恩墨-mogdb-数据库) 文档。
  221 +
178 222 ## 1.X 容器的销毁重建
179 223  
180 224 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。
... ...
sql/tools/docker-compose.yaml
... ... @@ -12,6 +12,7 @@ volumes:
12 12 oceanbase_config: { }
13 13 tidb_pd: { }
14 14 tidb_tikv: { }
  15 + mogdb: { }
15 16  
16 17 services:
17 18 mysql:
... ... @@ -272,3 +273,23 @@ services:
272 273 - ../mysql/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
273 274 - ../mysql/quartz.sql:/tmp/quartz.sql:ro
274 275 - ./tidb/create_schema.sh:/tmp/create_schema.sh:ro
  276 +
  277 + # MogDB 测试库:云和恩墨官方容器镜像(仅用于测试),属于 mogdb profile,默认不启动
  278 + # 启动:docker compose up -d mogdb
  279 + # 初始化:docker compose exec -u omm mogdb bash /tmp/create_schema.sh(创建 PG 兼容模式的 ruoyi-vue-pro 数据库,并导入 sql/opengauss 脚本)
  280 + mogdb:
  281 + image: swr.cn-north-4.myhuaweicloud.com/mogdb/mogdb:5.0.0
  282 + profiles: ["mogdb"]
  283 + restart: unless-stopped
  284 + shm_size: 1g
  285 + environment:
  286 + # omm 超级用户、mogdb 用户的密码:至少 8 位,包含大小写字母、数字及特殊字符
  287 + GS_PASSWORD: Yudao@2024
  288 + ports:
  289 + # 只绑定本机,应用使用 mogdb 用户连接(omm 初始用户不允许远程连接)
  290 + - "127.0.0.1:15432:5432"
  291 + volumes:
  292 + - mogdb:/var/lib/mogdb
  293 + - ../opengauss/quartz.sql:/tmp/quartz.sql:ro
  294 + - ../opengauss/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
  295 + - ./mogdb/create_schema.sh:/tmp/create_schema.sh:ro
... ...
sql/tools/gaussdb/create_schema.sh 0 → 100644
  1 +#!/bin/bash
  2 +# 初始化 GaussDB 数据库:使用 GaussDB 的 gsql 客户端,创建 PG 兼容模式(DBCOMPATIBILITY = 'PG')、UTF8 编码的全新数据库,并导入 sql/opengauss 脚本
  3 +# GaussDB 已内置 dual 视图(sys.dual、pg_catalog.dual),导入时只跳过 sql/opengauss/ruoyi-vue-pro.sql 开头创建 dual 表的一段,不修改内置视图,其余 SQL 保持不变
  4 +# 数据库已存在(包括空库)时拒绝导入;任一步骤失败都以非零状态退出
  5 +# 执行:GAUSSDB_PORT=8000 GAUSSDB_USER='数据库账号' GAUSSDB_PASSWORD='数据库密码' bash sql/tools/gaussdb/create_schema.sh
  6 +set -euo pipefail
  7 +
  8 +host="${GAUSSDB_HOST:-}" # 为空时,使用本机的 Unix Socket 连接
  9 +port="${GAUSSDB_PORT:?GAUSSDB_PORT 未设置,请设置为 GaussDB 的端口}"
  10 +user="${GAUSSDB_USER:?GAUSSDB_USER 未设置,请设置为具有 CREATEDB 权限的数据库账号}"
  11 +password="${GAUSSDB_PASSWORD:?GAUSSDB_PASSWORD 未设置,请设置为数据库账号的实际密码}"
  12 +maintenance_db="${GAUSSDB_MAINTENANCE_DB:-postgres}"
  13 +database="${GAUSSDB_DATABASE:-ruoyi-vue-pro}"
  14 +sql_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../opengauss" && pwd)"
  15 +
  16 +if [[ ! "${database}" =~ ^[A-Za-z0-9_-]+$ ]]; then
  17 + echo "数据库名 ${database} 不合法,只允许字母、数字、下划线和中划线" >&2
  18 + exit 1
  19 +fi
  20 +if ! command -v gsql >/dev/null 2>&1; then
  21 + echo "未找到 gsql 客户端:请在安装了 GaussDB gsql 客户端的机器上执行,例如以数据库安装用户登录 GaussDB 服务器" >&2
  22 + exit 1
  23 +fi
  24 +
  25 +client=(gsql -p "${port}" -U "${user}" -W "${password}" -X -q -v ON_ERROR_STOP=1)
  26 +if [ -n "${host}" ]; then
  27 + client+=(-h "${host}")
  28 +fi
  29 +
  30 +# 1. 生成跳过 dual 段的临时脚本:只允许跳过「-- Table structure for dual」到其后第一个「-- @formatter:on」之间的 dual 建表、注释及插入语句
  31 +schema_file="$(mktemp)"
  32 +trap 'rm -f "${schema_file}"' EXIT
  33 +# 注释语句只允许单个字符串常量(内部的单引号写作 ''),字符串后只能是行尾的分号,避免同一行中的其它 SQL 被一起跳过
  34 +if ! awk -v q="'" '
  35 + BEGIN { comment = "^COMMENT ON TABLE dual IS " q "([^" q "]|" q q ")*" q ";$" }
  36 + /^-- Table structure for dual$/ { skipping = 1; blocks++ }
  37 + skipping {
  38 + if ($0 ~ /^(DROP TABLE IF EXISTS dual;|CREATE TABLE dual|INSERT INTO dual VALUES \(1\);)$/) {
  39 + statements++
  40 + } else if ($0 !~ /^(--.*|\(|[[:space:]]*id int2|\);)?$/ && $0 !~ comment) {
  41 + unexpected = 1
  42 + }
  43 + if ($0 ~ /^-- @formatter:on$/) {
  44 + skipping = 0
  45 + }
  46 + next
  47 + }
  48 + { print }
  49 + END { exit (blocks == 1 && statements == 3 && !skipping && !unexpected) ? 0 : 1 }
  50 +' "${sql_dir}/ruoyi-vue-pro.sql" > "${schema_file}" \
  51 + || grep -Eq '^(DROP TABLE IF EXISTS dual;|CREATE TABLE dual|INSERT INTO dual )' "${schema_file}"; then
  52 + echo "${sql_dir}/ruoyi-vue-pro.sql 中 dual 表一段的格式与预期不一致,拒绝导入;请检查脚本是否由 sql/tools/convertor.py 生成" >&2
  53 + exit 1
  54 +fi
  55 +
  56 +# 2. 校验账号、密码及认证方式
  57 +if ! "${client[@]}" -d "${maintenance_db}" -c 'SELECT 1' >/dev/null; then
  58 + echo "无法使用账号 ${user} 登录 ${maintenance_db} 数据库:请检查 GAUSSDB_HOST、GAUSSDB_PORT、GAUSSDB_USER、GAUSSDB_PASSWORD,以及 gs_hba.conf 中该账号、客户端地址的认证配置" >&2
  59 + exit 1
  60 +fi
  61 +
  62 +# 3. 校验 GaussDB 内置的 dual 视图:openGauss、MogDB 没有该视图,请直接导入 sql/opengauss 脚本
  63 +dual_count=$("${client[@]}" -d "${maintenance_db}" -At -c "SELECT COUNT(*) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 'dual' AND c.relkind = 'v' AND n.nspname IN ('sys', 'pg_catalog');")
  64 +if [ "${dual_count}" = "0" ]; then
  65 + echo "未检测到 GaussDB 内置的 dual 视图,拒绝导入:该脚本只适用于 GaussDB,openGauss、MogDB 请直接导入 sql/opengauss 脚本" >&2
  66 + exit 1
  67 +fi
  68 +
  69 +# 4. 创建全新数据库:不判断是否存在,数据库已存在时直接失败,避免项目 SQL 中的 DROP TABLE 重建已有的同名表
  70 +# 注意:GaussDB 默认创建的是 A(Oracle)兼容模式的数据库,项目脚本需要使用 PG 兼容模式
  71 +if ! "${client[@]}" -d "${maintenance_db}" -c "CREATE DATABASE \"${database}\" ENCODING 'UTF8' DBCOMPATIBILITY = 'PG';"; then
  72 + echo "创建数据库 ${database} 失败:只允许初始化全新的数据库,数据库已存在(包括空库)时拒绝导入;如需重新初始化,请确认数据可以删除后,手动删除该数据库" >&2
  73 + exit 1
  74 +fi
  75 +compatibility=$("${client[@]}" -d "${database}" -At -c 'SHOW sql_compatibility;')
  76 +if [ "${compatibility}" != "PG" ]; then
  77 + echo "数据库 ${database} 的兼容模式为 ${compatibility},不是 PG,拒绝导入" >&2
  78 + exit 1
  79 +fi
  80 +
  81 +# 5. 按 quartz、ruoyi-vue-pro 的顺序导入脚本(ON_ERROR_STOP 遇到错误立即退出,失败时保留数据库,便于排查)
  82 +"${client[@]}" -d "${database}" -f "${sql_dir}/quartz.sql"
  83 +"${client[@]}" -d "${database}" -f "${schema_file}"
  84 +
  85 +# 只统计普通表、分区表;dual 使用 GaussDB 内置视图,不计入
  86 +table_count=$("${client[@]}" -d "${database}" -At -c "SELECT COUNT(*) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = current_schema() AND c.relkind IN ('r', 'p');")
  87 +echo "GaussDB 初始化完成:数据库 ${database} 共 ${table_count} 张表"
... ...
sql/tools/mogdb/create_schema.sh 0 → 100644
  1 +#!/bin/bash
  2 +# 初始化 MogDB 测试库:在 mogdb 容器内以 omm 用户执行,创建 PG 兼容模式(DBCOMPATIBILITY = 'PG')、UTF8 编码的全新数据库,并导入 sql/opengauss 脚本
  3 +# 数据库已存在(包括空库)时拒绝导入;任一步骤失败都以非零状态退出
  4 +# 执行:docker compose exec -u omm mogdb bash /tmp/create_schema.sh
  5 +set -euo pipefail
  6 +
  7 +# 官方镜像只在 omm 用户的 .profile 中设置 gsql 的 PATH,这里显式设置
  8 +export GAUSSHOME="${GAUSSHOME:-/usr/local/mogdb}"
  9 +export PATH="${GAUSSHOME}/bin:${PATH}"
  10 +export LD_LIBRARY_PATH="${GAUSSHOME}/lib:${LD_LIBRARY_PATH:-}"
  11 +
  12 +port="${GS_PORT:-5432}"
  13 +owner="${MOGDB_OWNER:-mogdb}" # 应用连接使用的账号,官方镜像默认创建 mogdb 用户;omm 初始用户不允许远程连接
  14 +password="${GS_PASSWORD:?GS_PASSWORD 未设置,请检查 docker-compose.yaml 中 mogdb 服务的环境变量}"
  15 +database="${MOGDB_DATABASE:-ruoyi-vue-pro}"
  16 +
  17 +if [[ ! "${database}" =~ ^[A-Za-z0-9_-]+$ ]]; then
  18 + echo "数据库名 ${database} 不合法,只允许字母、数字、下划线和中划线" >&2
  19 + exit 1
  20 +fi
  21 +if [[ ! "${owner}" =~ ^[A-Za-z0-9_]+$ ]]; then
  22 + echo "用户名 ${owner} 不合法,只允许字母、数字和下划线" >&2
  23 + exit 1
  24 +fi
  25 +if [ "$(id -un)" != "omm" ]; then
  26 + echo "请使用 omm 用户执行:docker compose exec -u omm mogdb bash /tmp/create_schema.sh" >&2
  27 + exit 1
  28 +fi
  29 +
  30 +client=(gsql -p "${port}" -X -q -v ON_ERROR_STOP=1)
  31 +
  32 +# 1. 等待 MogDB 可连接(首次启动时需要初始化数据目录)
  33 +for i in $(seq 1 60); do
  34 + if "${client[@]}" -d postgres -c 'SELECT 1' >/dev/null 2>&1; then
  35 + break
  36 + fi
  37 + if [ "${i}" -eq 60 ]; then
  38 + echo "等待 MogDB 启动超时,请执行 docker compose logs mogdb 检查启动日志" >&2
  39 + exit 1
  40 + fi
  41 + sleep 5
  42 +done
  43 +
  44 +# 2. 校验数据库所有者
  45 +if [ "$("${client[@]}" -d postgres -At -c "SELECT COUNT(*) FROM pg_catalog.pg_roles WHERE rolname = '${owner}';")" != "1" ]; then
  46 + echo "用户 ${owner} 不存在:请通过 MOGDB_OWNER 指定应用连接使用的账号" >&2
  47 + exit 1
  48 +fi
  49 +
  50 +# 3. 创建全新数据库:不判断是否存在,数据库已存在时直接失败,避免项目 SQL 中的 DROP TABLE 重建已有的同名表
  51 +# 注意:MogDB 默认创建的是 A(Oracle)兼容模式的数据库,导入项目脚本会失败,需要使用 PG 兼容模式
  52 +if ! "${client[@]}" -d postgres -c "CREATE DATABASE \"${database}\" OWNER \"${owner}\" ENCODING 'UTF8' DBCOMPATIBILITY = 'PG';"; then
  53 + echo "创建数据库 ${database} 失败:只允许初始化全新的数据库,数据库已存在(包括空库)时拒绝导入;如需重新初始化,请删除 MogDB 数据卷后重建" >&2
  54 + exit 1
  55 +fi
  56 +
  57 +# 4. 使用数据库所有者,按 quartz、ruoyi-vue-pro 的顺序导入脚本(ON_ERROR_STOP 遇到错误立即退出,失败时保留数据库,便于排查)
  58 +owner_client=("${client[@]}" -U "${owner}" -W "${password}" -d "${database}")
  59 +"${owner_client[@]}" -f /tmp/quartz.sql
  60 +"${owner_client[@]}" -f /tmp/schema.sql
  61 +
  62 +# 只统计普通表、分区表
  63 +table_count=$("${owner_client[@]}" -At -c "SELECT COUNT(*) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = current_schema() AND c.relkind IN ('r', 'p');")
  64 +echo "MogDB 初始化完成:数据库 ${database} 共 ${table_count} 张表"
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java
... ... @@ -36,7 +36,7 @@ public enum DbTypeEnum {
36 36 /**
37 37 * PostgreSQL
38 38 *
39   - * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同
  39 + * 华为 openGauss(PostgreSQL JDBC 驱动)、MogDB(io.mogdb 的 openGauss JDBC 驱动)使用 ProductName 与 PostgreSQL 相同
40 40 */
41 41 POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"),
42 42  
... ... @@ -62,7 +62,22 @@ public enum DbTypeEnum {
62 62 /**
63 63 * OceanBase
64 64 */
65   - OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0")
  65 + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0"),
  66 +
  67 + /**
  68 + * 华为 GaussDB
  69 + *
  70 + * 原生驱动(jdbc:gaussdb:)使用的 ProductName 为 GaussDB,需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库
  71 + */
  72 + GAUSS_DB(DbType.GAUSS_DB, "GaussDB", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"),
  73 +
  74 + /**
  75 + * openGauss
  76 + *
  77 + * org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)使用的 ProductName 为 openGauss,连接 MogDB 时也是如此;
  78 + * 需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库
  79 + */
  80 + OPENGAUSS(DbType.OPENGAUSS, "openGauss", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0")
66 81  
67 82 ;
68 83  
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java
... ... @@ -38,14 +38,20 @@ public class JdbcUtils {
38 38 /**
39 39 * 获得 URL 对应的 DB 类型
40 40 *
41   - * 特殊:瀚高 HighGo 原生驱动(jdbc:highgo:)兼容 PostgreSQL,按 PostgreSQL 处理,复用其序列主键、Quartz 等适配
  41 + * 特殊:以下原生驱动连接的数据库兼容 PostgreSQL,按 PostgreSQL 处理,复用其序列主键、Quartz、代码生成等适配:
  42 + * 1. 瀚高 HighGo(jdbc:highgo:)
  43 + * 2. openGauss、MogDB(jdbc:opengauss:),需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库
  44 + * 3. 华为 GaussDB(jdbc:gaussdb:),需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库
  45 + *
  46 + * 注意:MyBatis Plus 的分页仍按原生 URL 识别,例如说 GaussDB 使用 GaussDBDialect
42 47 *
43 48 * @param url URL
44 49 * @return DB 类型
45 50 */
46 51 public static DbType getDbType(String url) {
47 52 DbType dbType = com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url);
48   - return dbType == DbType.HIGH_GO ? DbType.POSTGRE_SQL : dbType;
  53 + return ObjectUtils.equalsAny(dbType, DbType.HIGH_GO, DbType.OPENGAUSS, DbType.GAUSS_DB)
  54 + ? DbType.POSTGRE_SQL : dbType;
49 55 }
50 56  
51 57 /**
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java
... ... @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test;
9 9 import org.springframework.core.env.MapPropertySource;
10 10 import org.springframework.core.env.StandardEnvironment;
11 11  
  12 +import java.util.Arrays;
12 13 import java.util.HashMap;
13 14 import java.util.Map;
14 15  
... ... @@ -51,6 +52,51 @@ public class IdTypeEnvironmentPostProcessorTest {
51 52 }
52 53  
53 54 @Test
  55 + public void testPostProcessEnvironment_gaussDBNativeUrl() {
  56 + // 准备参数
  57 + StandardEnvironment environment = buildEnvironment("jdbc:gaussdb://127.0.0.1:8000/ruoyi-vue-pro", null);
  58 +
  59 + // 调用
  60 + processor.postProcessEnvironment(environment, null);
  61 +
  62 + // 断言:GaussDB(PG 兼容模式)与 PostgreSQL 一致,使用序列主键及 PostgreSQLDelegate
  63 + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY));
  64 + assertEquals(POSTGRESQL_DELEGATE, environment.getProperty(QUARTZ_DELEGATE_KEY));
  65 + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment));
  66 + }
  67 +
  68 + @Test
  69 + public void testPostProcessEnvironment_openGaussNativeUrl() {
  70 + // 准备参数:openGauss、MogDB 使用 openGauss JDBC 驱动
  71 + StandardEnvironment environment = buildEnvironment("jdbc:opengauss://127.0.0.1:15432/ruoyi-vue-pro", null);
  72 +
  73 + // 调用
  74 + processor.postProcessEnvironment(environment, null);
  75 +
  76 + // 断言:与 PostgreSQL 一致,使用序列主键及 PostgreSQLDelegate
  77 + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY));
  78 + assertEquals(POSTGRESQL_DELEGATE, environment.getProperty(QUARTZ_DELEGATE_KEY));
  79 + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment));
  80 + }
  81 +
  82 + @Test
  83 + public void testPostProcessEnvironment_notNormalizedNativeUrls() {
  84 + // 海量 Vastbase 原生驱动、神通、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate
  85 + for (String url : Arrays.asList("jdbc:vastbase://127.0.0.1:5432/ruoyi-vue-pro",
  86 + "jdbc:oscar://127.0.0.1:2003/OSRDB", "jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro")) {
  87 + // 准备参数
  88 + StandardEnvironment environment = buildEnvironment(url, null);
  89 +
  90 + // 调用
  91 + processor.postProcessEnvironment(environment, null);
  92 +
  93 + // 断言
  94 + assertEquals("AUTO", environment.getProperty(ID_TYPE_KEY), url);
  95 + assertNull(environment.getProperty(QUARTZ_DELEGATE_KEY), url);
  96 + }
  97 + }
  98 +
  99 + @Test
54 100 public void testPostProcessEnvironment_postgresqlUrl() {
55 101 // 准备参数
56 102 StandardEnvironment environment = buildEnvironment("jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro", null);
... ... @@ -95,12 +141,19 @@ public class IdTypeEnvironmentPostProcessorTest {
95 141 }
96 142  
97 143 @Test
98   - public void testGetDbType_onlyNormalizeHighGo() {
  144 + public void testGetDbType_normalizePostgreSQLCompatible() {
  145 + // 按 PostgreSQL 处理:瀚高、openGauss(MogDB)、GaussDB 的原生驱动
99 146 assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro"));
  147 + assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:opengauss://127.0.0.1:15432/ruoyi-vue-pro"));
  148 + assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:gaussdb://127.0.0.1:8000/ruoyi-vue-pro"));
100 149 assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro"));
  150 + // 保持 MyBatis Plus 的识别结果
101 151 assertEquals(DbType.MYSQL, JdbcUtils.getDbType("jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro"));
102 152 assertEquals(DbType.KINGBASE_ES, JdbcUtils.getDbType("jdbc:kingbase8://127.0.0.1:54321/test"));
103 153 assertEquals(DbType.DM, JdbcUtils.getDbType("jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO"));
  154 + assertEquals(DbType.VASTBASE, JdbcUtils.getDbType("jdbc:vastbase://127.0.0.1:5432/ruoyi-vue-pro"));
  155 + assertEquals(DbType.OSCAR, JdbcUtils.getDbType("jdbc:oscar://127.0.0.1:2003/OSRDB"));
  156 + assertEquals(DbType.OTHER, JdbcUtils.getDbType("jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro"));
104 157 }
105 158  
106 159 private static StandardEnvironment buildEnvironment(String url, Map<String, Object> extra) {
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnumTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mybatis.core.enums;
  2 +
  3 +import com.baomidou.mybatisplus.annotation.DbType;
  4 +import org.junit.jupiter.api.Test;
  5 +
  6 +import static org.junit.jupiter.api.Assertions.assertEquals;
  7 +import static org.junit.jupiter.api.Assertions.assertNull;
  8 +
  9 +/**
  10 + * {@link DbTypeEnum} 的单元测试
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class DbTypeEnumTest {
  15 +
  16 + @Test
  17 + public void testFind() {
  18 + // 华为 GaussDB 原生驱动(jdbc:gaussdb:)返回的 ProductName
  19 + assertEquals(DbType.GAUSS_DB, DbTypeEnum.find("GaussDB"));
  20 + // org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)连接 MogDB 时返回的 ProductName
  21 + assertEquals(DbType.OPENGAUSS, DbTypeEnum.find("openGauss"));
  22 + // io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时,返回的 ProductName 与 PostgreSQL 相同
  23 + assertEquals(DbType.POSTGRE_SQL, DbTypeEnum.find("PostgreSQL"));
  24 + assertEquals(DbType.KINGBASE_ES, DbTypeEnum.find("KingbaseES"));
  25 + assertNull(DbTypeEnum.find(""));
  26 + }
  27 +
  28 + @Test
  29 + public void testGetFindInSetTemplate_postgreSQLCompatible() {
  30 + // GaussDB、openGauss(PG 兼容模式)与 PostgreSQL 使用相同的 POSITION 模板
  31 + assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.GAUSS_DB));
  32 + assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.OPENGAUSS));
  33 + }
  34 +
  35 +}
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java
... ... @@ -113,6 +113,10 @@ public class MyBatisUtilsTest {
113 113 MyBatisUtils.findInSet(DbType.POSTGRE_SQL, "websites", 1));
114 114 assertEquals("CHARINDEX(',' + CAST({2} AS varchar(255)) + ',', ',' + websites + ',') > 0",
115 115 MyBatisUtils.findInSet(DbType.SQL_SERVER, "websites", 2));
  116 + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0",
  117 + MyBatisUtils.findInSet(DbType.GAUSS_DB, "websites", 0));
  118 + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0",
  119 + MyBatisUtils.findInSet(DbType.OPENGAUSS, "websites", 0));
116 120 }
117 121  
118 122 @Test
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.config;
2 2  
3 3 import cn.hutool.core.collection.ListUtil;
  4 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  5 +import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
4 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory;
5 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker;
6 8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
7 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
8 10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  11 +import lombok.extern.slf4j.Slf4j;
9 12 import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate;
10 13 import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
  14 +import org.flowable.common.engine.impl.AbstractEngineConfiguration;
11 15 import org.flowable.spring.SpringProcessEngineConfiguration;
12 16 import org.flowable.spring.boot.EngineConfigurationConfigurer;
13 17 import org.springframework.beans.factory.ObjectProvider;
... ... @@ -18,6 +22,8 @@ import org.springframework.context.annotation.Configuration;
18 22 import org.springframework.core.task.AsyncTaskExecutor;
19 23 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
20 24  
  25 +import java.sql.Connection;
  26 +import java.sql.SQLException;
21 27 import java.util.List;
22 28  
23 29 /**
... ... @@ -26,6 +32,7 @@ import java.util.List;
26 32 * @author jason
27 33 */
28 34 @Configuration(proxyBeanMethods = false)
  35 +@Slf4j
29 36 public class BpmFlowableConfiguration {
30 37  
31 38 /**
... ... @@ -53,6 +60,7 @@ public class BpmFlowableConfiguration {
53 60 *
54 61 * 1. 设置各种监听器
55 62 * 2. 设置自定义的 ActivityBehaviorFactory 实现
  63 + * 3. 设置华为 GaussDB、openGauss(含 MogDB)的数据库类型
56 64 */
57 65 @Bean
58 66 public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer(
... ... @@ -66,9 +74,36 @@ public class BpmFlowableConfiguration {
66 74 configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
67 75 // 设置自定义的函数
68 76 configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator()));
  77 + // 设置华为 GaussDB、openGauss(含 MogDB)的数据库类型
  78 + initPostgreSQLCompatibleDatabaseType(configuration);
69 79 };
70 80 }
71 81  
  82 + /**
  83 + * 以下原生驱动返回的 ProductName 不是 PostgreSQL,Flowable 无法识别,启动时会报错:
  84 + * 1. 华为 GaussDB 的原生驱动(jdbc:gaussdb:),返回 GaussDB
  85 + * 2. org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:),返回 openGauss,连接 MogDB 时也是如此
  86 + *
  87 + * 因此,在未显式设置 databaseType 时,按 PostgreSQL 处理(需要使用 PG 兼容模式创建的数据库)。
  88 + * 只读取 Flowable 自身数据源的元数据,其它数据库不做处理,仍由 Flowable 自动识别
  89 + *
  90 + * @param configuration Flowable 配置
  91 + */
  92 + private static void initPostgreSQLCompatibleDatabaseType(SpringProcessEngineConfiguration configuration) {
  93 + if (configuration.getDatabaseType() != null || configuration.getDataSource() == null) {
  94 + return;
  95 + }
  96 + try (Connection connection = configuration.getDataSource().getConnection()) {
  97 + if (ObjectUtils.equalsAny(connection.getMetaData().getDatabaseProductName(),
  98 + DbTypeEnum.GAUSS_DB.getProductName(), DbTypeEnum.OPENGAUSS.getProductName())) {
  99 + configuration.setDatabaseType(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES);
  100 + }
  101 + } catch (SQLException ex) {
  102 + // 获取失败时,不做处理,由 Flowable 自动识别时报错
  103 + log.warn("[initPostgreSQLCompatibleDatabaseType][获取数据库类型失败]", ex);
  104 + }
  105 + }
  106 +
72 107 // =========== 审批人相关的 Bean ==========
73 108  
74 109 @Bean
... ...
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfigurationTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.bpm.framework.flowable.config;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  4 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory;
  5 +import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate;
  6 +import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
  7 +import org.flowable.common.engine.impl.AbstractEngineConfiguration;
  8 +import org.flowable.spring.SpringProcessEngineConfiguration;
  9 +import org.junit.jupiter.api.Test;
  10 +import org.mockito.Mock;
  11 +import org.springframework.beans.factory.ObjectProvider;
  12 +
  13 +import javax.sql.DataSource;
  14 +import java.sql.Connection;
  15 +import java.sql.DatabaseMetaData;
  16 +import java.sql.SQLException;
  17 +import java.util.Collections;
  18 +import java.util.stream.Stream;
  19 +
  20 +import static org.junit.jupiter.api.Assertions.assertEquals;
  21 +import static org.junit.jupiter.api.Assertions.assertNull;
  22 +import static org.mockito.Mockito.never;
  23 +import static org.mockito.Mockito.verify;
  24 +import static org.mockito.Mockito.when;
  25 +
  26 +/**
  27 + * {@link BpmFlowableConfiguration} 的单元测试
  28 + *
  29 + * @author 芋道源码
  30 + */
  31 +public class BpmFlowableConfigurationTest extends BaseMockitoUnitTest {
  32 +
  33 + @Mock
  34 + private ObjectProvider<FlowableEventListener> listeners;
  35 + @Mock
  36 + private ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates;
  37 +
  38 + @Mock
  39 + private DataSource dataSource;
  40 + @Mock
  41 + private Connection connection;
  42 + @Mock
  43 + private DatabaseMetaData metaData;
  44 +
  45 + @Test
  46 + public void testBpmProcessEngineConfigurationConfigurer_gaussDB() throws SQLException {
  47 + // mock 方法:GaussDB 原生驱动返回的 ProductName
  48 + mockDatabaseProductName("GaussDB");
  49 + // 准备参数
  50 + SpringProcessEngineConfiguration configuration = buildConfiguration(null);
  51 +
  52 + // 调用
  53 + configure(configuration);
  54 +
  55 + // 断言:按 PostgreSQL 处理,并关闭连接
  56 + assertEquals(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES, configuration.getDatabaseType());
  57 + verify(connection).close();
  58 + }
  59 +
  60 + @Test
  61 + public void testBpmProcessEngineConfigurationConfigurer_openGauss() throws SQLException {
  62 + // mock 方法:org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)连接 MogDB 时返回的 ProductName
  63 + mockDatabaseProductName("openGauss");
  64 + // 准备参数
  65 + SpringProcessEngineConfiguration configuration = buildConfiguration(null);
  66 +
  67 + // 调用
  68 + configure(configuration);
  69 +
  70 + // 断言:按 PostgreSQL 处理,并关闭连接
  71 + assertEquals(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES, configuration.getDatabaseType());
  72 + verify(connection).close();
  73 + }
  74 +
  75 + @Test
  76 + public void testBpmProcessEngineConfigurationConfigurer_postgreSQL() throws SQLException {
  77 + // mock 方法:PostgreSQL,以及 io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时返回的 ProductName
  78 + mockDatabaseProductName("PostgreSQL");
  79 + // 准备参数
  80 + SpringProcessEngineConfiguration configuration = buildConfiguration(null);
  81 +
  82 + // 调用
  83 + configure(configuration);
  84 +
  85 + // 断言:不处理,仍由 Flowable 自动识别
  86 + assertNull(configuration.getDatabaseType());
  87 + verify(connection).close();
  88 + }
  89 +
  90 + @Test
  91 + public void testBpmProcessEngineConfigurationConfigurer_explicitDatabaseType() throws SQLException {
  92 + // 准备参数:用户显式设置 databaseType
  93 + SpringProcessEngineConfiguration configuration = buildConfiguration(AbstractEngineConfiguration.DATABASE_TYPE_MYSQL);
  94 +
  95 + // 调用
  96 + configure(configuration);
  97 +
  98 + // 断言:不覆盖,且不获取连接
  99 + assertEquals(AbstractEngineConfiguration.DATABASE_TYPE_MYSQL, configuration.getDatabaseType());
  100 + verify(dataSource, never()).getConnection();
  101 + }
  102 +
  103 + @Test
  104 + public void testBpmProcessEngineConfigurationConfigurer_connectionError() throws SQLException {
  105 + // mock 方法:获取连接失败
  106 + when(dataSource.getConnection()).thenThrow(new SQLException("connection refused"));
  107 + // 准备参数
  108 + SpringProcessEngineConfiguration configuration = buildConfiguration(null);
  109 +
  110 + // 调用
  111 + configure(configuration);
  112 +
  113 + // 断言:不处理,由 Flowable 自动识别时报错
  114 + assertNull(configuration.getDatabaseType());
  115 + }
  116 +
  117 + private void mockDatabaseProductName(String productName) throws SQLException {
  118 + when(dataSource.getConnection()).thenReturn(connection);
  119 + when(connection.getMetaData()).thenReturn(metaData);
  120 + when(metaData.getDatabaseProductName()).thenReturn(productName);
  121 + }
  122 +
  123 + private SpringProcessEngineConfiguration buildConfiguration(String databaseType) {
  124 + SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration();
  125 + configuration.setDataSource(dataSource);
  126 + configuration.setDatabaseType(databaseType);
  127 + return configuration;
  128 + }
  129 +
  130 + private void configure(SpringProcessEngineConfiguration configuration) {
  131 + when(listeners.iterator()).thenReturn(Collections.emptyIterator());
  132 + when(customFlowableFunctionDelegates.stream()).thenReturn(Stream.empty());
  133 + new BpmFlowableConfiguration().bpmProcessEngineConfigurationConfigurer(listeners,
  134 + customFlowableFunctionDelegates, new BpmActivityBehaviorFactory()).configure(configuration);
  135 + }
  136 +
  137 +}
... ...