refs #703: route schema metadata through MyBatis
This commit is contained in:
@@ -1,20 +1,25 @@
|
||||
package com.cloudhandson.vpdbackoffice.service;
|
||||
|
||||
import com.cloudhandson.vpdbackoffice.domain.schemametadata.SchemaAnnotation;
|
||||
import com.cloudhandson.vpdbackoffice.domain.schemametadata.SchemaMetadataAnnotationRow;
|
||||
import com.cloudhandson.vpdbackoffice.domain.schemametadata.SchemaMetadataColumn;
|
||||
import com.cloudhandson.vpdbackoffice.domain.schemametadata.SchemaMetadataColumnRow;
|
||||
import com.cloudhandson.vpdbackoffice.domain.schemametadata.SchemaMetadataView;
|
||||
import com.cloudhandson.vpdbackoffice.domain.structured.StructuredDataTable;
|
||||
import com.cloudhandson.vpdbackoffice.mapper.SchemaMetadataMapper;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Screen-level orchestration for schema metadata. Database reads and DDL are
|
||||
* intentionally delegated only to {@link SchemaMetadataMapper}.
|
||||
*/
|
||||
@Service
|
||||
public class SchemaMetadataService {
|
||||
|
||||
@@ -23,11 +28,14 @@ public class SchemaMetadataService {
|
||||
private static final int MAX_ANNOTATION_VALUE_LENGTH = 4000;
|
||||
private static final Pattern ORACLE_SIMPLE_NAME = Pattern.compile("[A-Z][A-Z0-9_$#]{0,127}");
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final SchemaMetadataMapper mapper;
|
||||
private final StructuredDataService structuredDataService;
|
||||
|
||||
public SchemaMetadataService(JdbcTemplate jdbcTemplate, StructuredDataService structuredDataService) {
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
public SchemaMetadataService(
|
||||
SchemaMetadataMapper mapper,
|
||||
StructuredDataService structuredDataService
|
||||
) {
|
||||
this.mapper = mapper;
|
||||
this.structuredDataService = structuredDataService;
|
||||
}
|
||||
|
||||
@@ -41,13 +49,12 @@ public class SchemaMetadataService {
|
||||
|
||||
public SchemaMetadataView find(String tableKey) {
|
||||
StructuredDataTable table = structuredDataService.requireTable(tableKey);
|
||||
String tableName = table.tableName();
|
||||
String tableComment = tableComment(tableName);
|
||||
String tableName = requireSimpleName(table.tableName(), "table name");
|
||||
Map<String, List<SchemaAnnotation>> annotations = annotationsByTarget(tableName);
|
||||
List<SchemaMetadataColumn> columns = columns(tableName, annotations);
|
||||
return new SchemaMetadataView(
|
||||
table,
|
||||
nullToEmpty(tableComment),
|
||||
nullToEmpty(mapper.findTableComment(OWNER, tableName)),
|
||||
annotations.getOrDefault(tableTargetKey(), List.of()),
|
||||
columns
|
||||
);
|
||||
@@ -56,24 +63,24 @@ public class SchemaMetadataService {
|
||||
@Transactional
|
||||
public void updateTableComment(String tableKey, String comment) {
|
||||
StructuredDataTable table = structuredDataService.requireTable(tableKey);
|
||||
String tableName = requireSimpleName(table.tableName(), "table name");
|
||||
String normalizedComment = normalizeText(comment, MAX_COMMENT_LENGTH, "테이블 comment");
|
||||
jdbcTemplate.execute("COMMENT ON TABLE " + qualifiedTable(table.tableName())
|
||||
+ " IS " + quoteLiteral(normalizedComment));
|
||||
mapper.updateTableComment(OWNER, tableName, quoteLiteral(normalizedComment));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateColumnComment(String tableKey, String columnName, String comment) {
|
||||
StructuredDataTable table = structuredDataService.requireTable(tableKey);
|
||||
String column = requireColumn(table.tableName(), columnName);
|
||||
String tableName = requireSimpleName(table.tableName(), "table name");
|
||||
String column = requireColumn(tableName, columnName);
|
||||
String normalizedComment = normalizeText(comment, MAX_COMMENT_LENGTH, "컬럼 comment");
|
||||
jdbcTemplate.execute("COMMENT ON COLUMN " + qualifiedTable(table.tableName()) + "."
|
||||
+ quoteName(column) + " IS " + quoteLiteral(normalizedComment));
|
||||
mapper.updateColumnComment(OWNER, tableName, column, quoteLiteral(normalizedComment));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateTableAnnotation(String tableKey, String annotationName, String annotationValue) {
|
||||
StructuredDataTable table = structuredDataService.requireTable(tableKey);
|
||||
updateAnnotation(table.tableName(), null, annotationName, annotationValue);
|
||||
updateAnnotation(requireSimpleName(table.tableName(), "table name"), null, annotationName, annotationValue);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -84,8 +91,9 @@ public class SchemaMetadataService {
|
||||
String annotationValue
|
||||
) {
|
||||
StructuredDataTable table = structuredDataService.requireTable(tableKey);
|
||||
String column = requireColumn(table.tableName(), columnName);
|
||||
updateAnnotation(table.tableName(), column, annotationName, annotationValue);
|
||||
String tableName = requireSimpleName(table.tableName(), "table name");
|
||||
String column = requireColumn(tableName, columnName);
|
||||
updateAnnotation(tableName, column, annotationName, annotationValue);
|
||||
}
|
||||
|
||||
private void updateAnnotation(
|
||||
@@ -97,75 +105,55 @@ public class SchemaMetadataService {
|
||||
String key = requireSimpleName(annotationName, "annotation name");
|
||||
String value = normalizeText(annotationValue, MAX_ANNOTATION_VALUE_LENGTH, "annotation value");
|
||||
if (annotationExists(tableName, columnName, key)) {
|
||||
jdbcTemplate.execute(annotationSql(tableName, columnName, "DROP " + quoteName(key)));
|
||||
if (columnName == null) {
|
||||
mapper.dropTableAnnotation(OWNER, tableName, key);
|
||||
} else {
|
||||
mapper.dropColumnAnnotation(OWNER, tableName, columnName, key);
|
||||
}
|
||||
}
|
||||
if (!value.isBlank()) {
|
||||
jdbcTemplate.execute(annotationSql(tableName, columnName,
|
||||
"ADD " + quoteName(key) + " " + quoteLiteral(value)));
|
||||
if (columnName == null) {
|
||||
mapper.addTableAnnotation(OWNER, tableName, key, quoteLiteral(value));
|
||||
} else {
|
||||
mapper.addColumnAnnotation(OWNER, tableName, columnName, key, quoteLiteral(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String tableComment(String tableName) {
|
||||
List<String> values = jdbcTemplate.query("""
|
||||
SELECT comments
|
||||
FROM all_tab_comments
|
||||
WHERE owner = ?
|
||||
AND table_name = ?
|
||||
""", (rs, rowNum) -> rs.getString(1), OWNER, tableName);
|
||||
return values.isEmpty() ? "" : values.getFirst();
|
||||
}
|
||||
|
||||
private List<SchemaMetadataColumn> columns(
|
||||
String tableName,
|
||||
Map<String, List<SchemaAnnotation>> annotations
|
||||
) {
|
||||
return jdbcTemplate.query("""
|
||||
SELECT c.column_name,
|
||||
CASE
|
||||
WHEN c.data_type IN ('VARCHAR2', 'CHAR', 'NVARCHAR2', 'NCHAR')
|
||||
THEN c.data_type || '(' || c.char_length || ')'
|
||||
WHEN c.data_type = 'NUMBER' AND c.data_precision IS NOT NULL AND c.data_scale IS NOT NULL
|
||||
THEN c.data_type || '(' || c.data_precision || ',' || c.data_scale || ')'
|
||||
WHEN c.data_type = 'NUMBER' AND c.data_precision IS NOT NULL
|
||||
THEN c.data_type || '(' || c.data_precision || ')'
|
||||
ELSE c.data_type
|
||||
END AS display_type,
|
||||
c.nullable,
|
||||
cc.comments
|
||||
FROM all_tab_columns c
|
||||
LEFT JOIN all_col_comments cc
|
||||
ON cc.owner = c.owner
|
||||
AND cc.table_name = c.table_name
|
||||
AND cc.column_name = c.column_name
|
||||
WHERE c.owner = ?
|
||||
AND c.table_name = ?
|
||||
ORDER BY c.column_id
|
||||
""", (rs, rowNum) -> new SchemaMetadataColumn(
|
||||
rs.getString("column_name"),
|
||||
rs.getString("display_type"),
|
||||
"Y".equalsIgnoreCase(rs.getString("nullable")),
|
||||
nullToEmpty(rs.getString("comments")),
|
||||
annotations.getOrDefault(columnTargetKey(rs.getString("column_name")), List.of())
|
||||
), OWNER, tableName);
|
||||
return mapper.findColumns(OWNER, tableName).stream()
|
||||
.map(row -> toColumn(row, annotations))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private SchemaMetadataColumn toColumn(
|
||||
SchemaMetadataColumnRow row,
|
||||
Map<String, List<SchemaAnnotation>> annotations
|
||||
) {
|
||||
String columnName = requireSimpleName(row.columnName(), "column name");
|
||||
return new SchemaMetadataColumn(
|
||||
columnName,
|
||||
row.dataType(),
|
||||
"Y".equalsIgnoreCase(row.nullable()),
|
||||
nullToEmpty(row.comment()),
|
||||
annotations.getOrDefault(columnTargetKey(columnName), List.of())
|
||||
);
|
||||
}
|
||||
|
||||
private Map<String, List<SchemaAnnotation>> annotationsByTarget(String tableName) {
|
||||
Map<String, LinkedHashMap<String, List<String>>> grouped = new LinkedHashMap<>();
|
||||
jdbcTemplate.query("""
|
||||
SELECT column_name, annotation_name, annotation_value
|
||||
FROM all_annotations_usage
|
||||
WHERE object_name = ?
|
||||
AND object_type = 'TABLE'
|
||||
ORDER BY column_name NULLS FIRST, annotation_name, annotation_value
|
||||
""", rs -> {
|
||||
String target = rs.getString("column_name") == null
|
||||
? tableTargetKey()
|
||||
: columnTargetKey(rs.getString("column_name"));
|
||||
grouped
|
||||
.computeIfAbsent(target, ignored -> new LinkedHashMap<>())
|
||||
.computeIfAbsent(rs.getString("annotation_name"), ignored -> new ArrayList<>())
|
||||
.add(nullToEmpty(rs.getString("annotation_value")));
|
||||
}, tableName);
|
||||
for (SchemaMetadataAnnotationRow row : mapper.findAnnotations(OWNER, tableName)) {
|
||||
String target = row.columnName() == null
|
||||
? tableTargetKey()
|
||||
: columnTargetKey(row.columnName());
|
||||
grouped
|
||||
.computeIfAbsent(target, ignored -> new LinkedHashMap<>())
|
||||
.computeIfAbsent(row.annotationName(), ignored -> new ArrayList<>())
|
||||
.add(nullToEmpty(row.annotationValue()));
|
||||
}
|
||||
|
||||
Map<String, List<SchemaAnnotation>> result = new LinkedHashMap<>();
|
||||
grouped.forEach((target, valuesByName) -> {
|
||||
@@ -180,44 +168,14 @@ public class SchemaMetadataService {
|
||||
}
|
||||
|
||||
private boolean annotationExists(String tableName, String columnName, String annotationName) {
|
||||
Integer count = columnName == null
|
||||
? jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM all_annotations_usage
|
||||
WHERE object_name = ?
|
||||
AND object_type = 'TABLE'
|
||||
AND annotation_name = ?
|
||||
AND column_name IS NULL
|
||||
""", Integer.class, tableName, annotationName)
|
||||
: jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM all_annotations_usage
|
||||
WHERE object_name = ?
|
||||
AND object_type = 'TABLE'
|
||||
AND annotation_name = ?
|
||||
AND column_name = ?
|
||||
""", Integer.class, tableName, annotationName, columnName);
|
||||
return count != null && count > 0;
|
||||
}
|
||||
|
||||
private String annotationSql(String tableName, String columnName, String operation) {
|
||||
if (columnName == null) {
|
||||
return "ALTER TABLE " + qualifiedTable(tableName) + " ANNOTATIONS (" + operation + ")";
|
||||
}
|
||||
return "ALTER TABLE " + qualifiedTable(tableName) + " MODIFY " + quoteName(columnName)
|
||||
+ " ANNOTATIONS (" + operation + ")";
|
||||
return columnName == null
|
||||
? mapper.countTableAnnotation(OWNER, tableName, annotationName) > 0
|
||||
: mapper.countColumnAnnotation(OWNER, tableName, columnName, annotationName) > 0;
|
||||
}
|
||||
|
||||
private String requireColumn(String tableName, String columnName) {
|
||||
String column = requireSimpleName(columnName, "column name");
|
||||
Integer count = jdbcTemplate.queryForObject("""
|
||||
SELECT COUNT(*)
|
||||
FROM all_tab_columns
|
||||
WHERE owner = ?
|
||||
AND table_name = ?
|
||||
AND column_name = ?
|
||||
""", Integer.class, OWNER, tableName, column);
|
||||
if (count == null || count == 0) {
|
||||
if (mapper.countColumn(OWNER, tableName, column) == 0) {
|
||||
throw new AppException("선택한 테이블에 존재하지 않는 컬럼입니다.");
|
||||
}
|
||||
return column;
|
||||
@@ -242,14 +200,6 @@ public class SchemaMetadataService {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String qualifiedTable(String tableName) {
|
||||
return quoteName(OWNER) + "." + quoteName(requireSimpleName(tableName, "table name"));
|
||||
}
|
||||
|
||||
private String quoteName(String value) {
|
||||
return "\"" + value.replace("\"", "\"\"") + "\"";
|
||||
}
|
||||
|
||||
private String quoteLiteral(String value) {
|
||||
return "'" + value.replace("'", "''") + "'";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user