diff --git a/sql/tools/README.md b/sql/tools/README.md index 096ca91..5cf7f09 100644 --- a/sql/tools/README.md +++ b/sql/tools/README.md @@ -219,6 +219,44 @@ password: Yudao@2024 > 注意:端口只绑定本机 `127.0.0.1:15432`;`omm` 初始用户不允许远程连接,项目使用 `mogdb` 用户。`jdbc:opengauss:` 按 PostgreSQL 处理序列主键、Quartz 等差异,驱动需要引入到 `yudao-server/pom.xml` 的 `` 中。详见 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/#_11-云和恩墨-mogdb-数据库) 文档。 +### 1.13 神通数据库 + +神通数据库需要自行安装,安装介质和授权请通过 [神通官方下载中心](http://shentongdata.com/index.php/download/list-27) 获取。项目复用 `sql/opengauss` 脚本,默认仍然使用 MySQL。 + +① 初始化数据库:创建神通账号时,会自动创建同名的模式,项目的表创建在该模式中(`url` 中的 `currentSchema` 参数不生效)。神通已内置 `dual`,且 `varchar` 最大长度为 8000,所以先在 `sql/tools` 目录下生成临时脚本:只跳过创建 `dual` 表的一段,并将 `varchar(10240)` 改为 `text`,其余 SQL 保持不变: + +```Bash +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 +``` + +使用神通的客户端或其它 JDBC 工具,以该账号执行 `/tmp/ruoyi-vue-pro-shentong.sql`。导入完成后,再执行如下 SQL,让 `system_menu` 的主键默认使用 `system_menu_seq` 序列(代码生成的菜单 SQL 与 Oracle、达梦相同,插入菜单时不指定主键): + +```sql +ALTER TABLE system_menu ALTER COLUMN id SET DEFAULT nextval('system_menu_seq'); +``` + +> 注意:神通暂未适配 Quartz,不需要导入 `sql/opengauss/quartz.sql`;`application-local.yaml` 默认排除了 Quartz 的自动配置,保持不变即可。 + +② 项目使用神通 JDBC 驱动(`com.oscar.Driver`),使用序列主键(复用 MyBatis Plus 的 `PostgreKeyGenerator`),分页使用 MyBatis Plus 内置的神通方言。将数据源配置中的地址、账号和密码修改为: + +```yaml +url: jdbc:oscar://127.0.0.1:2003/OSRDB +username: 神通账号 +password: 神通密码 +``` + +驱动需要引入到 `yudao-server/pom.xml` 的 `` 中(`yudao-dependencies`、`yudao-spring-boot-starter-mybatis` 不包含该驱动): + +```xml + + com.shentongdata + oscarJDBC8 + 4.1.152 + +``` + +> 注意:该驱动是 Maven 中央仓库上 `com.shentongdata` 发布的版本,不是神通官方下载中心的配套认证版本。项目适配时使用的是神通 7.0.14,其它版本请自行验证。 + ## 1.X 容器的销毁重建 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java index 0ea6bf2..534cd24 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java +++ b/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 private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass"; private static final Set INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C, - DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2); + DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2, DbType.OSCAR); @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { @@ -49,7 +49,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor if (idType != IdType.NONE) { return; } - // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2、神通数据库 if (INPUT_ID_TYPES.contains(dbType)) { setIdType(environment, IdType.INPUT); return; diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java index 4dd6718..f1f5396 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java +++ b/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 { if (dbType != null) { switch (dbType) { case POSTGRE_SQL: + case OSCAR: // 神通:与 PostgreSQL 相同,使用 nextval('序列名') 获取序列值 return new PostgreKeyGenerator(); case ORACLE: case ORACLE_12C: diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java index 43d3406..2742021 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java +++ b/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 { * org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)使用的 ProductName 为 openGauss,连接 MogDB 时也是如此; * 需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库 */ - OPENGAUSS(DbType.OPENGAUSS, "openGauss", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0") + OPENGAUSS(DbType.OPENGAUSS, "openGauss", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), + + /** + * 神通 + * + * 没有 FIND_IN_SET 函数;CAST(... AS VARCHAR) 不指定长度时为 VARCHAR(1),所以不使用 CAST + */ + OSCAR(DbType.OSCAR, "OSCAR", "POSITION(',' || #{value} || ',' IN ',' || #{column} || ',') > 0") ; diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java index bd5cc1c..2bf3eed 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java +++ b/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 { } @Test + public void testPostProcessEnvironment_oscarUrl() { + // 准备参数 + StandardEnvironment environment = buildEnvironment("jdbc:oscar://127.0.0.1:2003/OSRDB", null); + + // 调用 + processor.postProcessEnvironment(environment, null); + + // 断言:神通不按 PostgreSQL 处理,使用序列主键(复用 PostgreKeyGenerator),但不设置 Quartz Delegate + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY)); + assertNull(environment.getProperty(QUARTZ_DELEGATE_KEY)); + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment)); + } + + @Test public void testPostProcessEnvironment_notNormalizedNativeUrls() { - // 海量 Vastbase 原生驱动、神通、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate + // 海量 Vastbase 原生驱动、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate for (String url : Arrays.asList("jdbc:vastbase://127.0.0.1:5432/ruoyi-vue-pro", - "jdbc:oscar://127.0.0.1:2003/OSRDB", "jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro")) { + "jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro")) { // 准备参数 StandardEnvironment environment = buildEnvironment(url, null); diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnumTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnumTest.java index 9f3cc07..7a4698c 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnumTest.java +++ b/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 { // io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时,返回的 ProductName 与 PostgreSQL 相同 assertEquals(DbType.POSTGRE_SQL, DbTypeEnum.find("PostgreSQL")); assertEquals(DbType.KINGBASE_ES, DbTypeEnum.find("KingbaseES")); + // 神通 JDBC 驱动(jdbc:oscar:)返回的 ProductName + assertEquals(DbType.OSCAR, DbTypeEnum.find("OSCAR")); assertNull(DbTypeEnum.find("")); } @@ -32,4 +34,11 @@ public class DbTypeEnumTest { assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.OPENGAUSS)); } + @Test + public void testGetFindInSetTemplate_oscar() { + // 神通的 CAST(... AS VARCHAR) 不指定长度时为 VARCHAR(1),所以不使用 CAST + assertEquals("POSITION(',' || #{value} || ',' IN ',' || #{column} || ',') > 0", + DbTypeEnum.getFindInSetTemplate(DbType.OSCAR)); + } + } diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java index d278579..3549910 100644 --- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImpl.java +++ b/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; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; +import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.DateType; +import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; import com.baomidou.mybatisplus.generator.query.SQLQuery; +import com.baomidou.mybatisplus.generator.type.ITypeConvertHandler; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -27,6 +30,12 @@ import java.util.stream.Collectors; @Service public class DatabaseTableServiceImpl implements DatabaseTableService { + /** + * 神通的字段类型转换:TEXT 类型的 JDBC 类型为 OTHER,按 String 处理;其它类型,使用 MyBatis Plus Generator 默认的转换 + */ + static final ITypeConvertHandler OSCAR_TYPE_CONVERT_HANDLER = (globalConfig, typeRegistry, metaInfo) -> + StrUtil.equalsIgnoreCase(metaInfo.getTypeName(), "TEXT") ? DbColumnType.STRING : typeRegistry.getColumnType(metaInfo); + @Resource private DataSourceConfigService dataSourceConfigService; @@ -54,6 +63,9 @@ public class DatabaseTableServiceImpl implements DatabaseTableService { if (JdbcUtils.isSQLServer(config.getUrl())) { // 特殊:SQLServer jdbc 非标准,参见 https://github.com/baomidou/mybatis-plus/issues/5419 dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class); } + if (JdbcUtils.getDbType(config.getUrl()) == DbType.OSCAR) { + dataSourceConfigBuilder.typeConvertHandler(OSCAR_TYPE_CONVERT_HANDLER); + } StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图,业务上一般用不到 if (StrUtil.isNotEmpty(name)) { strategyConfig.addInclude(name); diff --git a/yudao-module-infra/src/main/resources/codegen/sql/sql.vm b/yudao-module-infra/src/main/resources/codegen/sql/sql.vm index 4d05660..fb1b3db 100644 --- a/yudao-module-infra/src/main/resources/codegen/sql/sql.vm +++ b/yudao-module-infra/src/main/resources/codegen/sql/sql.vm @@ -50,8 +50,8 @@ VALUES ( ); #end ## -## ======================= Oracle / 达梦 DM ======================= -#elseif ($dbType.name() == 'ORACLE' || $dbType.name() == 'DM') +## ======================= Oracle / 达梦 DM / 神通 OSCAR ======================= +#elseif ($dbType.name() == 'ORACLE' || $dbType.name() == 'DM' || $dbType.name() == 'OSCAR') -- 菜单 SQL INSERT INTO system_menu( name, permission, type, sort, parent_id, @@ -63,7 +63,10 @@ VALUES ( ); -- 按钮父菜单ID --- 说明:Oracle/达梦 使用序列获取上一个插入的 ID +-- 说明:Oracle/达梦/神通 使用序列获取上一个插入的 ID +#if ($dbType.name() == 'OSCAR') +-- 注意:神通需要 system_menu 的 id 默认使用 system_menu_seq 序列,参见 sql/tools/README.md 的神通部分 +#end DECLARE v_parent_id NUMBER; BEGIN diff --git a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java b/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java index 15d05bb..78918dd 100644 --- a/yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/db/DatabaseTableServiceImplTest.java +++ b/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; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; +import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.po.TableField; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; +import com.baomidou.mybatisplus.generator.type.TypeRegistry; import jakarta.annotation.Resource; import org.apache.ibatis.type.JdbcType; import org.junit.jupiter.api.Test; @@ -61,6 +63,26 @@ public class DatabaseTableServiceImplTest extends BaseDbUnitTest { assertTableInfo(tableInfo); } + @Test + public void testOscarTypeConvertHandler() { + TypeRegistry typeRegistry = new TypeRegistry(new GlobalConfig.Builder().build()); + // 神通的 TEXT 类型,JDBC 类型为 OTHER,按 String 处理 + assertEquals(DbColumnType.STRING, DatabaseTableServiceImpl.OSCAR_TYPE_CONVERT_HANDLER + .convert(null, typeRegistry, buildMetaInfo(JdbcType.OTHER, "TEXT"))); + // 其它 OTHER 类型,保持 MyBatis Plus Generator 默认的转换 + assertEquals(DbColumnType.OBJECT, DatabaseTableServiceImpl.OSCAR_TYPE_CONVERT_HANDLER + .convert(null, typeRegistry, buildMetaInfo(JdbcType.OTHER, "JSON"))); + assertEquals(DbColumnType.LONG, DatabaseTableServiceImpl.OSCAR_TYPE_CONVERT_HANDLER + .convert(null, typeRegistry, buildMetaInfo(JdbcType.BIGINT, "BIGINT"))); + } + + private static TableField.MetaInfo buildMetaInfo(JdbcType jdbcType, String typeName) { + TableField.MetaInfo metaInfo = new TableField.MetaInfo(null, null); + metaInfo.setJdbcType(jdbcType); + metaInfo.setTypeName(typeName); + return metaInfo; + } + private void assertTableInfo(TableInfo tableInfo) { assertEquals("infra_config", tableInfo.getName()); assertEquals("参数配置表", tableInfo.getComment());