Commit 679d1bbdbdf003abff02f3ec426ac43773acb0f1

Authored by YunaiV
1 parent 8c5e7048

feat: 支持神通数据库普通 CRUD 与代码生成

sql/tools/README.md
@@ -219,6 +219,44 @@ password: Yudao@2024 @@ -219,6 +219,44 @@ password: Yudao@2024
219 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-数据库) 文档。 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 221
  222 +### 1.13 神通数据库
  223 +
  224 +神通数据库需要自行安装,安装介质和授权请通过 [神通官方下载中心](http://shentongdata.com/index.php/download/list-27) 获取。项目复用 `sql/opengauss` 脚本,默认仍然使用 MySQL。
  225 +
  226 +① 初始化数据库:创建神通账号时,会自动创建同名的模式,项目的表创建在该模式中(`url` 中的 `currentSchema` 参数不生效)。神通已内置 `dual`,且 `varchar` 最大长度为 8000,所以先在 `sql/tools` 目录下生成临时脚本:只跳过创建 `dual` 表的一段,并将 `varchar(10240)` 改为 `text`,其余 SQL 保持不变:
  227 +
  228 +```Bash
  229 +sed -e '/^-- Table structure for dual$/,/^-- @formatter:on$/d' -e 's/varchar(10240)/text/' ../opengauss/ruoyi-vue-pro.sql > /tmp/ruoyi-vue-pro-shentong.sql
  230 +```
  231 +
  232 +使用神通的客户端或其它 JDBC 工具,以该账号执行 `/tmp/ruoyi-vue-pro-shentong.sql`。导入完成后,再执行如下 SQL,让 `system_menu` 的主键默认使用 `system_menu_seq` 序列(代码生成的菜单 SQL 与 Oracle、达梦相同,插入菜单时不指定主键):
  233 +
  234 +```sql
  235 +ALTER TABLE system_menu ALTER COLUMN id SET DEFAULT nextval('system_menu_seq');
  236 +```
  237 +
  238 +> 注意:神通暂未适配 Quartz,不需要导入 `sql/opengauss/quartz.sql`;`application-local.yaml` 默认排除了 Quartz 的自动配置,保持不变即可。
  239 +
  240 +② 项目使用神通 JDBC 驱动(`com.oscar.Driver`),使用序列主键(复用 MyBatis Plus 的 `PostgreKeyGenerator`),分页使用 MyBatis Plus 内置的神通方言。将数据源配置中的地址、账号和密码修改为:
  241 +
  242 +```yaml
  243 +url: jdbc:oscar://127.0.0.1:2003/OSRDB
  244 +username: 神通账号
  245 +password: 神通密码
  246 +```
  247 +
  248 +驱动需要引入到 `yudao-server/pom.xml` 的 `<dependencies>` 中(`yudao-dependencies`、`yudao-spring-boot-starter-mybatis` 不包含该驱动):
  249 +
  250 +```xml
  251 +<dependency>
  252 + <groupId>com.shentongdata</groupId>
  253 + <artifactId>oscarJDBC8</artifactId>
  254 + <version>4.1.152</version>
  255 +</dependency>
  256 +```
  257 +
  258 +> 注意:该驱动是 Maven 中央仓库上 `com.shentongdata` 发布的版本,不是神通官方下载中心的配套认证版本。项目适配时使用的是神通 7.0.14,其它版本请自行验证。
  259 +
222 ## 1.X 容器的销毁重建 260 ## 1.X 容器的销毁重建
223 261
224 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 262 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java
@@ -30,7 +30,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor @@ -30,7 +30,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor
30 private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass"; 30 private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass";
31 31
32 private static final Set<DbType> INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C, 32 private static final Set<DbType> INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C,
33 - DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2); 33 + DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2, DbType.OSCAR);
34 34
35 @Override 35 @Override
36 public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { 36 public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
@@ -49,7 +49,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor @@ -49,7 +49,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor
49 if (idType != IdType.NONE) { 49 if (idType != IdType.NONE) {
50 return; 50 return;
51 } 51 }
52 - // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 52 + // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2、神通数据库
53 if (INPUT_ID_TYPES.contains(dbType)) { 53 if (INPUT_ID_TYPES.contains(dbType)) {
54 setIdType(environment, IdType.INPUT); 54 setIdType(environment, IdType.INPUT);
55 return; 55 return;
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
@@ -87,6 +87,7 @@ public class YudaoMybatisAutoConfiguration { @@ -87,6 +87,7 @@ public class YudaoMybatisAutoConfiguration {
87 if (dbType != null) { 87 if (dbType != null) {
88 switch (dbType) { 88 switch (dbType) {
89 case POSTGRE_SQL: 89 case POSTGRE_SQL:
  90 + case OSCAR: // 神通:与 PostgreSQL 相同,使用 nextval('序列名') 获取序列值
90 return new PostgreKeyGenerator(); 91 return new PostgreKeyGenerator();
91 case ORACLE: 92 case ORACLE:
92 case ORACLE_12C: 93 case ORACLE_12C:
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java
@@ -77,7 +77,14 @@ public enum DbTypeEnum { @@ -77,7 +77,14 @@ public enum DbTypeEnum {
77 * org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)使用的 ProductName 为 openGauss,连接 MogDB 时也是如此; 77 * org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)使用的 ProductName 为 openGauss,连接 MogDB 时也是如此;
78 * 需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库 78 * 需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库
79 */ 79 */
80 - OPENGAUSS(DbType.OPENGAUSS, "openGauss", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0") 80 + OPENGAUSS(DbType.OPENGAUSS, "openGauss", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"),
  81 +
  82 + /**
  83 + * 神通
  84 + *
  85 + * 没有 FIND_IN_SET 函数;CAST(... AS VARCHAR) 不指定长度时为 VARCHAR(1),所以不使用 CAST
  86 + */
  87 + OSCAR(DbType.OSCAR, "OSCAR", "POSITION(',' || #{value} || ',' IN ',' || #{column} || ',') > 0")
81 88
82 ; 89 ;
83 90
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java
@@ -80,10 +80,24 @@ public class IdTypeEnvironmentPostProcessorTest { @@ -80,10 +80,24 @@ public class IdTypeEnvironmentPostProcessorTest {
80 } 80 }
81 81
82 @Test 82 @Test
  83 + public void testPostProcessEnvironment_oscarUrl() {
  84 + // 准备参数
  85 + StandardEnvironment environment = buildEnvironment("jdbc:oscar://127.0.0.1:2003/OSRDB", null);
  86 +
  87 + // 调用
  88 + processor.postProcessEnvironment(environment, null);
  89 +
  90 + // 断言:神通不按 PostgreSQL 处理,使用序列主键(复用 PostgreKeyGenerator),但不设置 Quartz Delegate
  91 + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY));
  92 + assertNull(environment.getProperty(QUARTZ_DELEGATE_KEY));
  93 + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment));
  94 + }
  95 +
  96 + @Test
83 public void testPostProcessEnvironment_notNormalizedNativeUrls() { 97 public void testPostProcessEnvironment_notNormalizedNativeUrls() {
84 - // 海量 Vastbase 原生驱动、神通、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate 98 + // 海量 Vastbase 原生驱动、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate
85 for (String url : Arrays.asList("jdbc:vastbase://127.0.0.1:5432/ruoyi-vue-pro", 99 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")) { 100 + "jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro")) {
87 // 准备参数 101 // 准备参数
88 StandardEnvironment environment = buildEnvironment(url, null); 102 StandardEnvironment environment = buildEnvironment(url, null);
89 103
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnumTest.java
@@ -22,6 +22,8 @@ public class DbTypeEnumTest { @@ -22,6 +22,8 @@ public class DbTypeEnumTest {
22 // io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时,返回的 ProductName 与 PostgreSQL 相同 22 // io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时,返回的 ProductName 与 PostgreSQL 相同
23 assertEquals(DbType.POSTGRE_SQL, DbTypeEnum.find("PostgreSQL")); 23 assertEquals(DbType.POSTGRE_SQL, DbTypeEnum.find("PostgreSQL"));
24 assertEquals(DbType.KINGBASE_ES, DbTypeEnum.find("KingbaseES")); 24 assertEquals(DbType.KINGBASE_ES, DbTypeEnum.find("KingbaseES"));
  25 + // 神通 JDBC 驱动(jdbc:oscar:)返回的 ProductName
  26 + assertEquals(DbType.OSCAR, DbTypeEnum.find("OSCAR"));
25 assertNull(DbTypeEnum.find("")); 27 assertNull(DbTypeEnum.find(""));
26 } 28 }
27 29
@@ -32,4 +34,11 @@ public class DbTypeEnumTest { @@ -32,4 +34,11 @@ public class DbTypeEnumTest {
32 assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.OPENGAUSS)); 34 assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.OPENGAUSS));
33 } 35 }
34 36
  37 + @Test
  38 + public void testGetFindInSetTemplate_oscar() {
  39 + // 神通的 CAST(... AS VARCHAR) 不指定长度时为 VARCHAR(1),所以不使用 CAST
  40 + assertEquals("POSITION(',' || #{value} || ',' IN ',' || #{column} || ',') > 0",
  41 + DbTypeEnum.getFindInSetTemplate(DbType.OSCAR));
  42 + }
  43 +
35 } 44 }
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java
@@ -5,13 +5,16 @@ import cn.hutool.core.lang.Assert; @@ -5,13 +5,16 @@ import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; 6 import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
7 import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; 7 import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
  8 +import com.baomidou.mybatisplus.annotation.DbType;
8 import com.baomidou.mybatisplus.generator.config.DataSourceConfig; 9 import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
9 import com.baomidou.mybatisplus.generator.config.GlobalConfig; 10 import com.baomidou.mybatisplus.generator.config.GlobalConfig;
10 import com.baomidou.mybatisplus.generator.config.StrategyConfig; 11 import com.baomidou.mybatisplus.generator.config.StrategyConfig;
11 import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder; 12 import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
12 import com.baomidou.mybatisplus.generator.config.po.TableInfo; 13 import com.baomidou.mybatisplus.generator.config.po.TableInfo;
13 import com.baomidou.mybatisplus.generator.config.rules.DateType; 14 import com.baomidou.mybatisplus.generator.config.rules.DateType;
  15 +import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
14 import com.baomidou.mybatisplus.generator.query.SQLQuery; 16 import com.baomidou.mybatisplus.generator.query.SQLQuery;
  17 +import com.baomidou.mybatisplus.generator.type.ITypeConvertHandler;
15 import jakarta.annotation.Resource; 18 import jakarta.annotation.Resource;
16 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
17 20
@@ -27,6 +30,12 @@ import java.util.stream.Collectors; @@ -27,6 +30,12 @@ import java.util.stream.Collectors;
27 @Service 30 @Service
28 public class DatabaseTableServiceImpl implements DatabaseTableService { 31 public class DatabaseTableServiceImpl implements DatabaseTableService {
29 32
  33 + /**
  34 + * 神通的字段类型转换:TEXT 类型的 JDBC 类型为 OTHER,按 String 处理;其它类型,使用 MyBatis Plus Generator 默认的转换
  35 + */
  36 + static final ITypeConvertHandler OSCAR_TYPE_CONVERT_HANDLER = (globalConfig, typeRegistry, metaInfo) ->
  37 + StrUtil.equalsIgnoreCase(metaInfo.getTypeName(), "TEXT") ? DbColumnType.STRING : typeRegistry.getColumnType(metaInfo);
  38 +
30 @Resource 39 @Resource
31 private DataSourceConfigService dataSourceConfigService; 40 private DataSourceConfigService dataSourceConfigService;
32 41
@@ -54,6 +63,9 @@ public class DatabaseTableServiceImpl implements DatabaseTableService { @@ -54,6 +63,9 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
54 if (JdbcUtils.isSQLServer(config.getUrl())) { // 特殊:SQLServer jdbc 非标准,参见 https://github.com/baomidou/mybatis-plus/issues/5419 63 if (JdbcUtils.isSQLServer(config.getUrl())) { // 特殊:SQLServer jdbc 非标准,参见 https://github.com/baomidou/mybatis-plus/issues/5419
55 dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class); 64 dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class);
56 } 65 }
  66 + if (JdbcUtils.getDbType(config.getUrl()) == DbType.OSCAR) {
  67 + dataSourceConfigBuilder.typeConvertHandler(OSCAR_TYPE_CONVERT_HANDLER);
  68 + }
57 StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图,业务上一般用不到 69 StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图,业务上一般用不到
58 if (StrUtil.isNotEmpty(name)) { 70 if (StrUtil.isNotEmpty(name)) {
59 strategyConfig.addInclude(name); 71 strategyConfig.addInclude(name);
yudao-module-infra/src/main/resources/codegen/sql/sql.vm
@@ -50,8 +50,8 @@ VALUES ( @@ -50,8 +50,8 @@ VALUES (
50 ); 50 );
51 #end 51 #end
52 ## 52 ##
53 -## ======================= Oracle / 达梦 DM =======================  
54 -#elseif ($dbType.name() == 'ORACLE' || $dbType.name() == 'DM') 53 +## ======================= Oracle / 达梦 DM / 神通 OSCAR =======================
  54 +#elseif ($dbType.name() == 'ORACLE' || $dbType.name() == 'DM' || $dbType.name() == 'OSCAR')
55 -- 菜单 SQL 55 -- 菜单 SQL
56 INSERT INTO system_menu( 56 INSERT INTO system_menu(
57 name, permission, type, sort, parent_id, 57 name, permission, type, sort, parent_id,
@@ -63,7 +63,10 @@ VALUES ( @@ -63,7 +63,10 @@ VALUES (
63 ); 63 );
64 64
65 -- 按钮父菜单ID 65 -- 按钮父菜单ID
66 --- 说明:Oracle/达梦 使用序列获取上一个插入的 ID 66 +-- 说明:Oracle/达梦/神通 使用序列获取上一个插入的 ID
  67 +#if ($dbType.name() == 'OSCAR')
  68 +-- 注意:神通需要 system_menu 的 id 默认使用 system_menu_seq 序列,参见 sql/tools/README.md 的神通部分
  69 +#end
67 DECLARE 70 DECLARE
68 v_parent_id NUMBER; 71 v_parent_id NUMBER;
69 BEGIN 72 BEGIN
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java
@@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.infra.service.db; @@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.infra.service.db;
2 2
3 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 3 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
4 import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; 4 import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
  5 +import com.baomidou.mybatisplus.generator.config.GlobalConfig;
5 import com.baomidou.mybatisplus.generator.config.po.TableField; 6 import com.baomidou.mybatisplus.generator.config.po.TableField;
6 import com.baomidou.mybatisplus.generator.config.po.TableInfo; 7 import com.baomidou.mybatisplus.generator.config.po.TableInfo;
7 import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; 8 import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
  9 +import com.baomidou.mybatisplus.generator.type.TypeRegistry;
8 import jakarta.annotation.Resource; 10 import jakarta.annotation.Resource;
9 import org.apache.ibatis.type.JdbcType; 11 import org.apache.ibatis.type.JdbcType;
10 import org.junit.jupiter.api.Test; 12 import org.junit.jupiter.api.Test;
@@ -61,6 +63,26 @@ public class DatabaseTableServiceImplTest extends BaseDbUnitTest { @@ -61,6 +63,26 @@ public class DatabaseTableServiceImplTest extends BaseDbUnitTest {
61 assertTableInfo(tableInfo); 63 assertTableInfo(tableInfo);
62 } 64 }
63 65
  66 + @Test
  67 + public void testOscarTypeConvertHandler() {
  68 + TypeRegistry typeRegistry = new TypeRegistry(new GlobalConfig.Builder().build());
  69 + // 神通的 TEXT 类型,JDBC 类型为 OTHER,按 String 处理
  70 + assertEquals(DbColumnType.STRING, DatabaseTableServiceImpl.OSCAR_TYPE_CONVERT_HANDLER
  71 + .convert(null, typeRegistry, buildMetaInfo(JdbcType.OTHER, "TEXT")));
  72 + // 其它 OTHER 类型,保持 MyBatis Plus Generator 默认的转换
  73 + assertEquals(DbColumnType.OBJECT, DatabaseTableServiceImpl.OSCAR_TYPE_CONVERT_HANDLER
  74 + .convert(null, typeRegistry, buildMetaInfo(JdbcType.OTHER, "JSON")));
  75 + assertEquals(DbColumnType.LONG, DatabaseTableServiceImpl.OSCAR_TYPE_CONVERT_HANDLER
  76 + .convert(null, typeRegistry, buildMetaInfo(JdbcType.BIGINT, "BIGINT")));
  77 + }
  78 +
  79 + private static TableField.MetaInfo buildMetaInfo(JdbcType jdbcType, String typeName) {
  80 + TableField.MetaInfo metaInfo = new TableField.MetaInfo(null, null);
  81 + metaInfo.setJdbcType(jdbcType);
  82 + metaInfo.setTypeName(typeName);
  83 + return metaInfo;
  84 + }
  85 +
64 private void assertTableInfo(TableInfo tableInfo) { 86 private void assertTableInfo(TableInfo tableInfo) {
65 assertEquals("infra_config", tableInfo.getName()); 87 assertEquals("infra_config", tableInfo.getName());
66 assertEquals("参数配置表", tableInfo.getComment()); 88 assertEquals("参数配置表", tableInfo.getComment());