This commit is contained in:
AiKrai 2025-03-20 11:53:57 +08:00
parent eaeca63f31
commit 7677bab363
4 changed files with 394 additions and 48 deletions

View File

@ -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<String>) {
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,12 +56,12 @@ object GenerateMigration {
propertyName = "value"
)
// 设置列映射
// 设置列映射
mapper.addColumnMapping(
ColumnMapping(
nameMapping = AnnotationMapping(
annotationClass = TableField::class,
propertyName = "value"
propertyName = "name"
),
typeMapping = AnnotationMapping(
annotationClass = TableField::class,
@ -45,7 +73,7 @@ object GenerateMigration {
),
defaultValueMapping = AnnotationMapping(
annotationClass = TableField::class,
propertyName = "default"
propertyName = "defaultValue"
)
)
)
@ -58,17 +86,4 @@ object GenerateMigration {
return mapper
}
/**
* 测试数据库迁移生成
*/
private fun migrationGeneration(entityPackage: String) {
// 创建注解映射器
val mapper = createMapper()
// 生成迁移文件
SqlMigrationGenerator.generateMigrations(entityPackage, mapper)
println("数据库迁移文件生成完成")
}
}

View File

@ -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迁移脚本基于实体类及其注解的变更
*
* <p>
* 通常在开发人员对模型进行了一组更改后作为测试阶段的主要方法运行
* </p>
*
* <h3>示例: 运行生成PostgreSQL迁移脚本</h3>
*
* <pre>{@code
*
* val migration = DbMigration.create()
*
* // 可选:指定版本和名称
* migration.setName("添加用户表索引")
*
* // 设置实体包路径
* migration.setEntityPackage("org.aikrai.vertx.entity")
*
* // 设置SQL注解映射器
* migration.setSqlAnnotationMapper(createMapper())
*
* // 生成迁移
* migration.generateMigration()
*
* }</pre>
*/
interface DbMigration {
companion object {
/**
* 创建DbMigration实现实例
*/
fun create(): DbMigration {
return DefaultDbMigration()
}
}
/**
* 设置实体类所在的包路径
*/
fun setEntityPackage(packagePath: String)
/**
* 设置SQL注解映射器
*/
fun setSqlAnnotationMapper(mapper: SqlAnnotationMapper)
/**
* 设置资源文件路径
* <p>
* 默认为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
* <p>
* 不会实际运行迁移或DDL脚本只是生成它们
* </p>
*
* @return 生成的迁移版本或null如果没有变更
*/
fun generateMigration(): String?
/**
* 生成包含所有变更的"初始"迁移
* <p>
* "初始"迁移只能在尚未对其运行任何先前迁移的数据库上执行和使用
* </p>
*
* @return 生成的迁移版本
*/
fun generateInitMigration(): String?
/**
* 返回迁移主目录
*/
fun migrationDirectory(): File
}

View File

@ -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)
}
}

View File

@ -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
}