1
This commit is contained in:
parent
7677bab363
commit
cc415e82d0
@ -30,17 +30,22 @@ object GenerateMigration {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
// 创建SQL注解映射器
|
||||
val mapper = createSqlAnnotationMapper()
|
||||
try {
|
||||
// 创建SQL注解映射器
|
||||
val mapper = createSqlAnnotationMapper()
|
||||
|
||||
// 设置迁移生成器
|
||||
val dbMigration = DbMigration.create()
|
||||
dbMigration.setEntityPackage("org.aikrai.vertx.entity") // 替换为你的实体类包路径
|
||||
dbMigration.setSqlAnnotationMapper(mapper)
|
||||
// 设置迁移生成器
|
||||
val dbMigration = DbMigration.create()
|
||||
dbMigration.setEntityPackage("app.data.domain") // 指定实体类包路径
|
||||
dbMigration.setSqlAnnotationMapper(mapper)
|
||||
|
||||
// 生成迁移
|
||||
val migrationVersion = dbMigration.generateMigration()
|
||||
println("生成的迁移版本: $migrationVersion")
|
||||
// 生成迁移
|
||||
val migrationVersion = dbMigration.generateMigration()
|
||||
println("生成的迁移版本: $migrationVersion")
|
||||
} catch (e: Exception) {
|
||||
println("生成迁移失败: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,24 +64,24 @@ object GenerateMigration {
|
||||
// 设置列映射
|
||||
mapper.addColumnMapping(
|
||||
ColumnMapping(
|
||||
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"
|
||||
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"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// 设置主键映射
|
||||
mapper.primaryKeyMapping = AnnotationMapping(
|
||||
|
||||
@ -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 = ""
|
||||
)
|
||||
|
||||
|
||||
@ -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 columnInfo = ColumnInfo()
|
||||
val fields = entityClass.java.declaredFields
|
||||
if (fields.isEmpty()) {
|
||||
throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有声明任何字段")
|
||||
}
|
||||
|
||||
// 处理每个列映射
|
||||
mapper.columnMappings.forEach { columnMapping ->
|
||||
val nameAnnotation = field.annotations.find {
|
||||
it.annotationClass.java == columnMapping.nameMapping.annotationClass.java
|
||||
}
|
||||
// 创建已处理字段名集合,用于记录已经处理过的字段
|
||||
val processedFields = mutableSetOf<String>()
|
||||
|
||||
if (nameAnnotation != null) {
|
||||
val nameMethod = nameAnnotation.javaClass.getMethod(columnMapping.nameMapping.propertyName)
|
||||
columnInfo.name = nameMethod.invoke(nameAnnotation) as String? ?: field.name
|
||||
// 查找实体类中的@Transient注解类
|
||||
val transientAnnotationClasses = listOf(
|
||||
"kotlin.jvm.Transient",
|
||||
"javax.persistence.Transient",
|
||||
"jakarta.persistence.Transient",
|
||||
"java.beans.Transient",
|
||||
"org.aikrai.vertx.db.annotation.Transient"
|
||||
)
|
||||
|
||||
// 处理类型映射
|
||||
columnMapping.typeMapping?.let { typeMapping ->
|
||||
val typeAnnotation = field.annotations.find {
|
||||
it.annotationClass.java == typeMapping.annotationClass.java
|
||||
}
|
||||
if (typeAnnotation != null) {
|
||||
val typeMethod = typeAnnotation.javaClass.getMethod(typeMapping.propertyName)
|
||||
columnInfo.type = typeMethod.invoke(typeAnnotation) as String
|
||||
}
|
||||
}
|
||||
|
||||
// 处理可空映射
|
||||
columnMapping.nullableMapping?.let { nullableMapping ->
|
||||
val nullableAnnotation = field.annotations.find {
|
||||
it.annotationClass.java == nullableMapping.annotationClass.java
|
||||
}
|
||||
if (nullableAnnotation != null) {
|
||||
val nullableMethod = nullableAnnotation.javaClass.getMethod(nullableMapping.propertyName)
|
||||
columnInfo.nullable = nullableMethod.invoke(nullableAnnotation) as Boolean
|
||||
}
|
||||
}
|
||||
|
||||
// 处理默认值映射
|
||||
columnMapping.defaultValueMapping?.let { defaultValueMapping ->
|
||||
val defaultValueAnnotation = field.annotations.find {
|
||||
it.annotationClass.java == defaultValueMapping.annotationClass.java
|
||||
}
|
||||
if (defaultValueAnnotation != null) {
|
||||
val defaultValueMethod = defaultValueAnnotation.javaClass.getMethod(defaultValueMapping.propertyName)
|
||||
columnInfo.defaultValue = defaultValueMethod.invoke(defaultValueAnnotation) as String
|
||||
}
|
||||
}
|
||||
|
||||
sqlInfo.columns.add(columnInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理主键
|
||||
mapper.primaryKeyMapping?.let { pkMapping ->
|
||||
val pkAnnotation = field.annotations.find {
|
||||
it.annotationClass.java == pkMapping.annotationClass.java
|
||||
}
|
||||
if (pkAnnotation != null) {
|
||||
val pkMethod = pkAnnotation.javaClass.getMethod(pkMapping.propertyName)
|
||||
val isPk = pkMethod.invoke(pkAnnotation)
|
||||
if (isPk is Boolean && isPk) {
|
||||
columnInfo.isPrimaryKey = true
|
||||
sqlInfo.primaryKeys.add(columnInfo.name)
|
||||
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.qualifiedName == columnMapping.nameMapping.annotationClass.qualifiedName
|
||||
}
|
||||
|
||||
if (nameAnnotation != null) {
|
||||
foundColumnMapping = true
|
||||
try {
|
||||
val nameMethod = nameAnnotation.javaClass.getMethod(columnMapping.nameMapping.propertyName)
|
||||
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.qualifiedName == typeMapping.annotationClass.qualifiedName
|
||||
}
|
||||
if (typeAnnotation != null) {
|
||||
try {
|
||||
val typeMethod = typeAnnotation.javaClass.getMethod(typeMapping.propertyName)
|
||||
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.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.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理主键
|
||||
if (mapper.primaryKeyMapping != null) {
|
||||
val pkMapping = mapper.primaryKeyMapping!!
|
||||
val pkAnnotation = field.annotations.find {
|
||||
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
|
||||
)
|
||||
@ -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 实体类
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user