From ad4b1d9fe0f8454978f296a2fffa29c2750d28d6 Mon Sep 17 00:00:00 2001 From: vran Date: Tue, 19 Apr 2022 22:32:22 +0800 Subject: [PATCH] fix: duplicate project version when concurrency (#108) --- .../main/java/com/databasir/job/ProjectSyncJob.java | 7 ++++++- .../java/com/databasir/core/domain/DomainErrors.java | 2 +- .../domain/document/service/DocumentService.java | 10 +++++++++- .../core/domain/project/service/ProjectService.java | 6 +++--- .../jooq/main/java/com/databasir/dao/Indexes.java | 2 -- .../jooq/main/java/com/databasir/dao/Keys.java | 1 + .../com/databasir/dao/tables/DatabaseDocument.java | 12 +++++------- .../main/resources/db/migration/V1.4__project_uk.sql | 4 ++++ 8 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 dao/src/main/resources/db/migration/V1.4__project_uk.sql diff --git a/api/src/main/java/com/databasir/job/ProjectSyncJob.java b/api/src/main/java/com/databasir/job/ProjectSyncJob.java index a7e2443..5d62ac8 100644 --- a/api/src/main/java/com/databasir/job/ProjectSyncJob.java +++ b/api/src/main/java/com/databasir/job/ProjectSyncJob.java @@ -1,5 +1,6 @@ package com.databasir.job; +import com.databasir.common.DatabasirException; import com.databasir.core.domain.project.service.ProjectService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,7 +19,11 @@ public class ProjectSyncJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getMergedJobDataMap(); Integer projectId = dataMap.getInt("projectId"); - projectService.createSyncTask(projectId, -1, true); + try { + projectService.createSyncTask(projectId, -1, true); + } catch (DatabasirException e) { + log.warn("Failed to create sync task for project {}, {}", projectId, e.getMessage()); + } } } diff --git a/core/src/main/java/com/databasir/core/domain/DomainErrors.java b/core/src/main/java/com/databasir/core/domain/DomainErrors.java index a4fa7c9..c488dab 100644 --- a/core/src/main/java/com/databasir/core/domain/DomainErrors.java +++ b/core/src/main/java/com/databasir/core/domain/DomainErrors.java @@ -45,7 +45,7 @@ public enum DomainErrors implements DatabasirErrors { INVALID_MOCK_DATA_SCRIPT("A_10029", "不合法的表达式"), CANNOT_DELETE_SELF("A_10030", "无法对自己执行删除账号操作"), DRIVER_CLASS_NOT_FOUND("A_10031", "获取驱动类名失败"), - ; + DATABASE_DOCUMENT_DUPLICATE_KEY("A_10032", "文档版本重复"); private final String errCode; 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 99c0e8b..d66218e 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,5 +1,6 @@ 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; @@ -21,6 +22,7 @@ import com.databasir.dao.value.DocumentDiscussionCountPojo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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; @@ -146,7 +148,13 @@ public class DocumentService { Integer projectId) { var pojo = documentPojoConverter.toDatabasePojo(projectId, meta, version); - final Integer docId = databaseDocumentDao.insertAndReturnId(pojo); + final Integer docId; + try { + docId = databaseDocumentDao.insertAndReturnId(pojo); + } 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 -> { TableDocumentPojo tableMeta = documentPojoConverter.toTablePojo(docId, table); diff --git a/core/src/main/java/com/databasir/core/domain/project/service/ProjectService.java b/core/src/main/java/com/databasir/core/domain/project/service/ProjectService.java index 661289a..def1458 100644 --- a/core/src/main/java/com/databasir/core/domain/project/service/ProjectService.java +++ b/core/src/main/java/com/databasir/core/domain/project/service/ProjectService.java @@ -188,12 +188,12 @@ public class ProjectService { @Transactional public Optional createSyncTask(Integer projectId, Integer userId, boolean ignoreIfExists) { if (!projectDao.existsById(projectId)) { - log.info("create sync task failed, because project not exists, projectId={}", projectId); - return Optional.empty(); + log.warn("create sync task failed, because project not exists, projectId={}", projectId); + throw DomainErrors.PROJECT_NOT_FOUND.exception(); } var validTaskStatus = List.of(ProjectSyncTaskStatus.NEW, ProjectSyncTaskStatus.RUNNING); if (ignoreIfExists && projectSyncTaskDao.existsByProjectId(projectId, validTaskStatus)) { - log.info("create sync task failed, it's already exists, projectId={}", projectId); + log.warn("create sync task failed, it's already exists, projectId={}", projectId); return Optional.empty(); } ProjectSyncTaskPojo projectSyncTask = new ProjectSyncTaskPojo(); diff --git a/dao/generated-src/jooq/main/java/com/databasir/dao/Indexes.java b/dao/generated-src/jooq/main/java/com/databasir/dao/Indexes.java index a07d9f6..36802cd 100644 --- a/dao/generated-src/jooq/main/java/com/databasir/dao/Indexes.java +++ b/dao/generated-src/jooq/main/java/com/databasir/dao/Indexes.java @@ -5,7 +5,6 @@ package com.databasir.dao; import com.databasir.dao.tables.DataSourceProperty; -import com.databasir.dao.tables.DatabaseDocument; import com.databasir.dao.tables.DocumentDiscussion; import com.databasir.dao.tables.ProjectSyncTask; import com.databasir.dao.tables.TableColumnDocument; @@ -36,7 +35,6 @@ public class Indexes { public static final Index TABLE_FOREIGN_KEY_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableForeignKeyDocument.TABLE_FOREIGN_KEY_DOCUMENT, new OrderField[] { TableForeignKeyDocument.TABLE_FOREIGN_KEY_DOCUMENT.DATABASE_DOCUMENT_ID }, false); public static final Index TABLE_INDEX_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableIndexDocument.TABLE_INDEX_DOCUMENT, new OrderField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.DATABASE_DOCUMENT_ID }, false); public static final Index TABLE_TRIGGER_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, new OrderField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.DATABASE_DOCUMENT_ID }, false); - public static final Index DATABASE_DOCUMENT_IDX_PROJECT_ID = Internal.createIndex(DSL.name("idx_project_id"), DatabaseDocument.DATABASE_DOCUMENT, new OrderField[] { DatabaseDocument.DATABASE_DOCUMENT.PROJECT_ID }, false); public static final Index DOCUMENT_DISCUSSION_IDX_PROJECT_ID = Internal.createIndex(DSL.name("idx_project_id"), DocumentDiscussion.DOCUMENT_DISCUSSION, new OrderField[] { DocumentDiscussion.DOCUMENT_DISCUSSION.PROJECT_ID }, false); public static final Index PROJECT_SYNC_TASK_IDX_PROJECT_ID = Internal.createIndex(DSL.name("idx_project_id"), ProjectSyncTask.PROJECT_SYNC_TASK, new OrderField[] { ProjectSyncTask.PROJECT_SYNC_TASK.PROJECT_ID }, false); public static final Index TABLE_COLUMN_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableColumnDocument.TABLE_COLUMN_DOCUMENT, new OrderField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.TABLE_DOCUMENT_ID }, false); diff --git a/dao/generated-src/jooq/main/java/com/databasir/dao/Keys.java b/dao/generated-src/jooq/main/java/com/databasir/dao/Keys.java index a59d5ce..3c580c1 100644 --- a/dao/generated-src/jooq/main/java/com/databasir/dao/Keys.java +++ b/dao/generated-src/jooq/main/java/com/databasir/dao/Keys.java @@ -76,6 +76,7 @@ public class Keys { public static final UniqueKey KEY_DATA_SOURCE_UK_PROJECT_ID = Internal.createUniqueKey(DataSource.DATA_SOURCE, DSL.name("KEY_data_source_uk_project_id"), new TableField[] { DataSource.DATA_SOURCE.PROJECT_ID }, true); public static final UniqueKey KEY_DATA_SOURCE_PROPERTY_PRIMARY = Internal.createUniqueKey(DataSourceProperty.DATA_SOURCE_PROPERTY, DSL.name("KEY_data_source_property_PRIMARY"), new TableField[] { DataSourceProperty.DATA_SOURCE_PROPERTY.ID }, true); public static final UniqueKey KEY_DATABASE_DOCUMENT_PRIMARY = Internal.createUniqueKey(DatabaseDocument.DATABASE_DOCUMENT, DSL.name("KEY_database_document_PRIMARY"), new TableField[] { DatabaseDocument.DATABASE_DOCUMENT.ID }, true); + public static final UniqueKey KEY_DATABASE_DOCUMENT_UK_PROJECT_ID_VERSION_IS_ARCHIVE = Internal.createUniqueKey(DatabaseDocument.DATABASE_DOCUMENT, DSL.name("KEY_database_document_uk_project_id_version_is_archive"), new TableField[] { DatabaseDocument.DATABASE_DOCUMENT.PROJECT_ID, DatabaseDocument.DATABASE_DOCUMENT.VERSION, DatabaseDocument.DATABASE_DOCUMENT.IS_ARCHIVE }, true); public static final UniqueKey KEY_DATABASE_TYPE_PRIMARY = Internal.createUniqueKey(DatabaseType.DATABASE_TYPE, DSL.name("KEY_database_type_PRIMARY"), new TableField[] { DatabaseType.DATABASE_TYPE.ID }, true); public static final UniqueKey KEY_DATABASE_TYPE_UK_DATABASE_TYPE_DELETED_DELETED_TOKEN = Internal.createUniqueKey(DatabaseType.DATABASE_TYPE, DSL.name("KEY_database_type_uk_database_type_deleted_deleted_token"), new TableField[] { DatabaseType.DATABASE_TYPE.DATABASE_TYPE_, DatabaseType.DATABASE_TYPE.DELETED, DatabaseType.DATABASE_TYPE.DELETED_TOKEN }, true); public static final UniqueKey KEY_DOCUMENT_DESCRIPTION_PRIMARY = Internal.createUniqueKey(DocumentDescription.DOCUMENT_DESCRIPTION, DSL.name("KEY_document_description_PRIMARY"), new TableField[] { DocumentDescription.DOCUMENT_DESCRIPTION.ID }, true); diff --git a/dao/generated-src/jooq/main/java/com/databasir/dao/tables/DatabaseDocument.java b/dao/generated-src/jooq/main/java/com/databasir/dao/tables/DatabaseDocument.java index 18ff589..8a4a7cb 100644 --- a/dao/generated-src/jooq/main/java/com/databasir/dao/tables/DatabaseDocument.java +++ b/dao/generated-src/jooq/main/java/com/databasir/dao/tables/DatabaseDocument.java @@ -5,7 +5,6 @@ package com.databasir.dao.tables; import com.databasir.dao.Databasir; -import com.databasir.dao.Indexes; import com.databasir.dao.Keys; import com.databasir.dao.tables.records.DatabaseDocumentRecord; @@ -16,7 +15,6 @@ import java.util.List; import org.jooq.Field; import org.jooq.ForeignKey; import org.jooq.Identity; -import org.jooq.Index; import org.jooq.Name; import org.jooq.Record; import org.jooq.Row10; @@ -141,11 +139,6 @@ public class DatabaseDocument extends TableImpl { return aliased() ? null : Databasir.DATABASIR; } - @Override - public List getIndexes() { - return Arrays.asList(Indexes.DATABASE_DOCUMENT_IDX_PROJECT_ID); - } - @Override public Identity getIdentity() { return (Identity) super.getIdentity(); @@ -156,6 +149,11 @@ public class DatabaseDocument extends TableImpl { return Keys.KEY_DATABASE_DOCUMENT_PRIMARY; } + @Override + public List> getUniqueKeys() { + return Arrays.asList(Keys.KEY_DATABASE_DOCUMENT_UK_PROJECT_ID_VERSION_IS_ARCHIVE); + } + @Override public DatabaseDocument as(String alias) { return new DatabaseDocument(DSL.name(alias), this); diff --git a/dao/src/main/resources/db/migration/V1.4__project_uk.sql b/dao/src/main/resources/db/migration/V1.4__project_uk.sql new file mode 100644 index 0000000..932dadf --- /dev/null +++ b/dao/src/main/resources/db/migration/V1.4__project_uk.sql @@ -0,0 +1,4 @@ +ALTER TABLE database_document + ADD CONSTRAINT UNIQUE uk_project_id_version_is_archive (project_id, version, is_archive); +ALTER TABLE database_document + DROP INDEX idx_project_id; \ No newline at end of file