上一节我们讲了数据开发页面的提交作业类型和提交作业的源码分析,这节我们讲一下元数据中心的源码,元数据对一个平台的重要性不言而喻,几乎每一个平台都会有元数据管理,为什么要有这个模块呢,因为随着业务的扩张,数据和数据表的数量会激增,可能还有增加非常多的数据库,到时候这些表的管理就会非常头疼,对于产品或者一个新人来说,如何知道每张表中有哪些数据,在哪能找到想要的数据就会非常难,但是如果我们有了元数据中心来管理这些表,就会非常方便
上图即为dinky的元数据中心的页面,我这里创建了两个数据源,一个mysql,一个starrocks
1.Dinky支持的数据源
在dinky的注册中心数据源管理模块,我们可以看到目前dinky(0.7.3)总共支持10中数据源,目前比较火的doris和starrocks也是支持的,我们经常用作离线数仓的hive也是支持的,数据源的增删就是在这个模块中进行的
2.Dinky元数据中心的功能介绍
我们先看看元数据中心有哪些功能,举个例子,我们先点击mysql数据源可以清楚的看到mysql数据库中有哪些库
点开dinky数据库的dlink_alert_group,可以看到这个表的所有元数据信息,内容非常的全面
然后点击数据查询页面可以看到这个表中的数据,也可以根据筛选条件和排序字段进行自定义查询
点击SQL生成可以生成相关的DDL sql
在控制台就可以自己写各种sql操作我们的数据库和表
3.Dinky元数据中心源码解析
有些小伙伴可能会有疑惑,源码有那么多模块我们怎么能精准的找到对应的模块调用的api接口呢,这里就需要用到前后端的一些功底了,我们再元数据中心页面打开开发者工具的网络页面,然后刷新一下网页
可以看到出现了两个接口 ,然后点击第一个接口的标头就可以看到该接口的请求url
然后点击预览 ,datas放的就是该链接返回的数据
从这个数据我们可以大致判断出这应该就是我们的登录信息,我们再dlink-admin这个模块的AdminController可以看到/api/current这个方法对应的就是获取当前用户信息
1.获取数据源
然后我们点击第二个链接可以看到第二个链接调用的是/api/database/lostEnabledAll这个接口
最终我们再DatabaseController这个代码中找到了对应的接口方法
这个方法最终调用了DataBaseServiceImpl的listEnabledAll方法,他会去dlink_database这个表中查询enabled为1的所有数据
可以看到这两条数据刚好对应的就是我们前台显示的这俩数据源
2.获取数据源的所有库和表
我们点击mysql数据源,他就显示出这个数据源中的所有数据库,同时右边也出现了一个链接
可以看到他调用的接口是: http://hadoop100:8888/api/database/getSchemasAndTables?id=2
并且还传了一个id=2的参数,下面为接口对应的方法,它又调用了同名的service层方法
service层的方法:
1.根据id获取数据源,这个就是去dlink_database那张表中查询数据源信息
2. 根据数据源获取数据源的名称,type,url,用户名,密码然后获取驱动
3. 根据驱动获取schema信息,他有三个实现类
我们选择AbstractDriver的对应方法进行查看逻辑
可以看到这个方法首先调用了listSchemas方法获取所有的schema,以mysql为例,schemaAllSql的sql就是show database,然后执行sql获取sql的执行结果ResultSet,从其中遍历拿出结果封装成schema对象
@Override
public List<Schema> listSchemas() {
List<Schema> schemas = new ArrayList<>();
PreparedStatement preparedStatement = null;
ResultSet results = null;
//获取查询所有数据库的sql (show database)
String schemasSql = getDBQuery().schemaAllSql();
try {
preparedStatement = conn.get().prepareStatement(schemasSql);
results = preparedStatement.executeQuery();
while (results.next()) {
String schemaName = results.getString(getDBQuery().schemaName());
if (Asserts.isNotNullString(schemaName)) {
Schema schema = new Schema(schemaName);
schemas.add(schema);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close(preparedStatement, results);
}
return schemas;
}
然后刚才的listTables方法,会根据具体的数据源不同获取对应的查询表元数据的sql,然后执行该sql,从resultSet中获取信息封装成table对象
@Override
public List<Table> listTables(String schemaName) {
List<Table> tableList = new ArrayList<>();
PreparedStatement preparedStatement = null;
ResultSet results = null;
IDBQuery dbQuery = getDBQuery();
//这里以mysql为例,将库名传进去查询表的元数据,下面为对应的sql
/**
* select TABLE_NAME AS `NAME`,TABLE_SCHEMA AS `Database`,TABLE_COMMENT AS COMMENT,TABLE_CATALOG AS `CATALOG`"
* + ",TABLE_TYPE AS `TYPE`,ENGINE AS `ENGINE`,CREATE_OPTIONS AS `OPTIONS`,TABLE_ROWS AS `ROWS`"
* + ",CREATE_TIME,UPDATE_TIME from information_schema.tables"
* + " where TABLE_SCHEMA = '" + schemaName + "'
*/
String sql = dbQuery.tablesSql(schemaName);
try {
preparedStatement = conn.get().prepareStatement(sql);
results = preparedStatement.executeQuery();
ResultSetMetaData metaData = results.getMetaData();
List<String> columnList = new ArrayList<>();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
columnList.add(metaData.getColumnLabel(i));
}
while (results.next()) {
String tableName = results.getString(dbQuery.tableName());
if (Asserts.isNotNullString(tableName)) {
Table tableInfo = new Table();
tableInfo.setName(tableName);
if (columnList.contains(dbQuery.tableComment())) {
tableInfo.setComment(results.getString(dbQuery.tableComment()));
}
tableInfo.setSchema(schemaName);
if (columnList.contains(dbQuery.tableType())) {
tableInfo.setType(results.getString(dbQuery.tableType()));
}
if (columnList.contains(dbQuery.catalogName())) {
tableInfo.setCatalog(results.getString(dbQuery.catalogName()));
}
if (columnList.contains(dbQuery.engine())) {
tableInfo.setEngine(results.getString(dbQuery.engine()));
}
if (columnList.contains(dbQuery.options())) {
tableInfo.setOptions(results.getString(dbQuery.options()));
}
if (columnList.contains(dbQuery.rows())) {
tableInfo.setRows(results.getLong(dbQuery.rows()));
}
if (columnList.contains(dbQuery.createTime())) {
tableInfo.setCreateTime(results.getTimestamp(dbQuery.createTime()));
}
if (columnList.contains(dbQuery.updateTime())) {
tableInfo.setUpdateTime(results.getTimestamp(dbQuery.updateTime()));
}
tableList.add(tableInfo);
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(preparedStatement, results);
}
return tableList;
}
3.获取指定表的所有列信息
该链接会查询指定表的所有列信息,然后会将结果数据封装成column对象,我们先找找该链接对应的接口
下面的方法即为该链接对应的接口方法,可以看到他调用了service层的listColumns方法
还是和上面一样的套路,获取数据源后获取驱动,然后调用listColumns方法,最后然后column的list集合
这里和上面一样还是有三个抽象。我们依然选择abstractJdbcDriver,该方法会将库名和表名传进去查询表的元数据,然后将结果封装成column
@Override
public List<Column> listColumns(String schemaName, String tableName) {
List<Column> columns = new ArrayList<>();
PreparedStatement preparedStatement = null;
ResultSet results = null;
IDBQuery dbQuery = getDBQuery();
//这里以mysql为例,将库名和表名传进去查询表的元数据,下面为对应的sql
/**
* select COLUMN_NAME,COLUMN_TYPE,COLUMN_COMMENT,COLUMN_KEY,EXTRA AS AUTO_INCREMENT"
* + ",COLUMN_DEFAULT,IS_NULLABLE,NUMERIC_PRECISION,NUMERIC_SCALE,CHARACTER_SET_NAME"
* + ",COLLATION_NAME,ORDINAL_POSITION from INFORMATION_SCHEMA.COLUMNS "
* + "where TABLE_SCHEMA = '" + schemaName + "' and TABLE_NAME = '" + tableName + "' "
* + "order by ORDINAL_POSITION"
*/
String tableFieldsSql = dbQuery.columnsSql(schemaName, tableName);
try {
preparedStatement = conn.get().prepareStatement(tableFieldsSql);
results = preparedStatement.executeQuery();
ResultSetMetaData metaData = results.getMetaData();
List<String> columnList = new ArrayList<>();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
columnList.add(metaData.getColumnLabel(i));
}
while (results.next()) {
Column field = new Column();
String columnName = results.getString(dbQuery.columnName());
if (columnList.contains(dbQuery.columnKey())) {
String key = results.getString(dbQuery.columnKey());
field.setKeyFlag(Asserts.isNotNullString(key) && Asserts.isEqualsIgnoreCase(dbQuery.isPK(), key));
}
field.setName(columnName);
if (columnList.contains(dbQuery.columnType())) {
String columnType = results.getString(dbQuery.columnType());
if (columnType.contains("(")) {
String type = columnType.replaceAll("\\(.*\\)", "");
if (!columnType.contains(",")) {
Integer length = Integer.valueOf(columnType.replaceAll("\\D", ""));
field.setLength(length);
} else {
// some database does not have precision
if (dbQuery.precision() != null) {
// 例如浮点类型的长度和精度是一样的,decimal(10,2)
field.setLength(results.getInt(dbQuery.precision()));
}
}
field.setType(type);
} else {
field.setType(columnType);
}
}
if (columnList.contains(dbQuery.columnComment())
&& Asserts.isNotNull(results.getString(dbQuery.columnComment()))) {
String columnComment = results.getString(dbQuery.columnComment()).replaceAll("\"|'", "");
field.setComment(columnComment);
}
if (columnList.contains(dbQuery.columnLength())) {
int length = results.getInt(dbQuery.columnLength());
if (!results.wasNull()) {
field.setLength(length);
}
}
if (columnList.contains(dbQuery.isNullable())) {
field.setNullable(Asserts.isEqualsIgnoreCase(results.getString(dbQuery.isNullable()),
dbQuery.nullableValue()));
}
if (columnList.contains(dbQuery.characterSet())) {
field.setCharacterSet(results.getString(dbQuery.characterSet()));
}
if (columnList.contains(dbQuery.collation())) {
field.setCollation(results.getString(dbQuery.collation()));
}
if (columnList.contains(dbQuery.columnPosition())) {
field.setPosition(results.getInt(dbQuery.columnPosition()));
}
if (columnList.contains(dbQuery.precision())) {
field.setPrecision(results.getInt(dbQuery.precision()));
}
if (columnList.contains(dbQuery.scale())) {
field.setScale(results.getInt(dbQuery.scale()));
}
if (columnList.contains(dbQuery.defaultValue())) {
field.setDefaultValue(results.getString(dbQuery.defaultValue()));
}
if (columnList.contains(dbQuery.autoIncrement())) {
field.setAutoIncrement(
Asserts.isEqualsIgnoreCase(results.getString(dbQuery.autoIncrement()), "auto_increment"));
}
if (columnList.contains(dbQuery.defaultValue())) {
field.setDefaultValue(results.getString(dbQuery.defaultValue()));
}
field.setJavaType(getTypeConvert().convert(field, config));
columns.add(field);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(preparedStatement, results);
}
return columns;
}
下面为前端页面显示的表格和column对应的属性,配合这些图去看上面的代码会容易得多
4.数据查询页面源码解析
我们可以看到数据查询页面会默认给我们查出指定表的数据,当然我们也可以根据自己的条件对数据进行筛选和排序
下图即为查询数据对应的接口方法
下图为service层的方法,还是和之前一样获取数据源,获取驱动然后构建sql,执行sql
@Override
public JdbcSelectResult query(String sql, Integer limit) {
ProcessEntity process = ProcessContextHolder.getProcess();
if (Asserts.isNull(limit)) {
limit = 100;
}
JdbcSelectResult result = new JdbcSelectResult();
List<LinkedHashMap<String, Object>> datas = new ArrayList<>();
List<Column> columns = new ArrayList<>();
List<String> columnNameList = new ArrayList<>();
PreparedStatement preparedStatement = null;
ResultSet results = null;
int count = 0;
try {
preparedStatement = conn.get().prepareStatement(sql);
results = preparedStatement.executeQuery();
if (Asserts.isNull(results)) {
result.setSuccess(true);
close(preparedStatement, results);
return result;
}
ResultSetMetaData metaData = results.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
columnNameList.add(metaData.getColumnLabel(i));
Column column = new Column();
column.setName(metaData.getColumnLabel(i));
column.setType(metaData.getColumnTypeName(i));
column.setAutoIncrement(metaData.isAutoIncrement(i));
column.setNullable(metaData.isNullable(i) == 0 ? false : true);
column.setJavaType(getTypeConvert().convert(column, config));
columns.add(column);
}
result.setColumns(columnNameList);
while (results.next()) {
LinkedHashMap<String, Object> data = new LinkedHashMap<>();
for (int i = 0; i < columns.size(); i++) {
Object value = getTypeConvert().convertValue(results, columns.get(i).getName(),
columns.get(i).getType());
if (Asserts.isNotNull(value)) {
data.put(columns.get(i).getName(), value.toString());
} else {
data.put(columns.get(i).getName(), null);
}
}
datas.add(data);
count++;
if (count >= limit) {
break;
}
}
result.setSuccess(true);
} catch (Exception e) {
result.setError(LogUtil.getError(e));
result.setSuccess(false);
process.error(e.getMessage());
} finally {
close(preparedStatement, results);
result.setRowData(datas);
return result;
}
}
5.控制台页面源码解析
下图为控制台页面的接口链接,他对应的接口方法为
他最终还是调用driver的query方法,和数据查询页面一样
到这里,元数据中心的重要功能我们就讲完了,希望对大家有帮助,可以看到后端代码的套路都是一样的,写到后面人都麻了,不想看了,大家如果觉得有帮助的话,麻烦三连哦。