From 530f682f17b26ec3555bd5ea7faafa37f2615f41 Mon Sep 17 00:00:00 2001 From: AiKrai Date: Fri, 21 Mar 2025 14:58:13 +0800 Subject: [PATCH] 1 --- .../kotlin/app/data/domain/account/Account.kt | 1 + .../src/test/kotlin/app/GenerateMigration.kt | 8 +- .../aikrai/vertx/db/annotation/Annotation.kt | 7 + .../vertx/db/migration/DefaultDbMigration.kt | 4 + .../vertx/db/migration/SqlAnnotationMapper.kt | 6 +- .../migration/SqlAnnotationMapperGenerator.kt | 1030 +++++++++-------- .../aikrai/vertx/db/migration/SqlGenerator.kt | 59 +- .../org/aikrai/vertx/db/migration/SqlInfo.kt | 3 +- .../db/migration/SqlMigrationGenerator.kt | 61 +- 9 files changed, 657 insertions(+), 522 deletions(-) diff --git a/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt b/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt index ebb5796..08be747 100644 --- a/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt +++ b/vertx-demo/src/main/kotlin/app/data/domain/account/Account.kt @@ -11,6 +11,7 @@ import java.sql.Timestamp class Account : BaseEntity() { @TableId(type = IdType.ASSIGN_ID) + @TableFieldComment("用户ID") var userId: Long = 0L @TableField("user_name") diff --git a/vertx-demo/src/test/kotlin/app/GenerateMigration.kt b/vertx-demo/src/test/kotlin/app/GenerateMigration.kt index 9734c4b..1782b6d 100644 --- a/vertx-demo/src/test/kotlin/app/GenerateMigration.kt +++ b/vertx-demo/src/test/kotlin/app/GenerateMigration.kt @@ -5,6 +5,7 @@ import org.aikrai.vertx.db.annotation.TableId import org.aikrai.vertx.db.annotation.TableName import org.aikrai.vertx.db.annotation.TableIndex 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.ColumnMapping import org.aikrai.vertx.db.migration.DbMigration @@ -38,6 +39,7 @@ object GenerateMigration { // 设置迁移生成器 val dbMigration = DbMigration.create() + dbMigration.setPathToResources("vertx-demo/src/main/resources") dbMigration.setEntityPackage("app.data.domain") // 指定实体类包路径 dbMigration.setSqlAnnotationMapper(mapper) dbMigration.setGenerateDropStatements(false) // 不生成删除语句 @@ -61,7 +63,6 @@ object GenerateMigration { // 设置实体类注解映射 mapper.entityMapping = AnnotationMapping( annotationClass = TableName::class, - propertyName = "value" ) // 设置表名映射 @@ -96,6 +97,10 @@ object GenerateMigration { uniqueMapping = AnnotationMapping( annotationClass = TableField::class, propertyName = "unique" + ), + commentMapping = AnnotationMapping( + annotationClass = TableFieldComment::class, + propertyName = "value" ) ) ) @@ -103,7 +108,6 @@ object GenerateMigration { // 设置主键映射 mapper.primaryKeyMapping = AnnotationMapping( annotationClass = TableId::class, - propertyName = "value" ) // 设置索引映射 diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt index 8443692..fe05575 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/annotation/Annotation.kt @@ -52,6 +52,13 @@ annotation class TableField( val default: String = "" ) +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS) +annotation class TableFieldComment( + val value: String = "", +) + @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION) diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt index c5fb0ea..50d4560 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/DefaultDbMigration.kt @@ -159,6 +159,10 @@ class DefaultDbMigration : DbMigration { if (sqlAnnotationMapper == null) { throw IllegalStateException("SQL注解映射器未设置,请调用setSqlAnnotationMapper()") } + + if (sqlAnnotationMapper?.entityMapping == null) { + throw IllegalStateException("实体注解映射未设置,请配置entityMapping") + } } /** diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapper.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapper.kt index 56a8573..92a0ff3 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapper.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapper.kt @@ -66,7 +66,7 @@ data class AnnotationMapping( /** 注解类 */ val annotationClass: KClass, /** 注解属性名 */ - val propertyName: String = "value" + val propertyName: String = "" ) /** @@ -84,5 +84,7 @@ data class ColumnMapping( /** 字段长度映射,可选 */ val lengthMapping: AnnotationMapping? = null, /** 是否唯一映射,可选 */ - val uniqueMapping: AnnotationMapping? = null + val uniqueMapping: AnnotationMapping? = null, + /** 字段注释映射,可选 */ + val commentMapping: AnnotationMapping? = null ) \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapperGenerator.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapperGenerator.kt index e4cd47f..8dedac7 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapperGenerator.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlAnnotationMapperGenerator.kt @@ -1,5 +1,6 @@ package org.aikrai.vertx.db.migration +import cn.hutool.core.util.StrUtil import java.time.LocalDateTime import kotlin.reflect.KClass @@ -8,509 +9,568 @@ import kotlin.reflect.KClass * 用于生成和使用SQL注解映射中间类 */ class SqlAnnotationMapperGenerator { - - companion object { - /** - * 从实体类获取SQL信息 - * @param entityClass 实体类 - * @param mapper 注解映射中间类 - * @return SQL信息 - */ - fun extractSqlInfo(entityClass: KClass<*>, mapper: SqlAnnotationMapper): SqlInfo { - val sqlInfo = SqlInfo() - - // 验证实体类注解 - val isEntityClass = validateEntityClass(entityClass, mapper) - if (!isEntityClass) { - throw IllegalArgumentException("类 ${entityClass.simpleName} 不是有效的实体类,未标记所需的实体类注解") - } - - // 获取表名 - 优先从表名注解中获取,如果没有则使用类名转换 + + companion object { + /** + * 从实体类获取SQL信息 + * @param entityClass 实体类 + * @param mapper 注解映射中间类 + * @return SQL信息 + */ + fun extractSqlInfo(entityClass: KClass<*>, mapper: SqlAnnotationMapper): SqlInfo { + val sqlInfo = SqlInfo() + + // 获取表名 - 优先从表名注解中获取,如果没有则使用类名转换 + try { + if (mapper.tableName != null) { + val tableNameMapping = mapper.tableName!! + val annotation = entityClass.annotations.find { + it.annotationClass.qualifiedName == tableNameMapping.annotationClass.qualifiedName + } + + if (annotation != null) { try { - if (mapper.tableName != null) { - val tableNameMapping = mapper.tableName!! - val annotation = entityClass.annotations.find { - it.annotationClass.qualifiedName == tableNameMapping.annotationClass.qualifiedName - } - - if (annotation != null) { - try { - val method = annotation.javaClass.getMethod(tableNameMapping.propertyName) - val tableName = method.invoke(annotation) as String - - if (tableName.isNotEmpty()) { - sqlInfo.tableName = tableName - } else { - // 使用类名转蛇形命名作为表名 - sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "") - } - } catch (e: Exception) { - // 使用类名转蛇形命名作为表名 - sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "") - } + val method = annotation.javaClass.getMethod(tableNameMapping.propertyName) + val tableName = method.invoke(annotation) as String + + if (tableName.isNotBlank()) { + sqlInfo.tableName = tableName + } else { + // 使用类名转蛇形命名作为表名 + sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "") + } + } catch (e: Exception) { + // 使用类名转蛇形命名作为表名 + sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "") + } + } else { + // 使用类名转蛇形命名作为表名 + sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "") + } + } else { + // 使用类名转蛇形命名作为表名 + sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "") + } + } catch (e: Exception) { + throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的表名时出错: ${e.message}", e) + } + + // 获取实体类的所有字段 + val fields = entityClass.java.declaredFields + if (fields.isEmpty()) { + throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有声明任何字段") + } + + // 创建已处理字段名集合,用于记录已经处理过的字段 + val processedFields = mutableSetOf() + + // 查找实体类中的@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.qualifiedName == columnMapping.nameMapping.annotationClass.qualifiedName + } + + + foundColumnMapping = true + try { + val nameMethod = nameAnnotation?.javaClass?.getMethod(columnMapping.nameMapping.propertyName) + val columnName = nameMethod?.invoke(nameAnnotation) as? String + columnInfo.name = if (columnName.isNullOrEmpty()) StrUtil.toUnderlineCase(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, mapper) } else { - // 使用类名转蛇形命名作为表名 - sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "") + typeName } + } catch (e: Exception) { + throw IllegalArgumentException("处理字段 ${field.name} 的类型映射时出错: ${e.message}", e) + } } else { - // 使用类名转蛇形命名作为表名 - sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "") + columnInfo.type = inferSqlType(field.type, columnInfo, mapper) } - } catch (e: Exception) { - throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的表名时出错: ${e.message}", e) - } - - // 获取实体类的所有字段 - val fields = entityClass.java.declaredFields - if (fields.isEmpty()) { - throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有声明任何字段") - } - - // 创建已处理字段名集合,用于记录已经处理过的字段 - val processedFields = mutableSetOf() - - // 查找实体类中的@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 -> + } ?: { + columnInfo.type = inferSqlType(field.type, columnInfo, mapper) + } + + // 检查字段是否为枚举类型,并且有默认初始值 + if (field.type.isEnum) { try { - // 检查字段是否有@Transient注解,如果有则跳过 - val isTransient = field.annotations.any { annotation -> - transientAnnotationClasses.any { className -> - annotation.annotationClass.qualifiedName == className + // 使字段可访问 + field.isAccessible = true + + // 获取声明类的实例(暂时创建一个实例) + val declaringClass = field.declaringClass + val instance = try { + declaringClass.getDeclaredConstructor().newInstance() + } catch (e: Exception) { + null + } + + if (instance != null) { + // 获取默认枚举值 + val defaultEnumValue = field.get(instance) + if (defaultEnumValue != null) { + // 如果有EnumValue注解的方法,使用它获取枚举值 + if (mapper.enumValueMapping != null) { + val enumValueMethod = field.type.methods.find { method -> + method.annotations.any { + it.annotationClass.qualifiedName == mapper.enumValueMapping!!.annotationClass.qualifiedName + } } - } - - 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, mapper) - } else { - typeName - } - } catch (e: Exception) { - throw IllegalArgumentException("处理字段 ${field.name} 的类型映射时出错: ${e.message}", e) - } - } else { - columnInfo.type = inferSqlType(field.type, columnInfo, mapper) - } - } ?: { - columnInfo.type = inferSqlType(field.type, columnInfo, mapper) - } - - // 处理可空映射 - 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) - } - } - } - - // 处理长度映射 - columnMapping.lengthMapping?.let { lengthMapping -> - val lengthAnnotation = field.annotations.find { - it.annotationClass.qualifiedName == lengthMapping.annotationClass.qualifiedName - } - if (lengthAnnotation != null) { - try { - val lengthMethod = lengthAnnotation.javaClass.getMethod(lengthMapping.propertyName) - val lengthValue = lengthMethod.invoke(lengthAnnotation) - if (lengthValue is Int) { - columnInfo.length = lengthValue - // 如果是VARCHAR类型,更新类型定义中的长度 - if (columnInfo.type.startsWith("VARCHAR")) { - columnInfo.type = "VARCHAR(${columnInfo.length})" - } - } - } catch (e: Exception) { - throw IllegalArgumentException("处理字段 ${field.name} 的长度映射时出错: ${e.message}", e) - } - } - } - - // 处理唯一性映射 - columnMapping.uniqueMapping?.let { uniqueMapping -> - val uniqueAnnotation = field.annotations.find { - it.annotationClass.qualifiedName == uniqueMapping.annotationClass.qualifiedName - } - if (uniqueAnnotation != null) { - try { - val uniqueMethod = uniqueAnnotation.javaClass.getMethod(uniqueMapping.propertyName) - val uniqueValue = uniqueMethod.invoke(uniqueAnnotation) - if (uniqueValue is Boolean) { - columnInfo.unique = uniqueValue - } - } 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 (enumValueMethod != null) { + // 获取枚举值并设置为默认值 + val enumValue = enumValueMethod.invoke(defaultEnumValue) + if (enumValue != null) { + columnInfo.defaultValue = enumValue.toString() + } } + } } - - // 处理主键 - 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, mapper) - 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, mapper) - - sqlInfo.columns.add(defaultColumnInfo) - processedFields.add(field.name) - } + } } catch (e: Exception) { - throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的字段 ${field.name} 时出错: ${e.message}", e) + // 忽略获取默认值失败的情况 + println("获取枚举默认值失败: ${e.message}") } - } - - // 验证结果 - if (sqlInfo.tableName.isEmpty()) { - throw IllegalArgumentException("实体类 ${entityClass.simpleName} 的表名为空,请检查表名注解") - } - - if (sqlInfo.columns.isEmpty()) { - throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有可用的列信息,请检查列注解") - } - - // 处理表级别的索引注解 - processTableIndexes(entityClass, sqlInfo, mapper) - - return sqlInfo - } - - /** - * 验证类是否为实体类 - */ - private fun validateEntityClass(entityClass: KClass<*>, mapper: SqlAnnotationMapper): Boolean { - // 如果没有设置实体类映射,则认为所有类都是有效的实体类 - if (mapper.entityMapping == null) { - return true - } - - val entityMapping = mapper.entityMapping!! - return entityClass.annotations.any { - it.annotationClass.qualifiedName == entityMapping.annotationClass.qualifiedName - } - } - - /** - * 处理表级别的索引注解 - */ - private fun processTableIndexes(entityClass: KClass<*>, sqlInfo: SqlInfo, mapper: SqlAnnotationMapper) { - // 只有当有indexMapping配置时才处理 - if (mapper.indexMapping == null) { - return - } - - try { - val indexMapping = mapper.indexMapping!! - // 查找类上的所有TableIndex注解 - val tableIndexAnnotations = entityClass.annotations.filter { - it.annotationClass.qualifiedName == indexMapping.annotationClass.qualifiedName + } + + // 处理可空映射 + columnMapping.nullableMapping?.let { nullableMapping -> + val nullableAnnotation = field.annotations.find { + it.annotationClass.qualifiedName == nullableMapping.annotationClass.qualifiedName } - - // 处理每个TableIndex注解 - tableIndexAnnotations.forEach { annotation -> + 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.lengthMapping?.let { lengthMapping -> + val lengthAnnotation = field.annotations.find { + it.annotationClass.qualifiedName == lengthMapping.annotationClass.qualifiedName + } + if (lengthAnnotation != null) { + try { + val lengthMethod = lengthAnnotation.javaClass.getMethod(lengthMapping.propertyName) + val lengthValue = lengthMethod.invoke(lengthAnnotation) + if (lengthValue is Int) { + columnInfo.length = lengthValue + // 如果是VARCHAR类型,更新类型定义中的长度 + if (columnInfo.type.startsWith("VARCHAR")) { + columnInfo.type = "VARCHAR(${columnInfo.length})" + } + } + } catch (e: Exception) { + throw IllegalArgumentException("处理字段 ${field.name} 的长度映射时出错: ${e.message}", e) + } + } + } + + // 处理唯一性映射 + columnMapping.uniqueMapping?.let { uniqueMapping -> + val uniqueAnnotation = field.annotations.find { + it.annotationClass.qualifiedName == uniqueMapping.annotationClass.qualifiedName + } + if (uniqueAnnotation != null) { + try { + val uniqueMethod = uniqueAnnotation.javaClass.getMethod(uniqueMapping.propertyName) + val uniqueValue = uniqueMethod.invoke(uniqueAnnotation) + if (uniqueValue is Boolean) { + columnInfo.unique = uniqueValue + } + } catch (e: Exception) { + throw IllegalArgumentException("处理字段 ${field.name} 的唯一性映射时出错: ${e.message}", e) + } + } + } + + // 处理注释映射 + columnMapping.commentMapping?.let { commentMapping -> + val commentAnnotation = field.annotations.find { + it.annotationClass.qualifiedName == commentMapping.annotationClass.qualifiedName + } + if (commentAnnotation != null) { + try { + val commentMethod = commentAnnotation.javaClass.getMethod(commentMapping.propertyName) + val commentValue = commentMethod.invoke(commentAnnotation) + if (commentValue is String) { + columnInfo.comment = commentValue + } + } catch (e: Exception) { + throw IllegalArgumentException("处理字段 ${field.name} 的注释映射时出错: ${e.message}", e) + } + } + } + + // 处理默认值映射 - 只有在字段不是枚举或枚举但没有初始值时才处理注解中的默认值 + if (columnInfo.defaultValue.isEmpty()) { + columnMapping.defaultValueMapping?.let { defaultValueMapping -> + val defaultValueAnnotation = field.annotations.find { + it.annotationClass.qualifiedName == defaultValueMapping.annotationClass.qualifiedName + } + if (defaultValueAnnotation != null) { try { - // 提取索引信息 - val indexInfo = IndexInfo() - - // 获取索引名称 - val nameMethod = annotation.javaClass.getMethod("name") - val indexName = nameMethod.invoke(annotation) as String - indexInfo.name = if (indexName.isNotEmpty()) indexName else "idx_${sqlInfo.tableName}_${System.currentTimeMillis()}" - - // 获取唯一性 - val uniqueMethod = annotation.javaClass.getMethod("unique") - indexInfo.unique = uniqueMethod.invoke(annotation) as Boolean - - // 获取并发创建选项 - val concurrentMethod = annotation.javaClass.getMethod("concurrent") - indexInfo.concurrent = concurrentMethod.invoke(annotation) as Boolean - - // 获取列名列表 - val columnNamesMethod = annotation.javaClass.getMethod("columnNames") - val columnNames = columnNamesMethod.invoke(annotation) as Array<*> - indexInfo.columnNames = columnNames.map { it.toString() } - - // 获取自定义定义 - val definitionMethod = annotation.javaClass.getMethod("definition") - val definition = definitionMethod.invoke(annotation) as String - indexInfo.definition = definition - - // 只有当至少有一个列名或自定义定义时才添加索引 - if (indexInfo.columnNames.isNotEmpty() || indexInfo.definition.isNotEmpty()) { - sqlInfo.indexes.add(indexInfo) - } + val defaultValueMethod = + defaultValueAnnotation.javaClass.getMethod(defaultValueMapping.propertyName) + columnInfo.defaultValue = defaultValueMethod.invoke(defaultValueAnnotation) as String } catch (e: Exception) { - // 处理单个索引注解失败时记录错误但继续处理其他索引 - println("处理索引注解时出错: ${e.message}") + throw IllegalArgumentException("处理字段 ${field.name} 的默认值映射时出错: ${e.message}", e) } + } } + } + + sqlInfo.columns.add(columnInfo) + processedFields.add(field.name) // 记录已处理的字段 } catch (e: Exception) { - // 处理索引注解整体失败时记录错误 - println("处理表 ${sqlInfo.tableName} 的索引注解时出错: ${e.message}") + 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 + } + // 只要字段有@TableId注解,不管属性值如何,都视为主键 + if (pkAnnotation != null) { + // 如果字段还未处理,创建默认列信息 + if (!processedFields.contains(field.name)) { + columnInfo.name = StrUtil.toUnderlineCase(field.name) + columnInfo.type = inferSqlType(field.type, columnInfo, mapper) + columnInfo.isPrimaryKey = true + // 主键不可为空 + columnInfo.nullable = false + sqlInfo.columns.add(columnInfo) + sqlInfo.primaryKeys.add(columnInfo.name) + processedFields.add(field.name) + } else { + // 如果已处理,找到对应列并标记为主键 + val column = + sqlInfo.columns.find { it.name == StrUtil.toUnderlineCase(field.name) || it.name == field.name } + if (column != null) { + column.isPrimaryKey = true + // 主键不可为空 + column.nullable = false + if (!sqlInfo.primaryKeys.contains(column.name)) { + sqlInfo.primaryKeys.add(column.name) + } + } + } + } + } + + // 如果字段未被处理,并且不是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 = StrUtil.toUnderlineCase(field.name), + type = "", // 先不设置类型 + nullable = isNullable, + defaultValue = "", + isPrimaryKey = false + ) + + // 设置类型并处理枚举值 + defaultColumnInfo.type = inferSqlType(field.type, defaultColumnInfo, mapper) + + sqlInfo.columns.add(defaultColumnInfo) + processedFields.add(field.name) + } + } catch (e: Exception) { + throw IllegalArgumentException( + "处理实体类 ${entityClass.simpleName} 的字段 ${field.name} 时出错: ${e.message}", + e + ) } - - /** - * 将驼峰命名转换为蛇形命名 - */ - 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, mapper: SqlAnnotationMapper? = null): String { - val sqlType = when { - javaType == String::class.java -> { - if (columnInfo != null) { - "VARCHAR(${columnInfo.length})" - } else { - "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()) { - // 查找带有EnumValue注解的方法 - val enumValues = if (mapper?.enumValueMapping != null) { - val enumValueMethod = javaType.methods.find { method -> - method.annotations.any { - it.annotationClass.qualifiedName == mapper.enumValueMapping!!.annotationClass.qualifiedName - } - } - if (enumValueMethod != null) { - // 使用EnumValue标注的方法获取枚举值 - enumConstants.map { enumValueMethod.invoke(it).toString() } - } else { - // 如果没有找到EnumValue注解的方法,使用枚举名称 - enumConstants.map { (it as Enum<*>).name } - } - } else { - // 默认使用枚举的toString() - enumConstants.map { it.toString() } - } - columnInfo.enumValues = enumValues - } - } catch (e: Exception) { - // 忽略枚举值提取失败的情况 - println("提取枚举值失败: ${e.message}") - } - } - "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 - } + } + + // 验证结果 + if (sqlInfo.tableName.isEmpty()) { + throw IllegalArgumentException("实体类 ${entityClass.simpleName} 的表名为空,请检查表名注解") + } + + if (sqlInfo.columns.isEmpty()) { + throw IllegalArgumentException("实体类 ${entityClass.simpleName} 没有可用的列信息,请检查列注解") + } + + // 处理表级别的索引注解 + processTableIndexes(entityClass, sqlInfo, mapper) + + return sqlInfo } + + /** + * 处理表级别的索引注解 + */ + private fun processTableIndexes(entityClass: KClass<*>, sqlInfo: SqlInfo, mapper: SqlAnnotationMapper) { + // 只有当有indexMapping配置时才处理 + if (mapper.indexMapping == null) { + return + } + + try { + val indexMapping = mapper.indexMapping!! + // 查找类上的所有TableIndex注解 + val tableIndexAnnotations = entityClass.annotations.filter { + it.annotationClass.qualifiedName == indexMapping.annotationClass.qualifiedName + } + + // 处理每个TableIndex注解 + tableIndexAnnotations.forEach { annotation -> + try { + // 提取索引信息 + val indexInfo = IndexInfo() + + // 获取索引名称 + val nameMethod = annotation.javaClass.getMethod("name") + val indexName = nameMethod.invoke(annotation) as String + indexInfo.name = + if (indexName.isNotEmpty()) indexName else "idx_${sqlInfo.tableName}_${System.currentTimeMillis()}" + + // 获取唯一性 + val uniqueMethod = annotation.javaClass.getMethod("unique") + indexInfo.unique = uniqueMethod.invoke(annotation) as Boolean + + // 获取并发创建选项 + val concurrentMethod = annotation.javaClass.getMethod("concurrent") + indexInfo.concurrent = concurrentMethod.invoke(annotation) as Boolean + + // 获取列名列表 + val columnNamesMethod = annotation.javaClass.getMethod("columnNames") + val columnNames = columnNamesMethod.invoke(annotation) as Array<*> + indexInfo.columnNames = columnNames.map { it.toString() } + + // 获取自定义定义 + val definitionMethod = annotation.javaClass.getMethod("definition") + val definition = definitionMethod.invoke(annotation) as String + indexInfo.definition = definition + + // 只有当至少有一个列名或自定义定义时才添加索引 + if (indexInfo.columnNames.isNotEmpty() || indexInfo.definition.isNotEmpty()) { + sqlInfo.indexes.add(indexInfo) + } + } catch (e: Exception) { + // 处理单个索引注解失败时记录错误但继续处理其他索引 + println("处理索引注解时出错: ${e.message}") + } + } + } catch (e: Exception) { + // 处理索引注解整体失败时记录错误 + println("处理表 ${sqlInfo.tableName} 的索引注解时出错: ${e.message}") + } + } + + /** + * 根据Java类型推断SQL类型 + */ + private fun inferSqlType( + javaType: Class<*>, + columnInfo: ColumnInfo? = null, + mapper: SqlAnnotationMapper? = null + ): String { + val sqlType = when { + javaType == String::class.java -> { + if (columnInfo != null) { + "VARCHAR(${columnInfo.length})" + } else { + "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()) { + // 查找带有EnumValue注解的方法 + val enumValues = if (mapper?.enumValueMapping != null) { + val enumValueMethod = javaType.methods.find { method -> + method.annotations.any { + it.annotationClass.qualifiedName == mapper.enumValueMapping!!.annotationClass.qualifiedName + } + } + if (enumValueMethod != null) { + // 使用EnumValue标注的方法获取枚举值 + enumConstants.map { enumValueMethod.invoke(it).toString() } + } else { + // 如果没有找到EnumValue注解的方法,使用枚举名称 + enumConstants.map { (it as Enum<*>).name } + } + } else { + // 默认使用枚举的toString() + enumConstants.map { it.toString() } + } + columnInfo.enumValues = enumValues + + // 如果字段有默认值,确保默认值是通过带EnumValue的方法获取的 + val defaultEnumValue = javaType.enumConstants.find { (it as Enum<*>).name == columnInfo.defaultValue } + if (defaultEnumValue != null && mapper?.enumValueMapping != null) { + val enumValueMethod = javaType.methods.find { method -> + method.annotations.any { + it.annotationClass.qualifiedName == mapper.enumValueMapping!!.annotationClass.qualifiedName + } + } + if (enumValueMethod != null) { + // 更新默认值为EnumValue方法的返回值 + columnInfo.defaultValue = enumValueMethod.invoke(defaultEnumValue).toString() + } + } + } + } catch (e: Exception) { + // 忽略枚举值提取失败的情况 + println("提取枚举值失败: ${e.message}") + } + } + "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 + } + } } \ No newline at end of file diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlGenerator.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlGenerator.kt index dbe6151..86a68ab 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlGenerator.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlGenerator.kt @@ -29,27 +29,39 @@ class SqlGenerator { val columnDefinitions = columns.map { column -> // 特殊处理一些常见约定字段 val specialFieldDefaults = getSpecialFieldDefaults(column.name, column.type) - val defaultValue = if (column.defaultValue.isNotEmpty()) { + var defaultValue = "" + + // 处理默认值 + if (column.defaultValue.isNotEmpty()) { // 对于时间戳类型字段,特殊处理NOW()函数作为默认值 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 { - " DEFAULT ${column.defaultValue}" + defaultValue = " DEFAULT ${column.defaultValue}" } } else if (specialFieldDefaults.isNotEmpty()) { - specialFieldDefaults + defaultValue = 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 -> "" + column.type.contains("VARCHAR") -> defaultValue = " DEFAULT ''" + column.type == "INTEGER" || column.type == "BIGINT" -> defaultValue = " DEFAULT 0" + column.type == "BOOLEAN" -> defaultValue = " DEFAULT false" + column.type.contains("JSON") -> defaultValue = " DEFAULT '{}'" } } val nullable = if (column.nullable) "" else " NOT NULL" + // 移除内联注释,改用COMMENT ON语句 " ${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() } for (column in enumColumns) { 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(column.enumValues!!.joinToString(",") { "'$it'" }) + if (allNumeric) { + // 如果全是数字,不需要加引号 + sb.append(column.enumValues!!.joinToString(",")) + } else { + // 否则加上引号 + sb.append(column.enumValues!!.joinToString(",") { "'$it'" }) + } sb.append("))") } } @@ -78,6 +99,16 @@ class SqlGenerator { 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) if (indexSql.isNotEmpty()) { @@ -151,7 +182,13 @@ class SqlGenerator { } // 状态字段 fieldName == "status" || fieldName == "state" -> { - if (fieldType.contains("VARCHAR")) " DEFAULT 'NORMAL'" else "" + if (fieldType.contains("VARCHAR")) { + // 默认为空值,实际值将通过字段的默认值处理 + "" + } else { + // 对于数字类型状态默认为0 + " DEFAULT 0" + } } else -> "" } diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlInfo.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlInfo.kt index d4923c7..6dd32d1 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlInfo.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlInfo.kt @@ -22,7 +22,8 @@ data class ColumnInfo( var isPrimaryKey: Boolean = false, var enumValues: List? = null, var unique: Boolean = false, - var length: Int = 255 + var length: Int = 255, + var comment: String = "" ) /** diff --git a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt index 7125a07..6a0aa72 100644 --- a/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt +++ b/vertx-fw/src/main/kotlin/org/aikrai/vertx/db/migration/SqlMigrationGenerator.kt @@ -96,7 +96,7 @@ class SqlMigrationGenerator { createDirectories() log("开始扫描实体类...") - val entityClasses = scanEntityClasses(entityPackage) + val entityClasses = scanEntityClasses(mapper, entityPackage) if (entityClasses.isEmpty()) { throw IllegalArgumentException("未找到任何实体类,请检查包路径: $entityPackage") @@ -156,9 +156,11 @@ class SqlMigrationGenerator { /** * 扫描包路径下标记了@TableName注解的实体类 */ - private fun scanEntityClasses(packagePath: String): List> { + private fun scanEntityClasses(mapper: SqlAnnotationMapper, packagePath: String): List> { + val entityAnnotationClass = mapper.entityMapping?.annotationClass?.java + ?: throw IllegalStateException("实体类注解映射未设置,请配置SqlAnnotationMapper.entityMapping") val reflections = Reflections(packagePath) - val entityClasses = reflections.getTypesAnnotatedWith(TableName::class.java) + val entityClasses = reflections.getTypesAnnotatedWith(entityAnnotationClass) return entityClasses.map { it.kotlin } } @@ -477,17 +479,23 @@ class SqlMigrationGenerator { doc: Document, changeSetElement: Element ) { + // 检查是否需要生成删除语句 + val generateDropStatements = System.getProperty("ddl.migration.generateDropStatements", "false").toBoolean() + tablesToDrop.forEach { tableName -> - // 生成SQL - sqlBuilder.append("drop table if exists $tableName cascade;\n") + // 只有当配置为生成删除语句时才添加到SQL中 + if (generateDropStatements) { + // 生成SQL + sqlBuilder.append("drop table if exists $tableName cascade;\n") + } - // 添加到XML + // 无论如何都添加到XML中,以记录历史变更 val dropTableElement = doc.createElement("dropTable") dropTableElement.setAttribute("name", tableName) changeSetElement.appendChild(dropTableElement) } - if (tablesToDrop.isNotEmpty()) { + if (generateDropStatements && tablesToDrop.isNotEmpty()) { sqlBuilder.append("\n") } } @@ -538,27 +546,38 @@ class SqlMigrationGenerator { * 处理删除列 */ private fun processDropColumns( - columnsToDrop: Map>, + columnsToDrop: Map>, sqlBuilder: StringBuilder, doc: Document, changeSetElement: Element ) { + // 检查是否需要生成删除语句 + val generateDropStatements = System.getProperty("ddl.migration.generateDropStatements", "false").toBoolean() + + var addedSql = false + columnsToDrop.forEach { (tableName, columns) -> - // 为每个表创建一个dropColumn元素 - val dropColumnElement = doc.createElement("dropColumn") - dropColumnElement.setAttribute("tableName", tableName) - changeSetElement.appendChild(dropColumnElement) - - columns.forEach { columnName -> - // 生成SQL - sqlBuilder.append("alter table $tableName drop column if exists $columnName;\n") + if (columns.isNotEmpty()) { + val alterTableElement = doc.createElement("alterTable") + alterTableElement.setAttribute("name", tableName) + changeSetElement.appendChild(alterTableElement) - // 添加到XML - val columnElement = doc.createElement("column") - columnElement.setAttribute("name", columnName) - dropColumnElement.appendChild(columnElement) + columns.forEach { columnName -> + // 只有当配置为生成删除语句时才添加到SQL中 + if (generateDropStatements) { + 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") } }