需求场景:

若依框架的30张数据表 和 业务使用的数据表,同数据源,但分开的两个库,原生若依只支持主库的代码生成,故自己修改添加代码来实现

若依多数据源的使用

效果展示

在这里插入图片描述
在这里插入图片描述

前端

修改页面ruoyi-ui\src\views\tool\gen\importTable.vue
el-form 中新增 el-form-item

	<el-form-item label="数据源">
        <el-select v-model="queryParams.dataSource" placeholder="选择数据源">
          <el-option v-for="item in dataSources"
                     :label="item.desc"
                     :value="item.dataBaseCode"></el-option>
        </el-select>
      </el-form-item>

添加 data

// 查询参数
 data() {
    return {
    	………………
    	………………
	      queryParams: {
	        pageNum: 1,
	        pageSize: 10,
	        dataSource: '',
	        tableName: undefined,
	        tableComment: undefined
	      },
	      //数据源列表
	      dataSources:[{dataBaseCode:'',desc:''}],
      };
  },

添加方法

	// 显示弹框
    show() {
      this.getTableSource();
      this.getList();
      this.visible = true;
    },
	//查询数据源
    getTableSource(){
      tableSourceList().then(res=>{
        if(res.code===200){
          this.dataSources=res.data;
          if(this.queryParams.dataSource===''){
            this.queryParams.dataSource=this.dataSources[0].dataBaseCode
          }
        }
      })
    },

ruoyi-ui\src\api\tool\gen.js 添加方法

// 查询数据源列表
export function tableSourceList(){
  return request({
    url: '/tool/gen/tableSourceList',
    method: 'get',
  })
}

后端

一:修改枚举类

修改 数据源类型 枚举类 ——> com.ruoyi.common.enums.DataSourceType

package com.ruoyi.common.enums;

import com.fasterxml.jackson.annotation.JsonFormat;

/**
 * 数据源
 * 
 * @author ruoyi
 */
//使枚举类返回前端是完整的对象,否则是只会有name属性的String数组
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum DataSourceType {
    /**
     * 主库
     */
    MASTER("MASTER","若依框架数据库"),

    /**
     * 从库
     */
    SLAVE("SLAVE","业务数据库");

    private String dataBaseCode;

    private String desc;
    
    //……省略get set 构造函数……
}

二:添加接口

添加数据源列表获取的接口 ——> com.ruoyi.generator.controller.GenController

/**
     * 数据源列表
     * @return
     */
    @PreAuthorize("@ss.hasPermi('tool:gen:list')")
    @GetMapping("/tableSourceList")
    public AjaxResult tableSourceList(){
        return AjaxResult.success(DataSourceType.values());
    }

三:添加字段、属性

在 genTable 表中添加字段 form_data_source
新增字段后,对应的xml 中 resultMap、各种select以及insert、update方法,记得加上字段

查询条件 添加数据源的属性 ——> com.ruoyi.generator.domain.GenTable

public class GenTable extends BaseEntity
{
    private static final long serialVersionUID = 1L;

	………………
	
    @NotBlank(message = "请选择数据源")
    private String tableSource;

	/** 来自的数据源名 */
    private String formDataSource;

	………………
}

四:实现方法

修改查询库表 列表的方法 ——> com.wenhu.generator.service.selectDbTableList()

4.1:

在genTableMapper 新增 方法

 /**
 * 查询已经导入了的表名集合
 */
 public List<String> selectExcludeGenTable();

对应的 mapper xml中 GenTableMapper.xml

	<select id="selectExcludeGenTable" resultType="java.lang.String">
		select table_name from gen_table
	</select>
4.2:

找到 GenTableMapper 的 selectDbTableList 方法
新增参数 List excludeTables

/**
     * 查询据库列表
     *
     * @param genTable 业务信息
     * @return 数据库表集合
     */
    public List<GenTable> selectDbTableList(@Param("genTable") GenTable genTable, @Param("excludeTables") List<String> excludeTables);

修改sql语句 table_name not in 后面的参数使用 以及多参别称的引用

<select id="selectDbTableList" parameterType="com.ruoyi.generator.domain.GenTable" resultMap="GenTableResult">
		select table_name, table_comment, create_time, update_time from information_schema.tables
		where table_schema = (select database())
		AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'
		AND table_name NOT IN
		<foreach collection="excludeTables" item="tName" open="(" close=")" separator=",">
			#{tName}
		</foreach>
		<if test="genTable.tableName != null and genTable.tableName != ''">
			AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
		</if>
		<if test="genTable.tableComment != null and genTable.tableComment != ''">
			AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
		</if>
		<if test="genTable.params.beginTime != null and genTable.params.beginTime != ''"><!-- 开始时间检索 -->
			AND date_format(create_time,'%y%m%d') &gt;= date_format(#{genTable.params.beginTime},'%y%m%d')
		</if>
		<if test="genTable.params.endTime != null and genTable.params.endTime != ''"><!-- 结束时间检索 -->
			AND date_format(create_time,'%y%m%d') &lt;= date_format(#{genTable.params.endTime},'%y%m%d')
		</if>
        order by create_time desc
	</select>
4.3:
	/**
     * 查询据库列表
     * 
     * @param genTable 业务信息
     * @return 数据库表集合
     */
    @Override
    public List<GenTable> selectDbTableList(GenTable genTable) {
        List<String> excludeGenTable = genTableMapper.selectExcludeGenTable();
        //确保值不为空
        if (StringUtils.isNotEmpty(genTable.getDataSource())) {
            //找出对应的枚举
            DataSourceType sourceType = DataSourceType.valueOf(genTable.getDataSource());
            //手动切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(sourceType.name());
        }
        List<GenTable> genTables = genTableMapper.selectDbTableList(genTable,excludeGenTable);
        //清空数据源变量
        DynamicDataSourceContextHolder.clearDataSourceType();
        return genTables;
    }

五:修改导入表接口

因为没有引入 atomikos 分布式事务插件(若依官网提供的方法仅不分离版),所以在增删改类业务需要开启事务的方法中,Spring开启事务后会维护一个ConnectionHolder,保证在整个事务下,都是用同一个数据库连接
不想引入的简单方式,对方法的逻辑顺序进行改造

controller:
	/**
     * 导入表结构(保存)
     */
    @PreAuthorize("@ss.hasPermi('tool:gen:import')")
    @Log(title = "代码生成", businessType = BusinessType.IMPORT)
    @PostMapping("/importTable")
    public AjaxResult importTableSave(String tables,
                                      String dataSource)
    {
        String[] tableNames = Convert.toStrArray(tables);
        // 切换数据源并查询表信息
        // 确保值不为空
        if(StringUtils.isNotEmpty(dataSource)){
            //找出对应的枚举
            DataSourceType sourceType = DataSourceType.valueOf(dataSource);
            //手动切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(sourceType.name());
        }else{
            throw new ServiceException("指定了无效数据源");
        }
        //先把需要的表结构数据、字段数据整理好
        List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
        //导入到若依框架数据库
        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
        genTableService.importGenTable(tableList,dataSource);
        //清空数据源变量
        DynamicDataSourceContextHolder.clearDataSourceType();
        return success();
    }
service:
/**
     * 查询据库列表
     *
     * @param tableNames 表名称组
     * @return 数据库表集合
     */
    @Override
    public List<GenTable> selectDbTableListByNames(String[] tableNames) {
        List<GenTable> genTables = genTableMapper.selectDbTableListByNames(tableNames);
        String operName = SecurityUtils.getUsername();
        for (int i = 0; i < genTables.size(); i++) {
            String tableName = genTables.get(i).getTableName();
            GenUtils.initTable(genTables.get(i), operName);
            List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
            genTables.get(i).setColumns(genTableColumns);
        }
        return genTables;
    }
/**
     * 导入表结构
     *
     * @param tableList 导入表列表
     */
    @Override
    @Transactional
    public void importGenTable(List<GenTable> tableList,
                               String dataSource) {
        try {
            for (GenTable table : tableList) {
            	table.setFormDataSource(dataSource);
                int row = genTableMapper.insertGenTable(table);
                if (row > 0) {
                    for (GenTableColumn column : table.getColumns()) {
                        GenUtils.initColumnField(column, table);
                        genTableColumnMapper.insertGenTableColumn(column);
                    }
                }
            }
        } catch (Exception e) {
            throw new ServiceException("导入失败:" + e.getMessage());
        }
    }

六:修改同步表接口

controller:
	/**
     * 同步数据库
     */
    @PreAuthorize("@ss.hasPermi('tool:gen:edit')")
    @Log(title = "代码生成", businessType = BusinessType.UPDATE)
    @GetMapping("/synchDb/{tableName}")
    public AjaxResult synchDb(@PathVariable("tableName") String tableName) {
        //目标表的最新数据
        List<GenTableColumn> dbTableColumns = genTableService.getDbTableColumns(tableName);
        genTableService.synchDb(tableName,dbTableColumns);
        return success();
    }
service:

新增方法

	/**
     * 获取数据库中指定表的当前字段数据
     * @param tableName
     * @return
     */
    @Override
    public List<GenTableColumn> getDbTableColumns(String tableName){
        GenTable genTable = genTableMapper.selectGenTableByName(tableName);
        if(genTable==null){
            throw new ServiceException("表结构已被删除,请重新导入");
        }
        if(StringUtils.isNotEmpty(genTable.getFormDataSource())) {
            DynamicDataSourceContextHolder.setDataSourceType(genTable.getFormDataSource());
        }
        //最新的目标表的字段
        List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
        DynamicDataSourceContextHolder.clearDataSourceType();
        return genTableColumns;
    }

修改原方法
synchDb方法中,只是将原来的临时变量 dbTableColumns 在方法内获取的逻辑改为了用上面的方法单独获取
由controller调用传参进来
避免了事务中无法切换数据源的问题

	/**
     * 同步数据库
     *
     * @param tableName 表名称
     */
    @Override
    @Transactional
    public void synchDb(String tableName,List<GenTableColumn> dbTableColumns) {
        //找出已导入的当前表
        GenTable table = genTableMapper.selectGenTableByName(tableName);
        //表的字段
        List<GenTableColumn> tableColumns = table.getColumns();
        Map<String, GenTableColumn> tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity()));
        
        if (StringUtils.isEmpty(dbTableColumns)) {
            throw new ServiceException("同步数据失败,原表结构不存在");
        }
        List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList());

        dbTableColumns.forEach(column -> {
            GenUtils.initColumnField(column, table);
            if (tableColumnMap.containsKey(column.getColumnName())) {
                GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
                column.setColumnId(prevColumn.getColumnId());
                if (column.isList()) {
                    // 如果是列表,继续保留查询方式/字典类型选项
                    column.setDictType(prevColumn.getDictType());
                    column.setQueryType(prevColumn.getQueryType());
                }
                if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
                        && (column.isInsert() || column.isEdit())
                        && ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
                    // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
                    column.setIsRequired(prevColumn.getIsRequired());
                    column.setHtmlType(prevColumn.getHtmlType());
                }
                genTableColumnMapper.updateGenTableColumn(column);
            } else {
                genTableColumnMapper.insertGenTableColumn(column);
            }
        });

        List<GenTableColumn> delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
        if (StringUtils.isNotEmpty(delColumns)) {
            genTableColumnMapper.deleteGenTableColumns(delColumns);
        }
    }

至此满足了动态生成不同数据源的代码的需求
出现了什么问题,欢迎查缺补漏,提出bug

Logo

快速构建 Web 应用程序

更多推荐