1
This commit is contained in:
parent
eaeca63f31
commit
7677bab363
@ -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,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("数据库迁移文件生成完成")
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user