This commit is contained in:
AiKrai 2025-03-20 15:26:08 +08:00
parent 7677bab363
commit cc415e82d0
5 changed files with 518 additions and 116 deletions

View File

@ -30,17 +30,22 @@ object GenerateMigration {
*/
@JvmStatic
fun main(args: Array<String>) {
try {
// 创建SQL注解映射器
val mapper = createSqlAnnotationMapper()
// 设置迁移生成器
val dbMigration = DbMigration.create()
dbMigration.setEntityPackage("org.aikrai.vertx.entity") // 替换为你的实体类包路径
dbMigration.setEntityPackage("app.data.domain") // 指定实体类包路径
dbMigration.setSqlAnnotationMapper(mapper)
// 生成迁移
val migrationVersion = dbMigration.generateMigration()
println("生成的迁移版本: $migrationVersion")
} catch (e: Exception) {
println("生成迁移失败: ${e.message}")
e.printStackTrace()
}
}
/**
@ -61,7 +66,7 @@ object GenerateMigration {
ColumnMapping(
nameMapping = AnnotationMapping(
annotationClass = TableField::class,
propertyName = "name"
propertyName = "value"
),
typeMapping = AnnotationMapping(
annotationClass = TableField::class,
@ -73,7 +78,7 @@ object GenerateMigration {
),
defaultValueMapping = AnnotationMapping(
annotationClass = TableField::class,
propertyName = "defaultValue"
propertyName = "default"
)
)
)

View File

@ -46,7 +46,9 @@ annotation class TableField(
// val property: String = "",
// val numericScale: String = ""
val type: String = "",
val nullable: Boolean = false,
val length: Int = 255,
val nullable: Boolean = true,
val unique: Boolean = false,
val default: String = ""
)

View File

@ -1,5 +1,6 @@
package org.aikrai.vertx.db.migration
import java.time.LocalDateTime
import kotlin.reflect.KClass
/**
@ -84,84 +85,343 @@ class SqlAnnotationMapperGenerator {
val sqlInfo = SqlInfo()
// 获取表名
mapper.tableName?.let { tableNameMapping ->
val annotation = entityClass.annotations.find { it.annotationClass.toString() == tableNameMapping.annotationClass.toString() }
annotation?.let {
val method = it.javaClass.getMethod(tableNameMapping.propertyName)
sqlInfo.tableName = method.invoke(it) as String
if (mapper.tableName == null) {
throw IllegalArgumentException("表名映射未设置请检查SqlAnnotationMapper中的tableName配置")
}
try {
val tableNameMapping = mapper.tableName!!
val annotation = entityClass.annotations.find {
it.annotationClass.qualifiedName == tableNameMapping.annotationClass.qualifiedName
}
if (annotation == null) {
throw IllegalArgumentException("实体类 ${entityClass.simpleName} 未标记 @${tableNameMapping.annotationClass.simpleName} 注解")
}
try {
val method = annotation.javaClass.getMethod(tableNameMapping.propertyName)
sqlInfo.tableName = method.invoke(annotation) as String
if (sqlInfo.tableName.isEmpty()) {
throw IllegalArgumentException("实体类 ${entityClass.simpleName} 的表名为空,请检查 @${tableNameMapping.annotationClass.simpleName} 注解的 ${tableNameMapping.propertyName} 属性")
}
} catch (e: Exception) {
throw IllegalArgumentException("获取 ${entityClass.simpleName} 的表名失败: ${e.message}", e)
}
} catch (e: Exception) {
throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的表名时出错: ${e.message}", e)
}
// 获取实体类的所有字段
entityClass.java.declaredFields.forEach { field ->
val fields = entityClass.java.declaredFields
if (fields.isEmpty()) {
throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有声明任何字段")
}
// 创建已处理字段名集合,用于记录已经处理过的字段
val processedFields = mutableSetOf<String>()
// 查找实体类中的@Transient注解类
val transientAnnotationClasses = listOf(
"kotlin.jvm.Transient",
"javax.persistence.Transient",
"jakarta.persistence.Transient",
"java.beans.Transient",
"org.aikrai.vertx.db.annotation.Transient"
)
fields.forEach { field ->
try {
// 检查字段是否有@Transient注解如果有则跳过
val isTransient = field.annotations.any { annotation ->
transientAnnotationClasses.any { className ->
annotation.annotationClass.qualifiedName == className
}
}
if (isTransient) {
return@forEach
}
// 获取列信息
val columnInfo = ColumnInfo()
var foundColumnMapping = false
// 处理每个列映射
// 处理每个列映射 - 这部分处理带有特定注解的字段
mapper.columnMappings.forEach { columnMapping ->
val nameAnnotation = field.annotations.find {
it.annotationClass.java == columnMapping.nameMapping.annotationClass.java
it.annotationClass.qualifiedName == columnMapping.nameMapping.annotationClass.qualifiedName
}
if (nameAnnotation != null) {
foundColumnMapping = true
try {
val nameMethod = nameAnnotation.javaClass.getMethod(columnMapping.nameMapping.propertyName)
columnInfo.name = nameMethod.invoke(nameAnnotation) as String? ?: field.name
val columnName = nameMethod.invoke(nameAnnotation) as? String
columnInfo.name = if (columnName.isNullOrEmpty()) toSnakeCase(field.name) else columnName
// 处理类型映射
columnMapping.typeMapping?.let { typeMapping ->
val typeAnnotation = field.annotations.find {
it.annotationClass.java == typeMapping.annotationClass.java
it.annotationClass.qualifiedName == typeMapping.annotationClass.qualifiedName
}
if (typeAnnotation != null) {
try {
val typeMethod = typeAnnotation.javaClass.getMethod(typeMapping.propertyName)
columnInfo.type = typeMethod.invoke(typeAnnotation) as String
val typeName = typeMethod.invoke(typeAnnotation) as String
columnInfo.type = if (typeName.isEmpty()) {
inferSqlType(field.type, columnInfo)
} else {
typeName
}
} catch (e: Exception) {
throw IllegalArgumentException("处理字段 ${field.name} 的类型映射时出错: ${e.message}", e)
}
} else {
columnInfo.type = inferSqlType(field.type, columnInfo)
}
}
// 处理可空映射
columnMapping.nullableMapping?.let { nullableMapping ->
val nullableAnnotation = field.annotations.find {
it.annotationClass.java == nullableMapping.annotationClass.java
it.annotationClass.qualifiedName == nullableMapping.annotationClass.qualifiedName
}
if (nullableAnnotation != null) {
try {
val nullableMethod = nullableAnnotation.javaClass.getMethod(nullableMapping.propertyName)
columnInfo.nullable = nullableMethod.invoke(nullableAnnotation) as Boolean
} catch (e: Exception) {
throw IllegalArgumentException("处理字段 ${field.name} 的可空性映射时出错: ${e.message}", e)
}
}
}
// 处理默认值映射
columnMapping.defaultValueMapping?.let { defaultValueMapping ->
val defaultValueAnnotation = field.annotations.find {
it.annotationClass.java == defaultValueMapping.annotationClass.java
it.annotationClass.qualifiedName == defaultValueMapping.annotationClass.qualifiedName
}
if (defaultValueAnnotation != null) {
try {
val defaultValueMethod = defaultValueAnnotation.javaClass.getMethod(defaultValueMapping.propertyName)
columnInfo.defaultValue = defaultValueMethod.invoke(defaultValueAnnotation) as String
} catch (e: Exception) {
throw IllegalArgumentException("处理字段 ${field.name} 的默认值映射时出错: ${e.message}", e)
}
}
}
sqlInfo.columns.add(columnInfo)
processedFields.add(field.name) // 记录已处理的字段
} catch (e: Exception) {
throw IllegalArgumentException("处理字段 ${field.name} 时出错: ${e.message}", e)
}
}
}
// 处理主键
mapper.primaryKeyMapping?.let { pkMapping ->
if (mapper.primaryKeyMapping != null) {
val pkMapping = mapper.primaryKeyMapping!!
val pkAnnotation = field.annotations.find {
it.annotationClass.java == pkMapping.annotationClass.java
it.annotationClass.qualifiedName == pkMapping.annotationClass.qualifiedName
}
if (pkAnnotation != null) {
try {
val pkMethod = pkAnnotation.javaClass.getMethod(pkMapping.propertyName)
val isPk = pkMethod.invoke(pkAnnotation)
if (isPk is Boolean && isPk) {
// 如果字段还未处理,创建默认列信息
if (!processedFields.contains(field.name)) {
columnInfo.name = toSnakeCase(field.name)
columnInfo.type = inferSqlType(field.type, columnInfo)
columnInfo.isPrimaryKey = true
sqlInfo.columns.add(columnInfo)
sqlInfo.primaryKeys.add(columnInfo.name)
processedFields.add(field.name)
} else {
// 如果已处理,找到对应列并标记为主键
val column = sqlInfo.columns.find { it.name == toSnakeCase(field.name) || it.name == field.name }
if (column != null) {
column.isPrimaryKey = true
if (!sqlInfo.primaryKeys.contains(column.name)) {
sqlInfo.primaryKeys.add(column.name)
}
}
}
}
} catch (e: Exception) {
throw IllegalArgumentException("处理字段 ${field.name} 的主键映射时出错: ${e.message}", e)
}
}
}
// 如果字段未被处理并且不是static或transient添加默认处理
if (!processedFields.contains(field.name) &&
!java.lang.reflect.Modifier.isStatic(field.modifiers) &&
!java.lang.reflect.Modifier.isTransient(field.modifiers)) {
// 检查字段类型是否可空
val isNullable = isNullableType(field)
// 创建默认列信息
val defaultColumnInfo = ColumnInfo(
name = toSnakeCase(field.name),
type = "", // 先不设置类型
nullable = isNullable,
defaultValue = "",
isPrimaryKey = false
)
// 设置类型并处理枚举值
defaultColumnInfo.type = inferSqlType(field.type, defaultColumnInfo)
sqlInfo.columns.add(defaultColumnInfo)
processedFields.add(field.name)
}
} catch (e: Exception) {
throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的字段 ${field.name} 时出错: ${e.message}", e)
}
}
// 验证结果
if (sqlInfo.tableName.isEmpty()) {
throw IllegalArgumentException("实体类 ${entityClass.simpleName} 的表名为空,请检查表名注解")
}
if (sqlInfo.columns.isEmpty()) {
throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有可用的列信息,请检查列注解")
}
return sqlInfo
}
/**
* 将驼峰命名转换为蛇形命名
*/
private fun toSnakeCase(camelCase: String): String {
return camelCase.replace(Regex("([a-z])([A-Z])"), "$1_$2").toLowerCase()
}
/**
* 根据Java类型推断SQL类型
*/
private fun inferSqlType(javaType: Class<*>, columnInfo: ColumnInfo? = null): String {
val sqlType = when {
javaType == String::class.java -> "VARCHAR(255)"
javaType == Int::class.java || javaType == Integer::class.java -> "INTEGER"
javaType == Long::class.java || javaType == java.lang.Long::class.java -> "BIGINT"
javaType == Double::class.java || javaType == java.lang.Double::class.java -> "DOUBLE PRECISION"
javaType == Float::class.java || javaType == java.lang.Float::class.java -> "REAL"
javaType == Boolean::class.java || javaType == java.lang.Boolean::class.java -> "BOOLEAN"
javaType == Char::class.java || javaType == Character::class.java -> "CHAR(1)"
javaType == java.util.Date::class.java || javaType == java.sql.Date::class.java -> "DATE"
javaType == java.sql.Timestamp::class.java -> "TIMESTAMPTZ"
javaType == LocalDateTime::class.java -> "TIMESTAMPTZ"
javaType == ByteArray::class.java -> "BYTEA"
javaType.name.contains("Map") || javaType.name.contains("HashMap") -> "JSONB"
javaType.name.contains("List") || javaType.name.contains("ArrayList") -> "JSONB"
javaType.name.contains("Set") || javaType.name.contains("HashSet") -> "JSONB"
javaType.name.endsWith("DTO") || javaType.name.endsWith("Dto") -> "JSONB"
javaType.name.contains("Json") || javaType.name.contains("JSON") -> "JSONB"
javaType.isEnum -> {
// 处理枚举类型提取枚举值并保存到columnInfo中
if (columnInfo != null) {
try {
// 获取枚举类中的所有枚举常量
val enumConstants = javaType.enumConstants
if (enumConstants != null && enumConstants.isNotEmpty()) {
// 提取枚举常量的名称
val enumValues = enumConstants.map { it.toString() }
columnInfo.enumValues = enumValues
}
} catch (e: Exception) {
// 忽略枚举值提取失败的情况
}
}
"VARCHAR(50)"
}
else -> "VARCHAR(255)"
}
return sqlType
}
/**
* 判断字段类型是否可空
*/
private fun isNullableType(field: java.lang.reflect.Field): Boolean {
// 检查字段类型名称中是否包含Nullable标记
val typeName = field.genericType.typeName
// 1. 先检查字段类型名是否包含"?"这是Kotlin可空类型的标志
if (typeName.contains("?")) {
return true
}
// 2. 检查字段是否为Java原始类型这些类型不可为空
if (field.type.isPrimitive) {
return false
}
// 3. 通过Java反射获取字段的声明可空性
try {
// 检查是否有@Nullable相关注解
val hasNullableAnnotation = field.annotations.any {
val name = it.annotationClass.qualifiedName ?: ""
name.contains("Nullable") || name.contains("nullable")
}
if (hasNullableAnnotation) {
return true
}
} catch (e: Exception) {
// 忽略注解检查错误
}
// 4. 检查字段的类型并判断其可空性
// Kotlin的String类型不可为空而Java的String类型可为空
if (field.type == String::class.java) {
// 尝试通过字段的初始值判断
try {
field.isAccessible = true
// 如果是非静态字段且初始值为null则可能为可空类型
if (!java.lang.reflect.Modifier.isStatic(field.modifiers)) {
// 对于具有初始值的非静态字段如果初始值为非null字符串则认为是非可空类型
// 如果字段名以OrNull或Optional结尾认为是可空类型
if (field.name.endsWith("OrNull") || field.name.endsWith("Optional")) {
return true
}
// 对于Kotlin中的非空String类型如果有初始值"",则不可为空
// 检查是否为Kotlin类型
val isKotlinType = typeName.startsWith("kotlin.")
if (isKotlinType) {
return false // Kotlin中的String类型不可为空
}
}
} catch (e: Exception) {
// 忽略访问字段值的错误
}
}
// 5. 检查字段是否为其他Kotlin基本类型且非可空
if (field.type == Int::class.java ||
field.type == Long::class.java ||
field.type == Boolean::class.java ||
field.type == Float::class.java ||
field.type == Double::class.java ||
field.type == Char::class.java ||
field.type == Byte::class.java ||
field.type == Short::class.java) {
// Kotlin基本类型不带?就不可为空
return false
}
// 6. 默认情况: 引用类型默认认为是可空的
return true
}
}
}
@ -183,5 +443,6 @@ data class ColumnInfo(
var type: String = "",
var nullable: Boolean = true,
var defaultValue: String = "",
var isPrimaryKey: Boolean = false
var isPrimaryKey: Boolean = false,
var enumValues: List<String>? = null
)

View File

@ -23,26 +23,87 @@ class SqlGenerator {
}
val sb = StringBuilder()
sb.append("CREATE TABLE IF NOT EXISTS $tableName (\n")
sb.append("CREATE TABLE $tableName (\n")
// 添加列定义
// 添加列定义 - 改为ANSI标准格式
val columnDefinitions = columns.map { column ->
// 特殊处理一些常见约定字段
val specialFieldDefaults = getSpecialFieldDefaults(column.name, column.type)
val defaultValue = if (column.defaultValue.isNotEmpty()) {
// 对于时间戳类型字段特殊处理NOW()函数作为默认值
if (column.type.contains("TIMESTAMP") && column.defaultValue.equals("now()", ignoreCase = true)) {
" DEFAULT 'now()'"
} else {
" DEFAULT ${column.defaultValue}"
}
} else if (specialFieldDefaults.isNotEmpty()) {
specialFieldDefaults
} else {
// 为一些常见类型提供合理的默认值
when {
column.type.contains("VARCHAR") -> " DEFAULT ''"
column.type == "INTEGER" || column.type == "BIGINT" -> " DEFAULT 0"
column.type == "BOOLEAN" -> " DEFAULT false"
column.type.contains("JSON") -> " DEFAULT '{}'"
else -> ""
}
}
val nullable = if (column.nullable) "" else " NOT NULL"
val defaultValue = if (column.defaultValue.isNotEmpty()) " DEFAULT ${column.defaultValue}" else ""
" ${column.name} ${column.type}$nullable$defaultValue"
" ${column.name} ${column.type}$defaultValue$nullable"
}
sb.append(columnDefinitions.joinToString(",\n"))
// 添加主键
// 添加枚举约束 - 识别枚举类型的列并添加CHECK约束
val enumColumns = columns.filter { it.type.contains("VARCHAR") && it.enumValues != null && it.enumValues!!.isNotEmpty() }
for (column in enumColumns) {
if (column.enumValues != null && column.enumValues!!.isNotEmpty()) {
sb.append(",\n CONSTRAINT ck_${tableName}_${column.name} CHECK ( ${column.name} in (")
sb.append(column.enumValues!!.joinToString(",") { "'$it'" })
sb.append("))")
}
}
// 添加主键约束
if (sqlInfo.primaryKeys.isNotEmpty()) {
sb.append(",\n PRIMARY KEY (${sqlInfo.primaryKeys.joinToString(", ")})")
sb.append(",\n CONSTRAINT pk_$tableName PRIMARY KEY (${sqlInfo.primaryKeys.joinToString(", ")})")
}
sb.append("\n);")
return sb.toString()
}
/**
* 获取特殊字段的默认值定义
*/
private fun getSpecialFieldDefaults(fieldName: String, fieldType: String): String {
return when {
// 版本字段
fieldName == "version" && (fieldType == "INTEGER" || fieldType == "BIGINT") ->
" DEFAULT 0"
// 创建时间字段
fieldName == "created" || fieldName == "create_time" || fieldName == "creation_time" || fieldName == "created_at" -> {
if (fieldType.contains("TIMESTAMP")) " DEFAULT 'now()'" else ""
}
// 更新时间字段
fieldName == "updated" || fieldName == "update_time" || fieldName == "last_update" || fieldName == "updated_at" -> {
if (fieldType.contains("TIMESTAMP")) " DEFAULT 'now()'" else ""
}
// 是否删除标记
fieldName == "deleted" || fieldName == "is_deleted" || fieldName == "del_flag" -> {
if (fieldType == "BOOLEAN") " DEFAULT false"
else if (fieldType.contains("INTEGER") || fieldType.contains("CHAR")) " DEFAULT 0"
else ""
}
// 状态字段
fieldName == "status" || fieldName == "state" -> {
if (fieldType.contains("VARCHAR")) " DEFAULT 'NORMAL'" else ""
}
else -> ""
}
}
/**
* 生成插入SQL
* @param entityClass 实体类

View File

@ -92,21 +92,67 @@ class SqlMigrationGenerator {
* @param mapper 注解映射中间类
*/
fun generateMigrations(entityPackage: String, mapper: SqlAnnotationMapper) {
// 确保日志路径存在
createDirectories()
log("开始扫描实体类...")
val entityClasses = scanEntityClasses(entityPackage)
// 创建必要的目录
createDirectories()
if (entityClasses.isEmpty()) {
throw IllegalArgumentException("未找到任何实体类,请检查包路径: $entityPackage")
}
log("找到 ${entityClasses.size} 个实体类,准备验证...")
// 先验证所有实体类是否能够正确解析,如果有错误就立刻抛出异常
validateEntityClasses(entityClasses, mapper)
// 检查是否是初始迁移
val isInitialMigration = isInitialMigration()
if (isInitialMigration) {
log("开始生成初始迁移...")
generateInitialMigration(entityClasses, mapper)
} else {
log("开始生成差异迁移...")
generateDiffMigration(entityClasses, mapper)
}
}
/**
* 验证所有实体类是否可以正确解析SQL信息
*/
private fun validateEntityClasses(entityClasses: List<KClass<*>>, mapper: SqlAnnotationMapper) {
val errorMessages = mutableListOf<String>()
entityClasses.forEach { entityClass ->
try {
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
// 验证表名
if (sqlInfo.tableName.isEmpty()) {
errorMessages.add("实体类 ${entityClass.simpleName} 的表名为空")
}
// 验证是否有列
if (sqlInfo.columns.isEmpty()) {
errorMessages.add("实体类 ${entityClass.simpleName} 没有任何列信息")
}
} catch (e: Exception) {
// 记录错误信息
errorMessages.add("验证实体类 ${entityClass.simpleName} 失败: ${e.message}")
}
}
// 如果有任何错误,抛出异常并停止生成
if (errorMessages.isNotEmpty()) {
val errorMessage = "验证实体类时发现以下错误:\n" + errorMessages.joinToString("\n")
throw IllegalArgumentException(errorMessage)
}
log("所有实体类验证通过,可以生成迁移")
}
/**
* 扫描包路径下标记了@TableName注解的实体类
*/
@ -157,7 +203,12 @@ class SqlMigrationGenerator {
// 添加到模型文件
addCreateTableToModel(modelDocument, changeSetElement, sqlInfo)
log("已生成 ${entityClass.simpleName} 的创建表SQL")
} catch (e: Exception) {
// 由于前面已经验证过所有实体类,这里不应该再有错误
// 但为了健壮性,还是添加错误处理
log("警告: 处理实体类 ${entityClass.simpleName} 时出错: ${e.message}")
sqlBuilder.append("-- 处理实体类 ${entityClass.simpleName} 时出错: ${e.message}\n\n")
}
}
@ -165,12 +216,14 @@ class SqlMigrationGenerator {
// 写入SQL文件
val sqlFileName = "$MIGRATION_PATH/$version.sql"
File(sqlFileName).writeText(sqlBuilder.toString())
log("生成SQL文件: $sqlFileName")
// 写入模型文件
val modelFileName = "$MODEL_PATH/$version.model.xml"
val modelFileName = "$MODEL_PATH/$version$MODEL_SUFFIX"
writeModelToFile(modelDocument, modelFileName)
log("生成模型文件: $modelFileName")
println("初始迁移生成完成: $version")
log("初始迁移生成完成: $version")
}
/**
@ -180,12 +233,12 @@ class SqlMigrationGenerator {
// 获取最新的模型文件
val latestModelFile = findLatestModelFile()
if (latestModelFile == null) {
println("未找到现有模型文件,将生成初始迁移")
log("未找到现有模型文件,将生成初始迁移")
generateInitialMigration(entityClasses, mapper)
return
}
println("找到最新模型文件: ${latestModelFile.name}")
log("找到最新模型文件: ${latestModelFile.name}")
// 解析最新的模型文件并构建完整模型
val allTables = buildFullModel(latestModelFile)
@ -193,16 +246,16 @@ class SqlMigrationGenerator {
// 获取当前版本号和生成新版本号
val currentVersion = extractVersionFromFileName(latestModelFile.name)
if (!currentVersion.contains(".")) {
println("错误: 提取的当前版本号 '$currentVersion' 格式不正确,将使用默认的 '1.0'")
log("错误: 提取的当前版本号 '$currentVersion' 格式不正确,将使用默认的 '1.0'")
val nextVersion = findNextVersionNumber("1.0")
println("基于默认版本找到下一个版本号: $nextVersion")
log("基于默认版本找到下一个版本号: $nextVersion")
continueWithMigration(nextVersion, allTables, entityClasses, mapper)
return
}
// 检查是否存在已有的版本号,找出当前最大版本号
val nextVersion = findNextVersionNumber(currentVersion)
println("当前版本: $currentVersion, 新版本: $nextVersion")
log("当前版本: $currentVersion, 新版本: $nextVersion")
// 继续生成迁移
continueWithMigration(nextVersion, allTables, entityClasses, mapper)
@ -223,20 +276,40 @@ class SqlMigrationGenerator {
try {
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
currentTables[sqlInfo.tableName] = sqlInfo
log("已提取 ${entityClass.simpleName} 的模型信息,表名: ${sqlInfo.tableName}")
} catch (e: Exception) {
println("处理实体类 ${entityClass.simpleName} 时出错: ${e.message}")
// 由于前面已验证,这里不应该再有错误
log("警告: 处理实体类 ${entityClass.simpleName} 时出错: ${e.message}")
}
}
// 比较差异并生成SQL
log("比较数据库结构差异...")
val diffResult = compareTables(allTables, currentTables)
// 如果没有差异,不生成迁移文件
if (diffResult.isEmpty()) {
println("没有发现数据库结构变化,不生成迁移文件")
log("没有发现数据库结构变化,不生成迁移文件")
return
}
log("发现以下变更:")
if (diffResult.tablesToCreate.isNotEmpty()) {
log("- 新增表: ${diffResult.tablesToCreate.map { it.tableName }.joinToString(", ")}")
}
if (diffResult.tablesToDrop.isNotEmpty()) {
log("- 删除表: ${diffResult.tablesToDrop.joinToString(", ")}")
}
if (diffResult.columnsToAdd.isNotEmpty()) {
log("- 新增列: ${diffResult.columnsToAdd.entries.joinToString(", ") { "${it.key}(${it.value.size}列)" }}")
}
if (diffResult.columnsToDrop.isNotEmpty()) {
log("- 删除列: ${diffResult.columnsToDrop.entries.joinToString(", ") { "${it.key}(${it.value.size}列)" }}")
}
if (diffResult.columnsToAlter.isNotEmpty()) {
log("- 修改列: ${diffResult.columnsToAlter.entries.joinToString(", ") { "${it.key}(${it.value.size}列)" }}")
}
// 创建新的模型文档,只包含变更
val modelDocument = createModelDocument()
val changeSetElement = modelDocument.createElement("changeSet")
@ -265,21 +338,21 @@ class SqlMigrationGenerator {
val sqlFileName = "$MIGRATION_PATH/$nextVersion.sql"
val sqlFile = File(sqlFileName)
if (sqlFile.exists()) {
println("警告: SQL文件 $sqlFileName 已存在,将被覆盖")
log("警告: SQL文件 $sqlFileName 已存在,将被覆盖")
}
sqlFile.writeText(sqlBuilder.toString())
println("生成SQL文件: $sqlFileName")
log("生成SQL文件: $sqlFileName")
// 写入模型文件
val modelFileName = "$MODEL_PATH/$nextVersion.model.xml"
val modelFileName = "$MODEL_PATH/$nextVersion$MODEL_SUFFIX"
val modelFile = File(modelFileName)
if (modelFile.exists()) {
println("警告: 模型文件 $modelFileName 已存在,将被覆盖")
log("警告: 模型文件 $modelFileName 已存在,将被覆盖")
}
writeModelToFile(modelDocument, modelFileName)
println("生成模型文件: $modelFileName")
log("生成模型文件: $modelFileName")
println("差异迁移生成完成: $nextVersion")
log("差异迁移生成完成: $nextVersion")
}
/**