NACOS适配达梦(达梦兼容oracle)

NACOS适配达梦(达梦兼容oracle)

编码文章call10242025-02-01 3:51:289A+A-

项目上要求信创,需要适配达梦数据库。我们项目使用Nacos作为服务注册中心和配置中心,Nacos目前开源版本只支持MySQL和Derby数据库,现需要改造成支持达梦数据库,我们使用的版本为2.2.3

添加驱动包

在nacos-all模块的pom文件里添加

xml代码解读复制代码<dependency>
  <groupId>com.dameng</groupId>
  <artifactId>DmJdbcDriver18</artifactId>
  <version>${dameng8.version}</version>
</dependency>

分别在nacos-config模块和nacos-naming模块的pom.xml文件里引用达梦数据库驱动包。(这里我全局搜索了一下mysql-connector驱动,发现Nacos-naming模块里也有引用,所以就无脑添加上去了。实际应该没有用到,服务不会持久化到数据库里)

xml代码解读复制代码<dependency>
    <groupId>com.dameng</groupId>
    <artifactId>DmJdbcDriver18</artifactId>
</dependency>

插件式开发

在plugin模块下有个datasource子模块

需要在这个子模块下添加达梦数据库实现

  1. DataSourceConstant类里面添加关于达梦数据库的标识常量
java代码解读复制代码public class DataSourceConstant {
    public static final String MYSQL = "mysql";
    
    public static final String DERBY = "derby";

    public static final String DM = "dm";
}
  1. 在impl包下新增dm包,实现mapper包下的所有Mapper,我们以ConfigInfoMapper为例。
java代码解读复制代码// 继承AbstractMapper抽象类 并实现ConfigInfoMapper接口
public class ConfigInfoMapperByDm extends AbstractMapper implements ConfigInfoMapper {
    
    private static final String DATA_ID = "dataId";
    
    private static final String GROUP = "group";
    
    private static final String APP_NAME = "appName";
    
    private static final String CONTENT = "content";
    
    private static final String TENANT = "tenant";

    @Override
    public String findConfigInfoByAppFetchRows(int startRow, int pageSize) {
        return "SELECT id,data_id,group_id,tenant_id,app_name,content FROM config_info"
                + " WHERE tenant_id LIKE ? AND app_name= ?" + " LIMIT " + startRow + "," + pageSize;
    }

    @Override
    public String getTenantIdList(int startRow, int pageSize) {
        return "SELECT tenant_id FROM config_info WHERE tenant_id != '" + NamespaceUtil.getNamespaceDefaultId()
                + "' GROUP BY tenant_id LIMIT " + startRow + ","
                + pageSize;
    }
    
    @Override
    public String getGroupIdList(int startRow, int pageSize) {
        return "SELECT group_id FROM config_info WHERE tenant_id ='" + NamespaceUtil.getNamespaceDefaultId()
                + "' GROUP BY group_id LIMIT " + startRow + ","
                + pageSize;
    }
    
    @Override
    public String findAllConfigKey(int startRow, int pageSize) {
        return " SELECT data_id,group_id,app_name  FROM ( "
                + " SELECT id FROM config_info WHERE tenant_id LIKE ? ORDER BY id LIMIT " + startRow + "," + pageSize
                + " )" + " g, config_info t WHERE g.id = t.id  ";
    }
    
    @Override
    public String findAllConfigInfoBaseFetchRows(int startRow, int pageSize) {
        return "SELECT t.id,data_id,group_id,content,md5"
                + " FROM ( SELECT id FROM config_info ORDER BY id LIMIT ?,?  ) "
                + " g, config_info t  WHERE g.id = t.id ";
    }
    
    @Override
    public String findAllConfigInfoFragment(int startRow, int pageSize) {
        return "SELECT id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,type,encrypted_data_key "
                + "FROM config_info WHERE id > ? ORDER BY id ASC LIMIT " + startRow + "," + pageSize;
    }

    @Override
    public String findChangeConfigFetchRows(Map<String, String> params, final Timestamp startTime,
            final Timestamp endTime, int startRow, int pageSize, long lastMaxId) {
        final String tenant = params.get(TENANT);
        final String dataId = params.get(DATA_ID);
        final String group = params.get(GROUP);
        final String appName = params.get(APP_NAME);
        final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
        final String sqlFetchRows = "SELECT id,data_id,group_id,tenant_id,app_name,content,type,md5,gmt_modified FROM config_info WHERE ";
        String where = " 1=1 ";
        if (!StringUtils.isBlank(dataId)) {
            where += " AND data_id LIKE ? ";
        }
        if (!StringUtils.isBlank(group)) {
            where += " AND group_id LIKE ? ";
        }
        
        if (!StringUtils.isBlank(tenantTmp)) {
            where += " AND tenant_id = ? ";
        }
        
        if (!StringUtils.isBlank(appName)) {
            where += " AND app_name = ? ";
        }
        if (startTime != null) {
            where += " AND gmt_modified >=? ";
        }
        if (endTime != null) {
            where += " AND gmt_modified <=? ";
        }
        return sqlFetchRows + where + " AND id > " + lastMaxId + " ORDER BY id ASC" + " LIMIT " + 0 + "," + pageSize;
    }
    
    @Override
    public String listGroupKeyMd5ByPageFetchRows(int startRow, int pageSize) {
        return "SELECT t.id,data_id,group_id,tenant_id,app_name,md5,type,gmt_modified,encrypted_data_key FROM "
                + "( SELECT id FROM config_info ORDER BY id LIMIT " + startRow + "," + pageSize
                + " ) g, config_info t WHERE g.id = t.id";
    }

    @Override
    public String findConfigInfoBaseLikeFetchRows(Map<String, String> params, int startRow, int pageSize) {
        final String sqlFetchRows = "SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE ";
        String where = " 1=1 AND tenant_id='" + NamespaceUtil.getNamespaceDefaultId() + "' ";
        if (!StringUtils.isBlank(params.get(DATA_ID))) {
            where += " AND data_id LIKE ? ";
        }
        if (!StringUtils.isBlank(params.get(GROUP))) {
            where += " AND group_id LIKE ";
        }
        if (!StringUtils.isBlank(params.get(CONTENT))) {
            where += " AND content LIKE ? ";
        }
        return sqlFetchRows + where + " LIMIT " + startRow + "," + pageSize;
    }

    @Override
    public String findConfigInfo4PageFetchRows(Map<String, String> params, int startRow, int pageSize) {
        final String appName = params.get(APP_NAME);
        final String dataId = params.get(DATA_ID);
        final String group = params.get(GROUP);
        final String content = params.get(CONTENT);
        final String sql = "SELECT id,data_id,group_id,tenant_id,app_name,content,type,encrypted_data_key FROM config_info";
        StringBuilder where = new StringBuilder(" WHERE ");
        where.append(" tenant_id=? ");
        if (StringUtils.isNotBlank(dataId)) {
            where.append(" AND data_id=? ");
        }
        if (StringUtils.isNotBlank(group)) {
            where.append(" AND group_id=? ");
        }
        if (StringUtils.isNotBlank(appName)) {
            where.append(" AND app_name=? ");
        }
        if (!StringUtils.isBlank(content)) {
            where.append(" AND content LIKE ? ");
        }
        return sql + where + " LIMIT " + startRow + "," + pageSize;
    }
    
    @Override
    public String findConfigInfoBaseByGroupFetchRows(int startRow, int pageSize) {
        return "SELECT id,data_id,group_id,content FROM config_info WHERE group_id=? AND tenant_id=?" + " LIMIT "
                + startRow + "," + pageSize;
    }
    
    @Override
    public String findConfigInfoLike4PageFetchRows(Map<String, String> params, int startRow, int pageSize) {
        String dataId = params.get(DATA_ID);
        String group = params.get(GROUP);
        final String appName = params.get(APP_NAME);
        final String content = params.get(CONTENT);
        final String sqlFetchRows = "SELECT id,data_id,group_id,tenant_id,app_name,content,encrypted_data_key FROM config_info";
        StringBuilder where = new StringBuilder(" WHERE ");
        where.append(" tenant_id LIKE ? ");
        if (!StringUtils.isBlank(dataId)) {
            where.append(" AND data_id LIKE ? ");
        }
        if (!StringUtils.isBlank(group)) {
            where.append(" AND group_id LIKE ? ");
        }
        if (!StringUtils.isBlank(appName)) {
            where.append(" AND app_name = ? ");
        }
        if (!StringUtils.isBlank(content)) {
            where.append(" AND content LIKE ? ");
        }
        return sqlFetchRows + where + " LIMIT " + startRow + "," + pageSize;
    }
    
    @Override
    public String findAllConfigInfoFetchRows(int startRow, int pageSize) {
        return "SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5 "
                + " FROM (  SELECT id FROM config_info WHERE tenant_id LIKE ? ORDER BY id LIMIT ?,? )"
                + " g, config_info t  WHERE g.id = t.id ";
    }

    @Override
    public String getDataSource() {
        return DataSourceConstant.DM;
    }
    
}
  1. 在resources目录下,添加达梦数据库实现,Nacos会通过SPI自动加载。文件名为:com.alibaba.nacos.plugin.datasource.mapper.Mapper
properties代码解读复制代码com.alibaba.nacos.plugin.datasource.impl.dm.ConfigInfoAggrMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.ConfigInfoBetaMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.ConfigInfoMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.ConfigInfoTagMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.ConfigTagsRelationMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.HistoryConfigInfoMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.TenantInfoMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.TenantCapacityMapperByDm
com.alibaba.nacos.plugin.datasource.impl.dm.GroupCapacityMapperByDm

集成Nacos

  1. 修改com.alibaba.nacos.config.server.service.datasource.ExternalDataSourceProperties代码
java代码解读复制代码public class ExternalDataSourceProperties {
    
    private static final String JDBC_DRIVER_NAME = "com.mysql.cj.jdbc.Driver";
    
    private static final String TEST_QUERY = "SELECT 1";
    
    private Integer num;
    
    private List<String> url = new ArrayList<>();
    
    private List<String> user = new ArrayList<>();
    
    private List<String> password = new ArrayList<>();
    
    private List<String> driver = new ArrayList<>();
    
    public void setNum(Integer num) {
        this.num = num;
    }
    
    public void setUrl(List<String> url) {
        this.url = url;
    }
    
    public void setUser(List<String> user) {
        this.user = user;
    }
    
    public void setPassword(List<String> password) {
        this.password = password;
    }

    public List<String> getDriver() {
        return driver;
    }

    public void setDriver(List<String> driver) {
        this.driver = driver;
    }


    List<HikariDataSource> build(Environment environment, Callback<HikariDataSource> callback) {
        List<HikariDataSource> dataSources = new ArrayList<>();
        Binder.get(environment).bind("db", Bindable.ofInstance(this));
        Preconditions.checkArgument(Objects.nonNull(num), "db.num is null");
        Preconditions.checkArgument(CollectionUtils.isNotEmpty(user), "db.user or db.user.[index] is null");
        Preconditions.checkArgument(CollectionUtils.isNotEmpty(password), "db.password or db.password.[index] is null");
        for (int index = 0; index < num; index++) {
            int currentSize = index + 1;
            Preconditions.checkArgument(url.size() >= currentSize, "db.url.%s is null", index);
            DataSourcePoolProperties poolProperties = DataSourcePoolProperties.build(environment);

            if (StringUtils.isEmpty(poolProperties.getDataSource().getDriverClassName())) {
                if (StringUtils.isNotEmpty(getOrDefault(driver, index, driver.get(0)).trim())) {
                    // 增加其他数据库驱动的支持
                    poolProperties.setDriverClassName(getOrDefault(driver, index, driver.get(0)).trim());
                } else {
                    //默认使用mysql驱动
                    poolProperties.setDriverClassName(JDBC_DRIVER_NAME);
                }

            }
            poolProperties.setJdbcUrl(url.get(index).trim());
            poolProperties.setUsername(getOrDefault(user, index, user.get(0)).trim());
            poolProperties.setPassword(getOrDefault(password, index, password.get(0)).trim());
            HikariDataSource ds = poolProperties.getDataSource();
            if (StringUtils.isEmpty(ds.getConnectionTestQuery())) {
                ds.setConnectionTestQuery(TEST_QUERY);
            }
            dataSources.add(ds);
            callback.accept(ds);
        }
        Preconditions.checkArgument(CollectionUtils.isNotEmpty(dataSources), "no datasource available");
        return dataSources;
    }
    
    interface Callback<D> {
        
        /**
         * Perform custom logic.
         *
         * @param datasource dataSource.
         */
        void accept(D datasource);
    }
}

注意:

本身以为改到这里以后完事大吉,只剩下测试了。但测试下来后,发现新增配置没问题,但是修改配置报错,报的是主键冲突。其主要原因在于com.alibaba.nacos.config.server.service.repository.extrnal.ExternalConfigInfoPersistServiceImpl#insertOrUpdate(java.lang.String, java.lang.String, com.alibaba.nacos.config.server.model.ConfigInfo, java.sql.Timestamp, java.util.Map<java.lang.String,java.lang.Object>, boolean)

java代码解读复制代码    @Override
    public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time,
            Map<String, Object> configAdvanceInfo, boolean notify) {
        try {
            addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo, notify);
        } catch (DuplicateKeyException ive) { // Unique constraint conflict
            updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo, notify);
        }
    }

这里会捕获DuplicateKeyException,然后在进行update。但由于达梦抛出的异常不是这个,而是DataIntegrityViolationException,所以这里没有被捕获到,导致没有执行该代码。

查看DataIntegrityViolationException和DuplicateKeyException关系时,可以发现DataIntegrityViolationException是DuplicateKeyException的子类,所以可以大胆的将这个异常改成DataIntegrityViolationException

java代码解读复制代码    @Override
    public void insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Timestamp time,
            Map<String, Object> configAdvanceInfo, boolean notify) {
        try {
            addConfigInfo(srcIp, srcUser, configInfo, time, configAdvanceInfo, notify);
        } catch (DataIntegrityViolationException ive) { // Unique constraint conflict
            updateConfigInfo(configInfo, srcIp, srcUser, time, configAdvanceInfo, notify);
        }
    }

但仅仅是这样也不能解决问题,

原来代码:

java代码解读复制代码    @Override
    public void addConfigInfo(final String srcIp, final String srcUser, final ConfigInfo configInfo,
            final Timestamp time, final Map<String, Object> configAdvanceInfo, final boolean notify) {
        tjt.execute(status -> {
            try {
                long configId = addConfigInfoAtomic(-1, srcIp, srcUser, configInfo, time, configAdvanceInfo);
                String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
                addConfigTagsRelation(configId, configTags, configInfo.getDataId(), configInfo.getGroup(),
                        configInfo.getTenant());
                
                historyConfigInfoPersistService.insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, time, "I");
            } catch (CannotGetJdbcConnectionException e) {
                LogUtil.FATAL_LOG.error("[db-error] " + e, e);
                throw e;
            }
            return Boolean.TRUE;
        });
    }

该代码抛出的是CannotGetJdbcConnectionException,所以导致DataIntegrityViolationException,捕获不到,查看CannotGetJdbcConnectionException和DataIntegrityViolationException的关系,发现他们公共的父类为NonTransientDataAccessException。所以可以把该异常改成NonTransientDataAccessException。

java代码解读复制代码    @Override
    public void addConfigInfo(final String srcIp, final String srcUser, final ConfigInfo configInfo,
            final Timestamp time, final Map<String, Object> configAdvanceInfo, final boolean notify) {
        tjt.execute(status -> {
            try {
                long configId = addConfigInfoAtomic(-1, srcIp, srcUser, configInfo, time, configAdvanceInfo);
                String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags");
                addConfigTagsRelation(configId, configTags, configInfo.getDataId(), configInfo.getGroup(),
                        configInfo.getTenant());
                
                historyConfigInfoPersistService.insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, time, "I");
            } catch (NonTransientDataAccessException e) {
                LogUtil.FATAL_LOG.error("[db-error] " + e, e);
                throw e;
            }
            return Boolean.TRUE;
        });
    }

后面如果测试还遇到类似的问题,可以参考此方案修改。

测试

  1. console模块下修改application.properties文件
properties代码解读复制代码db.num=1
## Connect URL of DB:
# db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
# db.user=root
# db.password=123456

spring.sql.init.platform=dm
db.driver.0=dm.jdbc.driver.DmDriver
db.url.0=jdbc:dm://192.168.5.179:5236?SCHEMA=QSD
db.user.0=SYSDBA
db.password.0=SYSDBA
  1. 配置com.alibaba.nacos.Nacos 运行时参数,添加-Dnacos.standalone=true参数
  1. 最后启动Nacos类即可

重新编译

执行命令:

shell代码解读复制代码mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4