nacos适配达梦、瀚高、人大金仓数据库及部分源码探究
一.插件实现
1.插件目录结构
2.pom依赖
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-datasource-plugin</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.1.3.RELEASE</version>
</dependency>
<!-- 达梦 -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>Dm8JdbcDriver18</artifactId>
<version>8.1.1.49</version>
</dependency>
<!-- 瀚高 -->
<dependency>
<groupId>com.highgo</groupId>
<artifactId>HgdbJdbc</artifactId>
<version>6.2.2</version>
</dependency>
<!--人大金仓 -->
<dependency>
<groupId>com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>8.2.0</version>
</dependency>
3.代码实现
3.1.DataSourceConstant
public class DataSourceConstant {
public static final String MYSQL = "mysql"; public static final String DERBY = "derby"; public static final String DM="dm"; public static final String HIGHGO="highgo"; public static final String KINGBASE="kingbase"; }
3.2.公共方法
3.2.1.AbstractConfigInfoAggrMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoAggrMapper; public abstract class AbstractConfigInfoAggrMapperCommon extends AbstractMapper implements ConfigInfoAggrMapper { @Override
public String batchRemoveAggr(int datumSize) {
final StringBuilder placeholderString = new StringBuilder();
for (int i = 0; i < datumSize; i++) {
if (i != 0) {
placeholderString.append(", ");
}
placeholderString.append('?');
}
return "DELETE FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND datum_id IN ("
+ placeholderString + ")";
} @Override
public String aggrConfigInfoCount(int size, boolean isIn) {
StringBuilder sql = new StringBuilder(
"SELECT count(*) FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND datum_id");
if (isIn) {
sql.append(" IN (");
} else {
sql.append(" NOT IN (");
}
for (int i = 0; i < size; i++) {
if (i > 0) {
sql.append(", ");
}
sql.append('?');
}
sql.append(')'); return sql.toString();
} public String findConfigInfoAggrIsOrdered() {
return "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM "
+ "config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY datum_id";
} public String findConfigInfoAggrByPageFetchRows(int startRow, int pageSize) {
return "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM config_info_aggr WHERE data_id= ? AND "
+ "group_id= ? AND tenant_id= ? ORDER BY datum_id LIMIT " + pageSize + " OFFSET " + startRow;
} public String findAllAggrGroupByDistinct() {
return "SELECT DISTINCT data_id, group_id, tenant_id FROM config_info_aggr";
} public String getTableName() {
return TableConstant.CONFIG_INFO_AGGR;
} }
3.2.2.AbstractConfigInfoBetaMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoBetaMapper; public abstract class AbstractConfigInfoBetaMapperCommon extends AbstractMapper implements ConfigInfoBetaMapper { public String updateConfigInfo4BetaCas() {
return "UPDATE config_info_beta SET content = ?,md5 = ?,beta_ips = ?,src_ip = ?,src_user = ?,gmt_modified = ?,app_name = ? "
+ "WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND (md5 = ? or md5 is null or md5 = '')";
} public String findAllConfigInfoBetaForDumpAllFetchRows(int startRow, int pageSize) {
return " SELECT t.id,data_id,group_id,tenant_id,app_name,content,md5,gmt_modified,beta_ips,encrypted_data_key "
+ " FROM ( SELECT id FROM config_info_beta ORDER BY id LIMIT " + pageSize + " OFFSET " + startRow
+ " )" + " g, config_info_beta t WHERE g.id = t.id ";
} public String getTableName() {
return TableConstant.CONFIG_INFO_BETA;
}
}
3.2.3.AbstractConfigInfoTagMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoTagMapper; public abstract class AbstractConfigInfoTagMapperCommon extends AbstractMapper implements ConfigInfoTagMapper { public String updateConfigInfo4TagCas() {
return "UPDATE config_info_tag SET content = ?, md5 = ?, src_ip = ?,src_user = ?,gmt_modified = ?,app_name = ? "
+ "WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND tag_id = ? AND (md5 = ? OR md5 IS NULL OR md5 = '')";
} public String findAllConfigInfoTagForDumpAllFetchRows(int startRow, int pageSize) {
return " SELECT t.id,data_id,group_id,tenant_id,tag_id,app_name,content,md5,gmt_modified "
+ " FROM ( SELECT id FROM config_info_tag ORDER BY id LIMIT " + pageSize + " OFFSET " + startRow
+ " ) " + "g, config_info_tag t WHERE g.id = t.id ";
} public String getTableName() {
return TableConstant.CONFIG_INFO_TAG;
} }
3.2.4.AbstractConfigTagsRelationMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ConfigTagsRelationMapper; import java.util.Map; public abstract class AbstractConfigTagsRelationMapperCommon extends AbstractMapper implements ConfigTagsRelationMapper { public String findConfigInfo4PageCountRows(final Map<String, String> params, final int tagSize) {
final String appName = params.get("appName");
final String dataId = params.get("dataId");
final String group = params.get("group");
StringBuilder where = new StringBuilder(" WHERE ");
final String sqlCount = "SELECT count(*) FROM config_info a LEFT JOIN config_tags_relation b ON a.id=b.id";
where.append(" a.tenant_id=? ");
if (StringUtils.isNotBlank(dataId)) {
where.append(" AND a.data_id=? ");
}
if (StringUtils.isNotBlank(group)) {
where.append(" AND a.group_id=? ");
}
if (StringUtils.isNotBlank(appName)) {
where.append(" AND a.app_name=? ");
}
where.append(" AND b.tag_name IN (");
for (int i = 0; i < tagSize; i++) {
if (i != 0) {
where.append(", ");
}
where.append('?');
}
where.append(") ");
return sqlCount + where;
} public String findConfigInfo4PageFetchRows(Map<String, String> params, int tagSize, int startRow, int pageSize) {
final String appName = params.get("appName");
final String dataId = params.get("dataId");
final String group = params.get("group");
StringBuilder where = new StringBuilder(" WHERE ");
final String sql = "SELECT a.id,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content FROM config_info a LEFT JOIN "
+ "config_tags_relation b ON a.id=b.id"; where.append(" a.tenant_id=? "); if (StringUtils.isNotBlank(dataId)) {
where.append(" AND a.data_id=? ");
}
if (StringUtils.isNotBlank(group)) {
where.append(" AND a.group_id=? ");
}
if (StringUtils.isNotBlank(appName)) {
where.append(" AND a.app_name=? ");
} where.append(" AND b.tag_name IN (");
for (int i = 0; i < tagSize; i++) {
if (i != 0) {
where.append(", ");
}
where.append('?');
}
where.append(") ");
return sql + where + " LIMIT " + pageSize + " OFFSET " + startRow;
} public String findConfigInfoLike4PageCountRows(final Map<String, String> params, int tagSize) {
final String appName = params.get("appName");
final String content = params.get("content");
final String dataId = params.get("dataId");
final String group = params.get("group");
StringBuilder where = new StringBuilder(" WHERE ");
final String sqlCountRows = "SELECT count(*) FROM config_info a LEFT JOIN config_tags_relation b ON a.id=b.id "; where.append(" a.tenant_id LIKE ? ");
if (!StringUtils.isBlank(dataId)) {
where.append(" AND a.data_id LIKE ? ");
}
if (!StringUtils.isBlank(group)) {
where.append(" AND a.group_id LIKE ? ");
}
if (!StringUtils.isBlank(appName)) {
where.append(" AND a.app_name = ? ");
}
if (!StringUtils.isBlank(content)) {
where.append(" AND a.content LIKE ? ");
} where.append(" AND b.tag_name IN (");
for (int i = 0; i < tagSize; i++) {
if (i != 0) {
where.append(", ");
}
where.append('?');
}
where.append(") ");
return sqlCountRows + where;
} public String findConfigInfoLike4PageFetchRows(final Map<String, String> params, int tagSize, int startRow,
int pageSize) {
final String appName = params.get("appName");
final String content = params.get("content");
final String dataId = params.get("dataId");
final String group = params.get("group");
StringBuilder where = new StringBuilder(" WHERE ");
final String sqlFetchRows = "SELECT a.id,a.data_id,a.group_id,a.tenant_id,a.app_name,a.content "
+ "FROM config_info a LEFT JOIN config_tags_relation b ON a.id=b.id "; where.append(" a.tenant_id LIKE ? ");
if (!StringUtils.isBlank(dataId)) {
where.append(" AND a.data_id LIKE ? ");
}
if (!StringUtils.isBlank(group)) {
where.append(" AND a.group_id LIKE ? ");
}
if (!StringUtils.isBlank(appName)) {
where.append(" AND a.app_name = ? ");
}
if (!StringUtils.isBlank(content)) {
where.append(" AND a.content LIKE ? ");
} where.append(" AND b.tag_name IN (");
for (int i = 0; i < tagSize; i++) {
if (i != 0) {
where.append(", ");
}
where.append('?');
}
where.append(") ");
return sqlFetchRows + where + " LIMIT " + startRow + "," + pageSize;
} public String getTableName() {
return TableConstant.CONFIG_TAGS_RELATION;
} }
3.2.5.AbstractGroupCapacityMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.GroupCapacityMapper; public abstract class AbstractGroupCapacityMapperCommon extends AbstractMapper implements GroupCapacityMapper { public String insertIntoSelect() {
return "INSERT INTO group_capacity (group_id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size,gmt_create,"
+ " gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info";
} public String insertIntoSelectByWhere() {
return "INSERT INTO group_capacity (group_id, quota,`usage`, `max_size`, max_aggr_count, max_aggr_size, gmt_create,"
+ " gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE group_id=? AND tenant_id = ''";
} public String incrementUsageByWhereQuotaEqualZero() {
return "UPDATE group_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE group_id = ? AND `usage` < ? AND quota = 0";
} public String incrementUsageByWhereQuotaNotEqualZero() {
return "UPDATE group_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE group_id = ? AND `usage` < quota AND quota != 0";
} public String incrementUsageByWhere() {
return "UPDATE group_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE group_id = ?";
} public String decrementUsageByWhere() {
return "UPDATE group_capacity SET `usage` = `usage` - 1, gmt_modified = ? WHERE group_id = ? AND `usage` > 0";
} public String updateUsage() {
return "UPDATE group_capacity SET `usage` = (SELECT count(*) FROM config_info), gmt_modified = ? WHERE group_id = ?";
} public String updateUsageByWhere() {
return "UPDATE group_capacity SET `usage` = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = ''),"
+ " gmt_modified = ? WHERE group_id= ?";
} public String selectGroupInfoBySize() {
return "SELECT id, group_id FROM group_capacity WHERE id > ? LIMIT ?";
} public String getTableName() {
return TableConstant.GROUP_CAPACITY;
} }
3.2.6.AbstractHistoryConfigInfoMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.HistoryConfigInfoMapper; public abstract class AbstractHistoryConfigInfoMapperCommon extends AbstractMapper implements HistoryConfigInfoMapper { public String removeConfigHistory() {
return "DELETE FROM his_config_info WHERE gmt_modified < ? LIMIT ?";
} public String findConfigHistoryCountByTime() {
return "SELECT count(*) FROM his_config_info WHERE gmt_modified < ?";
} public String findDeletedConfig() {
return "SELECT DISTINCT data_id, group_id, tenant_id FROM his_config_info WHERE op_type = 'D' AND gmt_modified >= ? AND gmt_modified <= ?";
} public String findConfigHistoryFetchRows() {
return "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info "
+ "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC";
} public String detailPreviousConfigHistory() {
return "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified "
+ "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?) ";
} public String pageFindConfigHistoryFetchRows(int pageNo, int pageSize) {
final int offset = (pageNo - 1) * pageSize;
final int limit = pageSize;
return "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info "
+ "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC LIMIT " + offset + "," + limit;
} public String getTableName() {
return TableConstant.HIS_CONFIG_INFO;
} }
3.2.7.AbstractTenantCapacityMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.TenantCapacityMapper; public abstract class AbstractTenantCapacityMapperCommon extends AbstractMapper implements TenantCapacityMapper { public String incrementUsageWithDefaultQuotaLimit() {
return "UPDATE tenant_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE tenant_id = ? AND `usage` <"
+ " ? AND quota = 0";
} public String incrementUsageWithQuotaLimit() {
return "UPDATE tenant_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE tenant_id = ? AND `usage` < "
+ "quota AND quota != 0";
} public String incrementUsage() {
return "UPDATE tenant_capacity SET `usage` = `usage` + 1, gmt_modified = ? WHERE tenant_id = ?";
} public String decrementUsage() {
return "UPDATE tenant_capacity SET `usage` = `usage` - 1, gmt_modified = ? WHERE tenant_id = ? AND `usage` > 0";
} public String correctUsage() {
return "UPDATE tenant_capacity SET `usage` = (SELECT count(*) FROM config_info WHERE tenant_id = ?), "
+ "gmt_modified = ? WHERE tenant_id = ?";
} public String getCapacityList4CorrectUsage() {
return "SELECT id, tenant_id FROM tenant_capacity WHERE id>? LIMIT ?";
} public String insertTenantCapacity() {
return "INSERT INTO tenant_capacity (tenant_id, quota, `usage`, `max_size`, max_aggr_count, max_aggr_size, "
+ "gmt_create, gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE tenant_id=?;";
} public String getTableName() {
return TableConstant.TENANT_CAPACITY;
} }
3.2.8.AbstractConfigInfoMapperCommon
package com.alibaba.nacos.plugin.datasource.impl.common; import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoMapper; import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; public abstract class AbstractConfigInfoMapperCommon 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"; public String findConfigMaxId() {
return "SELECT MAX(id) FROM config_info";
} public String findAllDataIdAndGroup() {
return "SELECT DISTINCT data_id, group_id FROM config_info";
} public String findConfigInfoByAppCountRows() {
return "SELECT count(*) FROM config_info WHERE tenant_id LIKE ? AND app_name= ?";
} 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 " + pageSize + " OFFSET " + startRow;
} public String configInfoLikeTenantCount() {
return "SELECT count(*) FROM config_info WHERE tenant_id LIKE ?";
} public String getTenantIdList(int startRow, int pageSize) {
return "SELECT tenant_id FROM config_info WHERE tenant_id != '' GROUP BY tenant_id LIMIT " + pageSize
+ " OFFSET " + startRow;
} public String getGroupIdList(int startRow, int pageSize) {
return "SELECT group_id FROM config_info WHERE tenant_id ='' GROUP BY group_id LIMIT " + pageSize + " OFFSET "
+ startRow;
} 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 " + pageSize + " OFFSET "
+ startRow + " )" + " g, config_info t WHERE g.id = t.id ";
} 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 ";
} 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 " + pageSize + " OFFSET " + startRow;
} public String findChangeConfig() {
return "SELECT data_id, group_id, tenant_id, app_name, content, gmt_modified,encrypted_data_key "
+ "FROM config_info WHERE gmt_modified >= ? AND gmt_modified <= ?";
} public String findChangeConfigCountRows(Map<String, String> params, final Timestamp startTime,
final Timestamp endTime) {
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 sqlCountRows = "SELECT count(*) 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 sqlCountRows + where;
} 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 + " OFFSET "
+ pageSize;
} 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 " + pageSize + " OFFSET " + startRow
+ " ) g, config_info t WHERE g.id = t.id";
} public String findAllConfigInfo4Export(List<Long> ids, Map<String, String> params) {
String tenant = params.get("tenant");
String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
String sql = "SELECT id,data_id,group_id,tenant_id,app_name,content,type,md5,gmt_create,gmt_modified,src_user,src_ip,"
+ "c_desc,c_use,effect,c_schema,encrypted_data_key FROM config_info";
StringBuilder where = new StringBuilder(" WHERE ");
List<Object> paramList = new ArrayList<>();
if (!CollectionUtils.isEmpty(ids)) {
where.append(" id IN (");
for (int i = 0; i < ids.size(); i++) {
if (i != 0) {
where.append(", ");
}
where.append('?');
paramList.add(ids.get(i));
}
where.append(") ");
} else {
where.append(" tenant_id= ? ");
paramList.add(tenantTmp);
if (!StringUtils.isBlank(params.get(DATA_ID))) {
where.append(" AND data_id LIKE ? ");
}
if (StringUtils.isNotBlank(params.get(GROUP))) {
where.append(" AND group_id= ? ");
}
if (StringUtils.isNotBlank(params.get(APP_NAME))) {
where.append(" AND app_name= ? ");
}
}
return sql + where;
} public String findConfigInfoBaseLikeCountRows(Map<String, String> params) {
final String sqlCountRows = "SELECT count(*) FROM config_info WHERE ";
String where = " 1=1 AND tenant_id='' "; 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 sqlCountRows + where;
} 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='' ";
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;
} public String findConfigInfo4PageCountRows(Map<String, String> params) {
final String appName = params.get(APP_NAME);
final String dataId = params.get(DATA_ID);
final String group = params.get(GROUP);
final String sqlCount = "SELECT count(*) 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=? ");
}
return sqlCount + where;
} 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 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=? ");
}
return sql + where + " LIMIT " + pageSize + " OFFSET " + startRow;
} 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 "
+ pageSize + " OFFSET " + startRow;
} public String findConfigInfoLike4PageCountRows(Map<String, String> params) {
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 sqlCountRows = "SELECT count(*) 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 sqlCountRows + where;
} 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 " + pageSize + " OFFSET " + startRow;
} 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 " + pageSize
+ " OFFSET " + startRow + " )" + " g, config_info t WHERE g.id = t.id ";
} public String findConfigInfosByIds(int idSize) {
StringBuilder sql = new StringBuilder(
"SELECT ID,data_id,group_id,tenant_id,app_name,content,md5 FROM config_info WHERE ");
sql.append("id IN (");
for (int i = 0; i < idSize; i++) {
if (i != 0) {
sql.append(", ");
}
sql.append('?');
}
sql.append(") ");
return sql.toString();
} public String removeConfigInfoByIdsAtomic(int size) {
StringBuilder sql = new StringBuilder("DELETE FROM config_info WHERE ");
sql.append("id IN (");
for (int i = 0; i < size; i++) {
if (i != 0) {
sql.append(", ");
}
sql.append('?');
}
sql.append(") ");
return sql.toString();
} public String updateConfigInfoAtomicCas() {
return "UPDATE config_info SET "
+ "content=?, md5 = ?, src_ip=?,src_user=?,gmt_modified=?, app_name=?,c_desc=?,c_use=?,effect=?,type=?,c_schema=? "
+ "WHERE data_id=? AND group_id=? AND tenant_id=? AND (md5=? OR md5 IS NULL OR md5='')";
} public String getTableName() {
return TableConstant.CONFIG_INFO;
} }
3.3.达梦数据源适配实现
3.3.1.ConfigInfoAggrMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractConfigInfoAggrMapperCommon; public class ConfigInfoAggrMapperByDM extends AbstractConfigInfoAggrMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.2.ConfigInfoBetaMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractConfigInfoBetaMapperCommon; public class ConfigInfoBetaMapperByDM extends AbstractConfigInfoBetaMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.3.ConfigInfoMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractConfigInfoMapperCommon; public class ConfigInfoMapperByDM extends AbstractConfigInfoMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.4.ConfigInfoTagMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractConfigInfoTagMapperCommon; public class ConfigInfoTagMapperByDM extends AbstractConfigInfoTagMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.5.ConfigTagsRelationMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractConfigTagsRelationMapperCommon; public class ConfigTagsRelationMapperByDM extends AbstractConfigTagsRelationMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.6.GroupCapacityMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractGroupCapacityMapperCommon; public class GroupCapacityMapperByDM extends AbstractGroupCapacityMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.7.HistoryConfigInfoMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractHistoryConfigInfoMapperCommon; public class HistoryConfigInfoMapperByDM extends AbstractHistoryConfigInfoMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.8.TenantCapacityMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.impl.common.AbstractTenantCapacityMapperCommon; public class TenantCapacityMapperByDM extends AbstractTenantCapacityMapperCommon { @Override
public String getDataSource() {
return DataSourceConstant.DM;
}
}
3.3.9.TenantInfoMapperByDM
package com.alibaba.nacos.plugin.datasource.impl.dm; import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant;
import com.alibaba.nacos.plugin.datasource.constants.TableConstant;
import com.alibaba.nacos.plugin.datasource.mapper.AbstractMapper;
import com.alibaba.nacos.plugin.datasource.mapper.TenantInfoMapper; public class TenantInfoMapperByDM extends AbstractMapper implements TenantInfoMapper { @Override
public String getTableName() {
return TableConstant.TENANT_INFO;
} @Override
public String getDataSource() {
return DataSourceConstant.DM;
} }
3.4.其他数据源适配实现
跟上面达梦数据源适配实现一致,只需将 getDataSource 方法替换成对应的数据源类型就OK;
3.5.service文件
在resources下面创建resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper文件
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.GroupCapacityMapperByDM
com.alibaba.nacos.plugin.datasource.impl.dm.HistoryConfigInfoMapperByDM
com.alibaba.nacos.plugin.datasource.impl.dm.TenantCapacityMapperByDM
com.alibaba.nacos.plugin.datasource.impl.dm.TenantInfoMapperByDM #其他类似..............
3.6.nacos-server配置
1)mvn install后该jar包放入到Nacos(版本2.2.0才支持数据源插件化)的plugins目录下(没有则新建),Nacos的启动脚本startup.sh中-Dloader.path参数将会加载外部jar包文件到Nacos的环境中;
2)修改Nacos的conf/application.properties,增加对应数据源的连接参数信息:
db.num=1
#指定数据源类型,对应DataSourceConstant参数
spring.sql.init.platform=dm
db.url.0=jdbc:dm://ip:5236/
db.user.0=username
db.password.0=password
#指定driver
db.pool.config.driverClassName=dm.jdbc.driver.DmDriver
3.7.启动
1)启动命令:
单机模式:sudo sh startup.sh -m standalone
集群模式:./startup.sh
PS:
集群模式下修改cluster.conf.example文件为cluster.conf,不然报错误: Caused by: com.alibaba.nacos.api.exception.NacosException: java.net.UnknownHostException: jmenv.tbsite.net
2)ip访问验证:http://ip:8848/nacos/index.html
二.源码探究
1.准备
源码nacos-2.2.0:https://github.com/alibaba/nacos.git
2.网页入口
nacos-console模块com.alibaba.nacos.Nacos
3.nacos数据持久化入口
3.1.com.alibaba.nacos.config.server.service.repository.extrnal.ExternalStoragePersistServiceImpl#init()
@PostConstruct
public void init() {
//获取数据源
dataSourceService = DynamicDataSource.getInstance().getDataSource(); jt = getJdbcTemplate();
tjt = getTransactionTemplate();
Boolean isDataSourceLogEnable = EnvUtil
.getProperty(Constants.NACOS_PLUGIN_DATASOURCE_LOG, Boolean.class, false);
mapperManager = MapperManager.instance(isDataSourceLogEnable);
} com.alibaba.nacos.config.server.service.datasource.DynamicDataSource#getDataSource
public synchronized DataSourceService getDataSource() {
try { // Embedded storage is used by default in stand-alone mode
// In cluster mode, external databases are used by default
//单机模式
if (PropertyUtil.isEmbeddedStorage()) {
if (localDataSourceService == null) {
localDataSourceService = new LocalDataSourceServiceImpl();
localDataSourceService.init();
}
return localDataSourceService;
} else {
//集群模式
if (basicDataSourceService == null) {
basicDataSourceService = new ExternalDataSourceServiceImpl();
basicDataSourceService.init();
}
return basicDataSourceService;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
3.2.com.alibaba.nacos.config.server.service.datasource.ExternalDataSourceServiceImpl#init
@Override
public void init() {
queryTimeout = ConvertUtils.toInt(System.getProperty("QUERYTIMEOUT"), 3);
jt = new JdbcTemplate();
// Set the maximum number of records to prevent memory expansion
jt.setMaxRows(50000);
jt.setQueryTimeout(queryTimeout); testMasterJT = new JdbcTemplate();
testMasterJT.setQueryTimeout(queryTimeout); testMasterWritableJT = new JdbcTemplate();
// Prevent the login interface from being too long because the main library is not available
testMasterWritableJT.setQueryTimeout(1); // Database health check testJtList = new ArrayList<>();
isHealthList = new ArrayList<>(); tm = new DataSourceTransactionManager();
tjt = new TransactionTemplate(tm); // Transaction timeout needs to be distinguished from ordinary operations.
tjt.setTimeout(TRANSACTION_QUERY_TIMEOUT);
//获取数据源类型:spring.sql.init.platform或spring.datasource.platform指定的参数值
dataSourceType = DatasourcePlatformUtil.getDatasourcePlatform(defaultDataSourceType); if (PropertyUtil.isUseExternalDB()) {
try {
//加载数据源连接
reload();
} catch (IOException e) {
FATAL_LOG.error("[ExternalDataSourceService] datasource reload error", e);
throw new RuntimeException(DB_LOAD_ERROR_MSG, e);
} if (this.dataSourceList.size() > DB_MASTER_SELECT_THRESHOLD) {
ConfigExecutor.scheduleConfigTask(new SelectMasterTask(), 10, 10, TimeUnit.SECONDS);
}
ConfigExecutor.scheduleConfigTask(new CheckDbHealthTask(), 10, 10, TimeUnit.SECONDS);
}
}
3.3.com.alibaba.nacos.config.server.service.datasource.ExternalDataSourceServiceImpl#reload
@Override
public synchronized void reload() throws IOException {
try {
final List<JdbcTemplate> testJtListNew = new ArrayList<JdbcTemplate>();
final List<Boolean> isHealthListNew = new ArrayList<Boolean>();
//com.alibaba.nacos.config.server.service.datasource.ExternalDataSourceProperties#build:获取数据源
List<HikariDataSource> dataSourceListNew = new ExternalDataSourceProperties()
.build(EnvUtil.getEnvironment(), (dataSource) -> {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setQueryTimeout(queryTimeout);
jdbcTemplate.setDataSource(dataSource);
testJtListNew.add(jdbcTemplate);
isHealthListNew.add(Boolean.TRUE);
});
final List<HikariDataSource> dataSourceListOld = dataSourceList;
final List<JdbcTemplate> testJtListOld = testJtList;
dataSourceList = dataSourceListNew;
testJtList = testJtListNew;
isHealthList = isHealthListNew;
//选择主数据源:测试数据源,数据源设置到JdbcTemplate jt中(重点,后续请求从jt中获取dataSource)
new SelectMasterTask().run();
//校验数据源的状态
new CheckDbHealthTask().run(); //close old datasource.
if (dataSourceListOld != null && !dataSourceListOld.isEmpty()) {
for (HikariDataSource dataSource : dataSourceListOld) {
dataSource.close();
}
}
if (testJtListOld != null && !testJtListOld.isEmpty()) {
for (JdbcTemplate oldJdbc : testJtListOld) {
oldJdbc.setDataSource(null);
}
}
} catch (RuntimeException e) {
FATAL_LOG.error(DB_LOAD_ERROR_MSG, e);
throw new IOException(e);
}
}
3.4.com.alibaba.nacos.config.server.service.datasource.ExternalDataSourceProperties#build:根据application.properties配置连接获取数据源
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);
//获取driverClassName
DataSourcePoolProperties poolProperties = DataSourcePoolProperties.build(environment);
if (StringUtils.isEmpty(poolProperties.getDataSource().getDriverClassName())) {
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;
}
3.5.com.alibaba.nacos.config.server.service.datasource.DataSourcePoolProperties#build
public static DataSourcePoolProperties build(Environment environment) {
DataSourcePoolProperties result = new DataSourcePoolProperties();
//绑定driverClassName值
Binder.get(environment).bind("db.pool.config", Bindable.ofInstance(result.getDataSource()));
return result;
}
3.6.com.alibaba.nacos.plugin.datasource.MapperManager#loadInitial:nacos-datasource-plugin
public void loadInitial() {
//spi加载插件实现类
Collection<Mapper> mappers = NacosServiceLoader.load(Mapper.class);
for (Mapper mapper : mappers) {
Map<String, Mapper> mapperMap = MAPPER_SPI_MAP.getOrDefault(mapper.getDataSource(), new HashMap<>(16));
mapperMap.put(mapper.getTableName(), mapper);
MAPPER_SPI_MAP.put(mapper.getDataSource(), mapperMap);
LOGGER.info("[MapperManager] Load Mapper({}) datasource({}) tableName({}) successfully.",
mapper.getClass(), mapper.getDataSource(), mapper.getTableName());
}
}
ps:
SPI:Service Provider Interface (服务提供接口) 是java提供的一套用来被第三方实现或扩展的接口。它可以用来启用框架 扩展和替换 组件;
SPI 与API 区别与联系
API: (Application Programming Interface) 在绝大多数情况下,都是实现方定制接口并进行接口的实现,调用者只能选择调用,而无权选择不同实现;
SPI: 调用方制定接口规范,提供给外部实现。调用方在调用时选择自己需要的外部实现;
流程:
1)定义一个接口,定义接口的实现类;
2)在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名;
3)通过serviceLoader加载实现类并调用
public static void main(String[] args) {
ServiceLoader<Mapper> mappers = ServiceLoader.load(Mapper.class);
for (Mapper m : mappers) {
m.getTableName();
}
}
pps:在 ServiceLoader.load 后遍历 mappers 时可以通过 BeanDefinitionRegistry 可以将mapper实现类注册到 ApplicationContext中,这样就可以对接口实现类使用 @Autowired 来解决依赖注入问题了;
3.7.com.alibaba.nacos.config.server.service.dump.ExternalDumpService#init:将数据库的config_info信息dump一份到本地磁盘
@PostConstruct
@Override
protected void init() throws Throwable {
dumpOperate(processor, dumpAllProcessor, dumpAllBetaProcessor, dumpAllTagProcessor);
}
ps:
有兴趣可研究 ExternalDumpService#init -> DumpService#dumpConfigInfo -> DumpAllProcessor#process(第一行定位到3.8) -> ExternalStoragePersistServiceImpl#findAllConfigInfoFragment -> 分页将数据库的config_info(nacos的配置管理的配置列表信息)dump到本地磁盘 -> ConfigCacheService#dump -> DiskUtil#saveToDisk
pps:所以nacos的配置信息优先从本地取,本地为空再从数据库取并更新到本地;
可从 ConfigController#getConfig -> ConfigServletInner#doGetConfig() 中看到从本地缓存取,失败再从数据库查询并更新到本地;
3.8.com.alibaba.nacos.config.server.service.repository.extrnal.ExternalConfigInfoPersistServiceImpl#findConfigMaxId
public long findConfigMaxId() {
//获取config_info表实现类
ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(),
TableConstant.CONFIG_INFO);
String sql = configInfoMapper.findConfigMaxId();
try {
return jt.queryForObject(sql, Long.class);
} catch (NullPointerException e) {
return 0;
}
}
3.9.com.alibaba.nacos.plugin.datasource.MapperManager#findMapper
public <R extends Mapper> R findMapper(String dataSource, String tableName) {
LOGGER.info("[MapperManager] findMapper dataSource: {}, tableName: {}", dataSource, tableName);
if (StringUtils.isBlank(dataSource) || StringUtils.isBlank(tableName)) {
throw new NacosRuntimeException(FIND_DATASOURCE_ERROR_CODE, "dataSource or tableName is null");
}
Map<String, Mapper> tableMapper = MAPPER_SPI_MAP.get(dataSource);
if (Objects.isNull(tableMapper)) {
throw new NacosRuntimeException(FIND_DATASOURCE_ERROR_CODE,
"[MapperManager] Failed to find the datasource,dataSource:" + dataSource);
}
Mapper mapper = tableMapper.get(tableName);
if (Objects.isNull(mapper)) {
throw new NacosRuntimeException(FIND_TABLE_ERROR_CODE,
"[MapperManager] Failed to find the table ,tableName:" + tableName);
}
//nacos.plugin.datasource.log.enabled=true开启日志:jdk代理
if (dataSourceLogEnable) {
MapperProxy mapperProxy = new MapperProxy();
return (R) mapperProxy.createProxy(mapper);
}
return (R) mapper;
}
nacos适配达梦、瀚高、人大金仓数据库及部分源码探究的更多相关文章
- 通过ODBC接口访问人大金仓数据库
国产化软件和国产化芯片的窘境一样,一方面市场已经存在性能优越的同类软件,成本很低,但小众的国产化软件不仅需要高价买入版权,并且软件开发维护成本高:另一方面,国产软件目前普遍难用,性能不稳定,Bug ...
- QT 之 ODBC连接人大金仓数据库
QT 之 使用 ODBC 驱动连接人大金仓数据库 获取数据库驱动和依赖动态库 此操作可在人大金仓官网下载与系统匹配的接口动态库,或者从架构数据库的源码中获取驱动和依赖动态库 分别为: 驱动动态库:kd ...
- 通过jmeter连接人大金仓数据库
某项目用的人大金仓数据库,做性能测试,需要用jmeter来连接数据库处理一批数据.jmeter连接人大金仓,做个记录. 1. 概要 在"配置元件"中添加"JDBC Con ...
- DBeaver连接达梦|虚谷|人大金仓等国产数据库
前言 工作中有些项目可能会接触到「达梦.虚谷.人大金仓」等国产数据库,但通常这些数据库自带的连接工具使用并不方便,所以这篇文章记录一下 DBeaver 连接国产数据库的通用模版,下文以达梦为例(其他国 ...
- 教你10分钟对接人大金仓EF Core 6.x
前言 目前.NET Core中据我了解到除了官方的EF Core外,还用的比较多的ORM框架(恕我孤陋寡闻哈,可能还有别的)有FreeSql.SqlSugar(排名不分先后).FreeSql和SqlS ...
- 高仿精仿开心网应用android源码
今天早上看到了一个不错的安卓应用源码项目,真的非常不错高仿精仿开心网应用android源码下载,希望大家能够喜欢. 原文:http://android.662p.com/thread-29 ...
- 高仿精仿快播应用android源码下载
今天给大家在网上找到的一款高仿精仿快播应用android源码,分享给大家,希望大家功能喜欢. 说明源码更新中.... 源码即将上传 也可以到这个网站下载:download
- VopSdk一个高逼格微信公众号开发SDK(源码下载)
看之前回复很多说明大家很有热情&文章被误删掉了,不想让有的朋友错失这个高逼格的东西,现在重新发布,这次就直接放出源码,文章最末下载地址. 看之前回复很多说明大家很有热情&文章被误删掉了 ...
- 高仿ios版美团框架项目源码
高仿美团框架基本已搭好.代码简单易懂,适合新人.适合新人.新人. <ignore_js_op> 源码你可以到ios教程网那里下载吧,这里我就不上传了,http://ios.662p ...
- 高仿一元云购IOS应用源码项目
高仿一元云购IOS应用(高仿自一元云购安卓客户端) 本App因官方没有IOS客户端故开发,利用业务时间历时2个星期,终于开发完成,又因苹果的各大审核规则对此App的影响,又历时1个多月才终于成功上架, ...
随机推荐
- Kubuesphere部署Ruoyi(二):部署kubesphere
先决条件: 更换DNS 更换apt的镜像源 Ubuntu下永久性修改DNS vi /etc/systemd/resolved.conf DNS字段取消注释,并修改DNS为223.5.5.5 223.5 ...
- chatgpt接口开发笔记1:completions接口
chatgpt接口开发笔记1:completions接口 个人博客地址: https://note.raokun.top 拥抱ChatGPT,国内访问网站:https://www.playchat.t ...
- C# 禁用窗口激活
如果界面点击时,不想让窗口激活,可以按如下操作: 1 public MainWindow() 2 { 3 InitializeComponent(); 4 SourceInitialized += O ...
- Python_15 ddt驱动与日志
一.查缺补漏 1. 在测试报告中添加注释,写在类名下面就行,方法名下面,三引号 2. 直接import ddt引用的时候需要ddt.ddt, ddt.data, ddt.unpack from ddt ...
- JSP课设之项目管理
一.准备: JDK1.8,eclipse,Tomcat(Web服务器),JDBC驱动(链接数据库) java+html+css+jsp+javabean+DAO+servlet 二.步骤: 1.装JD ...
- STL------sort三种比较算子定义
sort的三种比较算子的定义方式 例:一道细碎的细节模拟题: http://newoj.acmclub.cn/contests/1258/problem/3 1932: 2018蓝桥杯培训-STL应用 ...
- sh: vue-cli-service: command not found
mac环境下运行vue项目报错sh: vue-cli-service: command not found 解决方法:cd到项目目录下,执行命令sudo rm -rf node_modules pac ...
- vue全家桶进阶之路16:自定义过滤器及开发插件
过渡 过渡(transition)是Vue提供的一种在元素在插入.更新或移除时,自动添加动画效果的方式.Vue提供了多种过渡效果,其中包括基于CSS动画的过渡,以及JavaScript过渡. 过渡可以 ...
- union()并集intersection()交集difference()差集
union并集,即:合并 intersection()交集 difference()差集 qs1=Course.objects.filter(price__get=240) qs2=Course.ob ...
- Python获取当前时间、获取当月第一天、最后一天日期等方法
```python先导入包: import calendarimport datetimefrom datetime import timedelta获取今天日期: #返回datetime格式:eg: ...