diff --git a/api/src/main/java/com/databasir/job/ProjectSyncTaskScheduler.java b/api/src/main/java/com/databasir/job/ProjectSyncTaskScheduler.java index 66d7d93..ef9e80d 100644 --- a/api/src/main/java/com/databasir/job/ProjectSyncTaskScheduler.java +++ b/api/src/main/java/com/databasir/job/ProjectSyncTaskScheduler.java @@ -1,7 +1,7 @@ package com.databasir.job; import com.databasir.common.JsonData; -import com.databasir.core.domain.document.service.DocumentService; +import com.databasir.core.domain.document.service.DocumentSyncService; import com.databasir.core.domain.log.data.OperationLogRequest; import com.databasir.core.domain.log.service.OperationLogService; import com.databasir.dao.enums.ProjectSyncTaskStatus; @@ -26,7 +26,7 @@ import java.util.stream.Collectors; @Slf4j public class ProjectSyncTaskScheduler { - private final DocumentService documentService; + private final DocumentSyncService documentSyncService; private final OperationLogService operationLogService; @@ -64,7 +64,7 @@ public class ProjectSyncTaskScheduler { private void sync(Integer taskId, Integer groupId, Integer projectId, Integer userId) { try { updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.RUNNING, "running"); - documentService.syncByProjectId(projectId); + documentSyncService.syncByProjectId(projectId); updateSyncTaskStatus(taskId, ProjectSyncTaskStatus.FINISHED, "ok"); saveOperationLog(groupId, projectId, userId, null); } catch (Exception e) { diff --git a/build.gradle b/build.gradle index 5ee1b14..d397b41 100644 --- a/build.gradle +++ b/build.gradle @@ -48,9 +48,11 @@ subprojects { implementation "org.projectlombok:lombok:${lombokVersion}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" implementation "org.mapstruct:mapstruct:${mapstructVersion}" annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" + testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}" implementation "org.slf4j:slf4j-api:${slf4jVersion}" implementation "com.alibaba:easyexcel:${easyExcelVersion}" diff --git a/core/src/main/java/com/databasir/core/domain/document/comparator/DiffResult.java b/core/src/main/java/com/databasir/core/domain/document/comparator/DiffResult.java deleted file mode 100644 index 254bc3f..0000000 --- a/core/src/main/java/com/databasir/core/domain/document/comparator/DiffResult.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.databasir.core.domain.document.comparator; - -import com.databasir.core.diff.data.DiffType; -import lombok.Data; -import lombok.RequiredArgsConstructor; - -@Data -@RequiredArgsConstructor -public class DiffResult { - - private final String id; - - private final DiffType diffType; - -} diff --git a/core/src/main/java/com/databasir/core/domain/document/comparator/DocumentDiffs.java b/core/src/main/java/com/databasir/core/domain/document/comparator/DocumentDiffs.java deleted file mode 100644 index e3e06f7..0000000 --- a/core/src/main/java/com/databasir/core/domain/document/comparator/DocumentDiffs.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.databasir.core.domain.document.comparator; - -import com.databasir.core.diff.data.DiffType; -import com.databasir.core.meta.data.*; -import lombok.extern.slf4j.Slf4j; - -import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.Collectors; - -@Slf4j -public class DocumentDiffs { - - public static List tableDiff(DatabaseMeta original, DatabaseMeta current) { - return tableDiff(original.getTables(), current.getTables()); - } - - public static List tableDiff(List original, List current) { - Map originalTablesMap = toMap(original, TableMeta::getName); - Map currentTablesMap = toMap(current, TableMeta::getName); - var added = added(originalTablesMap, currentTablesMap) - .stream() - .map(curr -> { - TableDiffResult result = new TableDiffResult(curr.getName(), DiffType.ADDED); - List columnDiffs = - doDiff(Collections.emptyList(), curr.getColumns(), ColumnMeta::getName); - List indexDiffs = - doDiff(Collections.emptyList(), curr.getIndexes(), IndexMeta::getName); - List triggerDiffs = - doDiff(Collections.emptyList(), curr.getTriggers(), TriggerMeta::getName); - List fkDiffs = - doDiff(Collections.emptyList(), - curr.getForeignKeys(), - fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); - result.setColumnDiffResults(columnDiffs); - result.setIndexDiffResults(indexDiffs); - result.setTriggerDiffResults(triggerDiffs); - result.setForeignKeyDiffResults(fkDiffs); - return result; - }) - .collect(Collectors.toList()); - var removed = removed(originalTablesMap, currentTablesMap) - .stream() - .map(old -> { - TableDiffResult result = new TableDiffResult(old.getName(), DiffType.REMOVED); - List columnDiffs = - doDiff(old.getColumns(), Collections.emptyList(), ColumnMeta::getName); - List indexDiffs = - doDiff(old.getIndexes(), Collections.emptyList(), IndexMeta::getName); - List triggerDiffs = - doDiff(old.getTriggers(), Collections.emptyList(), TriggerMeta::getName); - List fkDiffs = - doDiff(old.getForeignKeys(), - Collections.emptyList(), - fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); - result.setColumnDiffResults(columnDiffs); - result.setIndexDiffResults(indexDiffs); - result.setTriggerDiffResults(triggerDiffs); - result.setForeignKeyDiffResults(fkDiffs); - return result; - }) - .collect(Collectors.toList()); - List sameOrModified = currentTablesMap.entrySet() - .stream() - .filter(entry -> originalTablesMap.containsKey(entry.getKey())) - .map(entry -> { - String tableName = entry.getKey(); - TableMeta currentTable = entry.getValue(); - TableMeta originalTable = originalTablesMap.get(tableName); - - List columnDiffs = - doDiff(originalTable.getColumns(), currentTable.getColumns(), ColumnMeta::getName); - List indexDiffs = - doDiff(originalTable.getIndexes(), currentTable.getIndexes(), IndexMeta::getName); - List triggerDiffs = - doDiff(originalTable.getTriggers(), currentTable.getTriggers(), TriggerMeta::getName); - List fkDiffs = - doDiff(originalTable.getForeignKeys(), - currentTable.getForeignKeys(), - fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); - - DiffType diffType = Objects.equals(currentTable, originalTable) ? DiffType.NONE : DiffType.MODIFIED; - TableDiffResult diffResult = new TableDiffResult(tableName, diffType); - diffResult.setColumnDiffResults(columnDiffs); - diffResult.setIndexDiffResults(indexDiffs); - diffResult.setTriggerDiffResults(triggerDiffs); - diffResult.setForeignKeyDiffResults(fkDiffs); - return diffResult; - }) - .collect(Collectors.toList()); - - List all = new ArrayList<>(16); - all.addAll(sameOrModified); - all.addAll(added); - all.addAll(removed); - return all; - } - - private static List doDiff(List original, List current, Function idMapping) { - Map originalColumnMap = toMap(original, idMapping); - Map currentColumnMap = toMap(current, idMapping); - BiFunction mapping = (meta, diffType) -> { - return new DiffResult(idMapping.apply(meta), diffType); - }; - List added = added(originalColumnMap, currentColumnMap) - .stream() - .map(col -> mapping.apply(col, DiffType.ADDED)) - .collect(Collectors.toList()); - List removed = removed(originalColumnMap, currentColumnMap) - .stream() - .map(col -> mapping.apply(col, DiffType.REMOVED)) - .collect(Collectors.toList()); - List modified = modified(originalColumnMap, currentColumnMap) - .stream() - .map(col -> mapping.apply(col, DiffType.MODIFIED)) - .collect(Collectors.toList()); - List same = same(originalColumnMap, currentColumnMap) - .stream() - .map(col -> mapping.apply(col, DiffType.NONE)) - .collect(Collectors.toList()); - List columnResults = new ArrayList<>(); - columnResults.addAll(same); - columnResults.addAll(added); - columnResults.addAll(modified); - columnResults.addAll(removed); - return columnResults; - } - - private static Map toMap(List content, Function idMapping) { - return content - .stream() - .collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> { - log.warn("Duplicate key, origin = {}, current = {}", a, b); - return a; - })); - } - - private static List added(Map originalMapById, - Map currentMapById) { - return currentMapById.entrySet() - .stream() - .filter(entry -> !originalMapById.containsKey(entry.getKey())) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - } - - private static List removed(Map originalMapById, - Map currentMapById) { - return originalMapById.entrySet() - .stream() - .filter(entry -> !currentMapById.containsKey(entry.getKey())) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - } - - private static List modified(Map originalMapById, - Map currentMapById) { - return modified(originalMapById, currentMapById, Objects::equals); - } - - private static List modified(Map originalMapById, - Map currentMapById, - BiFunction sameFunction) { - return currentMapById.entrySet() - .stream() - .filter(entry -> originalMapById.containsKey(entry.getKey())) - .filter(entry -> !sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey()))) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - } - - private static List same(Map originalMapById, - Map currentMapById) { - return same(originalMapById, currentMapById, Objects::equals); - } - - private static List same(Map originalMapById, - Map currentMapById, - BiFunction sameFunction) { - return currentMapById.entrySet() - .stream() - .filter(entry -> originalMapById.containsKey(entry.getKey())) - .filter(entry -> sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey()))) - .map(Map.Entry::getValue) - .collect(Collectors.toList()); - } -} diff --git a/core/src/main/java/com/databasir/core/domain/document/comparator/TableDiffResult.java b/core/src/main/java/com/databasir/core/domain/document/comparator/TableDiffResult.java deleted file mode 100644 index 1c27e2d..0000000 --- a/core/src/main/java/com/databasir/core/domain/document/comparator/TableDiffResult.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.databasir.core.domain.document.comparator; - -import com.databasir.core.diff.data.DiffType; -import lombok.Data; -import lombok.RequiredArgsConstructor; - -import java.util.Collection; -import java.util.Collections; - -@Data -@RequiredArgsConstructor -public class TableDiffResult { - - private final String id; - - private final DiffType diffType; - - private Collection columnDiffResults = Collections.emptyList(); - - private Collection indexDiffResults = Collections.emptyList(); - - private Collection triggerDiffResults = Collections.emptyList(); - - private Collection foreignKeyDiffResults = Collections.emptyList(); -} diff --git a/core/src/main/java/com/databasir/core/domain/document/converter/DocumentDiffConverter.java b/core/src/main/java/com/databasir/core/domain/document/converter/DocumentDiffConverter.java new file mode 100644 index 0000000..3616037 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/converter/DocumentDiffConverter.java @@ -0,0 +1,67 @@ +package com.databasir.core.domain.document.converter; + +import com.databasir.core.domain.document.data.diff.*; +import com.databasir.dao.tables.pojos.*; +import org.mapstruct.Mapper; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Mapper(componentModel = "spring") +public interface DocumentDiffConverter { + + default DatabaseDocDiff of(DatabaseDocument databaseDocument, + List tables, + List columns, + List indexes, + List triggers, + List foreignKeys) { + List tableDocDiffList = of(tables, columns, indexes, triggers, foreignKeys); + return of(databaseDocument, tableDocDiffList); + } + + default List of(List tables, + List columns, + List indexes, + List triggers, + List foreignKeys) { + var columnGroupByTableId = groupBy(columns, TableColumnDocument::getTableDocumentId); + var indexGroupByTableId = groupBy(indexes, TableIndexDocument::getTableDocumentId); + var triggerGroupByTableId = groupBy(triggers, TableTriggerDocument::getTableDocumentId); + var fkGroupByTableId = groupBy(foreignKeys, TableForeignKeyDocument::getTableDocumentId); + return tables.stream() + .map(table -> { + Integer id = table.getId(); + var tableCols = columnGroupByTableId.getOrDefault(id, Collections.emptyList()); + var tableIndexes = indexGroupByTableId.getOrDefault(id, Collections.emptyList()); + var tableTriggers = triggerGroupByTableId.getOrDefault(id, Collections.emptyList()); + var tableForeignKeys = fkGroupByTableId.getOrDefault(id, Collections.emptyList()); + return of(table, tableCols, tableIndexes, tableTriggers, tableForeignKeys); + }) + .collect(Collectors.toList()); + } + + DatabaseDocDiff of(DatabaseDocument databaseDocument, List tables); + + TableDocDiff of(TableDocument table, + List columns, + List indexes, + List triggers, + List foreignKeys); + + ColumnDocDiff of(TableColumnDocument pojo); + + IndexDocDiff of(TableIndexDocument pojo); + + TriggerDocDiff of(TableTriggerDocument pojo); + + ForeignKeyDocDiff of(TableForeignKeyDocument pojo); + + default Map> groupBy(List content, Function idMapping) { + return content.stream() + .collect(Collectors.groupingBy(idMapping)); + } +} diff --git a/core/src/main/java/com/databasir/core/domain/document/converter/DocumentResponseConverter.java b/core/src/main/java/com/databasir/core/domain/document/converter/DocumentResponseConverter.java index eeb0c28..3e18ccf 100644 --- a/core/src/main/java/com/databasir/core/domain/document/converter/DocumentResponseConverter.java +++ b/core/src/main/java/com/databasir/core/domain/document/converter/DocumentResponseConverter.java @@ -2,6 +2,8 @@ package com.databasir.core.domain.document.converter; import com.databasir.core.domain.document.data.DatabaseDocumentResponse; import com.databasir.core.domain.document.data.TableDocumentResponse; +import com.databasir.core.domain.document.data.diff.ColumnDocDiff; +import com.databasir.core.domain.document.data.diff.TableDocDiff; import com.databasir.core.infrastructure.converter.JsonConverter; import com.databasir.dao.tables.pojos.*; import org.mapstruct.Mapper; @@ -64,4 +66,47 @@ public interface DocumentResponseConverter { @Mapping(target = "documentVersion", source = "databaseDocument.version") DatabaseDocumentResponse of(DatabaseDocument databaseDocument, List tables); + + default TableDocumentResponse ofDiff(TableDocDiff table, + Map discussionCountMap, + Map descriptionMap) { + List cols = toColumns(table, discussionCountMap, descriptionMap); + return ofDiff(table, cols, discussionCountMap, descriptionMap); + } + + @Mapping(target = "description", expression = "java(descriptionMap.get(table.getName()))") + @Mapping(target = "discussionCount", expression = "java(discussionCountMap.get(table.getName()))") + @Mapping(target = "columns", source = "cols") + TableDocumentResponse ofDiff(TableDocDiff table, + List cols, + Map discussionCountMap, + Map descriptionMap); + + default List toColumns(TableDocDiff table, + Map discussionCountMap, + Map descriptionMap) { + return table.getColumns() + .stream() + .map(col -> toColumn(table.getName(), col, discussionCountMap, descriptionMap)) + .collect(Collectors.toList()); + } + + @Mapping(target = "description", expression = "java(descriptionMap.get(tableName+\".\"+diff.getName()))") + @Mapping(target = "discussionCount", expression = "java(discussionCountMap.get(tableName+\".\"+diff.getName()))") + @Mapping(target = "original", + expression = "java(toOriginalColumn(tableName, diff.getOriginal(), discussionCountMap, descriptionMap))") + TableDocumentResponse.ColumnDocumentResponse toColumn(String tableName, + ColumnDocDiff diff, + Map discussionCountMap, + Map descriptionMap); + + default TableDocumentResponse.ColumnDocumentResponse toOriginalColumn(String tableName, + ColumnDocDiff diff, + Map discussionCountMap, + Map descriptionMap) { + if (diff == null) { + return null; + } + return toColumn(tableName, diff, discussionCountMap, descriptionMap); + } } diff --git a/core/src/main/java/com/databasir/core/domain/document/converter/DocumentSimpleResponseConverter.java b/core/src/main/java/com/databasir/core/domain/document/converter/DocumentSimpleResponseConverter.java index 3254580..3c371c5 100644 --- a/core/src/main/java/com/databasir/core/domain/document/converter/DocumentSimpleResponseConverter.java +++ b/core/src/main/java/com/databasir/core/domain/document/converter/DocumentSimpleResponseConverter.java @@ -2,6 +2,7 @@ package com.databasir.core.domain.document.converter; import com.databasir.core.diff.data.DiffType; import com.databasir.core.domain.document.data.DatabaseDocumentSimpleResponse; +import com.databasir.core.domain.document.data.diff.TableDocDiff; import com.databasir.core.infrastructure.converter.JsonConverter; import com.databasir.dao.tables.pojos.DatabaseDocument; import com.databasir.dao.tables.pojos.TableDocument; @@ -16,6 +17,11 @@ import java.util.stream.Collectors; @Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN) public interface DocumentSimpleResponseConverter { + @Mapping(target = "id", source = "database.id") + @Mapping(target = "createAt", source = "database.createAt") + @Mapping(target = "documentVersion", source = "database.version") + DatabaseDocumentSimpleResponse of(DatabaseDocument database, List tables, String projectName); + @Mapping(target = "id", source = "databaseDocument.id") @Mapping(target = "createAt", source = "databaseDocument.createAt") @Mapping(target = "documentVersion", source = "databaseDocument.version") @@ -32,7 +38,6 @@ public interface DocumentSimpleResponseConverter { Map discussionCountMapByTableName, Map descriptionMapByTableName) { return tables.stream() - .map(table -> { Integer count = discussionCountMapByTableName.get(table.getName()); String description = descriptionMapByTableName.get(table.getName()); @@ -40,4 +45,22 @@ public interface DocumentSimpleResponseConverter { }) .collect(Collectors.toList()); } + + default List ofDiff(List tables, + Map discussionCountMapByTableName, + Map descriptionMapByTableName) { + return tables.stream() + .map(tableDocDiff -> { + String tableName = tableDocDiff.getName(); + Integer count = discussionCountMapByTableName.get(tableName); + String description = descriptionMapByTableName.get(tableName); + return ofDiff(tableDocDiff, count, description); + }) + .collect(Collectors.toList()); + } + + DatabaseDocumentSimpleResponse.TableData ofDiff(TableDocDiff table, + Integer discussionCount, + String description); + } diff --git a/core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentSimpleResponse.java b/core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentSimpleResponse.java index 041dd9b..1b06d6e 100644 --- a/core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentSimpleResponse.java +++ b/core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentSimpleResponse.java @@ -1,6 +1,7 @@ package com.databasir.core.domain.document.data; import com.databasir.core.diff.data.DiffType; +import com.databasir.core.domain.document.data.diff.DiffAble; import lombok.Data; import java.time.LocalDateTime; @@ -48,5 +49,6 @@ public class DatabaseDocumentSimpleResponse { private DiffType diffType; private TableData original; + } } diff --git a/core/src/main/java/com/databasir/core/domain/document/data/TableDocumentResponse.java b/core/src/main/java/com/databasir/core/domain/document/data/TableDocumentResponse.java index 5f10bd3..fbce1e1 100644 --- a/core/src/main/java/com/databasir/core/domain/document/data/TableDocumentResponse.java +++ b/core/src/main/java/com/databasir/core/domain/document/data/TableDocumentResponse.java @@ -1,6 +1,7 @@ package com.databasir.core.domain.document.data; import com.databasir.core.diff.data.DiffType; +import com.databasir.core.domain.document.data.diff.DiffAble; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/core/src/main/java/com/databasir/core/domain/document/data/diff/ColumnDocDiff.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/ColumnDocDiff.java new file mode 100644 index 0000000..45ec749 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/ColumnDocDiff.java @@ -0,0 +1,42 @@ +package com.databasir.core.domain.document.data.diff; + +import com.databasir.core.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ColumnDocDiff implements DiffAble { + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String name; + + private String type; + + private Integer dataType; + + private String comment; + + private String defaultValue; + + private Integer size; + + private Integer decimalDigits; + + private Boolean isPrimaryKey; + + private String nullable; + + private String autoIncrement; + + private LocalDateTime createAt; + + private DiffType diffType; + + private ColumnDocDiff original; +} diff --git a/core/src/main/java/com/databasir/core/domain/document/data/diff/DatabaseDocDiff.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/DatabaseDocDiff.java new file mode 100644 index 0000000..8b83749 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/DatabaseDocDiff.java @@ -0,0 +1,32 @@ +package com.databasir.core.domain.document.data.diff; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Data +public class DatabaseDocDiff { + + private Integer id; + + private Integer projectId; + + private String databaseName; + + private String schemaName; + + private String productName; + + private String productVersion; + + private Long version; + + private List tables = Collections.emptyList(); + + private LocalDateTime updateAt; + + private LocalDateTime createAt; + +} diff --git a/core/src/main/java/com/databasir/core/domain/document/data/DiffAble.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/DiffAble.java similarity index 73% rename from core/src/main/java/com/databasir/core/domain/document/data/DiffAble.java rename to core/src/main/java/com/databasir/core/domain/document/data/diff/DiffAble.java index 50ce3c7..03b28fb 100644 --- a/core/src/main/java/com/databasir/core/domain/document/data/DiffAble.java +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/DiffAble.java @@ -1,4 +1,4 @@ -package com.databasir.core.domain.document.data; +package com.databasir.core.domain.document.data.diff; import com.databasir.core.diff.data.DiffType; diff --git a/core/src/main/java/com/databasir/core/domain/document/data/diff/ForeignKeyDocDiff.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/ForeignKeyDocDiff.java new file mode 100644 index 0000000..49457ec --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/ForeignKeyDocDiff.java @@ -0,0 +1,40 @@ +package com.databasir.core.domain.document.data.diff; + +import com.databasir.core.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class ForeignKeyDocDiff implements DiffAble { + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private Integer keySeq; + + private String fkName; + + private String fkTableName; + + private String fkColumnName; + + private String pkName; + + private String pkTableName; + + private String pkColumnName; + + private String updateRule; + + private String deleteRule; + + private LocalDateTime createAt; + + private DiffType diffType; + + private ForeignKeyDocDiff original; +} diff --git a/core/src/main/java/com/databasir/core/domain/document/data/diff/IndexDocDiff.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/IndexDocDiff.java new file mode 100644 index 0000000..97171e9 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/IndexDocDiff.java @@ -0,0 +1,29 @@ +package com.databasir.core.domain.document.data.diff; + +import com.databasir.core.diff.data.DiffType; +import lombok.Data; +import org.jooq.JSON; + +import java.time.LocalDateTime; + +@Data +public class IndexDocDiff implements DiffAble { + + private Integer id; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String name; + + private Boolean isUnique; + + private JSON columnNameArray; + + private LocalDateTime createAt; + + private DiffType diffType; + + private IndexDocDiff original; +} diff --git a/core/src/main/java/com/databasir/core/domain/document/data/diff/TableDocDiff.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/TableDocDiff.java new file mode 100644 index 0000000..47888d2 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/TableDocDiff.java @@ -0,0 +1,41 @@ +package com.databasir.core.domain.document.data.diff; + +import com.databasir.core.diff.data.DiffType; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; + +@Data +@Builder +public class TableDocDiff implements DiffAble { + + private Integer id; + + private String name; + + private String type; + + private String comment; + + @Builder.Default + private List columns = Collections.emptyList(); + + @Builder.Default + private List indexes = Collections.emptyList(); + + @Builder.Default + private List triggers = Collections.emptyList(); + + @Builder.Default + private List foreignKeys = Collections.emptyList(); + + private LocalDateTime createAt; + + private DiffType diffType; + + private TableDocDiff original; + +} diff --git a/core/src/main/java/com/databasir/core/domain/document/data/diff/TriggerDocDiff.java b/core/src/main/java/com/databasir/core/domain/document/data/diff/TriggerDocDiff.java new file mode 100644 index 0000000..ab1d047 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/data/diff/TriggerDocDiff.java @@ -0,0 +1,32 @@ +package com.databasir.core.domain.document.data.diff; + +import com.databasir.core.diff.data.DiffType; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class TriggerDocDiff implements DiffAble { + + private Integer id; + + private String name; + + private Integer tableDocumentId; + + private Integer databaseDocumentId; + + private String timing; + + private String manipulation; + + private String statement; + + private String triggerCreateAt; + + private LocalDateTime createAt; + + private DiffType diffType; + + private TriggerDocDiff original; +} diff --git a/core/src/main/java/com/databasir/core/domain/document/diff/BaseTypeFieldEqualFunction.java b/core/src/main/java/com/databasir/core/domain/document/diff/BaseTypeFieldEqualFunction.java new file mode 100644 index 0000000..f3fafd7 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/diff/BaseTypeFieldEqualFunction.java @@ -0,0 +1,82 @@ +package com.databasir.core.domain.document.diff; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Slf4j +public class BaseTypeFieldEqualFunction implements BiFunction { + + private final List ignoreFields; + + @Override + public Boolean apply(Object that, Object other) { + if (Objects.equals(that, other)) { + return true; + } + if (that == null || other == null) { + return false; + } + try { + BeanInfo thatBean = Introspector.getBeanInfo(that.getClass()); + BeanInfo otherBean = Introspector.getBeanInfo(other.getClass()); + Map otherBeanPropertyMap = Arrays.stream(otherBean.getPropertyDescriptors()) + .collect(Collectors.toMap(PropertyDescriptor::getName, p -> p)); + for (PropertyDescriptor thatProperty : thatBean.getPropertyDescriptors()) { + if (thatProperty.getReadMethod() == null || thatProperty.getWriteMethod() == null) { + continue; + } + if (ignoreFields.contains(thatProperty.getName())) { + continue; + } + if (!otherBeanPropertyMap.containsKey(thatProperty.getName())) { + return false; + } + if (Collection.class.isAssignableFrom(thatProperty.getPropertyType())) { + Collection thatValue = (Collection) thatProperty.getReadMethod().invoke(that); + Collection otherValue = (Collection) otherBeanPropertyMap.get(thatProperty.getName()) + .getReadMethod().invoke(other); + return handleCollection(thatValue, otherValue); + } + if (!thatProperty.getPropertyType().isPrimitive()) { + Object thatValue = thatProperty.getReadMethod().invoke(that); + Object otherValue = otherBeanPropertyMap.get(thatProperty.getName()).getReadMethod().invoke(other); + if (!apply(thatValue, otherValue)) { + return false; + } + } + Object thatValue = thatProperty.getReadMethod().invoke(that); + Object otherValue = otherBeanPropertyMap.get(thatProperty.getName()).getReadMethod().invoke(other); + if (!Objects.equals(thatValue, otherValue)) { + return false; + } + } + } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) { + log.error("Error comparing objects", e); + throw new RuntimeException(e); + } + return true; + } + + private boolean handleCollection(Collection that, Collection other) { + if (that.size() != other.size()) { + return false; + } + for (Object thatObj : that) { + boolean anyMatch = other.stream().anyMatch(otherObj -> this.apply(thatObj, otherObj)); + if (!anyMatch) { + return false; + } + } + return true; + } +} diff --git a/core/src/main/java/com/databasir/core/domain/document/diff/DiffTypeFills.java b/core/src/main/java/com/databasir/core/domain/document/diff/DiffTypeFills.java deleted file mode 100644 index 073efdd..0000000 --- a/core/src/main/java/com/databasir/core/domain/document/diff/DiffTypeFills.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.databasir.core.domain.document.diff; - -import com.databasir.core.diff.data.DiffType; -import com.databasir.core.domain.document.comparator.DiffResult; -import com.databasir.core.domain.document.comparator.TableDiffResult; -import com.databasir.core.domain.document.data.DiffAble; -import com.databasir.core.domain.document.data.TableDocumentResponse; -import com.databasir.core.domain.document.data.TableDocumentResponse.ForeignKeyDocumentResponse; - -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static java.util.Collections.emptyList; - -public class DiffTypeFills { - - public static TableDocumentResponse fillRemoved(TableDocumentResponse data) { - data.setDiffType(DiffType.REMOVED); - data.getColumns().forEach(col -> col.setDiffType(DiffType.REMOVED)); - data.getForeignKeys().forEach(col -> col.setDiffType(DiffType.REMOVED)); - data.getIndexes().forEach(col -> col.setDiffType(DiffType.REMOVED)); - data.getTriggers().forEach(col -> col.setDiffType(DiffType.REMOVED)); - return data; - } - - public static TableDocumentResponse fillAdded(TableDocumentResponse data, TableDiffResult diff) { - data.setDiffType(DiffType.ADDED); - var cols = - diff(diff.getColumnDiffResults(), emptyList(), data.getColumns(), i -> i.getName()); - data.setColumns(cols); - var indexes = - diff(diff.getIndexDiffResults(), emptyList(), data.getIndexes(), i -> i.getName()); - data.setIndexes(indexes); - var foreignKeys = foreignKeyDiff(diff.getForeignKeyDiffResults(), - emptyList(), data.getForeignKeys()); - data.setForeignKeys(foreignKeys); - var triggers = - diff(diff.getTriggerDiffResults(), emptyList(), data.getTriggers(), t -> t.getName()); - data.setTriggers(triggers); - return data; - } - - public static TableDocumentResponse fillModified(TableDocumentResponse current, - TableDocumentResponse original, - TableDiffResult diff) { - current.setDiffType(DiffType.MODIFIED); - current.setOriginal(original); - var cols = - diff(diff.getColumnDiffResults(), original.getColumns(), current.getColumns(), - col -> col.getName()); - current.setColumns(cols); - var indexes = - diff(diff.getIndexDiffResults(), original.getIndexes(), current.getIndexes(), i -> i.getName()); - current.setIndexes(indexes); - var foreignKeys = foreignKeyDiff(diff.getForeignKeyDiffResults(), - original.getForeignKeys(), current.getForeignKeys()); - current.setForeignKeys(foreignKeys); - var triggers = - diff(diff.getTriggerDiffResults(), original.getTriggers(), current.getTriggers(), - t -> t.getName()); - current.setTriggers(triggers); - return current; - } - - public static List diff(Collection diffs, - Collection original, - Collection current, - Function idMapping) { - var currentMapByName = current.stream() - .collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> a)); - var originalMapByName = original.stream() - .collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> a)); - return diffs.stream().map(diff -> { - if (diff.getDiffType() == DiffType.ADDED) { - var t = currentMapByName.get(diff.getId()); - t.setDiffType(DiffType.ADDED); - return t; - } - if (diff.getDiffType() == DiffType.REMOVED) { - var t = originalMapByName.get(diff.getId()); - t.setDiffType(DiffType.REMOVED); - return t; - } - if (diff.getDiffType() == DiffType.MODIFIED) { - var c = currentMapByName.get(diff.getId()); - var o = originalMapByName.get(diff.getId()); - c.setDiffType(DiffType.MODIFIED); - c.setOriginal(o); - return c; - } - var t = currentMapByName.get(diff.getId()); - t.setDiffType(DiffType.NONE); - return t; - }) - .collect(Collectors.toList()); - } - - public static List foreignKeyDiff(Collection diffs, - Collection original, - Collection current) { - Function idMapping = fk -> { - return fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq(); - }; - return diff(diffs, original, current, idMapping); - } -} diff --git a/core/src/main/java/com/databasir/core/domain/document/diff/DocumentDiffChecker.java b/core/src/main/java/com/databasir/core/domain/document/diff/DocumentDiffChecker.java new file mode 100644 index 0000000..ee77827 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/diff/DocumentDiffChecker.java @@ -0,0 +1,231 @@ +package com.databasir.core.domain.document.diff; + +import com.databasir.core.diff.data.DiffType; +import com.databasir.core.domain.document.data.diff.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +@Slf4j +public class DocumentDiffChecker { + + private static final List IGNORE_FIELDS = List.of( + "id", + "class", + "tableDocumentId", + "databaseDocumentId", + "createAt", + "updateAt", + "diffType", + "original" + ); + + public List diff(List original, List current) { + Map originalTablesMap = toMap(original, TableDocDiff::getName); + Map currentTablesMap = toMap(current, TableDocDiff::getName); + // added items + var added = added(originalTablesMap, currentTablesMap) + .stream() + .map(curr -> { + List columnDiffs = + doDiff(Collections.emptyList(), curr.getColumns(), ColumnDocDiff::getName); + List indexDiffs = + doDiff(Collections.emptyList(), curr.getIndexes(), IndexDocDiff::getName); + List triggerDiffs = + doDiff(Collections.emptyList(), curr.getTriggers(), TriggerDocDiff::getName); + List fkDiffs = + doDiff(Collections.emptyList(), + curr.getForeignKeys(), + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + return TableDocDiff.builder() + .id(curr.getId()) + .diffType(DiffType.ADDED) + .name(curr.getName()) + .comment(curr.getComment()) + .type(curr.getType()) + .createAt(curr.getCreateAt()) + .columns(columnDiffs) + .indexes(indexDiffs) + .triggers(triggerDiffs) + .foreignKeys(fkDiffs) + .build(); + }) + .collect(Collectors.toList()); + // removed items + var removed = removed(originalTablesMap, currentTablesMap) + .stream() + .map(old -> { + List columnDiffs = + doDiff(old.getColumns(), Collections.emptyList(), ColumnDocDiff::getName); + List indexDiffs = + doDiff(old.getIndexes(), Collections.emptyList(), IndexDocDiff::getName); + List triggerDiffs = + doDiff(old.getTriggers(), Collections.emptyList(), TriggerDocDiff::getName); + List fkDiffs = + doDiff(old.getForeignKeys(), + Collections.emptyList(), + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + return TableDocDiff.builder() + .id(old.getId()) + .diffType(DiffType.REMOVED) + .name(old.getName()) + .comment(old.getComment()) + .type(old.getType()) + .createAt(old.getCreateAt()) + .columns(columnDiffs) + .indexes(indexDiffs) + .triggers(triggerDiffs) + .foreignKeys(fkDiffs) + .build(); + }) + .collect(Collectors.toList()); + // unchanged or modified items + List sameOrModified = currentTablesMap.entrySet() + .stream() + .filter(entry -> originalTablesMap.containsKey(entry.getKey())) + .map(entry -> { + String tableName = entry.getKey(); + TableDocDiff currentTable = entry.getValue(); + TableDocDiff originalTable = originalTablesMap.get(tableName); + + List columnDiffs = + doDiff(originalTable.getColumns(), currentTable.getColumns(), ColumnDocDiff::getName); + List indexDiffs = + doDiff(originalTable.getIndexes(), currentTable.getIndexes(), IndexDocDiff::getName); + List triggerDiffs = + doDiff(originalTable.getTriggers(), currentTable.getTriggers(), TriggerDocDiff::getName); + List fkDiffs = + doDiff(originalTable.getForeignKeys(), + currentTable.getForeignKeys(), + fk -> fk.getFkTableName() + "." + fk.getFkColumnName() + "." + fk.getKeySeq()); + + BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS); + DiffType diffType = eq.apply(currentTable, originalTable) ? DiffType.NONE : DiffType.MODIFIED; + // workaround for diffType = NONE + if (diffType == DiffType.NONE) { + originalTable = null; + } + return TableDocDiff.builder() + .id(currentTable.getId()) + .diffType(diffType) + .original(originalTable) + .name(currentTable.getName()) + .comment(currentTable.getComment()) + .type(currentTable.getType()) + .createAt(currentTable.getCreateAt()) + .columns(columnDiffs) + .indexes(indexDiffs) + .triggers(triggerDiffs) + .foreignKeys(fkDiffs) + .build(); + }) + .collect(Collectors.toList()); + + List all = new ArrayList<>(16); + all.addAll(sameOrModified); + all.addAll(added); + all.addAll(removed); + return all; + } + + private > List doDiff(List original, List current, Function idMapping) { + Map originalMap = toMap(original, idMapping); + Map currentMap = toMap(current, idMapping); + List added = added(originalMap, currentMap); + List removed = removed(originalMap, currentMap); + List modified = modified(originalMap, currentMap); + List same = same(originalMap, currentMap); + List results = new ArrayList<>(); + results.addAll(same); + results.addAll(added); + results.addAll(modified); + results.addAll(removed); + return results; + } + + private Map toMap(List content, Function idMapping) { + return content + .stream() + .collect(Collectors.toMap(idMapping, Function.identity(), (a, b) -> { + log.warn("Duplicate key, origin = {}, current = {}", a, b); + return a; + })); + } + + private > List added(Map originalMapById, + Map currentMapById) { + return currentMapById.entrySet() + .stream() + .filter(entry -> !originalMapById.containsKey(entry.getKey())) + .map(Map.Entry::getValue) + .map(value -> { + value.setDiffType(DiffType.ADDED); + return value; + }) + .collect(Collectors.toList()); + } + + private > List removed(Map originalMapById, + Map currentMapById) { + return originalMapById.entrySet() + .stream() + .filter(entry -> !currentMapById.containsKey(entry.getKey())) + .map(Map.Entry::getValue) + .map(value -> { + value.setDiffType(DiffType.REMOVED); + return value; + }) + .collect(Collectors.toList()); + } + + private > List modified(Map originalMapById, + Map currentMapById) { + BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS); + return modified(originalMapById, currentMapById, eq); + } + + private > List modified(Map originalMapById, + Map currentMapById, + BiFunction sameFunction) { + return currentMapById.entrySet() + .stream() + .filter(entry -> originalMapById.containsKey(entry.getKey())) + .filter(entry -> !sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey()))) + .map(entry -> { + T value = entry.getValue(); + value.setDiffType(DiffType.MODIFIED); + value.setOriginal(originalMapById.get(entry.getKey())); + return value; + }) + .collect(Collectors.toList()); + } + + private > List same(Map originalMapById, + Map currentMapById) { + BaseTypeFieldEqualFunction eq = new BaseTypeFieldEqualFunction(IGNORE_FIELDS); + return same(originalMapById, currentMapById, eq); + } + + private > List same(Map originalMapById, + Map currentMapById, + BiFunction sameFunction) { + return currentMapById.entrySet() + .stream() + .filter(entry -> originalMapById.containsKey(entry.getKey())) + .filter(entry -> sameFunction.apply(entry.getValue(), originalMapById.get(entry.getKey()))) + .map(entry -> { + T value = entry.getValue(); + value.setDiffType(DiffType.NONE); + return value; + }) + .collect(Collectors.toList()); + } +} diff --git a/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java b/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java index f316a8f..3debdba 100644 --- a/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java +++ b/core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java @@ -1,27 +1,19 @@ package com.databasir.core.domain.document.service; -import com.databasir.common.DatabasirException; -import com.databasir.core.Databasir; -import com.databasir.core.DatabasirConfig; -import com.databasir.core.diff.Diffs; import com.databasir.core.diff.data.DiffType; -import com.databasir.core.diff.data.RootDiff; import com.databasir.core.domain.DomainErrors; -import com.databasir.core.domain.document.comparator.DocumentDiffs; -import com.databasir.core.domain.document.comparator.TableDiffResult; -import com.databasir.core.domain.document.converter.*; +import com.databasir.core.domain.document.converter.DocumentDiffConverter; +import com.databasir.core.domain.document.converter.DocumentResponseConverter; +import com.databasir.core.domain.document.converter.DocumentSimpleResponseConverter; +import com.databasir.core.domain.document.converter.TableResponseConverter; import com.databasir.core.domain.document.data.*; -import com.databasir.core.domain.document.diff.DiffTypeFills; +import com.databasir.core.domain.document.data.diff.DatabaseDocDiff; +import com.databasir.core.domain.document.data.diff.TableDocDiff; import com.databasir.core.domain.document.diff.DiffTypePredictor; -import com.databasir.core.domain.document.event.DocumentUpdated; +import com.databasir.core.domain.document.diff.DocumentDiffChecker; import com.databasir.core.domain.document.generator.DocumentFileGenerator; import com.databasir.core.domain.document.generator.DocumentFileGenerator.DocumentFileGenerateContext; import com.databasir.core.domain.document.generator.DocumentFileType; -import com.databasir.core.infrastructure.connection.DatabaseConnectionService; -import com.databasir.core.infrastructure.converter.JsonConverter; -import com.databasir.core.infrastructure.event.EventPublisher; -import com.databasir.core.meta.data.DatabaseMeta; -import com.databasir.core.meta.data.TableMeta; import com.databasir.dao.impl.*; import com.databasir.dao.tables.pojos.*; import com.databasir.dao.value.DocumentDiscussionCountPojo; @@ -29,17 +21,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.jooq.tools.StringUtils; -import org.springframework.dao.DuplicateKeyException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.io.OutputStream; -import java.sql.Connection; -import java.sql.SQLException; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; import static java.util.Collections.emptyList; @@ -49,18 +36,8 @@ import static java.util.Collections.emptyList; @Slf4j public class DocumentService { - private final GroupDao groupDao; - private final ProjectDao projectDao; - private final ProjectSyncRuleDao projectSyncRuleDao; - - private final DataSourceDao dataSourceDao; - - private final DataSourcePropertyDao dataSourcePropertyDao; - - private final DatabaseConnectionService databaseConnectionService; - private final DatabaseDocumentDao databaseDocumentDao; private final TableDocumentDao tableDocumentDao; @@ -75,166 +52,19 @@ public class DocumentService { private final DocumentDiscussionDao documentDiscussionDao; - private final DocumentFullTextDao documentFullTextDao; - private final DocumentDescriptionDao documentDescriptionDao; - private final DocumentPojoConverter documentPojoConverter; - - private final DocumentFullTextConverter documentFullTextConverter; - private final DocumentResponseConverter documentResponseConverter; private final DocumentSimpleResponseConverter documentSimpleResponseConverter; - private final DatabaseMetaConverter databaseMetaConverter; + private final DocumentDiffConverter documentDiffConverter; private final TableResponseConverter tableResponseConverter; - private final JsonConverter jsonConverter; - private final List documentFileGenerators; - private final EventPublisher eventPublisher; - - @Transactional - public void syncByProjectId(Integer projectId) { - projectDao.selectOptionalById(projectId) - .orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception); - DatabaseMeta current = retrieveDatabaseMeta(projectId); - Optional originalOption = databaseDocumentDao.selectNotArchivedByProjectId(projectId); - if (originalOption.isPresent()) { - DatabaseDocument original = originalOption.get(); - DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original); - RootDiff diff = Diffs.diff(originalMeta, current); - if (diff.getDiffType() == DiffType.NONE) { - log.info("ignore project {} {} sync data, because without change", - projectId, - original.getDatabaseName()); - return; - } - Integer previousDocumentId = original.getId(); - // archive old version - databaseDocumentDao.updateIsArchiveById(previousDocumentId, true); - Long version = original.getVersion(); - Integer docId = saveNewDocument(current, version + 1, original.getProjectId()); - eventPublisher.publish(new DocumentUpdated(diff, version + 1, version, projectId, docId)); - } else { - Integer docId = saveNewDocument(current, 1L, projectId); - RootDiff diff = null; - try { - diff = Diffs.diff(null, current); - } catch (Exception e) { - log.error("diff project " + projectId + " error, fallback diff type to NONE", e); - } - eventPublisher.publish(new DocumentUpdated(diff, 1L, null, projectId, docId)); - } - } - - private DatabaseMeta retrieveOriginalDatabaseMeta(DatabaseDocument original) { - Integer docId = original.getId(); - List tables = tableDocumentDao.selectByDatabaseDocumentId(docId); - List columns = tableColumnDocumentDao.selectByDatabaseDocumentId(docId); - List indexes = tableIndexDocumentDao.selectByDatabaseMetaId(docId); - List triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(docId); - List fks = tableForeignKeyDocumentDao.selectByDatabaseDocumentId(docId); - return databaseMetaConverter.of(original, tables, columns, indexes, triggers, fks); - } - - private DatabaseMeta retrieveDatabaseMeta(Integer projectId) { - ProjectSyncRule rule = projectSyncRuleDao.selectByProjectId(projectId); - DataSource dataSource = dataSourceDao.selectByProjectId(projectId); - List properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId()); - Connection jdbcConnection = databaseConnectionService.create(dataSource, properties); - DatabasirConfig databasirConfig = new DatabasirConfig(); - databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray())); - databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray())); - try { - if (jdbcConnection == null) { - throw DomainErrors.CONNECT_DATABASE_FAILED.exception(); - } - DatabaseMeta databaseMeta = Databasir.of(databasirConfig) - .get(jdbcConnection, dataSource.getDatabaseName(), dataSource.getSchemaName()) - .orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception); - return databaseMeta; - } finally { - try { - if (jdbcConnection != null && !jdbcConnection.isClosed()) { - jdbcConnection.close(); - } - } catch (SQLException e) { - log.error("close jdbc connection error", e); - } - } - } - - private Integer saveNewDocument(DatabaseMeta meta, - Long version, - Integer projectId) { - - var dbDocPojo = documentPojoConverter.toDatabasePojo(projectId, meta, version); - final Integer docId; - try { - docId = databaseDocumentDao.insertAndReturnId(dbDocPojo); - dbDocPojo.setId(docId); - } catch (DuplicateKeyException e) { - log.warn("ignore insert database document projectId={} version={}", projectId, version); - throw new DatabasirException(DomainErrors.DATABASE_DOCUMENT_DUPLICATE_KEY); - } - meta.getTables().forEach(table -> { - TableDocument tableMeta = - documentPojoConverter.toTablePojo(docId, table); - Integer tableMetaId = tableDocumentDao.insertAndReturnId(tableMeta); - tableMeta.setId(tableMetaId); - // column - var columns = documentPojoConverter.toColumnPojo(docId, tableMetaId, table.getColumns()); - tableColumnDocumentDao.batchInsert(columns); - // index - var indexes = documentPojoConverter.toIndexPojo(docId, tableMetaId, table.getIndexes()) - .stream() - .filter(index -> { - if (index.getName() != null) { - return true; - } else { - log.warn("ignore table {} index {}, cause name is null", table.getName(), index); - return false; - } - }) - .collect(Collectors.toList()); - tableIndexDocumentDao.batchInsert(indexes); - // foreign key - var foreignKeys = documentPojoConverter.toForeignKeyPojo(docId, tableMetaId, table.getForeignKeys()); - tableForeignKeyDocumentDao.batchInsert(foreignKeys); - - // trigger - var triggers = documentPojoConverter.toTriggerPojo(docId, tableMetaId, table.getTriggers()); - tableTriggerDocumentDao.batchInsert(triggers); - }); - log.info("save new version document success: projectId = {}, name = {}, version = {}", - projectId, meta.getDatabaseName(), version); - return docId; - } - - private void saveDocumentFullText(Integer projectId, - DatabaseDocument database, - TableDocument table, - Map descriptionMapByJoinName) { - Project project = projectDao.selectById(projectId); - Group group = groupDao.selectById(project.getGroupId()); - List columns = tableColumnDocumentDao.selectByTableDocumentId(table.getId()); - // clear outdated data before save - documentFullTextDao.deleteTableFullText(table.getId()); - List fullTextPojoList = columns.stream() - .map(column -> { - String tableName = table.getName(); - String tableDescription = descriptionMapByJoinName.get(tableName); - String columnDescription = descriptionMapByJoinName.get(tableName + "." + column.getName()); - return documentFullTextConverter.toPojo(group, project, database, table, column, - tableDescription, columnDescription); - }) - .collect(Collectors.toList()); - documentFullTextDao.batchInsert(fullTextPojoList); - } + private final DocumentDiffChecker documentDiffChecker; public Optional getSimpleOneByProjectId(Integer projectId, Long version, @@ -251,8 +81,6 @@ public class DocumentService { } return documentOption.map(document -> { - Integer id = document.getId(); - var tables = tableDocumentDao.selectByDatabaseDocumentId(id); var discussionCountMapByTableName = documentDiscussionDao.selectTableDiscussionCount(projectId) .stream() @@ -261,59 +89,27 @@ public class DocumentService { documentDescriptionDao.selectTableDescriptionByProjectId(projectId) .stream() .collect(Collectors.toMap(d -> d.getTableName(), d -> d.getContent(), (a, b) -> a)); - var tableMetas = documentSimpleResponseConverter.of( - tables, - discussionCountMapByTableName, - descriptionMapByTableName - ); - - // if original version is not null means version diff enabled if (originalVersion != null) { - var originalDocument = - databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, originalVersion) - .orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception); - var originalTables = tableDocumentDao.selectByDatabaseDocumentId(originalDocument.getId()); - var originalTableMetas = documentSimpleResponseConverter.of( - originalTables, + var diffResults = tableDiffs(projectId, originalVersion, version); + var tableMetas = documentSimpleResponseConverter.ofDiff( + diffResults, discussionCountMapByTableName, descriptionMapByTableName); + DiffType diffType = DiffTypePredictor.predict(tableMetas); + tableMetas.sort(Comparator.comparing(DatabaseDocumentSimpleResponse.TableData::getName)); + return documentSimpleResponseConverter.of(document, tableMetas, diffType, projectName); + } else { + Integer id = document.getId(); + var tables = tableDocumentDao.selectByDatabaseDocumentId(id); + var tableMetas = documentSimpleResponseConverter.of( + tables, discussionCountMapByTableName, descriptionMapByTableName ); - var originalMap = originalTableMetas.stream() - .collect(Collectors.toMap(t -> t.getName(), Function.identity(), (a, b) -> a)); - var currentMap = tableMetas.stream() - .collect(Collectors.toMap(t -> t.getName(), Function.identity(), (a, b) -> a)); - List diffResults = tableDiffs(projectId, originalVersion, version); - - List result = new ArrayList<>(); - for (TableDiffResult diffResult : diffResults) { - var cur = currentMap.get(diffResult.getId()); - var org = originalMap.get(diffResult.getId()); - if (diffResult.getDiffType() == DiffType.ADDED) { - cur.setDiffType(DiffType.ADDED); - result.add(cur); - } else if (diffResult.getDiffType() == DiffType.MODIFIED) { - cur.setDiffType(DiffType.MODIFIED); - cur.setOriginal(org); - result.add(cur); - } else if (diffResult.getDiffType() == DiffType.REMOVED) { - org.setDiffType(DiffType.REMOVED); - result.add(org); - } else { - cur.setDiffType(DiffType.NONE); - result.add(cur); - } - } - result.sort(Comparator.comparing(DatabaseDocumentSimpleResponse.TableData::getName)); - DiffType diffType = DiffTypePredictor.predict(result); - return documentSimpleResponseConverter.of(document, result, diffType, projectName); - } else { - tableMetas.sort(Comparator.comparing(DatabaseDocumentSimpleResponse.TableData::getName)); return documentSimpleResponseConverter.of(document, tableMetas, DiffType.NONE, projectName); } }); } - public List tableDiffs(Integer projectId, Long originalVersion, Long currentVersion) { + public List tableDiffs(Integer projectId, Long originalVersion, Long currentVersion) { var original = databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, originalVersion) .orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception); DatabaseDocument current; @@ -324,9 +120,9 @@ public class DocumentService { current = databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, currentVersion) .orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception); } - DatabaseMeta currMeta = retrieveOriginalDatabaseMeta(current); - DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original); - return DocumentDiffs.tableDiff(originalMeta, currMeta); + DatabaseDocDiff currentDiff = retrieveDatabaseDocumentDiff(current); + DatabaseDocDiff originalDiff = retrieveDatabaseDocumentDiff(original); + return documentDiffChecker.diff(originalDiff.getTables(), currentDiff.getTables()); } public Optional getOneByProjectId(Integer projectId, Long version) { @@ -472,48 +268,33 @@ public class DocumentService { if (CollectionUtils.isEmpty(request.getTableIds()) || !projectDao.existsById(projectId)) { return emptyList(); } - var current = this.getTableDetails(projectId, databaseDocumentId, request.getTableIds()); - List currentTableIds = current.stream().map(t -> t.getId()).collect(Collectors.toList()); - var missedIds = CollectionUtils.disjunction(currentTableIds, request.getTableIds()); + if (request.getOriginalVersion() != null) { - DatabaseDocument doc = - databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, request.getOriginalVersion()) - .orElseThrow(DomainErrors.DOCUMENT_VERSION_IS_INVALID::exception); - List tableNames = current.stream().map(t -> t.getName()).distinct().collect(Collectors.toList()); - List originalTableIds = - tableDocumentDao.selectTableIdsByDatabaseDocumentIdAndTableNameIn(doc.getId(), tableNames); - var allOriginalTableIds = CollectionUtils.union(missedIds, originalTableIds); - var original = this.getTableDetails(projectId, doc.getId(), allOriginalTableIds); - Map currentMapByName = current.stream() - .collect(Collectors.toMap(TableDocumentResponse::getName, Function.identity(), (a, b) -> a)); - Map originalMapByName = original.stream() - .collect(Collectors.toMap(TableDocumentResponse::getName, Function.identity(), (a, b) -> a)); - List currentMeta = databaseMetaConverter.of(current); - List originalMeta = databaseMetaConverter.of(original); - List diffs = DocumentDiffs.tableDiff(originalMeta, currentMeta); - return diffs.stream() - .map(diff -> { - if (diff.getDiffType() == DiffType.ADDED) { - TableDocumentResponse c = currentMapByName.get(diff.getId()); - return DiffTypeFills.fillAdded(c, diff); - } - if (diff.getDiffType() == DiffType.REMOVED) { - TableDocumentResponse t = originalMapByName.get(diff.getId()); - return DiffTypeFills.fillRemoved(t); - } - if (diff.getDiffType() == DiffType.MODIFIED) { - TableDocumentResponse c = currentMapByName.get(diff.getId()); - TableDocumentResponse o = originalMapByName.get(diff.getId()); - return DiffTypeFills.fillModified(c, o, diff); - } - TableDocumentResponse t = currentMapByName.get(diff.getId()); - t.setDiffType(DiffType.NONE); - return t; - }) + List diffResults = + this.tableDiffs(projectId, request.getOriginalVersion(), request.getCurrentVersion()); + // discussion + var discussions = documentDiscussionDao.selectAllDiscussionCount(projectId); + Map discussionCountMapByJoinName = discussions.stream() + .collect(Collectors.toMap( + d -> String.join(".", d.getTableName(), d.getColumnName()), + DocumentDiscussionCountPojo::getCount, + (a, b) -> a)); + + // description + var descriptions = documentDescriptionDao.selectByProjectId(projectId); + Map descriptionMapByJoinName = descriptions.stream() + .collect(Collectors.toMap( + d -> String.join(".", d.getTableName(), d.getColumnName()), + DocumentDescription::getContent, + (a, b) -> a)); + return diffResults.stream() + .filter(tableDocDiff -> request.getTableIds().contains(tableDocDiff.getId())) + .map(diff -> documentResponseConverter.ofDiff( + diff, discussionCountMapByJoinName, descriptionMapByJoinName)) .sorted(Comparator.comparing(TableDocumentResponse::getName)) .collect(Collectors.toList()); - } else { + var current = this.getTableDetails(projectId, databaseDocumentId, request.getTableIds()); current.sort(Comparator.comparing(TableDocumentResponse::getName)); return current; } @@ -569,4 +350,15 @@ public class DocumentService { return tableResponseConverter.from(tables, columnMapByTableId); } } + + private DatabaseDocDiff retrieveDatabaseDocumentDiff(DatabaseDocument databaseDocument) { + Integer docId = databaseDocument.getId(); + List tables = tableDocumentDao.selectByDatabaseDocumentId(docId); + List columns = tableColumnDocumentDao.selectByDatabaseDocumentId(docId); + List indexes = tableIndexDocumentDao.selectByDatabaseMetaId(docId); + List triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(docId); + List fks = tableForeignKeyDocumentDao.selectByDatabaseDocumentId(docId); + return documentDiffConverter.of(databaseDocument, tables, columns, indexes, triggers, fks); + } + } diff --git a/core/src/main/java/com/databasir/core/domain/document/service/DocumentSyncService.java b/core/src/main/java/com/databasir/core/domain/document/service/DocumentSyncService.java new file mode 100644 index 0000000..c5e9180 --- /dev/null +++ b/core/src/main/java/com/databasir/core/domain/document/service/DocumentSyncService.java @@ -0,0 +1,189 @@ +package com.databasir.core.domain.document.service; + +import com.databasir.common.DatabasirException; +import com.databasir.core.Databasir; +import com.databasir.core.DatabasirConfig; +import com.databasir.core.diff.Diffs; +import com.databasir.core.diff.data.DiffType; +import com.databasir.core.diff.data.RootDiff; +import com.databasir.core.domain.DomainErrors; +import com.databasir.core.domain.document.converter.DatabaseMetaConverter; +import com.databasir.core.domain.document.converter.DocumentFullTextConverter; +import com.databasir.core.domain.document.converter.DocumentPojoConverter; +import com.databasir.core.domain.document.event.DocumentUpdated; +import com.databasir.core.infrastructure.connection.DatabaseConnectionService; +import com.databasir.core.infrastructure.converter.JsonConverter; +import com.databasir.core.infrastructure.event.EventPublisher; +import com.databasir.core.meta.data.DatabaseMeta; +import com.databasir.dao.impl.*; +import com.databasir.dao.tables.pojos.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@Slf4j +@RequiredArgsConstructor +public class DocumentSyncService { + + private final ProjectDao projectDao; + + private final ProjectSyncRuleDao projectSyncRuleDao; + + private final DataSourceDao dataSourceDao; + + private final DataSourcePropertyDao dataSourcePropertyDao; + + private final DatabaseConnectionService databaseConnectionService; + + private final DatabaseDocumentDao databaseDocumentDao; + + private final TableDocumentDao tableDocumentDao; + + private final TableColumnDocumentDao tableColumnDocumentDao; + + private final TableIndexDocumentDao tableIndexDocumentDao; + + private final TableTriggerDocumentDao tableTriggerDocumentDao; + + private final TableForeignKeyDocumentDao tableForeignKeyDocumentDao; + + private final DocumentFullTextDao documentFullTextDao; + + private final DocumentPojoConverter documentPojoConverter; + + private final DocumentFullTextConverter documentFullTextConverter; + + private final JsonConverter jsonConverter; + + private final DatabaseMetaConverter databaseMetaConverter; + + private final EventPublisher eventPublisher; + + @Transactional + public void syncByProjectId(Integer projectId) { + projectDao.selectOptionalById(projectId) + .orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception); + DatabaseMeta current = retrieveDatabaseMeta(projectId); + Optional originalOption = databaseDocumentDao.selectNotArchivedByProjectId(projectId); + if (originalOption.isPresent()) { + DatabaseDocument original = originalOption.get(); + DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original); + RootDiff diff = Diffs.diff(originalMeta, current); + if (diff.getDiffType() == DiffType.NONE) { + log.info("ignore project {} {} sync data, because without change", + projectId, + original.getDatabaseName()); + return; + } + Integer previousDocumentId = original.getId(); + // archive old version + databaseDocumentDao.updateIsArchiveById(previousDocumentId, true); + Long version = original.getVersion(); + Integer docId = saveNewDocument(current, version + 1, original.getProjectId()); + eventPublisher.publish(new DocumentUpdated(diff, version + 1, version, projectId, docId)); + } else { + Integer docId = saveNewDocument(current, 1L, projectId); + RootDiff diff = null; + try { + diff = Diffs.diff(null, current); + } catch (Exception e) { + log.error("diff project " + projectId + " error, fallback diff type to NONE", e); + } + eventPublisher.publish(new DocumentUpdated(diff, 1L, null, projectId, docId)); + } + } + + private DatabaseMeta retrieveDatabaseMeta(Integer projectId) { + ProjectSyncRule rule = projectSyncRuleDao.selectByProjectId(projectId); + DataSource dataSource = dataSourceDao.selectByProjectId(projectId); + List properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId()); + Connection jdbcConnection = databaseConnectionService.create(dataSource, properties); + DatabasirConfig databasirConfig = new DatabasirConfig(); + databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray())); + databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray())); + try { + if (jdbcConnection == null) { + throw DomainErrors.CONNECT_DATABASE_FAILED.exception(); + } + DatabaseMeta databaseMeta = Databasir.of(databasirConfig) + .get(jdbcConnection, dataSource.getDatabaseName(), dataSource.getSchemaName()) + .orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception); + return databaseMeta; + } finally { + try { + if (jdbcConnection != null && !jdbcConnection.isClosed()) { + jdbcConnection.close(); + } + } catch (SQLException e) { + log.error("close jdbc connection error", e); + } + } + } + + private DatabaseMeta retrieveOriginalDatabaseMeta(DatabaseDocument original) { + Integer docId = original.getId(); + List tables = tableDocumentDao.selectByDatabaseDocumentId(docId); + List columns = tableColumnDocumentDao.selectByDatabaseDocumentId(docId); + List indexes = tableIndexDocumentDao.selectByDatabaseMetaId(docId); + List triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(docId); + List fks = tableForeignKeyDocumentDao.selectByDatabaseDocumentId(docId); + return databaseMetaConverter.of(original, tables, columns, indexes, triggers, fks); + } + + private Integer saveNewDocument(DatabaseMeta meta, + Long version, + Integer projectId) { + + var dbDocPojo = documentPojoConverter.toDatabasePojo(projectId, meta, version); + final Integer docId; + try { + docId = databaseDocumentDao.insertAndReturnId(dbDocPojo); + dbDocPojo.setId(docId); + } catch (DuplicateKeyException e) { + log.warn("ignore insert database document projectId={} version={}", projectId, version); + throw new DatabasirException(DomainErrors.DATABASE_DOCUMENT_DUPLICATE_KEY); + } + meta.getTables().forEach(table -> { + TableDocument tableMeta = + documentPojoConverter.toTablePojo(docId, table); + Integer tableMetaId = tableDocumentDao.insertAndReturnId(tableMeta); + tableMeta.setId(tableMetaId); + // column + var columns = documentPojoConverter.toColumnPojo(docId, tableMetaId, table.getColumns()); + tableColumnDocumentDao.batchInsert(columns); + // index + var indexes = documentPojoConverter.toIndexPojo(docId, tableMetaId, table.getIndexes()) + .stream() + .filter(index -> { + if (index.getName() != null) { + return true; + } else { + log.warn("ignore table {} index {}, cause name is null", table.getName(), index); + return false; + } + }) + .collect(Collectors.toList()); + tableIndexDocumentDao.batchInsert(indexes); + // foreign key + var foreignKeys = documentPojoConverter.toForeignKeyPojo(docId, tableMetaId, table.getForeignKeys()); + tableForeignKeyDocumentDao.batchInsert(foreignKeys); + + // trigger + var triggers = documentPojoConverter.toTriggerPojo(docId, tableMetaId, table.getTriggers()); + tableTriggerDocumentDao.batchInsert(triggers); + }); + log.info("save new version document success: projectId = {}, name = {}, version = {}", + projectId, meta.getDatabaseName(), version); + return docId; + } + +} diff --git a/core/src/test/java/com/databasir/core/domain/document/diff/BaseTypeFieldEqualFunctionTest.java b/core/src/test/java/com/databasir/core/domain/document/diff/BaseTypeFieldEqualFunctionTest.java new file mode 100644 index 0000000..675de9c --- /dev/null +++ b/core/src/test/java/com/databasir/core/domain/document/diff/BaseTypeFieldEqualFunctionTest.java @@ -0,0 +1,94 @@ +package com.databasir.core.domain.document.diff; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BaseTypeFieldEqualFunctionTest { + + @Test + public void testApplyAllNull() { + BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of()); + + // Setup + final Object that = null; + final Object other = null; + + // Run the test + final Boolean result = equalFunction.apply(that, other); + + // Verify the results + assertTrue(result); + } + + @Test + public void testApplySimpleObject() { + SimpleObject that = SimpleObject.builder() + .id(1) + .height(180L) + .name("test") + .build(); + + SimpleObject other = SimpleObject.builder() + .id(1) + .height(180L) + .name("test") + .build(); + + BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of()); + final Boolean result = equalFunction.apply(that, other); + assertTrue(result); + } + + @Test + public void testApplyComplexObject() { + SimpleObject thatItem = SimpleObject.builder() + .id(1) + .height(180L) + .name("test") + .build(); + ComplexObject thatObj = new ComplexObject(); + thatObj.setId(1); + thatObj.setName("eq"); + thatObj.setItems(List.of(thatItem)); + + SimpleObject otherItem = SimpleObject.builder() + .id(1) + .height(180L) + .name("test") + .build(); + ComplexObject otherObj = new ComplexObject(); + otherObj.setId(1); + otherObj.setName("eq"); + otherObj.setItems(List.of(otherItem)); + + BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of()); + final Boolean result = equalFunction.apply(thatObj, otherObj); + assertTrue(result); + } + + @Test + public void testApplyComplexObject2() { + SimpleObject thatItem = SimpleObject.builder() + .id(1) + .height(180L) + .name("test") + .build(); + ComplexObject thatObj = new ComplexObject(); + thatObj.setId(1); + thatObj.setName("eq"); + thatObj.setItems(List.of(thatItem)); + + ComplexObject otherObj = new ComplexObject(); + otherObj.setId(1); + otherObj.setName("eq"); + otherObj.setItems(List.of()); + + BaseTypeFieldEqualFunction equalFunction = new BaseTypeFieldEqualFunction(List.of()); + final Boolean result = equalFunction.apply(thatObj, otherObj); + assertFalse(result); + } +} diff --git a/core/src/test/java/com/databasir/core/domain/document/diff/ComplexObject.java b/core/src/test/java/com/databasir/core/domain/document/diff/ComplexObject.java new file mode 100644 index 0000000..c0c0a9c --- /dev/null +++ b/core/src/test/java/com/databasir/core/domain/document/diff/ComplexObject.java @@ -0,0 +1,17 @@ +package com.databasir.core.domain.document.diff; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ComplexObject { + + private Integer id; + + private String name; + + private List items = new ArrayList<>(); + +} diff --git a/core/src/test/java/com/databasir/core/domain/document/diff/SimpleObject.java b/core/src/test/java/com/databasir/core/domain/document/diff/SimpleObject.java new file mode 100644 index 0000000..d3fddea --- /dev/null +++ b/core/src/test/java/com/databasir/core/domain/document/diff/SimpleObject.java @@ -0,0 +1,16 @@ +package com.databasir.core.domain.document.diff; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class SimpleObject { + + private Integer id; + + private Long height; + + private String name; + +} \ No newline at end of file