mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-09-27 13:46:39 +08:00
#feat: 动态数据源模块
This commit is contained in:
parent
f7f2c1730d
commit
e1b221289d
7
pom.xml
7
pom.xml
@ -389,6 +389,13 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!--数据源模块-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-datasource</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
@ -81,6 +81,12 @@
|
||||
<artifactId>ruoyi-workflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--数据源模块-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-datasource</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
<artifactId>spring-boot-admin-starter-client</artifactId>
|
||||
|
@ -15,6 +15,7 @@
|
||||
<module>ruoyi-job</module>
|
||||
<module>ruoyi-system</module>
|
||||
<module>ruoyi-workflow</module>
|
||||
<module>ruoyi-datasource</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
|
94
ruoyi-modules/ruoyi-datasource/pom.xml
Normal file
94
ruoyi-modules/ruoyi-datasource/pom.xml
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>ruoyi-datasource</artifactId>
|
||||
|
||||
<properties>
|
||||
<clickhouse.version>0.6.0</clickhouse.version>
|
||||
</properties>
|
||||
|
||||
<description>
|
||||
数据源模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!--通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-idempotent</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--数据mybatis-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.clickhouse</groupId>
|
||||
<artifactId>clickhouse-jdbc</artifactId>
|
||||
<version>${clickhouse.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,226 @@
|
||||
package org.dromara.datasource.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.datasource.domain.bo.DsFieldQueryBo;
|
||||
import org.dromara.datasource.domain.bo.DsQueryBo;
|
||||
import org.dromara.datasource.domain.bo.SysDatasourceBo;
|
||||
import org.dromara.datasource.domain.vo.SysDatasourceVo;
|
||||
import org.dromara.datasource.jdbc.JdbcQuery;
|
||||
import org.dromara.datasource.jdbc.core.DbType;
|
||||
import org.dromara.datasource.service.ISysDatasourceService;
|
||||
import org.dromara.datasource.utils.Assert;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/system/datasource")
|
||||
public class SysDatasourceController extends BaseController {
|
||||
|
||||
private final ISysDatasourceService sysDatasourceService;
|
||||
|
||||
/**
|
||||
* 查询动态数据源列表
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<SysDatasourceVo> list(SysDatasourceBo bo, PageQuery pageQuery) {
|
||||
return sysDatasourceService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出动态数据源列表
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:export")
|
||||
@Log(title = "动态数据源", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(SysDatasourceBo bo, HttpServletResponse response) {
|
||||
List<SysDatasourceVo> list = sysDatasourceService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "动态数据源", SysDatasourceVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态数据源详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<SysDatasourceVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(sysDatasourceService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增动态数据源
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:add")
|
||||
@Log(title = "动态数据源", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysDatasourceBo bo) {
|
||||
return toAjax(sysDatasourceService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改动态数据源
|
||||
*/
|
||||
@SaCheckPermission("datasource:datasource:edit")
|
||||
@Log(title = "动态数据源", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysDatasourceBo bo) {
|
||||
return toAjax(sysDatasourceService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除动态数据源
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:remove")
|
||||
@Log(title = "动态数据源", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(sysDatasourceService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据源类型
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@GetMapping("/types")
|
||||
public R<DbType[]> types() {
|
||||
return R.ok(DbType.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库列表
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/databases")
|
||||
public R<List<String>> databases(@Validated @RequestBody DsQueryBo bo) {
|
||||
List<String> query = sysDatasourceService.getJdbcService().query(bo.getDsName(), bo.getDsType(), JdbcQuery::queryDatabases);
|
||||
return R.ok(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表列表
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/tables")
|
||||
public R<List<Map<String, Object>>> tables(@Validated @RequestBody DsQueryBo bo) {
|
||||
List<Map<String, Object>> query = sysDatasourceService.getJdbcService().query(bo.getDsName(), bo.getDsType(), jdbcQuery -> jdbcQuery.querySchemaTables(bo.getSchema(), null));
|
||||
return R.ok(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段列表
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/columns")
|
||||
public R<List<Map<String, Object>>> columns(@Validated @RequestBody DsQueryBo bo) {
|
||||
List<Map<String, Object>> query = sysDatasourceService.getJdbcService().query(bo.getDsName(), bo.getDsType(), jdbcQuery -> jdbcQuery.queryTableFields(bo.getSchema(), bo.getTable()));
|
||||
return R.ok(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询sql脚本
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/query")
|
||||
public R<Object> query(@Validated @RequestBody DsQueryBo bo) {
|
||||
String sqlScript = StrUtil.removeSuffix(bo.getSqlScript(), ";");
|
||||
Assert.isTrue(StrUtil.isNotBlank(sqlScript), "sql脚本不能为空");
|
||||
List<?> query = sysDatasourceService.getJdbcService().query(bo.getDsName(), bo.getDsType(),
|
||||
jdbcQuery -> {
|
||||
JdbcTemplate jdbc = jdbcQuery.getJdbc();
|
||||
if (StrUtil.isNotEmpty(bo.getSchema())) {
|
||||
jdbc.execute("use `%s`".formatted(bo.getSchema()));
|
||||
}
|
||||
|
||||
if (StrUtil.contains(sqlScript, ";")) {
|
||||
return Arrays.stream(sqlScript.split(";"))
|
||||
.distinct()
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.map(String::trim)
|
||||
.map(jdbc::queryForList)
|
||||
.toList();
|
||||
} else {
|
||||
return jdbc.queryForList(sqlScript);
|
||||
}
|
||||
});
|
||||
return R.ok(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询sql脚本
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/execute")
|
||||
public R<List<Map<String, Object>>> execute(@Validated @RequestBody DsQueryBo bo) {
|
||||
String sqlScript = StrUtil.removeSuffix(bo.getSqlScript(), ";");
|
||||
Assert.isTrue(StrUtil.isNotBlank(sqlScript), "sql脚本不能为空");
|
||||
sysDatasourceService.getJdbcService().execute(bo.getDsName(), bo.getDsType(),
|
||||
jdbcQuery -> {
|
||||
JdbcTemplate jdbc = jdbcQuery.getJdbc();
|
||||
if (StrUtil.isNotEmpty(bo.getSchema())) {
|
||||
jdbc.execute("use `%s`".formatted(bo.getSchema()));
|
||||
}
|
||||
|
||||
Arrays.stream(sqlScript.split(";"))
|
||||
.distinct()
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.map(String::trim)
|
||||
.forEach(jdbc::execute);
|
||||
});
|
||||
return R.ok("执行成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试数据源连接
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/test")
|
||||
public R<Boolean> testConn(@Validated @RequestBody SysDatasourceBo bo) {
|
||||
return R.ok(sysDatasourceService.testConnByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询字段数据
|
||||
*/
|
||||
@SaCheckPermission("system:datasource:query")
|
||||
@PostMapping("/fieldData")
|
||||
public R<Object> queryFieldData(@Validated @RequestBody DsFieldQueryBo bo) {
|
||||
return R.ok(sysDatasourceService.queryFieldData(bo));
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.dromara.datasource.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 动态数据源对象 sys_datasource
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_datasource")
|
||||
public class SysDatasource extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 数据源类型
|
||||
*/
|
||||
private String dsType;
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
*/
|
||||
private String dsName;
|
||||
|
||||
/**
|
||||
* 数据源url
|
||||
*/
|
||||
private String connUrl;
|
||||
|
||||
/**
|
||||
* 数据源用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据源密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 驱动类名
|
||||
*/
|
||||
private String driverClassName;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 2代表删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package org.dromara.datasource.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 数据源数据查询请求
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DsFieldQueryBo extends FieldQueryBo{
|
||||
|
||||
@NotBlank(message = "数据源名称不能为空")
|
||||
private String dsName;
|
||||
|
||||
@NotBlank(message = "数据库名称不能为空")
|
||||
private String schema;
|
||||
|
||||
@NotBlank(message = "数据表名称不能为空")
|
||||
private String table;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.dromara.datasource.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 数据源查询请求
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DsQueryBo {
|
||||
|
||||
@NotBlank(message = "数据源名称不能为空")
|
||||
private String dsName;
|
||||
|
||||
@NotBlank(message = "数据源类型不能为空")
|
||||
private String dsType;
|
||||
|
||||
private String schema;
|
||||
|
||||
private String table;
|
||||
|
||||
private String sqlScript;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package org.dromara.datasource.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 字段查询请求
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class FieldQueryBo {
|
||||
|
||||
@NotEmpty(message = "字段不为空")
|
||||
private Collection<String> columns;
|
||||
|
||||
private List<FieldCondition> fieldConditions;
|
||||
|
||||
private List<FieldOrder> fieldOrders;
|
||||
|
||||
private List<Integer> offsets;
|
||||
|
||||
private Integer limit;
|
||||
|
||||
private List<String> groupColumns;
|
||||
|
||||
@Data
|
||||
public static class FieldCondition {
|
||||
@NotEmpty(message = "字段名不能为空")
|
||||
private String field;
|
||||
|
||||
private String type;
|
||||
|
||||
@NotEmpty(message = "运算符不能为空")
|
||||
private String operator;
|
||||
|
||||
private String linkOperator;
|
||||
|
||||
@NotNull(message = "查询数据不能为空")
|
||||
private Object value;
|
||||
|
||||
private Object secondValue;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class FieldOrder {
|
||||
/**
|
||||
* 排序的字段
|
||||
*/
|
||||
@NotEmpty(message = "字段名不能为空")
|
||||
private String field;
|
||||
|
||||
/**
|
||||
* 排序方式(正序还是反序)
|
||||
*/
|
||||
private String direction;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package org.dromara.datasource.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.datasource.domain.SysDatasource;
|
||||
|
||||
/**
|
||||
* 动态数据源业务对象 sys_datasource
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = SysDatasource.class, reverseConvertGenerate = false)
|
||||
public class SysDatasourceBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 数据源类型
|
||||
*/
|
||||
@NotBlank(message = "数据源类型不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String dsType;
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
*/
|
||||
@NotBlank(message = "数据源名称不能为空", groups = { EditGroup.class })
|
||||
private String dsName;
|
||||
|
||||
@NotBlank(message = "数据源host不能为空", groups = { EditGroup.class })
|
||||
private String host;
|
||||
|
||||
@NotBlank(message = "数据源端口不能为空", groups = { EditGroup.class })
|
||||
private String port;
|
||||
|
||||
/**
|
||||
* 数据源url
|
||||
*/
|
||||
@NotBlank(message = "数据源url不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String connUrl;
|
||||
|
||||
/**
|
||||
* 数据源用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据源密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 驱动类名
|
||||
*/
|
||||
@NotBlank(message = "驱动类名不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String driverClassName;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package org.dromara.datasource.domain.vo;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.datasource.domain.SysDatasource;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 动态数据源视图对象 sys_datasource
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = SysDatasource.class)
|
||||
public class SysDatasourceVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@ExcelProperty(value = "主键")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 数据源类型
|
||||
*/
|
||||
@ExcelProperty(value = "数据源类型")
|
||||
private String dsType;
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
*/
|
||||
@ExcelProperty(value = "数据源名称")
|
||||
private String dsName;
|
||||
|
||||
/**
|
||||
* 数据源url
|
||||
*/
|
||||
@ExcelProperty(value = "数据源url")
|
||||
private String connUrl;
|
||||
|
||||
/**
|
||||
* 数据源用户名
|
||||
*/
|
||||
@ExcelProperty(value = "数据源用户名")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据源密码
|
||||
*/
|
||||
@ExcelProperty(value = "数据源密码")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 驱动类名
|
||||
*/
|
||||
@ExcelProperty(value = "驱动类名")
|
||||
private String driverClassName;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package org.dromara.datasource.jdbc;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* Database 异常类
|
||||
*
|
||||
* @author ixyxj
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public class DatabaseException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DatabaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DatabaseException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public DatabaseException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
package org.dromara.datasource.jdbc;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.datasource.jdbc.core.DataType;
|
||||
import org.dromara.datasource.jdbc.core.InternalFunction;
|
||||
import org.dromara.datasource.jdbc.core.StorageEngine;
|
||||
import org.dromara.datasource.utils.ExceptionUtils;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* jdbc基础查询接口
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public interface JdbcQuery {
|
||||
|
||||
String SQL_CURRENT_DB = "select database()";
|
||||
|
||||
/**
|
||||
* 获取JdbcTemplate
|
||||
*/
|
||||
JdbcTemplate getJdbc();
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
default IPage<Map<String, Object>> queryForPage(IPage<Map<String, Object>> page, String sql) throws DatabaseException {
|
||||
if (StringUtils.isEmpty(sql)) {
|
||||
throw ExceptionUtils.create("sql should not empty");
|
||||
}
|
||||
JdbcTemplate jdbc = getJdbc();
|
||||
int total = Optional.ofNullable(jdbc.queryForObject("select count(1) from (" + sql + ") t", Integer.class))
|
||||
.orElse(0);
|
||||
page.setTotal(total);
|
||||
if (total > 0) {
|
||||
List<Map<String, Object>> maps = jdbc.queryForList(sql + " LIMIT " + page.offset() + StringPool.COMMA + page.getSize());
|
||||
page.setRecords(maps);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
default <T> IPage<T> queryForPage(IPage<T> page, String sql, Class<T> elementType) throws DatabaseException {
|
||||
if (StringUtils.isEmpty(sql)) {
|
||||
throw ExceptionUtils.create("sql should not empty");
|
||||
}
|
||||
JdbcTemplate jdbc = getJdbc();
|
||||
int total = Optional.ofNullable(jdbc.queryForObject("select count(1) from (" + sql + ") t", Integer.class))
|
||||
.orElse(0);
|
||||
page.setTotal(total);
|
||||
if (total > 0) {
|
||||
BeanPropertyRowMapper<T> rowMapper = BeanPropertyRowMapper.newInstance(elementType);
|
||||
List<T> list = jdbc.query(sql + " LIMIT " + page.offset() + StringPool.COMMA + page.getSize(), rowMapper);
|
||||
page.setRecords(list);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接是否成功
|
||||
*/
|
||||
default boolean testConnection() {
|
||||
try {
|
||||
getJdbc().queryForMap("select 1");
|
||||
return true;
|
||||
} catch (Exception ignored) {
|
||||
// ignored
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据库
|
||||
*/
|
||||
default List<String> queryDatabases() throws DatabaseException {
|
||||
try {
|
||||
return getJdbc().queryForList("show databases", String.class);
|
||||
} catch (Exception e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询表数据, 当前数据库
|
||||
*/
|
||||
default List<Map<String, Object>> queryTables(String... tables) throws DatabaseException {
|
||||
return querySchemaTables(null, tables);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询表数据
|
||||
*/
|
||||
default List<Map<String, Object>> querySchemaTables(String schema, String... tables) throws DatabaseException {
|
||||
try {
|
||||
return getJdbc().queryForList(sqlQueryTable(schema, tables));
|
||||
} catch (Exception e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询表数据
|
||||
*/
|
||||
default IPage<Map<String, Object>> queryTablePage(IPage<Map<String, Object>> page) throws DatabaseException {
|
||||
try {
|
||||
return queryForPage(page, sqlQueryTable());
|
||||
} catch (Exception e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询表sql
|
||||
*/
|
||||
default String sqlQueryTable() {
|
||||
return sqlQueryTable(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询表,可以指定表名
|
||||
*/
|
||||
default String sqlQueryTable(String schema, String... tables) {
|
||||
throw ExceptionUtils.methodNotImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询表字段
|
||||
*/
|
||||
default List<Map<String, Object>> queryTableFields(String schema, String table) throws DatabaseException {
|
||||
throw ExceptionUtils.methodNotImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据类型
|
||||
*/
|
||||
default List<DataType> queryDateTypes() throws DatabaseException {
|
||||
throw ExceptionUtils.methodNotImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取引擎类型
|
||||
*/
|
||||
default List<StorageEngine> queryEngines() throws DatabaseException {
|
||||
throw ExceptionUtils.methodNotImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库内置方法
|
||||
*/
|
||||
default List<InternalFunction> queryFunctions() throws DatabaseException {
|
||||
throw ExceptionUtils.methodNotImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库集群数据
|
||||
*/
|
||||
default List<String> queryClusters() throws DatabaseException {
|
||||
throw ExceptionUtils.methodNotImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将map装换为javabean对象
|
||||
*/
|
||||
default <T> T mapToBean(Map<String, Object> map, Class<T> clz) {
|
||||
try {
|
||||
return mapToBean(map, clz.newInstance());
|
||||
} catch (InstantiationException | IllegalAccessException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
default <T> T mapToBean(Map<String, Object> map, T bean) {
|
||||
Field[] fields = bean.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
int mod = field.getModifiers();
|
||||
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
if (map.containsKey(field.getName())) {
|
||||
try {
|
||||
Object value = map.get(field.getName());
|
||||
if (value instanceof BigInteger) {
|
||||
value = ((BigInteger) value).intValue();
|
||||
}
|
||||
field.set(bean, value);
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* bean转Map
|
||||
*/
|
||||
default <T> Map<String, Object> beanToMap(T bean) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Field[] fields = bean.getClass().getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
field.setAccessible(true);
|
||||
try {
|
||||
map.put(field.getName(), field.get(bean));
|
||||
} catch (IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package org.dromara.datasource.jdbc;
|
||||
|
||||
import org.dromara.datasource.jdbc.core.DbType;
|
||||
import org.dromara.datasource.utils.ExceptionUtils;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
* jdbc query 构造器
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public class JdbcQueryBuilder {
|
||||
|
||||
private JdbcQueryBuilder() {
|
||||
}
|
||||
|
||||
// 构造者模式
|
||||
public static final class Builder {
|
||||
|
||||
private boolean ignoreWarnings = true;
|
||||
|
||||
private int fetchSize = -1;
|
||||
|
||||
private int maxRows = -1;
|
||||
|
||||
private int queryTimeout = -1;
|
||||
|
||||
private boolean skipResultsProcessing = false;
|
||||
|
||||
private boolean skipUndeclaredResults = false;
|
||||
|
||||
private boolean resultsMapCaseInsensitive = false;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
private DbType dbType = DbType.MYSQL;
|
||||
|
||||
public Builder setIgnoreWarnings(boolean ignoreWarnings) {
|
||||
this.ignoreWarnings = ignoreWarnings;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFetchSize(int fetchSize) {
|
||||
this.fetchSize = fetchSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxRows(int maxRows) {
|
||||
this.maxRows = maxRows;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setQueryTimeout(int queryTimeout) {
|
||||
this.queryTimeout = queryTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSkipResultsProcessing(boolean skipResultsProcessing) {
|
||||
this.skipResultsProcessing = skipResultsProcessing;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSkipUndeclaredResults(boolean skipUndeclaredResults) {
|
||||
this.skipUndeclaredResults = skipUndeclaredResults;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setResultsMapCaseInsensitive(boolean resultsMapCaseInsensitive) {
|
||||
this.resultsMapCaseInsensitive = resultsMapCaseInsensitive;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDbType(DbType dbType) {
|
||||
this.dbType = dbType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 必须设置Datasource
|
||||
*/
|
||||
public Builder setDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JdbcQuery build() throws DatabaseException {
|
||||
if (dataSource == null) {
|
||||
throw ExceptionUtils.create("必须设置DataSource");
|
||||
}
|
||||
if (dbType == null) {
|
||||
throw ExceptionUtils.create("数据类型未空");
|
||||
}
|
||||
JdbcQuery jdbcQuery;
|
||||
try {
|
||||
Class<?> clz = Class.forName(dbType.getQueryClass());
|
||||
Constructor<?> constructor = clz.getConstructor(DataSource.class);
|
||||
jdbcQuery = (JdbcQuery) constructor.newInstance(dataSource);
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtils.create("创建JdbcQuery失败:" + e.getMessage());
|
||||
}
|
||||
if (!ignoreWarnings) jdbcQuery.getJdbc().setIgnoreWarnings(false);
|
||||
if (-1 != fetchSize) jdbcQuery.getJdbc().setFetchSize(fetchSize);
|
||||
if (-1 != maxRows) jdbcQuery.getJdbc().setMaxRows(maxRows);
|
||||
if (-1 != queryTimeout) jdbcQuery.getJdbc().setQueryTimeout(queryTimeout);
|
||||
if (skipResultsProcessing) jdbcQuery.getJdbc().setSkipResultsProcessing(true);
|
||||
if (skipUndeclaredResults) jdbcQuery.getJdbc().setSkipUndeclaredResults(true);
|
||||
if (resultsMapCaseInsensitive) jdbcQuery.getJdbc().setResultsMapCaseInsensitive(true);
|
||||
return jdbcQuery;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
package org.dromara.datasource.jdbc;
|
||||
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.datasource.domain.SysDatasource;
|
||||
import org.dromara.datasource.jdbc.core.DbType;
|
||||
import org.dromara.datasource.utils.Assert;
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 动态数据源执行服务
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class JdbcService {
|
||||
|
||||
// 连接测试sql
|
||||
private static final String TEST_SQL = "SELECT 1";
|
||||
|
||||
/**
|
||||
* 查询缓存,没必要每次都去获取dataSource
|
||||
*/
|
||||
private final ConcurrentMap<String, JdbcQuery> jdbcQueryMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 动态数据源组件
|
||||
*/
|
||||
private final DynamicRoutingDataSource dynamicRoutingDataSource;
|
||||
|
||||
|
||||
/**
|
||||
* 获取数据源
|
||||
*/
|
||||
public DataSource getDataSource(String dataSourceName) {
|
||||
DataSource dataSource = dynamicRoutingDataSource.getDataSource(dataSourceName);
|
||||
Assert.notNull(dataSource, "数据源【%s】不存在".formatted(dataSourceName));
|
||||
Assert.isTrue(testConnection(dataSource), "数据源【%s】不存在".formatted(dataSourceName));
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
public boolean testConnection(String url, String username, String password, String driverClassName) {
|
||||
DataSourceBuilder<HikariDataSource> builder = DataSourceBuilder.create()
|
||||
.type(HikariDataSource.class)
|
||||
.driverClassName(driverClassName)
|
||||
.url(url)
|
||||
.username(username)
|
||||
.password(password);
|
||||
DataSource dataSource = builder.build();
|
||||
return testConnection(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
public boolean testConnection(DataSource dataSource) {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
ResultSet resultSet = stmt.executeQuery(TEST_SQL);
|
||||
return resultSet.next();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据源
|
||||
*/
|
||||
public void addDataSource(List<SysDatasource> datasourceList) {
|
||||
for (SysDatasource sysDatasource : datasourceList) {
|
||||
try {
|
||||
String dataSourceName = sysDatasource.getDsName();
|
||||
String url = sysDatasource.getConnUrl();
|
||||
String username = sysDatasource.getUsername();
|
||||
String password = sysDatasource.getPassword();
|
||||
String driverClassName = sysDatasource.getDriverClassName();
|
||||
addDataSource(dataSourceName, url, username, password, driverClassName);
|
||||
} catch (Exception ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据源
|
||||
*/
|
||||
public void addDataSource(String dataSourceName, String url, String username, String password, String driverClassName) {
|
||||
Assert.isTrue(!dynamicRoutingDataSource.getDataSources().containsKey(dataSourceName), "数据源【%s】不存在".formatted(dataSourceName));
|
||||
DataSourceBuilder<HikariDataSource> builder = DataSourceBuilder.create()
|
||||
.type(HikariDataSource.class)
|
||||
.driverClassName(driverClassName)
|
||||
.url(url)
|
||||
.username(username)
|
||||
.password(password);
|
||||
DataSource dataSource = builder.build();
|
||||
dynamicRoutingDataSource.addDataSource(dataSourceName, dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除数据源
|
||||
*/
|
||||
public void removeDataSource(String dataSourceName) {
|
||||
dynamicRoutingDataSource.removeDataSource(dataSourceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改数据源
|
||||
*/
|
||||
public void updateDataSource(String dataSourceName, String url, String username, String password, String driverClassName) {
|
||||
Assert.isTrue(dynamicRoutingDataSource.getDataSources().containsKey(dataSourceName), "数据源【%s】不存在".formatted(dataSourceName));
|
||||
addDataSource(dataSourceName, url, username, password, driverClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询sql
|
||||
*/
|
||||
public <R> R query(String dsName, String dsType, Function<JdbcQuery, R> func) {
|
||||
try {
|
||||
DynamicDataSourceContextHolder.push(dsName);
|
||||
return func.apply(getJdbcQuery(dsName, dsType));
|
||||
} finally {
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行,没有返回值
|
||||
*/
|
||||
public void execute(String dsName, String dsType, Consumer<JdbcQuery> consumer) {
|
||||
try {
|
||||
DynamicDataSourceContextHolder.push(dsName);
|
||||
consumer.accept(getJdbcQuery(dsName, dsType));
|
||||
} finally {
|
||||
DynamicDataSourceContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JdbcQuery
|
||||
*/
|
||||
private JdbcQuery getJdbcQuery(String dsName, String dsType) {
|
||||
DataSource dataSource = getDataSource(dsName);
|
||||
|
||||
JdbcQuery jdbcQuery = jdbcQueryMap.get(dsName);
|
||||
// 判断是否连接
|
||||
if (Objects.isNull(jdbcQuery) || !jdbcQuery.testConnection()) {
|
||||
jdbcQuery = new JdbcQueryBuilder.Builder()
|
||||
.setDbType(DbType.getDbType(dsType))
|
||||
.setDataSource(dataSource)
|
||||
.build();
|
||||
jdbcQueryMap.put(dsName, jdbcQuery);
|
||||
}
|
||||
return jdbcQuery;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.dromara.datasource.jdbc.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 数据类型
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DataType {
|
||||
private String name;
|
||||
private boolean signed;
|
||||
}
|
@ -0,0 +1,268 @@
|
||||
package org.dromara.datasource.jdbc.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 数据库类型,复制自com.baomidou.mybatisplus.core.enums.DbType
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DbType {
|
||||
/**
|
||||
* MYSQL
|
||||
*/
|
||||
MYSQL("mysql", "com.mysql.cj.jdbc.Driver", "org.dromara.datasource.jdbc.query.MysqlJdbcTemplate", "MySql数据库"),
|
||||
/**
|
||||
* MARIADB
|
||||
*/
|
||||
MARIADB("mariadb", "org.mariadb.jdbc.Driver", "org.dromara.datasource.jdbc.query.MysqlJdbcTemplate", "MariaDB数据库"),
|
||||
/**
|
||||
* ORACLE
|
||||
*/
|
||||
ORACLE("oracle", "oracle.jdbc.OracleDriver", "", "Oracle11g及以下数据库(高版本推荐使用ORACLE_NEW)"),
|
||||
/**
|
||||
* oracle12c new pagination
|
||||
*/
|
||||
ORACLE_12C("oracle12c", "oracle.jdbc.OracleDriver", "", "Oracle12c+数据库"),
|
||||
/**
|
||||
* DB2
|
||||
*/
|
||||
DB2("db2", "com.ibm.db2.jcc.DB2Driver", "", "DB2数据库"),
|
||||
/**
|
||||
* H2
|
||||
*/
|
||||
H2("h2", "org.h2.Driver", "", "H2数据库"),
|
||||
/**
|
||||
* HSQL
|
||||
*/
|
||||
HSQL("hsql", "org.hsqldb.jdbc.JDBCDriver", "", "HSQL数据库"),
|
||||
/**
|
||||
* SQLITE
|
||||
*/
|
||||
SQLITE("sqlite", "org.sqlite.JDBC", "", "SQLite数据库"),
|
||||
/**
|
||||
* POSTGRE
|
||||
*/
|
||||
POSTGRE_SQL("postgresql", "org.postgresql.Driver", "", "Postgre数据库"),
|
||||
/**
|
||||
* SQLSERVER2005
|
||||
*/
|
||||
SQL_SERVER2005("sqlserver2005", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "", "SQLServer2005数据库"),
|
||||
/**
|
||||
* SQLSERVER
|
||||
*/
|
||||
SQL_SERVER("sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "", "SQLServer数据库"),
|
||||
/**
|
||||
* DM
|
||||
*/
|
||||
DM("dm", "dm.jdbc.driver.DmDriver", "", "达梦数据库"),
|
||||
/**
|
||||
* xugu
|
||||
*/
|
||||
XU_GU("xugu", "com.xugu.cloudjdbc.Driver", "", "虚谷数据库"),
|
||||
/**
|
||||
* Kingbase
|
||||
*/
|
||||
KINGBASE_ES("kingbasees", "cn.kingbase.es.mapreduce.jdbc.KingbaseDriver", "", "人大金仓数据库"),
|
||||
/**
|
||||
* Phoenix
|
||||
*/
|
||||
PHOENIX("phoenix", "org.apache.phoenix.jdbc.PhoenixDriver", "", "Phoenix HBase数据库"),
|
||||
/**
|
||||
* Gauss
|
||||
*/
|
||||
GAUSS("zenith", "com.huawei.gauss.jdbc.ZenithDriver", "", "Gauss 数据库"),
|
||||
/**
|
||||
* ClickHouse
|
||||
*/
|
||||
CLICK_HOUSE("clickhouse", "ru.yandex.clickhouse.ClickHouseDriver", "org.dromara.datasource.jdbc.query.ClickHouseJdbcTemplate", "clickhouse 数据库"),
|
||||
/**
|
||||
* GBase
|
||||
*/
|
||||
GBASE("gbase", "com.gbase.jdbc.Driver", "", "南大通用(华库)数据库"),
|
||||
/**
|
||||
* GBase-8s
|
||||
*/
|
||||
GBASE_8S("gbase-8s", "com.gbase.jdbc.Driver", "", "南大通用数据库 GBase 8s"),
|
||||
/**
|
||||
* use {@link #GBASE_8S}
|
||||
*
|
||||
* @deprecated 2022-05-30
|
||||
*/
|
||||
@Deprecated
|
||||
GBASEDBT("gbasedbt", "com.gbasedbt.jdbc.Driver", "", "南大通用数据库"),
|
||||
/**
|
||||
* use {@link #GBASE_8S}
|
||||
*
|
||||
* @deprecated 2022-05-30
|
||||
*/
|
||||
@Deprecated
|
||||
GBASE_INFORMIX("gbase 8s", "com.gbase.jdbc.Driver", "", "南大通用数据库 GBase 8s"),
|
||||
/**
|
||||
* GBase8sPG
|
||||
*/
|
||||
GBASE8S_PG("gbase8s-pg", "com.gbasedbt.jdbc.Driver", "", "南大通用数据库 GBase 8s兼容pg"),
|
||||
/**
|
||||
* GBase8c
|
||||
*/
|
||||
GBASE_8C("gbase8c", "com.gbase.jdbc.Driver", "", "南大通用数据库 GBase 8c"),
|
||||
/**
|
||||
* Sinodb
|
||||
*/
|
||||
SINODB("sinodb", "com.sinodb.jdbc.Driver", "", "星瑞格数据库"),
|
||||
/**
|
||||
* Oscar
|
||||
*/
|
||||
OSCAR("oscar", "com.oscar.Driver", "", "神通数据库"),
|
||||
/**
|
||||
* Sybase
|
||||
*/
|
||||
SYBASE("sybase", "com.sybase.jdbc4.jdbc.SybDriver", "", "Sybase ASE 数据库"),
|
||||
/**
|
||||
* OceanBase
|
||||
*/
|
||||
OCEAN_BASE("oceanbase", "com.aliyun.oceanbase.jdbc.OceanBaseDriver", "", "OceanBase 数据库"),
|
||||
/**
|
||||
* Firebird
|
||||
*/
|
||||
FIREBIRD("Firebird", "org.firebirdsql.jdbc.FBDriver", "", "Firebird 数据库"),
|
||||
/**
|
||||
* HighGo
|
||||
*/
|
||||
HIGH_GO("highgo", "com.highgo.jdbc.Driver", "", "瀚高数据库"),
|
||||
/**
|
||||
* CUBRID
|
||||
*/
|
||||
CUBRID("cubrid", "cubrid.jdbc.driver.CUBRIDDriver", "", "CUBRID数据库"),
|
||||
/**
|
||||
* SUNDB
|
||||
*/
|
||||
SUNDB("sundb", "com.unicom.sundb.jdbc.sundbjdbcdriver", "", "SUNDB数据库"),
|
||||
/**
|
||||
* Hana
|
||||
*/
|
||||
SAP_HANA("hana", "com.sap.db.jdbc.Driver", "", "SAP_HANA数据库"),
|
||||
/**
|
||||
* Impala
|
||||
*/
|
||||
IMPALA("impala", "org.apache.hive.jdbc.HiveDriver", "", "impala数据库"),
|
||||
/**
|
||||
* Vertica
|
||||
*/
|
||||
VERTICA("vertica", "com.vertica.jdbc.Driver", "", "vertica数据库"),
|
||||
/**
|
||||
* xcloud
|
||||
*/
|
||||
XCloud("xcloud", "com.xcloud.jdbc.driver.XCloudDriver", "", "行云数据库"),
|
||||
/**
|
||||
* redshift
|
||||
*/
|
||||
REDSHIFT("redshift", "com.amazon.redshift.jdbc.Driver", "", "亚马逊redshift数据库"),
|
||||
/**
|
||||
* openGauss
|
||||
*/
|
||||
OPENGAUSS("openGauss", "org.opengauss.Driver", "", "华为 opengauss 数据库"),
|
||||
/**
|
||||
* TDengine
|
||||
*/
|
||||
TDENGINE("TDengine", "com.taosdata.jdbc.TSDBDriver", "", "TDengine数据库"),
|
||||
/**
|
||||
* Informix
|
||||
*/
|
||||
INFORMIX("informix", "com.informix.jdbc.IfxDriver", "", "Informix数据库"),
|
||||
/**
|
||||
* uxdb
|
||||
*/
|
||||
UXDB("uxdb", "com.uxun.jdbc.Driver", "", "优炫数据库"),
|
||||
/**
|
||||
* lealone
|
||||
*/
|
||||
LEALONE("lealone", "org.lealone.Driver", "", "Lealone数据库"),
|
||||
/**
|
||||
* trino
|
||||
*/
|
||||
TRINO("trino", "io.trino.jdbc.TrinoDriver", "", "Trino数据库"),
|
||||
/**
|
||||
* presto
|
||||
*/
|
||||
PRESTO("presto", "com.facebook.presto.jdbc.PrestoDriver", "", "Presto数据库"),
|
||||
/**
|
||||
* UNKNOWN DB
|
||||
*/
|
||||
OTHER("other", "", "", "其他数据库");
|
||||
|
||||
|
||||
/**
|
||||
* 数据库名称
|
||||
*/
|
||||
private final String db;
|
||||
/**
|
||||
* 驱动类
|
||||
*/
|
||||
private final String driverClass;
|
||||
/**
|
||||
* 查询类
|
||||
*/
|
||||
private final String queryClass;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
/**
|
||||
* 获取数据库类型
|
||||
*
|
||||
* @param dbType 数据库类型字符串
|
||||
*/
|
||||
public static DbType getDbType(String dbType) {
|
||||
for (DbType type : DbType.values()) {
|
||||
if (type.db.equalsIgnoreCase(dbType)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return OTHER;
|
||||
}
|
||||
|
||||
public boolean mysqlSameType() {
|
||||
return this == DbType.MYSQL
|
||||
|| this == DbType.MARIADB
|
||||
|| this == DbType.GBASE
|
||||
|| this == DbType.OSCAR
|
||||
|| this == DbType.XU_GU
|
||||
|| this == DbType.CLICK_HOUSE
|
||||
|| this == DbType.OCEAN_BASE
|
||||
|| this == DbType.CUBRID
|
||||
|| this == DbType.SUNDB;
|
||||
}
|
||||
|
||||
public boolean oracleSameType() {
|
||||
return this == DbType.ORACLE
|
||||
|| this == DbType.DM
|
||||
|| this == DbType.GAUSS;
|
||||
}
|
||||
|
||||
public boolean postgresqlSameType() {
|
||||
return this == DbType.POSTGRE_SQL
|
||||
|| this == DbType.H2
|
||||
|| this == DbType.LEALONE
|
||||
|| this == DbType.SQLITE
|
||||
|| this == DbType.HSQL
|
||||
|| this == DbType.KINGBASE_ES
|
||||
|| this == DbType.PHOENIX
|
||||
|| this == DbType.SAP_HANA
|
||||
|| this == DbType.IMPALA
|
||||
|| this == DbType.HIGH_GO
|
||||
|| this == DbType.VERTICA
|
||||
|| this == DbType.REDSHIFT
|
||||
|| this == DbType.OPENGAUSS
|
||||
|| this == DbType.TDENGINE
|
||||
|| this == DbType.UXDB
|
||||
|| this == DbType.GBASE8S_PG
|
||||
|| this == DbType.GBASE_8C;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.dromara.datasource.jdbc.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* InternalFunctions
|
||||
* 内置函数
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class InternalFunction {
|
||||
private String name;
|
||||
private String category;
|
||||
private String description;
|
||||
private String syntax;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package org.dromara.datasource.jdbc.core;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* StorageEngine
|
||||
* 储存引擎
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class StorageEngine {
|
||||
private String name;
|
||||
private int isDefault;
|
||||
private String comment;
|
||||
private List<Map<String, Object>> params;
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package org.dromara.datasource.jdbc.query;
|
||||
|
||||
import com.clickhouse.data.ClickHouseDataType;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.datasource.jdbc.DatabaseException;
|
||||
import org.dromara.datasource.jdbc.JdbcQuery;
|
||||
import org.dromara.datasource.jdbc.core.DataType;
|
||||
import org.dromara.datasource.jdbc.core.InternalFunction;
|
||||
import org.dromara.datasource.jdbc.core.StorageEngine;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* ClickHouse 查询
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public final class ClickHouseJdbcTemplate extends JdbcTemplate implements JdbcQuery {
|
||||
public ClickHouseJdbcTemplate(DataSource dataSource) {
|
||||
super(dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClickHouseJdbcTemplate getJdbc() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sqlQueryTable(String schema, String... tables) {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append("select name as tableName,\n")
|
||||
.append("database as tableSchema\n")
|
||||
.append("from system.tables\n");
|
||||
if (StringUtils.isEmpty(schema)) {
|
||||
sb.append("where database=(").append(SQL_CURRENT_DB).append(")\n");
|
||||
} else {
|
||||
sb.append("where database='").append(schema).append("'\n");
|
||||
}
|
||||
if (tables != null && tables.length != 0) {
|
||||
sb.append(" and name in ('").append(String.join("','", tables)).append("')\n");
|
||||
}
|
||||
sb.append(" and not startsWith(name, '.')\n")
|
||||
.append("order by database, name");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> queryTableFields(String schema, String table) throws DatabaseException {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append("select distinct c.table as tableName,\n")
|
||||
.append("c.name as columnName,\n")
|
||||
.append("c.type as columnType,\n")
|
||||
.append("c.type as dataType,\n")
|
||||
.append("int32(c.position) as sort\n")
|
||||
.append("from system.columns c,\n")
|
||||
.append("system.tables t\n")
|
||||
.append("where t.database = c.database and t.name = c.table\n");
|
||||
if (StringUtils.isEmpty(schema)) {
|
||||
sb.append(" and t.database=(" + SQL_CURRENT_DB + ")\n");
|
||||
} else {
|
||||
sb.append(" and t.database='").append(schema).append("'\n");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(table)) {
|
||||
sb.append(" and t.name='").append(table).append("'");
|
||||
}
|
||||
return queryForList(sb.toString());
|
||||
} catch (Exception e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DataType> queryDateTypes() throws DatabaseException {
|
||||
return Arrays.stream(ClickHouseDataType.values()).map(type -> new DataType(type.name(), type.isSigned())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StorageEngine> queryEngines() throws DatabaseException {
|
||||
String sql = "select name, comment from system.storage_engines";
|
||||
return getJdbc().query(sql, BeanPropertyRowMapper.newInstance(StorageEngine.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InternalFunction> queryFunctions() throws DatabaseException {
|
||||
// 将alias to 合并
|
||||
// select arrayJoin(if(f.alias_to <> '', [f.name,f.alias_to], [f.name])) as name, f.* from system.functions f where f.alias_to <> '';
|
||||
String sql = String.format("select arrayJoin(if(f.alias_to <> '', [f.name,f.alias_to], [f.name])) as name, f.*\n" +
|
||||
" from system.functions f\n" +
|
||||
" where name <> %s", "''");
|
||||
return getJdbc().queryForList(sql).stream().map(v -> {
|
||||
InternalFunction functions = new InternalFunction();
|
||||
functions.setName(v.getOrDefault("name", "").toString());
|
||||
return functions;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> queryClusters() throws DatabaseException {
|
||||
String sql = "select distinct cluster from system.clusters";
|
||||
return getJdbc().queryForList(sql, String.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package org.dromara.datasource.jdbc.query;
|
||||
|
||||
import com.mysql.cj.MysqlType;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.datasource.jdbc.DatabaseException;
|
||||
import org.dromara.datasource.jdbc.JdbcQuery;
|
||||
import org.dromara.datasource.jdbc.core.DataType;
|
||||
import org.dromara.datasource.jdbc.core.StorageEngine;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* mysql 查询类
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public class MysqlJdbcTemplate extends JdbcTemplate implements JdbcQuery {
|
||||
|
||||
public MysqlJdbcTemplate(DataSource dataSource) {
|
||||
super(dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MysqlJdbcTemplate getJdbc() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sqlQueryTable(String schema, String... tables) {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append("select table_name as tableName,\n")
|
||||
.append("table_comment as tableComment,\n")
|
||||
.append("create_time as createTime,\n")
|
||||
.append("update_time as updateTime,\n")
|
||||
.append("table_schema as tableSchema\n")
|
||||
.append("from information_schema.tables\n")
|
||||
.append("where table_type in ('BASE TABLE', 'SYSTEM VIEW')");
|
||||
if (StringUtils.isEmpty(schema)) {
|
||||
sb.append(" and table_schema=(" + SQL_CURRENT_DB + ")\n");
|
||||
} else {
|
||||
sb.append(" and table_schema='").append(schema).append("'\n");
|
||||
}
|
||||
if (tables != null && tables.length != 0) {
|
||||
sb.append(" and table_name in ('").append(String.join("','", tables)).append("')\n");
|
||||
}
|
||||
sb.append("order by 2, 1");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> queryTableFields(String schema, String table) throws DatabaseException {
|
||||
try {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
.append("select distinct c.table_name as tableName,\n")
|
||||
.append("c.column_name as columnName,\n")
|
||||
.append("c.column_type as columnType,\n")
|
||||
.append("c.column_comment as columnComment,\n")
|
||||
.append("c.data_type as dataType,\n")
|
||||
.append("(case when (c.is_nullable = 'no' && column_key != 'PRI') then '1' else '0' end) as isRequired,\n")
|
||||
.append("(case when column_key = 'PRI' then '1' else '0' end) as isPk,\n")
|
||||
.append("(case when extra = 'auto_increment' then '1' else '0' end) as isIncrement,\n")
|
||||
.append("c.character_maximum_length as dataLength,\n")
|
||||
.append("c.numeric_precision as dataPrecision,\n")
|
||||
.append("c.numeric_scale as dataScale,\n")
|
||||
.append("c.ordinal_position as sort\n")
|
||||
.append("from information_schema.columns c,\n")
|
||||
.append("information_schema.tables t\n")
|
||||
.append("where t.table_name = c.table_name\n")
|
||||
.append(" and t.table_type in ('BASE TABLE', 'SYSTEM VIEW')\n");
|
||||
if (StringUtils.isEmpty(schema)) {
|
||||
sb.append(" and t.table_schema=(" + SQL_CURRENT_DB + ")\n");
|
||||
} else {
|
||||
sb.append(" and t.table_schema='").append(schema).append("'\n");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(table)) {
|
||||
sb.append(" and t.table_name='").append(table).append("'");
|
||||
}
|
||||
return queryForList(sb.toString());
|
||||
} catch (Exception e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DataType> queryDateTypes() throws DatabaseException {
|
||||
return Arrays.stream(MysqlType.values()).map(type -> new DataType(type.getName(), type.getCreateParams().contains("UNSIGNED"))).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StorageEngine> queryEngines() throws DatabaseException {
|
||||
String sql = String.format("select e.engine as name,\n" +
|
||||
" if(e.SUPPORT='DEFAULT', 1, 0) as isDefault,\n" +
|
||||
" e.COMMENT as comment\n" +
|
||||
" from information_schema.engines e\n" +
|
||||
" where e.support in ('%s', '%s')", "DEFAULT", "YES");
|
||||
return getJdbc().queryForList(sql).stream()
|
||||
.map(v -> new StorageEngine(
|
||||
v.getOrDefault("name", "").toString(),
|
||||
Integer.parseInt(v.get("isDefault").toString()),
|
||||
v.getOrDefault("comment", "").toString(),
|
||||
null)
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package org.dromara.datasource.mapper;
|
||||
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.datasource.domain.SysDatasource;
|
||||
import org.dromara.datasource.domain.vo.SysDatasourceVo;
|
||||
|
||||
/**
|
||||
* 动态数据源Mapper接口
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
public interface SysDatasourceMapper extends BaseMapperPlus<SysDatasource, SysDatasourceVo> {
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package org.dromara.datasource.service;
|
||||
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.datasource.domain.bo.DsFieldQueryBo;
|
||||
import org.dromara.datasource.domain.bo.SysDatasourceBo;
|
||||
import org.dromara.datasource.domain.vo.SysDatasourceVo;
|
||||
import org.dromara.datasource.jdbc.JdbcService;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 动态数据源Service接口
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
public interface ISysDatasourceService {
|
||||
|
||||
/**
|
||||
* 查询动态数据源
|
||||
*/
|
||||
SysDatasourceVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 通过名称查询
|
||||
*/
|
||||
SysDatasourceVo queryByName(String name);
|
||||
|
||||
/**
|
||||
* 查询动态数据源列表
|
||||
*/
|
||||
TableDataInfo<SysDatasourceVo> queryPageList(SysDatasourceBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询动态数据源列表
|
||||
*/
|
||||
List<SysDatasourceVo> queryList(SysDatasourceBo bo);
|
||||
|
||||
/**
|
||||
* 新增动态数据源
|
||||
*/
|
||||
Boolean insertByBo(SysDatasourceBo bo);
|
||||
|
||||
/**
|
||||
* 修改动态数据源
|
||||
*/
|
||||
Boolean updateByBo(SysDatasourceBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除动态数据源信息
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 获取JdbcService
|
||||
*/
|
||||
JdbcService getJdbcService();
|
||||
|
||||
/**
|
||||
* 测试连接
|
||||
*/
|
||||
Boolean testConnByBo(SysDatasourceBo bo);
|
||||
|
||||
/**
|
||||
* 查询字段数据
|
||||
*/
|
||||
Object queryFieldData(DsFieldQueryBo bo);
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package org.dromara.datasource.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.compress.utils.Lists;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.datasource.domain.SysDatasource;
|
||||
import org.dromara.datasource.domain.bo.DsFieldQueryBo;
|
||||
import org.dromara.datasource.domain.bo.SysDatasourceBo;
|
||||
import org.dromara.datasource.domain.vo.SysDatasourceVo;
|
||||
import org.dromara.datasource.jdbc.JdbcService;
|
||||
import org.dromara.datasource.mapper.SysDatasourceMapper;
|
||||
import org.dromara.datasource.service.ISysDatasourceService;
|
||||
import org.dromara.datasource.utils.Assert;
|
||||
import org.dromara.datasource.utils.SqlUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 动态数据源Service业务层处理
|
||||
*
|
||||
* @author ixyxj
|
||||
* @date 2024-05-02
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class SysDatasourceServiceImpl implements ISysDatasourceService {
|
||||
|
||||
private final JdbcService jdbcService;
|
||||
private final SysDatasourceMapper baseMapper;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
List<SysDatasource> datasourceList = baseMapper.selectList();
|
||||
jdbcService.addDataSource(datasourceList);
|
||||
log.info("添加动态数据源:{}", datasourceList.stream().map(SysDatasource::getDsName).collect(Collectors.joining(",")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询动态数据源
|
||||
*/
|
||||
@Override
|
||||
public SysDatasourceVo queryById(Long id){
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysDatasourceVo queryByName(String name) {
|
||||
return baseMapper.selectVoOne(Wrappers.<SysDatasource>lambdaQuery().eq(SysDatasource::getDsName, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询动态数据源列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<SysDatasourceVo> queryPageList(SysDatasourceBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<SysDatasource> lqw = buildQueryWrapper(bo);
|
||||
Page<SysDatasourceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询动态数据源列表
|
||||
*/
|
||||
@Override
|
||||
public List<SysDatasourceVo> queryList(SysDatasourceBo bo) {
|
||||
LambdaQueryWrapper<SysDatasource> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<SysDatasource> buildQueryWrapper(SysDatasourceBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<SysDatasource> lqw = Wrappers.lambdaQuery();
|
||||
lqw.like(StringUtils.isNotBlank(bo.getDsName()), SysDatasource::getDsName, bo.getDsName());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getDsType()), SysDatasource::getDsType, bo.getDsType());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getUsername()), SysDatasource::getUsername, bo.getUsername());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getDriverClassName()), SysDatasource::getDriverClassName, bo.getDriverClassName());
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增动态数据源
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(SysDatasourceBo bo) {
|
||||
SysDatasource add = MapstructUtils.convert(bo, SysDatasource.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
jdbcService.addDataSource(bo.getDsName(), bo.getConnUrl(), bo.getUsername(), bo.getPassword(), bo.getDriverClassName());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改动态数据源
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(SysDatasourceBo bo) {
|
||||
SysDatasource update = MapstructUtils.convert(bo, SysDatasource.class);
|
||||
validEntityBeforeSave(update);
|
||||
jdbcService.updateDataSource(bo.getDsName(), bo.getConnUrl(), bo.getUsername(), bo.getPassword(), bo.getDriverClassName());
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(SysDatasource entity){
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除动态数据源
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if(isValid){
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
baseMapper.selectBatchIds(ids, sysDatasource -> jdbcService.removeDataSource(sysDatasource.getResultObject().getDsName()));
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JdbcService getJdbcService() {
|
||||
return jdbcService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean testConnByBo(SysDatasourceBo bo) {
|
||||
return jdbcService.testConnection(bo.getConnUrl(), bo.getUsername(), bo.getPassword(), bo.getDriverClassName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object queryFieldData(DsFieldQueryBo bo) {
|
||||
SysDatasourceVo datasource = queryByName(bo.getDsName());
|
||||
Assert.notNull(datasource, "数据库【%s】不存在".formatted(bo.getDsName()));
|
||||
if (datasource != null) {
|
||||
return jdbcService.query(datasource.getDsName(), datasource.getDsType(), jdbcQuery -> {
|
||||
String sql = SqlUtils.buildSqlString(bo, "`" + bo.getSchema() + "`.`" + bo.getTable() + "`");
|
||||
return SqlUtils.queryByOffsets(jdbcQuery.getJdbc(), sql, bo.getOffsets());
|
||||
});
|
||||
}
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package org.dromara.datasource.utils;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 断言类
|
||||
*
|
||||
* @author miemie
|
||||
* @since 2018-07-24
|
||||
*/
|
||||
public final class Assert {
|
||||
|
||||
private Assert() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 boolean 为 true
|
||||
* <p>为 false 则抛出异常</p>
|
||||
*
|
||||
* @param expression boolean 值
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void isTrue(boolean expression, String message, Object... params) {
|
||||
if (!expression) {
|
||||
throw ExceptionUtils.create(message, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 boolean 为 false
|
||||
* <p>为 true 则抛出异常</p>
|
||||
*
|
||||
* @param expression boolean 值
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void isFalse(boolean expression, String message, Object... params) {
|
||||
isTrue(!expression, message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 object 为 null
|
||||
* <p>不为 null 则抛异常</p>
|
||||
*
|
||||
* @param object 对象
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void isNull(Object object, String message, Object... params) {
|
||||
isTrue(object == null, message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 object 不为 null
|
||||
* <p>为 null 则抛异常</p>
|
||||
*
|
||||
* @param object 对象
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notNull(Object object, String message, Object... params) {
|
||||
isTrue(object != null, message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 map 为 empty
|
||||
* <p>为 empty 则抛异常</p>
|
||||
*
|
||||
* @param map 集合
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void isEmpty(Map<?, ?> map, String message, Object... params) {
|
||||
isTrue(CollUtil.isEmpty(map), message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 collection 为 empty
|
||||
* <p>为 empty 则抛异常</p>
|
||||
*
|
||||
* @param collection 集合
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void isEmpty(Collection<?> collection, String message, Object... params) {
|
||||
isTrue(CollUtil.isEmpty(collection), message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 value 不为 empty
|
||||
* <p>为 empty 则抛异常</p>
|
||||
*
|
||||
* @param value 字符串
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notEmpty(String value, String message, Object... params) {
|
||||
isTrue(StrUtil.isNotBlank(value), message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 collection 不为 empty
|
||||
* <p>为 empty 则抛异常</p>
|
||||
*
|
||||
* @param collection 集合
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notEmpty(Collection<?> collection, String message, Object... params) {
|
||||
isTrue(CollUtil.isNotEmpty(collection), message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 map 不为 empty
|
||||
* <p>为 empty 则抛异常</p>
|
||||
*
|
||||
* @param map 集合
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notEmpty(Map<?, ?> map, String message, Object... params) {
|
||||
isTrue(CollUtil.isNotEmpty(map), message, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言这个 数组 不为 empty
|
||||
* <p>为 empty 则抛异常</p>
|
||||
*
|
||||
* @param array 数组
|
||||
* @param message 消息
|
||||
*/
|
||||
public static void notEmpty(Object[] array, String message, Object... params) {
|
||||
isTrue(CollUtil.isNotEmpty(List.of(array)), message, params);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package org.dromara.datasource.utils;
|
||||
|
||||
import org.apache.ibatis.datasource.DataSourceException;
|
||||
|
||||
/**
|
||||
* ExceptionUtils
|
||||
* 异常工具类
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public final class ExceptionUtils {
|
||||
|
||||
private ExceptionUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个新的异常,统一构建,方便统一处理
|
||||
*
|
||||
* @param msg 消息
|
||||
* @param t 异常信息
|
||||
* @return 返回异常
|
||||
*/
|
||||
public static DataSourceException create(String msg, Throwable t, Object... params) {
|
||||
return new DataSourceException(String.format(msg, params), t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载的方法
|
||||
*
|
||||
* @param msg 消息
|
||||
* @return 返回异常
|
||||
*/
|
||||
public static DataSourceException create(String msg, Object... params) {
|
||||
return new DataSourceException(String.format(msg, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载的方法
|
||||
*
|
||||
* @param t 异常
|
||||
* @return 返回异常
|
||||
*/
|
||||
public static DataSourceException create(Throwable t) {
|
||||
return new DataSourceException(t);
|
||||
}
|
||||
|
||||
public static void throwOr(boolean condition, String msg, Object... params) throws DataSourceException {
|
||||
if (condition) {
|
||||
throw create(msg, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 方法为实现异常
|
||||
*/
|
||||
public static DataSourceException methodNotImplemented() {
|
||||
return create("method not implemented");
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package org.dromara.datasource.utils;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.sql.*;
|
||||
import org.dromara.datasource.domain.bo.FieldQueryBo;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* sql工具类
|
||||
*
|
||||
* @author xyxj xieyangxuejun@gmail.com
|
||||
* @since 2024/5/2
|
||||
*/
|
||||
public class SqlUtils {
|
||||
private SqlUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取拼接sql
|
||||
*/
|
||||
public static String buildSqlString(FieldQueryBo bo, String table) {
|
||||
SqlBuilder sqlBuilder = SqlBuilder.create();
|
||||
Collection<String> columnList = bo.getColumns();
|
||||
String[] columns = StrUtil.wrapAllWithPair(columnList.contains("*") ? "" : "`", columnList.toArray(new String[0]));
|
||||
sqlBuilder.select(columns).from(table);
|
||||
// 检索条件
|
||||
List<FieldQueryBo.FieldCondition> fieldConditions = bo.getFieldConditions();
|
||||
if (CollUtil.isNotEmpty(fieldConditions)) {
|
||||
sqlBuilder.where(fieldConditions.stream().filter(fc -> fc.getValue() != null && StrUtil.isNotEmpty(fc.getValue().toString())).map(fc -> {
|
||||
// value is wrapped
|
||||
Object value = fc.getValue();
|
||||
if (!StrUtil.isWrap(value.toString(), "'")) {
|
||||
value = StrUtil.wrap(value.toString(), "'");
|
||||
}
|
||||
Condition condition = new Condition(fc.getField(), fc.getOperator(), value);
|
||||
condition.setPlaceHolder(false);
|
||||
condition.setSecondValue(fc.getSecondValue());
|
||||
if (StrUtil.isNotEmpty(fc.getLinkOperator())) {
|
||||
condition.setLinkOperator(LogicalOperator.valueOf(fc.getLinkOperator().toUpperCase()));
|
||||
}
|
||||
return condition;
|
||||
}).toArray(Condition[]::new));
|
||||
}
|
||||
// 聚合
|
||||
if (CollUtil.isNotEmpty(bo.getGroupColumns())) {
|
||||
sqlBuilder.groupBy(bo.getGroupColumns().toArray(new String[0]));
|
||||
}
|
||||
// 排序
|
||||
List<FieldQueryBo.FieldOrder> fieldOrders = bo.getFieldOrders();
|
||||
if (CollUtil.isNotEmpty(fieldOrders)) {
|
||||
sqlBuilder.orderBy(fieldOrders.stream().map(fieldOrder -> {
|
||||
Order order = new Order();
|
||||
order.setField(fieldOrder.getField());
|
||||
order.setDirection(Direction.fromString(fieldOrder.getDirection()));
|
||||
return order;
|
||||
}).toArray(Order[]::new));
|
||||
}
|
||||
// 当有offset的时候,limit就不需要
|
||||
if (CollUtil.isEmpty(bo.getOffsets()) && bo.getLimit() != null) {
|
||||
sqlBuilder.append(" LIMIT " + bo.getLimit());
|
||||
}
|
||||
return sqlBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据offsets查询数据
|
||||
*
|
||||
* @param jdbc jdbc template
|
||||
* @param sql sql string
|
||||
* @param offsets offsets
|
||||
* @return list
|
||||
*/
|
||||
public static List<Map<String, Object>> queryByOffsets(@NonNull JdbcTemplate jdbc, @NonNull String sql, List<Integer> offsets) {
|
||||
if (CollUtil.isEmpty(offsets)) {
|
||||
return jdbc.queryForList(sql);
|
||||
}
|
||||
// 如果有offsets参数
|
||||
return offsets.parallelStream().map(offset -> {
|
||||
String append = " OFFSET " + (offset - 1) + " LIMIT 1";
|
||||
return jdbc.queryForMap(sql + append);
|
||||
}).toList();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.datasource.mapper.SysDatasourceMapper">
|
||||
|
||||
</mapper>
|
@ -292,6 +292,8 @@ insert into sys_menu values('118', '文件管理', '1', '10', 'oss',
|
||||
insert into sys_menu values('120', '任务调度中心', '2', '5', 'powerjob', 'monitor/powerjob/index', '', 1, 0, 'C', '0', '0', 'monitor:powerjob:list', 'job', 103, 1, sysdate(), null, null, 'PowerJob控制台菜单');
|
||||
-- retry server控制台
|
||||
insert into sys_menu values('130', 'EasyRetry控制台', '2', '6', 'easyretry', 'monitor/easyretry/index', '', 1, 0, 'C', '0', '0', 'monitor:easyretry:list', 'job', 103, 1, sysdate(), null, null, 'EasyRetry控制台菜单');
|
||||
-- 动态数据源管理
|
||||
insert into sys_menu values('124', '数据源管理', '1', '12', 'datasource', 'system/datasource/index', '', 1, 0, 'C', '0', '0', 'system:datasource:list', 'redis', 103, 1, sysdate(), null, null, '数据源管理菜单');
|
||||
|
||||
-- 三级菜单
|
||||
insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 103, 1, sysdate(), null, null, '操作日志菜单');
|
||||
@ -404,6 +406,12 @@ insert into sys_menu values('1508', '测试树表新增', '1506', '2', '#',
|
||||
insert into sys_menu values('1509', '测试树表修改', '1506', '3', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values('1510', '测试树表删除', '1506', '4', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values('1511', '测试树表导出', '1506', '5', '#', '', '', 1, 0, 'F', '0', '0', 'demo:tree:export', '#', 103, 1, sysdate(), null, null, '');
|
||||
-- 动态数据源管理按钮
|
||||
insert into sys_menu values('1700', '动态数据源查询', '124', '1', '#', '', '', 1, 0, 'F', '0', '0', 'system:datasource:query', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values('1701', '动态数据源新增', '124', '2', '#', '', '', 1, 0, 'F', '0', '0', 'system:datasource:add', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values('1702', '动态数据源修改', '124', '3', '#', '', '', 1, 0, 'F', '0', '0', 'system:datasource:edit', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values('1703', '动态数据源删除', '124', '4', '#', '', '', 1, 0, 'F', '0', '0', 'system:datasource:remove', '#', 103, 1, sysdate(), null, null, '');
|
||||
insert into sys_menu values('1704', '动态数据源导出', '124', '5', '#', '', '', 1, 0, 'F', '0', '0', 'system:datasource:export', '#', 103, 1, sysdate(), null, null, '');
|
||||
|
||||
-- ----------------------------
|
||||
-- 6、用户和角色关联表 用户N-1角色
|
||||
@ -936,3 +944,27 @@ INSERT INTO test_tree VALUES (10, '000000', 7, 108, 3, '子节点66', 0, 103, sy
|
||||
INSERT INTO test_tree VALUES (11, '000000', 7, 108, 3, '子节点77', 0, 103, sysdate(), 1, NULL, NULL, 0);
|
||||
INSERT INTO test_tree VALUES (12, '000000', 10, 108, 3, '子节点88', 0, 103, sysdate(), 1, NULL, NULL, 0);
|
||||
INSERT INTO test_tree VALUES (13, '000000', 10, 108, 3, '子节点99', 0, 103, sysdate(), 1, NULL, NULL, 0);
|
||||
|
||||
-- ------------------------
|
||||
-- 动态数据源表
|
||||
-- ------------------------
|
||||
drop table if exists sys_datasource;
|
||||
create table sys_datasource
|
||||
(
|
||||
id bigint(20) not null auto_increment comment '主键',
|
||||
tenant_id varchar(20) default '000000' comment '租户编号',
|
||||
ds_type varchar(20) comment '数据源类型',
|
||||
ds_name varchar(255) comment '数据源名称',
|
||||
conn_url varchar(1000) comment '数据源url',
|
||||
username varchar(50) comment '数据源用户名',
|
||||
password varchar(50) comment '数据源密码',
|
||||
driver_class_name varchar(50) comment '驱动类名',
|
||||
del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)',
|
||||
create_dept bigint(20) default null comment '创建部门',
|
||||
create_by bigint(20) default null comment '创建者',
|
||||
create_time datetime default null comment '创建时间',
|
||||
update_by bigint(20) default null comment '更新者',
|
||||
update_time datetime default null comment '更新时间',
|
||||
remark varchar(500) default null comment '备注',
|
||||
primary key (id, ds_name)
|
||||
) engine = innodb comment ='动态数据源';
|
||||
|
Loading…
Reference in New Issue
Block a user