Browse Source

migration file and datasource

masarifyuli 4 ngày trước cách đây
mục cha
commit
82ee9f8702

+ 1 - 1
config/general-setting.yml

@@ -182,7 +182,7 @@ redirectUrl:
 - http://localhost:4200/oauth2redirect
 - https://cmpd.telmessenger.com/oauth2redirect
 - https://app.insomnia.rest/oauth/redirect
-dataKey: aY6tKotSoUoMIi7JiTBZq%2Bt0qdYZz7AmZrTkYTSE5zO%2BuTlZFkXv0r4J7d1UwmZRRPglM6vrDhjKye0quA3seEFIxy1gdafb2t300b8GfzhcRMB5EFzVg%2FZDy5QL8OnE
+dataKey: jRA4iyjy1ZCaIdlJBe6Cm%2BRPGb%2BwcO85%2FWR7Z9%2B8AFX7dJUSJjVnyr4JXSEyayg6i74%2F7IcBsn9iY%2BJ1ArmTtLSCD0plJfr%2B1S6%2ByBesS3dVTDSwlrEAuv2Pm%2BhYLmQ6f560DMvqCuahhKkkybkl5g%3D%3D
 
 #database: 
 #  type: sqlserver

+ 146 - 17
config/migration.yml

@@ -1,26 +1,155 @@
 migration:
-  type: DATABASE
+  type: FILE
   sourceDatabase:
     type: sqlserver
     host: 192.168.100.25
     port: 1433
-    name: callpartner
+    name: cpcimb
     username: sa
     password: D4tacom!
     properties: encrypt=true;trustServerCertificate=true;
+  target: null
   schema:
-    - table: organization
-      history: organizationhistory
-      group:
-      attribute:
-        id: history.organization_id
-        code: organization_code
-        name: organization_name
-        description: description
-        email: organization_email
-        emailOnOverBudget: sendemail_overbudget
-        parent_id: history.parent_organization_id
-        structure: history.organization_id_structure
-        appliedDate: history.app_datetime
-        expiredDate: history.exp_datetime
-        cpid: history.organization_id
+  - target: pbx
+    table: pbxlist
+    history: null
+    group: null
+    attribute:
+      code: pbx_code
+      name: description
+      ipPbx: ip_pbx
+      pbxSupporter: pbx_supporter
+      sharePath: share_path
+      appliedDate: ext_start
+      expiredDate: ext_end
+  - target: corcos
+    table: womastercorcos
+    history: null
+    group: null
+    attribute:
+      command: command
+      name: short_desc
+      description: description
+  - target: provider
+    table: provider
+    history: null
+    group: null
+    attribute:
+      code: provider_prefix
+      name: description
+      description: description
+  - target: account
+    table: account
+    history: accounthistory
+    group: null
+    attribute:
+      id: history.account_id
+      code: account_code
+      name: history.account_number
+      description: description
+      appliedDate: history.app_datetime
+      expiredDate: history.exp_datetime
+  - target: trunk
+    table: trunk
+    history: trunkhistory
+    group: null
+    attribute:
+      id: history.trunk_id
+      code: trunk_code
+      name: description
+      pbx_id: location_code
+      direction: cap_direction
+      appliedDate: history.app_datetime
+      expiredDate: history.exp_datetime
+  - target: costCenter
+    table: costcenter
+    history: costcenterhistory
+    group: null
+    attribute:
+      id: history.costcenter_id
+      code: costcenter_code
+      name: costcenter_name
+      description: description
+      email: costcenter_email
+      parent_id: history.parent_costcenter_id
+      structure: history.costcenter_id_structure
+      appliedDate: history.app_datetime
+      expiredDate: history.exp_datetime
+  - target: organization
+    table: organization
+    history: organizationhistory
+    group: null
+    attribute:
+      id: history.organization_id
+      code: organization_code
+      name: organization_name
+      description: description
+      email: organization_email
+      emailOnOverBudget: sendemail_overbudget
+      parent_id: history.parent_organization_id
+      structure: history.organization_id_structure
+      appliedDate: history.app_datetime
+      expiredDate: history.exp_datetime
+      cpid: history.organization_id
+  - target: phoneUser
+    table: phoneuser
+    history: phoneuserhistory
+    group: phoneuserextlist
+    attribute:
+      id: history.phoneuser_id
+      code: phoneuser_code
+      name: phoneuser_name
+      email: phoneuser_email
+      whatsapp: phoneuser_mobile
+      position: position
+      emailOnOverBudget: sendemail_overbudget
+      pin: history.phoneuser_pin
+      extension: history.default_extension
+      organization_id: history.organization_code
+      costCenter_id: history.costcenter_code
+      pbx_id: history.pbx_code
+      appliedDate: history.app_datetime
+      expiredDate: history.exp_datetime
+      pbx__list: group.pbx_list
+      budget__maxCost: history.max_cost
+      budget__warnCost: history.warn_cost
+  - target: callTransaction
+    table: calltransaction_20260127
+    history: null
+    group: null
+    attribute:
+      direction: direction
+      startOfCall: start_of_call
+      duration: duration
+      extension: ext_used
+      pin: pin
+      phoneUser_id: phoneuser_code
+      extTransferFrom: ext_transfer_from
+      extTransferTo: ext_transfer_to
+      number: dialed_number
+      callerNumber: caller_number
+      accessNumber: access_number
+      organization_id: organization_code
+      costCenter_id: costcenter_code
+      cost: cost_1
+      service: cost_service
+      tax: cost_tax1
+      discount: cost_adjustment
+      currency: currency
+      pbx_id: pbx_code
+      terminationCode: call_termination
+      redirectReason: redirect_reason
+      account_id: account_code
+      trunk_id: trunk_code
+  - target: webUser
+    table: usersetup
+    history: null
+    group: null
+    attribute:
+      username: login_id
+      password: login_password
+      name: description
+      phoneUser_id: phoneuser_code
+      loginPin: used_pin
+  lastTarget: pbx;corcos;provider;account;trunk;costCenter;organization;phoneUser;callTransaction;webUser
+  lastAction: '2026-03-02 16:31:09'

+ 3 - 3
src/main/kotlin/com/datacomsolusindo/migration/MigrationApplication.kt

@@ -54,9 +54,9 @@ class AppEvent(
     @EventListener(ApplicationReadyEvent::class)
     fun doSomethingAfterStartup() {
         logger.info("started service migration data with support mandiri menghidupi")
-//        val folder = Paths.get("migration")
-//        migrationService.startInsertWorker()
-//        migrationService.scanAndQueue(folder)
+        val folder = Paths.get("migration")
+        migrationService.startInsertWorker()
+        migrationService.scanAndQueue(folder)
 //        exitProcess(1)
     }
 

+ 7 - 5
src/main/kotlin/com/datacomsolusindo/migration/MigrationData.kt

@@ -71,8 +71,8 @@ class SourceDataMigrationService(@param:Qualifier("migrationJdbcTemplate") priva
     private val logger = SimpleLogger.getLogger(this::class.java)
 
     fun getData(table: String, column: List<String>): MutableList<MutableMap<String, Any?>> {
-        val process = measureTimedValue {
-            if (table.isBlank()) mutableListOf() else {
+        return if (table.isBlank()) mutableListOf() else {
+            val process = measureTimedValue {
                 try {
                     migrationJdbcTemplate?.queryForList(
                         "SELECT * FROM $table"
@@ -82,10 +82,12 @@ class SourceDataMigrationService(@param:Qualifier("migrationJdbcTemplate") priva
                     mutableListOf()
                 }
             }
+            logger.info(
+                "get source data from table $table ${process.value.size} " +
+                        "takes time ${process.duration.inWholeMilliseconds}ms"
+            )
+            process.value
         }
-        logger.info("get source data from table $table ${process.value.size} " +
-                "takes time ${process.duration.inWholeMilliseconds}ms")
-        return process.value
     }
 
 }

+ 30 - 219
src/main/kotlin/com/datacomsolusindo/migration/MigrationEntity.kt

@@ -1,3 +1,5 @@
+@file:Suppress("UNCHECKED_CAST")
+
 package com.datacomsolusindo.migration
 
 import com.datacomsolusindo.cpx_shared_code.entity.Account
@@ -48,195 +50,16 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
 
     private val logger = SimpleLogger.getLogger(this::class.java)
 
-//    fun <T> dataToMap(
-//        clazz: Class<T>,
-//        fields: Map<String, String>,
-//        unique: String,
-//        rootFile: File,
-//        historyFile: File? = null,
-//        groupFile: File? = null
-//    ): List<MutableMap<String, Any?>> {
-//        logger.info("prepare data migration class ${clazz.simpleName}")
-//        val process = measureTimedValue {
-//            val historyData = historyFile?.let { readQueryDataToMap(it) }
-//            val groupData = groupFile?.let { readQueryDataToMap(it) }
-//            val rootData = readQueryDataToMap(rootFile)
-//            val mapRootData = rootData.mapIndexed { index, map ->
-//                val data: MutableMap<String, Any?> = mutableMapOf()
-//                val fieldRoots = fields.toList().filterNot { f -> f.second.contains(".") }
-//                val joinRoots = fields.toList().filter { f -> f.second.contains(".") }
-//
-//                fieldRoots.forEach { f ->
-//                    data[f.first] = map[f.second]
-//                }
-//                joinRoots.sortedByDescending { it.second }.forEach { f ->
-//                    data[f.first] = when {
-//                        f.second.startsWith("history.") ->
-//                            getValueAnotherFile(fields, historyData, unique, data, f.first, f.second, "history")
-//
-//                        f.second.toString().startsWith("group.") ->
-//                            getValueAnotherFile(fields, groupData, unique, data, f.first, f.second, "group")
-//
-//                        else -> map[f.second]
-//                    }
-//                }
-//                data
-//            }
-//            mapRootData
-//        }
-//        logger.info("finish prepare data [${process.value.size}] migration class ${clazz.simpleName} takes time ${process.duration.inWholeMilliseconds}ms")
-//        return process.value
-//    }
-//
-//    private fun getValueAnotherFile(
-//        fields: Map<String, String>,
-//        dataFile: List<Map<String, Any?>>?,
-//        unique: String,
-//        rootData: Map<String, Any?>,
-//        keyRoot: String,
-//        valRoot: String,
-//        key: String
-//    ): Any? {
-//        val uniqueField = dataFile?.firstOrNull()?.let {
-//            if (it.any { a -> a.key == fields[unique] }) unique else "id"
-//        } ?: "id"
-//        val fieldUnique = fields[uniqueField]!!.split(".").last()
-//
-//        val value = dataFile?.firstOrNull { f ->
-//            f[fieldUnique].toString() == rootData[uniqueField].toString()
-//        }?.get(valRoot.replace("${key}.", ""))
-//
-//        return if (keyRoot.contains("_")) value?.let { id ->
-//            dataFile.firstOrNull { f ->
-//                f[fields["id"]!!.split(".").last()].toString() == id.toString()
-//            }?.get(fields["code"]) ?: value
-//        } else value
-//    }
-//
-//    private fun readQueryDataToMap(file: File): List<Map<String, Any?>> {
-//        val map = measureTimedValue {
-//            file.bufferedReader(StandardCharsets.UTF_8)
-//                .readLines().filter {
-//                    it.startsWith("INSERT")
-//                }.mapNotNull {
-//                    try {
-//                        insertSqlToMap(it)
-//                    } catch (_: Exception) {
-//                        null
-//                    }
-//                }
-//        }
-//        logger.info("migration read query data ${map.value.size} takes time ${map.duration.inWholeMilliseconds}")
-//        return map.value
-//    }
-
-//    fun <T> dataToMap(
-//        clazz: Class<T>,
-//        fields: Map<String, String>,
-//        unique: String,
-//        rootFile: File,
-//        historyFile: File? = null,
-//        groupFile: File? = null
-//    ): List<MutableMap<String, Any?>> {
-//
-//        logger.info("prepare data migration class ${clazz.simpleName}")
-//
-//        val process = measureTimedValue {
-//
-//            // --- Read Files ---
-//            val historyData = historyFile?.let { readQueryDataToMap(it) }
-//            val groupData = groupFile?.let { readQueryDataToMap(it) }
-//            val rootData = readQueryDataToMap(rootFile)
-//
-//            // --- Pre-calc field names (avoid split(".") repeatedly) ---
-//            val fieldMapping = fields.mapValues { it.value.substringAfterLast(".") }
-//            val uniqueField = fieldMapping[unique] ?: "id"
-//            val uniqueFieldId = fieldMapping["id"]
-//
-//            // --- Create Indexes (O(n)) ---
-//            val historyIndex = historyData?.groupBy { it[uniqueField] }?.mapValues { (_, items) ->
-//                items.maxByOrNull {
-//                    it[uniqueFieldId!!.removePrefix("history.")].toString().toInt()
-//                }
-//            }
-//            //?.associateBy { it[uniqueField]?.toString() }
-//            val groupIndex = groupData?.associateBy { it[uniqueFieldId ?: uniqueField]?.toString() }
-//
-//            val fieldRoots = fields.filter { !it.value.contains(".") }
-//            val joinRoots = fields.filter { it.value.contains(".") }
-//                .toList().sortedByDescending { it.second }.toMap()
-//
-//            rootData.mapIndexed { index, row ->
-//                val data = mutableMapOf<String, Any?>()
-//
-//                // --- Direct Fields ---
-//                fieldRoots.forEach { (targetKey, sourceKey) ->
-//                    data[targetKey] = row[sourceKey]
-//                }
-//
-//                // --- Join Fields ---
-//                joinRoots.forEach { (targetKey, sourceKeyFull) ->
-//
-//                    val value = when {
-//                        sourceKeyFull.startsWith("history.") -> {
-//                            val sourceKey = sourceKeyFull.removePrefix("history.")
-//                            historyIndex
-//                                ?.get(row[uniqueField]?.toString())
-//                                ?.get(sourceKey.substringAfterLast("."))
-//                        }
-//
-//                        sourceKeyFull.startsWith("group.") -> {
-//                            val sourceKey = sourceKeyFull.removePrefix("group.")
-//                            val idKey = historyIndex
-//                                ?.get(row[uniqueField]?.toString())
-//                                ?.get(uniqueFieldId) ?: row[uniqueFieldId]
-//                            groupIndex
-//                                ?.get(idKey.toString())
-//                                ?.get(sourceKey.substringAfterLast("."))
-//                        }
-//
-//                        else -> row[sourceKeyFull]
-//                    }
-//
-//                    data[targetKey] = value
-//                }
-//                data
-//            }
-//        }
-//
-//        val value = process.value.map { m ->
-//            val dt = m as MutableMap<String, Any?>
-//            dt["password"]?.toString()?.let {
-//                dt["password"] = if (it.isBlank()) ""
-//                else cpDecrypt.decrypt(it)?.let { p ->
-//                    tempPassword[p] ?: run {
-//                        val pass = passwordEncoder.encode(p)
-//                        tempPassword[p] = pass
-//                        pass
-//                    }
-//                } ?: ""
-//            }
-//            dt
-//        }
-//        logger.info(
-//            "finish prepare data [${process.value.size}] migration class ${clazz.simpleName} " +
-//                    "takes time ${process.duration.inWholeMilliseconds}ms"
-//        )
-//        return value
-//    }
-
     private fun <T> dataToMap(
         clazz: Class<T>,
         fields: Map<String, String>,
-        unique: String,
         rootFile: File,
         historyFile: File? = null,
         groupFile: File? = null
-    ): List<MutableMap<String, Any?>> {
-        return processData(
+    ) {
+        processData(
             clazz,
             fields,
-            unique,
             rootData = readQueryDataToMap(rootFile) as MutableList<MutableMap<String, Any?>>,
             historyData = historyFile?.let { readQueryDataToMap(it) as MutableList<MutableMap<String, Any?>> },
             groupData = groupFile?.let { readQueryDataToMap(it) as MutableList<MutableMap<String, Any?>> }
@@ -246,15 +69,13 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
     private fun <T> dataToMapWithDataSource(
         clazz: Class<T>,
         fields: Map<String, String>,
-        unique: String,
         rootData: MutableList<MutableMap<String, Any?>>,
         historyData: MutableList<MutableMap<String, Any?>>? = null,
         groupData: MutableList<MutableMap<String, Any?>>? = null
-    ): List<MutableMap<String, Any?>> {
-        return processData(
+    ) {
+        processData(
             clazz,
             fields,
-            unique,
             rootData,
             historyData,
             groupData
@@ -264,26 +85,24 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
     private fun <T> processData(
         clazz: Class<T>,
         fields: Map<String, String>,
-        unique: String,
         rootData: MutableList<MutableMap<String, Any?>>,
         historyData: MutableList<MutableMap<String, Any?>>? = null,
         groupData: MutableList<MutableMap<String, Any?>>? = null
-    ): List<MutableMap<String, Any?>> {
-
-        logger.info("prepare data migration class ${clazz.simpleName}")
-
-        val process = measureTimedValue {
-            val fieldMapping = fields.mapValues { it.value.substringAfterLast(".") }
-            val uniqueField = fieldMapping[unique] ?: "id"
-            val uniqueFieldId = fieldMapping["id"]
+    ) {
+        val fieldMapping = fields.mapValues { it.value.substringAfterLast(".") }
+        val uniqueField = fieldMapping["code"] ?: "id"
+        val uniqueFieldId = fieldMapping["id"]
 
-            val historyIndex = buildHistoryIndex(historyData, uniqueField, uniqueFieldId)
-            val groupIndex = buildGroupIndex(groupData, uniqueFieldId ?: uniqueField)
+        val historyIndex = buildHistoryIndex(historyData, uniqueField, uniqueFieldId)
+        val groupIndex = buildGroupIndex(groupData, uniqueFieldId ?: uniqueField)
 
-            val fieldRoots = fields.filterValues { !it.contains(".") }
-            val joinRoots = fields.filterValues { it.contains(".") }
+        val fieldRoots = fields.filterValues { !it.contains(".") }
+        val joinRoots = fields.filterValues { it.contains(".") }
 
-            rootData.map { row ->
+        val chunkData = rootData.chunked(1000)
+        logger.info("data migration class ${clazz.simpleName} chunk data ${chunkData.size}")
+        chunkData.forEachIndexed { _, data ->
+            val dataMap = data.map { row ->
                 buildRow(
                     row,
                     fieldRoots,
@@ -293,15 +112,9 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                     uniqueField,
                     uniqueFieldId
                 )
-            }
+            }.map { postProcessPassword(it) }
+            queueInsertData.put((clazz as Class<out BaseEntity>) to dataMap)
         }
-
-        logger.info(
-            "finish prepare data [${process.value.size}] migration class ${clazz.simpleName} " +
-                    "takes time ${process.duration.inWholeMilliseconds}ms"
-        )
-
-        return process.value.map { postProcessPassword(it) }
     }
 
     private fun buildHistoryIndex(
@@ -358,6 +171,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                         ?.get(idKey?.toString())
                         ?.get(key.substringAfterLast("."))
                 }
+
                 else -> row[sourceFull]
             }
             data[target] = value
@@ -469,10 +283,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
         rootFile: File,
         historyFile: File?,
         groupFile: File?
-    ): List<MutableMap<String, Any?>> {
-        val data = dataToMap(clazz, fields, "code", rootFile, historyFile, groupFile)
-        return data
-    }
+    ) = dataToMap(clazz, fields, rootFile, historyFile, groupFile)
 
     fun <T : BaseEntity> executeWithDataSource(
         clazz: Class<T>,
@@ -480,10 +291,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
         rootData: MutableList<MutableMap<String, Any?>>,
         historyData: MutableList<MutableMap<String, Any?>>?,
         groupData: MutableList<MutableMap<String, Any?>>?
-    ): List<MutableMap<String, Any?>> {
-        val data = dataToMapWithDataSource(clazz, fields, "code", rootData, historyData, groupData)
-        return data
-    }
+    ) = dataToMapWithDataSource(clazz, fields,  rootData, historyData, groupData)
 
     fun clazzEntity(migrationTarget: String): Class<out BaseEntity>? {
         return when (migrationTarget) {
@@ -516,8 +324,8 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                 val phoneUserExtension = finalizer["extension"]
 
                 // budget
-                val budgetAnnual = finalizer["budget.maxCost"]?.toString()?.toDoubleOrNull()
-                val warningAnnual = finalizer["budget.warnCost"]?.toString()?.toDoubleOrNull()
+                val budgetAnnual = (finalizer["budget.maxCost"]?.toString() ?: finalizer["budget__maxCost"]?.toString())?.toDoubleOrNull()
+                val warningAnnual = (finalizer["budget.warnCost"]?.toString() ?: finalizer["budget__warnCost"]?.toString())?.toDoubleOrNull()
                 val budgetMaxCost = budgetAnnual?.let { max ->
                     List(12) { max }.joinToString(";")
                 }
@@ -530,7 +338,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                 // phoneUserPbx
                 val phoneUserPbxIds: MutableList<Any> = mutableListOf()
                 val data = if (clazz.simpleName == "PhoneUser") {
-                    finalizer["pbx.list"]?.toString()?.let {
+                    (finalizer["pbx.list"]?.toString() ?: finalizer["pbx__list"]?.toString())?.let {
                         it.split(";").forEach { fi ->
                             findUidByCode(Pbx::class.java, fi)?.let { id -> phoneUserPbxIds.add(id) }
                         }
@@ -543,11 +351,14 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                     val finalMap = finalizer.filterNot { fi ->
                         listOf(
                             "pbx.list",
+                            "pbx__list",
                             "pbx_id",
                             "extension",
                             "pin",
                             "budget.maxCost",
+                            "budget__maxCost",
                             "budget.warnCost",
+                            "budget__warnCost",
                             "maxCost",
                             "warnCost"
                         ).any { a -> a == fi.key }
@@ -558,7 +369,7 @@ class MigrationEntity(val passwordEncoder: PasswordEncoder, val queryNativeServi
                         }
                     }]"
                     if (phoneUserPbxIds.isEmpty()) {
-                        finalMap["expiredDate"] = LocalDate.now().atStartOfDay()
+                        finalMap["expiredDate"] = finalMap["expiredDate"] ?: LocalDate.now().atStartOfDay()
                     }
                     queryNativeService.insertDataWithNativeQuery(clazz, finalMap)
                 } else {

+ 61 - 42
src/main/kotlin/com/datacomsolusindo/migration/MigrationService.kt

@@ -2,11 +2,14 @@ package com.datacomsolusindo.migration
 
 import com.datacomsolusindo.cpx_shared_code.entity.BaseEntity
 import com.datacomsolusindo.cpx_shared_code.utility.SimpleLogger
-import com.datacomsolusindo.cpx_shared_code.utility.Util
 import org.springframework.beans.factory.config.YamlPropertiesFactoryBean
 import org.springframework.core.io.FileSystemResource
 import org.springframework.stereotype.Service
+import org.yaml.snakeyaml.DumperOptions
+import org.yaml.snakeyaml.Yaml
 import java.io.File
+import java.io.FileInputStream
+import java.io.FileWriter
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardCopyOption
@@ -15,7 +18,7 @@ import java.time.format.DateTimeFormatter
 import java.util.concurrent.Executors
 import java.util.concurrent.LinkedBlockingQueue
 import kotlin.time.measureTimedValue
-import kotlin.use
+
 
 data class PreparedMigration(
     val clazz: Class<out BaseEntity>,
@@ -29,6 +32,10 @@ val temporaryData: MutableMap<String, Map<String, Map<String, Any?>>> = mutableM
 val tempDataParent: MutableMap<String, String?> = mutableMapOf()
 val tempPassword: MutableMap<String, String?> = mutableMapOf()
 
+val executorPrepareData = Executors.newSingleThreadExecutor()
+val executorInsertData = Executors.newSingleThreadExecutor()
+val queueInsertData = LinkedBlockingQueue<Pair<Class<out BaseEntity>, List<MutableMap<String, Any?>>>>(20)
+
 @Service
 class MigrationService(
     val migrationEntity: MigrationEntity,
@@ -41,10 +48,6 @@ class MigrationService(
     private val MIGRATION = "_migration_"
     private val MIGRATION_CONFIG = "config/migration.yml"
 
-    private val executorPrepareData = Executors.newSingleThreadExecutor()
-    private val executorInsertData = Executors.newSingleThreadExecutor()
-    private val queueInsertData = LinkedBlockingQueue<Pair<Class<out BaseEntity>, List<MutableMap<String, Any?>>>>(20)
-
     fun isMigrationFile(path: Path): Boolean = path.fileName.toString().contains(MIGRATION)
 
     fun isTempFile(path: Path): Boolean = path.fileName.toString().startsWith(TEMP)
@@ -77,31 +80,32 @@ class MigrationService(
 
     private fun scanAndQueueWithDataSource() {
         logger.info("service migration prepare data migration with data source")
-        migrationSettingService.schema.forEach {
-            val rootTable = sourceDataMigrationService.getData(
-                it.table, it.attribute.filterNot { i ->
-                    i.value.toString().startsWith("history.") || i.value.toString().startsWith("group.")
-                }.map { m -> m.value.toString() }) ?: mutableListOf()
-            val rootGroup = it.group?.let { group ->
-                 sourceDataMigrationService.getData(
-                    group, it.attribute.filter { i -> i.value.toString().startsWith("group.") }
-                        .map { m -> m.value.toString().removePrefix("group.") })
-            } ?: mutableListOf()
-            val rootHistory = it.history?.let { history ->
-                sourceDataMigrationService.getData(
-                    history, it.attribute.filter { i -> i.value.toString().startsWith("history.") }
-                        .map { m -> m.value.toString().removePrefix("history.") })
-            } ?: mutableListOf()
-            migrationEntity.clazzEntity(it.table)?.let { clazz ->
-                queueInsertData.put(
-                    clazz to migrationEntity.executeWithDataSource(
-                        clazz,
-                        it.attribute as MutableMap<String, String>,
-                        rootTable,
-                        rootHistory,
-                        rootGroup
+        val migrationTarget = migrationSettingService.target ?: arrayOf()
+        val migrationSchema = migrationSettingService.schema.filter { migrationTarget.any { a -> a == it.target } }
+        rewriteMigrationYaml(migrationTarget.joinToString(";"))
+        migrationSchema.forEach {
+            try {
+                val rootTable = sourceDataMigrationService.getData(
+                    it.table, it.attribute.filterNot { i ->
+                        i.value.toString().startsWith("history.") || i.value.toString().startsWith("group.")
+                    }.map { m -> m.value.toString() }) ?: mutableListOf()
+                val rootGroup = it.group?.let { group ->
+                    sourceDataMigrationService.getData(
+                        group, it.attribute.filter { i -> i.value.toString().startsWith("group.") }
+                            .map { m -> m.value.toString().removePrefix("group.") })
+                } ?: mutableListOf()
+                val rootHistory = it.history?.let { history ->
+                    sourceDataMigrationService.getData(
+                        history, it.attribute.filter { i -> i.value.toString().startsWith("history.") }
+                            .map { m -> m.value.toString().removePrefix("history.") })
+                } ?: mutableListOf()
+                migrationEntity.clazzEntity(it.target)?.let { clazz ->
+                    migrationEntity.executeWithDataSource(
+                        clazz, it.attribute as MutableMap<String, String>, rootTable, rootHistory, rootGroup
                     )
-                )
+                }
+            } catch (e: Exception) {
+                logger.error("failed to migration data target ${it.target}", e)
             }
         }
     }
@@ -127,7 +131,9 @@ class MigrationService(
                 executorPrepareData.submit {
                     val filename = mi.fileName.toString()
                     val migrationTarget = filename.split("_")[2].replace(".txt", "")
-                    val fields = loadYamlAsMap(migrationTarget)
+                    val fields = migrationSettingService.schema.firstOrNull { fi ->
+                        fi.target == migrationTarget}?.attribute?.let { it as MutableMap<String, String> } ?: mutableMapOf()
+                    //loadYamlAsMap(migrationTarget)
 
                     if (fields.isNotEmpty()) {
                         val rootFile = renameToTemp(mi)
@@ -142,14 +148,12 @@ class MigrationService(
                             }?.let { addFile -> renameToTemp(addFile) }
                         } else null
                         migrationEntity.clazzEntity(migrationTarget)?.let { clazz ->
-                            queueInsertData.put(
-                                clazz to migrationEntity.execute(
-                                    clazz,
-                                    fields,
-                                    rootFile.toFile(),
-                                    historyFile?.toFile(),
-                                    groupFile?.toFile()
-                                )
+                            migrationEntity.execute(
+                                clazz,
+                                fields,
+                                rootFile.toFile(),
+                                historyFile?.toFile(),
+                                groupFile?.toFile()
                             )
                         }
                     }
@@ -159,15 +163,13 @@ class MigrationService(
     }
 
     fun startInsertWorker() {
-
         executorInsertData.submit {
             while (true) {
                 val data = queueInsertData.take()
                 try {
-                    logger.info("started process migration ${data.first.simpleName} data ${data.second.size}")
                     val process = measureTimedValue { migrationEntity.insertData(data.first, data.second) }
                     logger.info(
-                        "finished process migration ${data.first.simpleName} " +
+                        "add data migration ${data.first.simpleName} " +
                                 "data ${data.second.size} " +
                                 "success ${data.second.size - process.value.size} " +
                                 "failed ${process.value.size} " +
@@ -192,4 +194,21 @@ class MigrationService(
             .mapValues { it.value.toString() }
     }
 
+    private fun rewriteMigrationYaml(lastMigration: String) {
+        val options = DumperOptions()
+        options.defaultFlowStyle = DumperOptions.FlowStyle.BLOCK
+        options.isPrettyFlow = true
+        val yaml: Yaml = Yaml(options)
+        val obj: MutableMap<String?, Any?> = yaml.load(FileInputStream(MIGRATION_CONFIG))
+
+        val migration = obj["migration"] as MutableMap<String?, Any?>
+        migration["target"] = null
+        migration["lastTarget"] = lastMigration
+        migration["lastAction"] = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+
+        val writer = FileWriter(MIGRATION_CONFIG)
+        yaml.dump(obj, writer)
+        writer.close()
+    }
+
 }

+ 2 - 0
src/main/kotlin/com/datacomsolusindo/migration/MigrationSetting.kt

@@ -8,10 +8,12 @@ import org.springframework.stereotype.Service
 class MigrationSettingService {
     var type: String = "FILE"
     var sourceDatabase: MutableMap<String, Any> = mutableMapOf()
+    var target: Array<String>? = arrayOf()
     var schema: Array<MigrationSchema> = arrayOf()
 }
 
 class MigrationSchema(
+    val target: String,
     val table: String,
     val history: String? = null,
     val group: String? = null,