Commit 679d1bbdbdf003abff02f3ec426ac43773acb0f1
1 parent
8c5e7048
feat: 支持神通数据库普通 CRUD 与代码生成
Showing
9 changed files
with
114 additions
and
8 deletions
sql/tools/README.md
| ... | ... | @@ -219,6 +219,44 @@ password: Yudao@2024 |
| 219 | 219 | |
| 220 | 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 | 260 | ## 1.X 容器的销毁重建 |
| 223 | 261 | |
| 224 | 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 | 30 | private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass"; |
| 31 | 31 | |
| 32 | 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 | 35 | @Override |
| 36 | 36 | public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { |
| ... | ... | @@ -49,7 +49,7 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor |
| 49 | 49 | if (idType != IdType.NONE) { |
| 50 | 50 | return; |
| 51 | 51 | } |
| 52 | - // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 | |
| 52 | + // 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2、神通数据库 | |
| 53 | 53 | if (INPUT_ID_TYPES.contains(dbType)) { |
| 54 | 54 | setIdType(environment, IdType.INPUT); |
| 55 | 55 | return; | ... | ... |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
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 | 77 | * org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)使用的 ProductName 为 openGauss,连接 MogDB 时也是如此; |
| 78 | 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 | 80 | } |
| 81 | 81 | |
| 82 | 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 | 97 | public void testPostProcessEnvironment_notNormalizedNativeUrls() { |
| 84 | - // 海量 Vastbase 原生驱动、神通、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate | |
| 98 | + // 海量 Vastbase 原生驱动、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate | |
| 85 | 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 | 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 | 22 | // io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时,返回的 ProductName 与 PostgreSQL 相同 |
| 23 | 23 | assertEquals(DbType.POSTGRE_SQL, DbTypeEnum.find("PostgreSQL")); |
| 24 | 24 | assertEquals(DbType.KINGBASE_ES, DbTypeEnum.find("KingbaseES")); |
| 25 | + // 神通 JDBC 驱动(jdbc:oscar:)返回的 ProductName | |
| 26 | + assertEquals(DbType.OSCAR, DbTypeEnum.find("OSCAR")); | |
| 25 | 27 | assertNull(DbTypeEnum.find("")); |
| 26 | 28 | } |
| 27 | 29 | |
| ... | ... | @@ -32,4 +34,11 @@ public class DbTypeEnumTest { |
| 32 | 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 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | 6 | import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; |
| 7 | 7 | import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; |
| 8 | +import com.baomidou.mybatisplus.annotation.DbType; | |
| 8 | 9 | import com.baomidou.mybatisplus.generator.config.DataSourceConfig; |
| 9 | 10 | import com.baomidou.mybatisplus.generator.config.GlobalConfig; |
| 10 | 11 | import com.baomidou.mybatisplus.generator.config.StrategyConfig; |
| 11 | 12 | import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder; |
| 12 | 13 | import com.baomidou.mybatisplus.generator.config.po.TableInfo; |
| 13 | 14 | import com.baomidou.mybatisplus.generator.config.rules.DateType; |
| 15 | +import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; | |
| 14 | 16 | import com.baomidou.mybatisplus.generator.query.SQLQuery; |
| 17 | +import com.baomidou.mybatisplus.generator.type.ITypeConvertHandler; | |
| 15 | 18 | import jakarta.annotation.Resource; |
| 16 | 19 | import org.springframework.stereotype.Service; |
| 17 | 20 | |
| ... | ... | @@ -27,6 +30,12 @@ import java.util.stream.Collectors; |
| 27 | 30 | @Service |
| 28 | 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 | 39 | @Resource |
| 31 | 40 | private DataSourceConfigService dataSourceConfigService; |
| 32 | 41 | |
| ... | ... | @@ -54,6 +63,9 @@ public class DatabaseTableServiceImpl implements DatabaseTableService { |
| 54 | 63 | if (JdbcUtils.isSQLServer(config.getUrl())) { // 特殊:SQLServer jdbc 非标准,参见 https://github.com/baomidou/mybatis-plus/issues/5419 |
| 55 | 64 | dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class); |
| 56 | 65 | } |
| 66 | + if (JdbcUtils.getDbType(config.getUrl()) == DbType.OSCAR) { | |
| 67 | + dataSourceConfigBuilder.typeConvertHandler(OSCAR_TYPE_CONVERT_HANDLER); | |
| 68 | + } | |
| 57 | 69 | StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图,业务上一般用不到 |
| 58 | 70 | if (StrUtil.isNotEmpty(name)) { |
| 59 | 71 | strategyConfig.addInclude(name); | ... | ... |
yudao-module-infra/src/main/resources/codegen/sql/sql.vm
| ... | ... | @@ -50,8 +50,8 @@ VALUES ( |
| 50 | 50 | ); |
| 51 | 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 | 55 | -- 菜单 SQL |
| 56 | 56 | INSERT INTO system_menu( |
| 57 | 57 | name, permission, type, sort, parent_id, |
| ... | ... | @@ -63,7 +63,10 @@ VALUES ( |
| 63 | 63 | ); |
| 64 | 64 | |
| 65 | 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 | 70 | DECLARE |
| 68 | 71 | v_parent_id NUMBER; |
| 69 | 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 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| 4 | 4 | import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO; |
| 5 | +import com.baomidou.mybatisplus.generator.config.GlobalConfig; | |
| 5 | 6 | import com.baomidou.mybatisplus.generator.config.po.TableField; |
| 6 | 7 | import com.baomidou.mybatisplus.generator.config.po.TableInfo; |
| 7 | 8 | import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; |
| 9 | +import com.baomidou.mybatisplus.generator.type.TypeRegistry; | |
| 8 | 10 | import jakarta.annotation.Resource; |
| 9 | 11 | import org.apache.ibatis.type.JdbcType; |
| 10 | 12 | import org.junit.jupiter.api.Test; |
| ... | ... | @@ -61,6 +63,26 @@ public class DatabaseTableServiceImplTest extends BaseDbUnitTest { |
| 61 | 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 | 86 | private void assertTableInfo(TableInfo tableInfo) { |
| 65 | 87 | assertEquals("infra_config", tableInfo.getName()); |
| 66 | 88 | assertEquals("参数配置表", tableInfo.getComment()); | ... | ... |