mirror of
				https://github.com/vran-dev/databasir.git
				synced 2025-10-31 20:49:22 +08:00 
			
		
		
		
	fix: duplicate project version when concurrency (#108)
This commit is contained in:
		| @@ -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()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -188,12 +188,12 @@ public class ProjectService { | ||||
|     @Transactional | ||||
|     public Optional<Integer> 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(); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -76,6 +76,7 @@ public class Keys { | ||||
|     public static final UniqueKey<DataSourceRecord> 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<DataSourcePropertyRecord> 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<DatabaseDocumentRecord> 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<DatabaseDocumentRecord> 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<DatabaseTypeRecord> 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<DatabaseTypeRecord> 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<DocumentDescriptionRecord> KEY_DOCUMENT_DESCRIPTION_PRIMARY = Internal.createUniqueKey(DocumentDescription.DOCUMENT_DESCRIPTION, DSL.name("KEY_document_description_PRIMARY"), new TableField[] { DocumentDescription.DOCUMENT_DESCRIPTION.ID }, true); | ||||
|   | ||||
| @@ -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<DatabaseDocumentRecord> { | ||||
|         return aliased() ? null : Databasir.DATABASIR; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<Index> getIndexes() { | ||||
|         return Arrays.asList(Indexes.DATABASE_DOCUMENT_IDX_PROJECT_ID); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Identity<DatabaseDocumentRecord, Integer> getIdentity() { | ||||
|         return (Identity<DatabaseDocumentRecord, Integer>) super.getIdentity(); | ||||
| @@ -156,6 +149,11 @@ public class DatabaseDocument extends TableImpl<DatabaseDocumentRecord> { | ||||
|         return Keys.KEY_DATABASE_DOCUMENT_PRIMARY; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<UniqueKey<DatabaseDocumentRecord>> 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); | ||||
|   | ||||
							
								
								
									
										4
									
								
								dao/src/main/resources/db/migration/V1.4__project_uk.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								dao/src/main/resources/db/migration/V1.4__project_uk.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
		Reference in New Issue
	
	Block a user