This commit is contained in:
AiKrai 2025-03-21 14:58:13 +08:00
parent 4f31d1517f
commit 530f682f17
9 changed files with 657 additions and 522 deletions

View File

@ -11,6 +11,7 @@ import java.sql.Timestamp
class Account : BaseEntity() { class Account : BaseEntity() {
@TableId(type = IdType.ASSIGN_ID) @TableId(type = IdType.ASSIGN_ID)
@TableFieldComment("用户ID")
var userId: Long = 0L var userId: Long = 0L
@TableField("user_name") @TableField("user_name")

View File

@ -5,6 +5,7 @@ import org.aikrai.vertx.db.annotation.TableId
import org.aikrai.vertx.db.annotation.TableName import org.aikrai.vertx.db.annotation.TableName
import org.aikrai.vertx.db.annotation.TableIndex import org.aikrai.vertx.db.annotation.TableIndex
import org.aikrai.vertx.db.annotation.EnumValue import org.aikrai.vertx.db.annotation.EnumValue
import org.aikrai.vertx.db.annotation.TableFieldComment
import org.aikrai.vertx.db.migration.AnnotationMapping import org.aikrai.vertx.db.migration.AnnotationMapping
import org.aikrai.vertx.db.migration.ColumnMapping import org.aikrai.vertx.db.migration.ColumnMapping
import org.aikrai.vertx.db.migration.DbMigration import org.aikrai.vertx.db.migration.DbMigration
@ -38,6 +39,7 @@ object GenerateMigration {
// 设置迁移生成器 // 设置迁移生成器
val dbMigration = DbMigration.create() val dbMigration = DbMigration.create()
dbMigration.setPathToResources("vertx-demo/src/main/resources")
dbMigration.setEntityPackage("app.data.domain") // 指定实体类包路径 dbMigration.setEntityPackage("app.data.domain") // 指定实体类包路径
dbMigration.setSqlAnnotationMapper(mapper) dbMigration.setSqlAnnotationMapper(mapper)
dbMigration.setGenerateDropStatements(false) // 不生成删除语句 dbMigration.setGenerateDropStatements(false) // 不生成删除语句
@ -61,7 +63,6 @@ object GenerateMigration {
// 设置实体类注解映射 // 设置实体类注解映射
mapper.entityMapping = AnnotationMapping( mapper.entityMapping = AnnotationMapping(
annotationClass = TableName::class, annotationClass = TableName::class,
propertyName = "value"
) )
// 设置表名映射 // 设置表名映射
@ -96,6 +97,10 @@ object GenerateMigration {
uniqueMapping = AnnotationMapping( uniqueMapping = AnnotationMapping(
annotationClass = TableField::class, annotationClass = TableField::class,
propertyName = "unique" propertyName = "unique"
),
commentMapping = AnnotationMapping(
annotationClass = TableFieldComment::class,
propertyName = "value"
) )
) )
) )
@ -103,7 +108,6 @@ object GenerateMigration {
// 设置主键映射 // 设置主键映射
mapper.primaryKeyMapping = AnnotationMapping( mapper.primaryKeyMapping = AnnotationMapping(
annotationClass = TableId::class, annotationClass = TableId::class,
propertyName = "value"
) )
// 设置索引映射 // 设置索引映射

View File

@ -52,6 +52,13 @@ annotation class TableField(
val default: String = "" val default: String = ""
) )
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS)
annotation class TableFieldComment(
val value: String = "",
)
@MustBeDocumented @MustBeDocumented
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION) @Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION)

View File

@ -159,6 +159,10 @@ class DefaultDbMigration : DbMigration {
if (sqlAnnotationMapper == null) { if (sqlAnnotationMapper == null) {
throw IllegalStateException("SQL注解映射器未设置请调用setSqlAnnotationMapper()") throw IllegalStateException("SQL注解映射器未设置请调用setSqlAnnotationMapper()")
} }
if (sqlAnnotationMapper?.entityMapping == null) {
throw IllegalStateException("实体注解映射未设置请配置entityMapping")
}
} }
/** /**

View File

@ -66,7 +66,7 @@ data class AnnotationMapping(
/** 注解类 */ /** 注解类 */
val annotationClass: KClass<out Annotation>, val annotationClass: KClass<out Annotation>,
/** 注解属性名 */ /** 注解属性名 */
val propertyName: String = "value" val propertyName: String = ""
) )
/** /**
@ -84,5 +84,7 @@ data class ColumnMapping(
/** 字段长度映射,可选 */ /** 字段长度映射,可选 */
val lengthMapping: AnnotationMapping? = null, val lengthMapping: AnnotationMapping? = null,
/** 是否唯一映射,可选 */ /** 是否唯一映射,可选 */
val uniqueMapping: AnnotationMapping? = null val uniqueMapping: AnnotationMapping? = null,
/** 字段注释映射,可选 */
val commentMapping: AnnotationMapping? = null
) )

View File

@ -29,27 +29,39 @@ class SqlGenerator {
val columnDefinitions = columns.map { column -> val columnDefinitions = columns.map { column ->
// 特殊处理一些常见约定字段 // 特殊处理一些常见约定字段
val specialFieldDefaults = getSpecialFieldDefaults(column.name, column.type) val specialFieldDefaults = getSpecialFieldDefaults(column.name, column.type)
val defaultValue = if (column.defaultValue.isNotEmpty()) { var defaultValue = ""
// 处理默认值
if (column.defaultValue.isNotEmpty()) {
// 对于时间戳类型字段特殊处理NOW()函数作为默认值 // 对于时间戳类型字段特殊处理NOW()函数作为默认值
if (column.type.contains("TIMESTAMP") && column.defaultValue.equals("now()", ignoreCase = true)) { if (column.type.contains("TIMESTAMP") && column.defaultValue.equals("now()", ignoreCase = true)) {
" DEFAULT 'now()'" defaultValue = " DEFAULT 'now()'"
} else if (column.enumValues != null && column.enumValues!!.isNotEmpty()) {
// 对于枚举类型字段,直接使用默认值
// 如果是数字,就不带引号,否则加上引号
val isNumeric = column.defaultValue.matches(Regex("^[0-9]+$"))
if (isNumeric) {
defaultValue = " DEFAULT ${column.defaultValue}"
} else {
defaultValue = " DEFAULT '${column.defaultValue}'"
}
} else { } else {
" DEFAULT ${column.defaultValue}" defaultValue = " DEFAULT ${column.defaultValue}"
} }
} else if (specialFieldDefaults.isNotEmpty()) { } else if (specialFieldDefaults.isNotEmpty()) {
specialFieldDefaults defaultValue = specialFieldDefaults
} else { } else {
// 为一些常见类型提供合理的默认值 // 为一些常见类型提供合理的默认值
when { when {
column.type.contains("VARCHAR") -> " DEFAULT ''" column.type.contains("VARCHAR") -> defaultValue = " DEFAULT ''"
column.type == "INTEGER" || column.type == "BIGINT" -> " DEFAULT 0" column.type == "INTEGER" || column.type == "BIGINT" -> defaultValue = " DEFAULT 0"
column.type == "BOOLEAN" -> " DEFAULT false" column.type == "BOOLEAN" -> defaultValue = " DEFAULT false"
column.type.contains("JSON") -> " DEFAULT '{}'" column.type.contains("JSON") -> defaultValue = " DEFAULT '{}'"
else -> ""
} }
} }
val nullable = if (column.nullable) "" else " NOT NULL" val nullable = if (column.nullable) "" else " NOT NULL"
// 移除内联注释改用COMMENT ON语句
" ${column.name} ${column.type}$defaultValue$nullable" " ${column.name} ${column.type}$defaultValue$nullable"
} }
@ -65,8 +77,17 @@ class SqlGenerator {
val enumColumns = columns.filter { it.type.contains("VARCHAR") && it.enumValues != null && it.enumValues!!.isNotEmpty() } val enumColumns = columns.filter { it.type.contains("VARCHAR") && it.enumValues != null && it.enumValues!!.isNotEmpty() }
for (column in enumColumns) { for (column in enumColumns) {
if (column.enumValues != null && column.enumValues!!.isNotEmpty()) { if (column.enumValues != null && column.enumValues!!.isNotEmpty()) {
// 检查枚举值是否都是数字
val allNumeric = column.enumValues!!.all { it.matches(Regex("^[0-9]+$")) }
sb.append(",\n CONSTRAINT ck_${tableName}_${column.name} CHECK ( ${column.name} in (") sb.append(",\n CONSTRAINT ck_${tableName}_${column.name} CHECK ( ${column.name} in (")
sb.append(column.enumValues!!.joinToString(",") { "'$it'" }) if (allNumeric) {
// 如果全是数字,不需要加引号
sb.append(column.enumValues!!.joinToString(","))
} else {
// 否则加上引号
sb.append(column.enumValues!!.joinToString(",") { "'$it'" })
}
sb.append("))") sb.append("))")
} }
} }
@ -78,6 +99,16 @@ class SqlGenerator {
sb.append("\n);") sb.append("\n);")
// 添加字段注释 - 使用PostgreSQL的COMMENT ON语句
val columnsWithComment = columns.filter { it.comment.isNotEmpty() }
if (columnsWithComment.isNotEmpty()) {
sb.append("\n\n-- 添加字段注释\n")
for (column in columnsWithComment) {
sb.append("COMMENT ON COLUMN ${tableName}.${column.name} IS '${column.comment.replace("'", "''")}';")
sb.append("\n")
}
}
// 添加索引创建语句 // 添加索引创建语句
val indexSql = generateCreateIndexSql(sqlInfo) val indexSql = generateCreateIndexSql(sqlInfo)
if (indexSql.isNotEmpty()) { if (indexSql.isNotEmpty()) {
@ -151,7 +182,13 @@ class SqlGenerator {
} }
// 状态字段 // 状态字段
fieldName == "status" || fieldName == "state" -> { fieldName == "status" || fieldName == "state" -> {
if (fieldType.contains("VARCHAR")) " DEFAULT 'NORMAL'" else "" if (fieldType.contains("VARCHAR")) {
// 默认为空值,实际值将通过字段的默认值处理
""
} else {
// 对于数字类型状态默认为0
" DEFAULT 0"
}
} }
else -> "" else -> ""
} }

View File

@ -22,7 +22,8 @@ data class ColumnInfo(
var isPrimaryKey: Boolean = false, var isPrimaryKey: Boolean = false,
var enumValues: List<String>? = null, var enumValues: List<String>? = null,
var unique: Boolean = false, var unique: Boolean = false,
var length: Int = 255 var length: Int = 255,
var comment: String = ""
) )
/** /**

View File

@ -96,7 +96,7 @@ class SqlMigrationGenerator {
createDirectories() createDirectories()
log("开始扫描实体类...") log("开始扫描实体类...")
val entityClasses = scanEntityClasses(entityPackage) val entityClasses = scanEntityClasses(mapper, entityPackage)
if (entityClasses.isEmpty()) { if (entityClasses.isEmpty()) {
throw IllegalArgumentException("未找到任何实体类,请检查包路径: $entityPackage") throw IllegalArgumentException("未找到任何实体类,请检查包路径: $entityPackage")
@ -156,9 +156,11 @@ class SqlMigrationGenerator {
/** /**
* 扫描包路径下标记了@TableName注解的实体类 * 扫描包路径下标记了@TableName注解的实体类
*/ */
private fun scanEntityClasses(packagePath: String): List<KClass<*>> { private fun scanEntityClasses(mapper: SqlAnnotationMapper, packagePath: String): List<KClass<*>> {
val entityAnnotationClass = mapper.entityMapping?.annotationClass?.java
?: throw IllegalStateException("实体类注解映射未设置请配置SqlAnnotationMapper.entityMapping")
val reflections = Reflections(packagePath) val reflections = Reflections(packagePath)
val entityClasses = reflections.getTypesAnnotatedWith(TableName::class.java) val entityClasses = reflections.getTypesAnnotatedWith(entityAnnotationClass)
return entityClasses.map { it.kotlin } return entityClasses.map { it.kotlin }
} }
@ -477,17 +479,23 @@ class SqlMigrationGenerator {
doc: Document, doc: Document,
changeSetElement: Element changeSetElement: Element
) { ) {
// 检查是否需要生成删除语句
val generateDropStatements = System.getProperty("ddl.migration.generateDropStatements", "false").toBoolean()
tablesToDrop.forEach { tableName -> tablesToDrop.forEach { tableName ->
// 生成SQL // 只有当配置为生成删除语句时才添加到SQL中
sqlBuilder.append("drop table if exists $tableName cascade;\n") if (generateDropStatements) {
// 生成SQL
sqlBuilder.append("drop table if exists $tableName cascade;\n")
}
// 添加到XML // 无论如何都添加到XML中,以记录历史变更
val dropTableElement = doc.createElement("dropTable") val dropTableElement = doc.createElement("dropTable")
dropTableElement.setAttribute("name", tableName) dropTableElement.setAttribute("name", tableName)
changeSetElement.appendChild(dropTableElement) changeSetElement.appendChild(dropTableElement)
} }
if (tablesToDrop.isNotEmpty()) { if (generateDropStatements && tablesToDrop.isNotEmpty()) {
sqlBuilder.append("\n") sqlBuilder.append("\n")
} }
} }
@ -538,27 +546,38 @@ class SqlMigrationGenerator {
* 处理删除列 * 处理删除列
*/ */
private fun processDropColumns( private fun processDropColumns(
columnsToDrop: Map<String, List<String>>, columnsToDrop: Map<String, List<String>>,
sqlBuilder: StringBuilder, sqlBuilder: StringBuilder,
doc: Document, doc: Document,
changeSetElement: Element changeSetElement: Element
) { ) {
// 检查是否需要生成删除语句
val generateDropStatements = System.getProperty("ddl.migration.generateDropStatements", "false").toBoolean()
var addedSql = false
columnsToDrop.forEach { (tableName, columns) -> columnsToDrop.forEach { (tableName, columns) ->
// 为每个表创建一个dropColumn元素 if (columns.isNotEmpty()) {
val dropColumnElement = doc.createElement("dropColumn") val alterTableElement = doc.createElement("alterTable")
dropColumnElement.setAttribute("tableName", tableName) alterTableElement.setAttribute("name", tableName)
changeSetElement.appendChild(dropColumnElement) changeSetElement.appendChild(alterTableElement)
columns.forEach { columnName ->
// 生成SQL
sqlBuilder.append("alter table $tableName drop column if exists $columnName;\n")
// 添加到XML columns.forEach { columnName ->
val columnElement = doc.createElement("column") // 只有当配置为生成删除语句时才添加到SQL中
columnElement.setAttribute("name", columnName) if (generateDropStatements) {
dropColumnElement.appendChild(columnElement) sqlBuilder.append("alter table $tableName drop column if exists $columnName;\n")
addedSql = true
}
// 无论如何都添加到XML中以记录历史变更
val dropColumnElement = doc.createElement("dropColumn")
dropColumnElement.setAttribute("columnName", columnName)
alterTableElement.appendChild(dropColumnElement)
}
} }
}
if (addedSql) {
sqlBuilder.append("\n") sqlBuilder.append("\n")
} }
} }