From 7677bab363da7def2a63aba7e4c91e49be261ff2 Mon Sep 17 00:00:00 2001 From: AiKrai Date: Thu, 20 Mar 2025 11:53:57 +0800 Subject: [PATCH] 1 --- .../src/test/kotlin/app/GenerateMigration.kt | 85 +++++---- .../aikrai/vertx/db/migration/DbMigration.kt | 114 +++++++++++- .../vertx/db/migration/DefaultDbMigration.kt | 162 ++++++++++++++++++ .../db/migration/SqlMigrationGenerator.kt | 81 +++++++-- 4 files changed, 394 insertions(+), 48 deletions(-) create mode 100644 vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt diff --git a/vertx-demo/src/test/kotlin/app/GenerateMigration.kt b/vertx-demo/src/test/kotlin/app/GenerateMigration.kt index cacb6a1..289633d 100644 --- a/vertx-demo/src/test/kotlin/app/GenerateMigration.kt +++ b/vertx-demo/src/test/kotlin/app/GenerateMigration.kt @@ -5,21 +5,49 @@ import org.aikrai.vertx.db.annotation.TableId import org.aikrai.vertx.db.annotation.TableName import org.aikrai.vertx.db.migration.AnnotationMapping import org.aikrai.vertx.db.migration.ColumnMapping +import org.aikrai.vertx.db.migration.DbMigration import org.aikrai.vertx.db.migration.SqlAnnotationMapper -import org.aikrai.vertx.db.migration.SqlMigrationGenerator +/** + * PostgreSQL数据库迁移生成工具 + * 参考了ebean框架的设计 + */ object GenerateMigration { + /** + * 生成数据库迁移脚本 + * 使用示例: + * + * ``` + * // 创建SQL注解映射器 + * val mapper = createSqlAnnotationMapper() + * + * // 设置迁移生成器 + * val dbMigration = DbMigration.create() + * dbMigration.setEntityPackage("org.aikrai.vertx.entity") + * dbMigration.setSqlAnnotationMapper(mapper) + * dbMigration.generateMigration() + * ``` + */ @JvmStatic fun main(args: Array) { - val entityPackage = "app.data.domain" + // 创建SQL注解映射器 + val mapper = createSqlAnnotationMapper() - migrationGeneration(entityPackage) + // 设置迁移生成器 + val dbMigration = DbMigration.create() + dbMigration.setEntityPackage("org.aikrai.vertx.entity") // 替换为你的实体类包路径 + dbMigration.setSqlAnnotationMapper(mapper) + + // 生成迁移 + val migrationVersion = dbMigration.generateMigration() + println("生成的迁移版本: $migrationVersion") } /** - * 创建注解映射器 + * 创建SQL注解映射器 + * 根据项目中使用的注解配置映射关系 */ - private fun createMapper(): SqlAnnotationMapper { + private fun createSqlAnnotationMapper(): SqlAnnotationMapper { val mapper = SqlAnnotationMapper() // 设置表名映射 @@ -28,27 +56,27 @@ object GenerateMigration { propertyName = "value" ) - // 设置列名映射 + // 设置列映射 mapper.addColumnMapping( ColumnMapping( - nameMapping = AnnotationMapping( - annotationClass = TableField::class, - propertyName = "value" - ), - typeMapping = AnnotationMapping( - annotationClass = TableField::class, - propertyName = "type" - ), - nullableMapping = AnnotationMapping( - annotationClass = TableField::class, - propertyName = "nullable" - ), - defaultValueMapping = AnnotationMapping( - annotationClass = TableField::class, - propertyName = "default" - ) + nameMapping = AnnotationMapping( + annotationClass = TableField::class, + propertyName = "name" + ), + typeMapping = AnnotationMapping( + annotationClass = TableField::class, + propertyName = "type" + ), + nullableMapping = AnnotationMapping( + annotationClass = TableField::class, + propertyName = "nullable" + ), + defaultValueMapping = AnnotationMapping( + annotationClass = TableField::class, + propertyName = "defaultValue" ) ) + ) // 设置主键映射 mapper.primaryKeyMapping = AnnotationMapping( @@ -58,17 +86,4 @@ object GenerateMigration { return mapper } - - /** - * 测试数据库迁移生成 - */ - private fun migrationGeneration(entityPackage: String) { - // 创建注解映射器 - val mapper = createMapper() - - // 生成迁移文件 - SqlMigrationGenerator.generateMigrations(entityPackage, mapper) - - println("数据库迁移文件生成完成") - } } \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DbMigration.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DbMigration.kt index 53fdce1..dbc86b6 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DbMigration.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DbMigration.kt @@ -1,4 +1,116 @@ package org.aikrai.vertx.db.migration -class DbMigration { +import java.io.File +import java.io.IOException +import kotlin.reflect.KClass + +/** + * 生成PostgreSQL DDL迁移脚本,基于实体类及其注解的变更。 + * + *

+ * 通常在开发人员对模型进行了一组更改后,作为测试阶段的主要方法运行。 + *

+ * + *

示例: 运行生成PostgreSQL迁移脚本

+ * + *
{@code
+ *
+ *    val migration = DbMigration.create()
+ *
+ *    // 可选:指定版本和名称
+ *    migration.setName("添加用户表索引")
+ *
+ *    // 设置实体包路径
+ *    migration.setEntityPackage("org.aikrai.vertx.entity")
+ *
+ *    // 设置SQL注解映射器
+ *    migration.setSqlAnnotationMapper(createMapper())
+ *
+ *    // 生成迁移
+ *    migration.generateMigration()
+ *
+ * }
+ */ +interface DbMigration { + + companion object { + /** + * 创建DbMigration实现实例 + */ + fun create(): DbMigration { + return DefaultDbMigration() + } + } + + /** + * 设置实体类所在的包路径 + */ + fun setEntityPackage(packagePath: String) + + /** + * 设置SQL注解映射器 + */ + fun setSqlAnnotationMapper(mapper: SqlAnnotationMapper) + + /** + * 设置资源文件路径 + *

+ * 默认为Maven风格的'src/main/resources' + */ + fun setPathToResources(pathToResources: String) + + /** + * 设置迁移文件生成的路径(默认为"dbmigration") + */ + fun setMigrationPath(migrationPath: String) + + /** + * 设置模型文件生成的路径(默认为"model") + */ + fun setModelPath(modelPath: String) + + /** + * 设置模型文件后缀(默认为".model.xml") + */ + fun setModelSuffix(modelSuffix: String) + + /** + * 设置迁移的版本号 + */ + fun setVersion(version: String) + + /** + * 设置迁移的名称 + */ + fun setName(name: String) + + /** + * 设置是否输出日志到控制台(默认为true) + */ + fun setLogToSystemOut(logToSystemOut: Boolean) + + /** + * 生成下一次迁移SQL脚本和相关模型XML + *

+ * 不会实际运行迁移或DDL脚本,只是生成它们。 + *

+ * + * @return 生成的迁移版本或null(如果没有变更) + */ + fun generateMigration(): String? + + /** + * 生成包含所有变更的"初始"迁移 + *

+ * "初始"迁移只能在尚未对其运行任何先前迁移的数据库上执行和使用。 + *

+ * + * @return 生成的迁移版本 + */ + fun generateInitMigration(): String? + + /** + * 返回迁移主目录 + */ + fun migrationDirectory(): File } \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt new file mode 100644 index 0000000..589ca9a --- /dev/null +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt @@ -0,0 +1,162 @@ +package org.aikrai.vertx.db.migration + +import java.io.File +import java.io.IOException + +/** + * PostgreSQL数据库迁移工具的默认实现 + */ +class DefaultDbMigration : DbMigration { + private var entityPackage: String = "" + private var sqlAnnotationMapper: SqlAnnotationMapper? = null + private var pathToResources: String = "src/main/resources" + private var migrationPath: String = "dbmigration" + private var modelPath: String = "model" + private var modelSuffix: String = ".model.xml" + private var version: String? = null + private var name: String? = null + private var logToSystemOut: Boolean = true + + override fun setEntityPackage(packagePath: String) { + this.entityPackage = packagePath + } + + override fun setSqlAnnotationMapper(mapper: SqlAnnotationMapper) { + this.sqlAnnotationMapper = mapper + } + + override fun setPathToResources(pathToResources: String) { + this.pathToResources = pathToResources + } + + override fun setMigrationPath(migrationPath: String) { + this.migrationPath = migrationPath + } + + override fun setModelPath(modelPath: String) { + this.modelPath = modelPath + } + + override fun setModelSuffix(modelSuffix: String) { + this.modelSuffix = modelSuffix + } + + override fun setVersion(version: String) { + this.version = version + } + + override fun setName(name: String) { + this.name = name + } + + override fun setLogToSystemOut(logToSystemOut: Boolean) { + this.logToSystemOut = logToSystemOut + } + + override fun generateMigration(): String? { + validateConfiguration() + + configureMigrationGenerator() + + // 将版本和名称设置到系统属性中,以便SqlMigrationGenerator能够读取 + if (version != null) { + System.setProperty("ddl.migration.version", version!!) + } + if (name != null) { + System.setProperty("ddl.migration.name", name!!) + } + + try { + SqlMigrationGenerator.generateMigrations(entityPackage, sqlAnnotationMapper!!) + return version + } catch (e: Exception) { + if (logToSystemOut) { + println("生成迁移失败: ${e.message}") + e.printStackTrace() + } + throw e + } finally { + // 清理系统属性 + if (version != null) { + System.clearProperty("ddl.migration.version") + } + if (name != null) { + System.clearProperty("ddl.migration.name") + } + } + } + + override fun generateInitMigration(): String? { + validateConfiguration() + + configureMigrationGenerator() + + // 将版本和名称设置到系统属性中,以便SqlMigrationGenerator能够读取 + if (version != null) { + System.setProperty("ddl.migration.version", version!!) + } + if (name != null) { + System.setProperty("ddl.migration.name", name!!) + } + + try { + // 修改目录结构,强制生成初始迁移 + val modelDir = File("${pathToResources}/${migrationPath}/${modelPath}") + if (modelDir.exists()) { + // 备份原有文件 + val backupDir = File("${pathToResources}/${migrationPath}/${modelPath}_backup_${System.currentTimeMillis()}") + modelDir.renameTo(backupDir) + if (logToSystemOut) { + println("已将现有模型文件备份到: ${backupDir.absolutePath}") + } + } + + // 生成初始迁移 + SqlMigrationGenerator.generateMigrations(entityPackage, sqlAnnotationMapper!!) + return version + } catch (e: Exception) { + if (logToSystemOut) { + println("生成初始迁移失败: ${e.message}") + e.printStackTrace() + } + throw e + } finally { + // 清理系统属性 + if (version != null) { + System.clearProperty("ddl.migration.version") + } + if (name != null) { + System.clearProperty("ddl.migration.name") + } + } + } + + override fun migrationDirectory(): File { + return File("${pathToResources}/${migrationPath}") + } + + /** + * 验证配置,确保必要的配置项已经设置 + */ + private fun validateConfiguration() { + if (entityPackage.isEmpty()) { + throw IllegalStateException("实体包路径未设置,请调用setEntityPackage()") + } + + if (sqlAnnotationMapper == null) { + throw IllegalStateException("SQL注解映射器未设置,请调用setSqlAnnotationMapper()") + } + } + + /** + * 配置迁移生成器 + */ + private fun configureMigrationGenerator() { + // 设置静态字段,以便SqlMigrationGenerator能够使用配置的路径 + SqlMigrationGenerator.setResourcePath(pathToResources) + SqlMigrationGenerator.setMigrationPath("${pathToResources}/${migrationPath}") + SqlMigrationGenerator.setModelPath("${pathToResources}/${migrationPath}/${modelPath}") + SqlMigrationGenerator.setModelSuffix(modelSuffix) + SqlMigrationGenerator.setLogToSystemOut(logToSystemOut) + } +} \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt index 775fe90..63a993f 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt @@ -23,11 +23,68 @@ import kotlin.reflect.KClass */ class SqlMigrationGenerator { companion object { - private const val RESOURCE_PATH = "src/main/resources" - private const val MIGRATION_PATH = "$RESOURCE_PATH/dbmigration" - private const val MODEL_PATH = "$MIGRATION_PATH/model" + private var RESOURCE_PATH = "src/main/resources" + private var MIGRATION_PATH = "$RESOURCE_PATH/dbmigration" + private var MODEL_PATH = "$MIGRATION_PATH/model" + private var MODEL_SUFFIX = ".model.xml" private const val INITIAL_VERSION = "1.0" private const val INITIAL_SUFFIX = "__initial" + private var LOG_TO_SYSTEM_OUT = true + + /** + * 设置资源目录路径 + */ + fun setResourcePath(path: String) { + RESOURCE_PATH = path + // 更新依赖于资源路径的其他路径 + if (!MIGRATION_PATH.startsWith(path)) { + MIGRATION_PATH = "$path/dbmigration" + } + if (!MODEL_PATH.startsWith(MIGRATION_PATH)) { + MODEL_PATH = "$MIGRATION_PATH/model" + } + } + + /** + * 设置迁移目录路径 + */ + fun setMigrationPath(path: String) { + MIGRATION_PATH = path + // 更新依赖于迁移路径的模型路径 + if (!MODEL_PATH.startsWith(path)) { + MODEL_PATH = "$path/model" + } + } + + /** + * 设置模型目录路径 + */ + fun setModelPath(path: String) { + MODEL_PATH = path + } + + /** + * 设置模型文件后缀 + */ + fun setModelSuffix(suffix: String) { + MODEL_SUFFIX = suffix + } + + /** + * 设置是否输出日志到控制台 + */ + fun setLogToSystemOut(log: Boolean) { + LOG_TO_SYSTEM_OUT = log + } + + /** + * 记录日志信息 + */ + private fun log(message: String) { + if (LOG_TO_SYSTEM_OUT) { + println("DbMigration> $message") + } + } /** * 生成数据库迁移脚本 @@ -230,11 +287,11 @@ class SqlMigrationGenerator { */ private fun findNextVersionNumber(baseVersion: String): String { // 打印基础版本,便于调试 - println("查找下一个版本号,基础版本: $baseVersion") + log("查找下一个版本号,基础版本: $baseVersion") val parts = baseVersion.split(".") if (parts.size != 2) { - println("无效的基础版本格式,使用默认值 1.1") + log("无效的基础版本格式,使用默认值 1.1") return "1.1" } @@ -252,11 +309,11 @@ class SqlMigrationGenerator { val modelFiles = modelDir.listFiles() if (modelFiles != null) { for (file in modelFiles) { - if (file.isFile && file.name.endsWith(".model.xml")) { + if (file.isFile && file.name.endsWith(MODEL_SUFFIX)) { val fileVersion = extractVersionFromFileName(file.name) if (!fileVersion.contains("__")) { // 排除初始版本 existingVersions.add(fileVersion) - println("发现模型文件版本: $fileVersion (${file.name})") + log("发现模型文件版本: $fileVersion (${file.name})") } } } @@ -269,7 +326,7 @@ class SqlMigrationGenerator { if (file.isFile && file.name.endsWith(".sql") && !file.name.contains("__")) { val fileVersion = extractVersionFromFileName(file.name) existingVersions.add(fileVersion) - println("发现SQL文件版本: $fileVersion (${file.name})") + log("发现SQL文件版本: $fileVersion (${file.name})") } } } @@ -288,11 +345,11 @@ class SqlMigrationGenerator { if (vMajor > maxMajor || (vMajor == maxMajor && vMinor > maxMinor)) { maxMajor = vMajor maxMinor = vMinor - println("更新最大版本: $vMajor.$vMinor") + log("更新最大版本: $vMajor.$vMinor") } } } catch (e: NumberFormatException) { - println("警告: 版本号 '$version' 中包含非数字部分,忽略") + log("警告: 版本号 '$version' 中包含非数字部分,忽略") } } @@ -308,14 +365,14 @@ class SqlMigrationGenerator { val sqlFileExists = File("$MIGRATION_PATH/$candidateVersion.sql").exists() if (modelFileExists || sqlFileExists) { - println("版本号 $candidateVersion 已存在文件,尝试下一个版本") + log("版本号 $candidateVersion 已存在文件,尝试下一个版本") candidateMinor++ } else { break } } while (true) - println("确定下一个版本号: $candidateVersion") + log("确定下一个版本号: $candidateVersion") return candidateVersion }