NACOS适配达梦(达梦兼容oracle)
项目上要求信创,需要适配达梦数据库。我们项目使用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子模块
需要在这个子模块下添加达梦数据库实现
- DataSourceConstant类里面添加关于达梦数据库的标识常量
java代码解读复制代码public class DataSourceConstant {
public static final String MYSQL = "mysql";
public static final String DERBY = "derby";
public static final String DM = "dm";
}
- 在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;
}
}
- 在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
- 修改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;
});
}
后面如果测试还遇到类似的问题,可以参考此方案修改。
测试
- 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
- 配置com.alibaba.nacos.Nacos 运行时参数,添加-Dnacos.standalone=true参数
- 最后启动Nacos类即可
重新编译
执行命令:
shell代码解读复制代码mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U