1
This commit is contained in:
parent
4f31d1517f
commit
530f682f17
@ -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")
|
||||||
|
|||||||
@ -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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 设置索引映射
|
// 设置索引映射
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package org.aikrai.vertx.db.migration
|
package org.aikrai.vertx.db.migration
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -19,12 +20,6 @@ class SqlAnnotationMapperGenerator {
|
|||||||
fun extractSqlInfo(entityClass: KClass<*>, mapper: SqlAnnotationMapper): SqlInfo {
|
fun extractSqlInfo(entityClass: KClass<*>, mapper: SqlAnnotationMapper): SqlInfo {
|
||||||
val sqlInfo = SqlInfo()
|
val sqlInfo = SqlInfo()
|
||||||
|
|
||||||
// 验证实体类注解
|
|
||||||
val isEntityClass = validateEntityClass(entityClass, mapper)
|
|
||||||
if (!isEntityClass) {
|
|
||||||
throw IllegalArgumentException("类 ${entityClass.simpleName} 不是有效的实体类,未标记所需的实体类注解")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取表名 - 优先从表名注解中获取,如果没有则使用类名转换
|
// 获取表名 - 优先从表名注解中获取,如果没有则使用类名转换
|
||||||
try {
|
try {
|
||||||
if (mapper.tableName != null) {
|
if (mapper.tableName != null) {
|
||||||
@ -38,23 +33,23 @@ class SqlAnnotationMapperGenerator {
|
|||||||
val method = annotation.javaClass.getMethod(tableNameMapping.propertyName)
|
val method = annotation.javaClass.getMethod(tableNameMapping.propertyName)
|
||||||
val tableName = method.invoke(annotation) as String
|
val tableName = method.invoke(annotation) as String
|
||||||
|
|
||||||
if (tableName.isNotEmpty()) {
|
if (tableName.isNotBlank()) {
|
||||||
sqlInfo.tableName = tableName
|
sqlInfo.tableName = tableName
|
||||||
} else {
|
} else {
|
||||||
// 使用类名转蛇形命名作为表名
|
// 使用类名转蛇形命名作为表名
|
||||||
sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "")
|
sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// 使用类名转蛇形命名作为表名
|
// 使用类名转蛇形命名作为表名
|
||||||
sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "")
|
sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 使用类名转蛇形命名作为表名
|
// 使用类名转蛇形命名作为表名
|
||||||
sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "")
|
sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 使用类名转蛇形命名作为表名
|
// 使用类名转蛇形命名作为表名
|
||||||
sqlInfo.tableName = toSnakeCase(entityClass.simpleName ?: "")
|
sqlInfo.tableName = StrUtil.toUnderlineCase(entityClass.simpleName ?: "")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的表名时出错: ${e.message}", e)
|
throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的表名时出错: ${e.message}", e)
|
||||||
@ -101,12 +96,12 @@ class SqlAnnotationMapperGenerator {
|
|||||||
it.annotationClass.qualifiedName == columnMapping.nameMapping.annotationClass.qualifiedName
|
it.annotationClass.qualifiedName == columnMapping.nameMapping.annotationClass.qualifiedName
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nameAnnotation != null) {
|
|
||||||
foundColumnMapping = true
|
foundColumnMapping = true
|
||||||
try {
|
try {
|
||||||
val nameMethod = nameAnnotation.javaClass.getMethod(columnMapping.nameMapping.propertyName)
|
val nameMethod = nameAnnotation?.javaClass?.getMethod(columnMapping.nameMapping.propertyName)
|
||||||
val columnName = nameMethod.invoke(nameAnnotation) as? String
|
val columnName = nameMethod?.invoke(nameAnnotation) as? String
|
||||||
columnInfo.name = if (columnName.isNullOrEmpty()) toSnakeCase(field.name) else columnName
|
columnInfo.name = if (columnName.isNullOrEmpty()) StrUtil.toUnderlineCase(field.name) else columnName
|
||||||
|
|
||||||
// 处理类型映射
|
// 处理类型映射
|
||||||
columnMapping.typeMapping?.let { typeMapping ->
|
columnMapping.typeMapping?.let { typeMapping ->
|
||||||
@ -132,6 +127,48 @@ class SqlAnnotationMapperGenerator {
|
|||||||
columnInfo.type = inferSqlType(field.type, columnInfo, mapper)
|
columnInfo.type = inferSqlType(field.type, columnInfo, mapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查字段是否为枚举类型,并且有默认初始值
|
||||||
|
if (field.type.isEnum) {
|
||||||
|
try {
|
||||||
|
// 使字段可访问
|
||||||
|
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 (enumValueMethod != null) {
|
||||||
|
// 获取枚举值并设置为默认值
|
||||||
|
val enumValue = enumValueMethod.invoke(defaultEnumValue)
|
||||||
|
if (enumValue != null) {
|
||||||
|
columnInfo.defaultValue = enumValue.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 忽略获取默认值失败的情况
|
||||||
|
println("获取枚举默认值失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理可空映射
|
// 处理可空映射
|
||||||
columnMapping.nullableMapping?.let { nullableMapping ->
|
columnMapping.nullableMapping?.let { nullableMapping ->
|
||||||
val nullableAnnotation = field.annotations.find {
|
val nullableAnnotation = field.annotations.find {
|
||||||
@ -147,21 +184,6 @@ class SqlAnnotationMapperGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理默认值映射
|
|
||||||
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 ->
|
columnMapping.lengthMapping?.let { lengthMapping ->
|
||||||
val lengthAnnotation = field.annotations.find {
|
val lengthAnnotation = field.annotations.find {
|
||||||
@ -202,12 +224,48 @@ class SqlAnnotationMapperGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理注释映射
|
||||||
|
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 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)
|
sqlInfo.columns.add(columnInfo)
|
||||||
processedFields.add(field.name) // 记录已处理的字段
|
processedFields.add(field.name) // 记录已处理的字段
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw IllegalArgumentException("处理字段 ${field.name} 时出错: ${e.message}", e)
|
throw IllegalArgumentException("处理字段 ${field.name} 时出错: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理主键
|
// 处理主键
|
||||||
@ -216,47 +274,46 @@ class SqlAnnotationMapperGenerator {
|
|||||||
val pkAnnotation = field.annotations.find {
|
val pkAnnotation = field.annotations.find {
|
||||||
it.annotationClass.qualifiedName == pkMapping.annotationClass.qualifiedName
|
it.annotationClass.qualifiedName == pkMapping.annotationClass.qualifiedName
|
||||||
}
|
}
|
||||||
|
// 只要字段有@TableId注解,不管属性值如何,都视为主键
|
||||||
if (pkAnnotation != null) {
|
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)) {
|
if (!processedFields.contains(field.name)) {
|
||||||
columnInfo.name = toSnakeCase(field.name)
|
columnInfo.name = StrUtil.toUnderlineCase(field.name)
|
||||||
columnInfo.type = inferSqlType(field.type, columnInfo, mapper)
|
columnInfo.type = inferSqlType(field.type, columnInfo, mapper)
|
||||||
columnInfo.isPrimaryKey = true
|
columnInfo.isPrimaryKey = true
|
||||||
|
// 主键不可为空
|
||||||
|
columnInfo.nullable = false
|
||||||
sqlInfo.columns.add(columnInfo)
|
sqlInfo.columns.add(columnInfo)
|
||||||
sqlInfo.primaryKeys.add(columnInfo.name)
|
sqlInfo.primaryKeys.add(columnInfo.name)
|
||||||
processedFields.add(field.name)
|
processedFields.add(field.name)
|
||||||
} else {
|
} else {
|
||||||
// 如果已处理,找到对应列并标记为主键
|
// 如果已处理,找到对应列并标记为主键
|
||||||
val column = sqlInfo.columns.find { it.name == toSnakeCase(field.name) || it.name == field.name }
|
val column =
|
||||||
|
sqlInfo.columns.find { it.name == StrUtil.toUnderlineCase(field.name) || it.name == field.name }
|
||||||
if (column != null) {
|
if (column != null) {
|
||||||
column.isPrimaryKey = true
|
column.isPrimaryKey = true
|
||||||
|
// 主键不可为空
|
||||||
|
column.nullable = false
|
||||||
if (!sqlInfo.primaryKeys.contains(column.name)) {
|
if (!sqlInfo.primaryKeys.contains(column.name)) {
|
||||||
sqlInfo.primaryKeys.add(column.name)
|
sqlInfo.primaryKeys.add(column.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
throw IllegalArgumentException("处理字段 ${field.name} 的主键映射时出错: ${e.message}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果字段未被处理,并且不是static或transient,添加默认处理
|
// 如果字段未被处理,并且不是static或transient,添加默认处理
|
||||||
if (!processedFields.contains(field.name) &&
|
if (!processedFields.contains(field.name) &&
|
||||||
!java.lang.reflect.Modifier.isStatic(field.modifiers) &&
|
!java.lang.reflect.Modifier.isStatic(field.modifiers) &&
|
||||||
!java.lang.reflect.Modifier.isTransient(field.modifiers)) {
|
!java.lang.reflect.Modifier.isTransient(field.modifiers)
|
||||||
|
) {
|
||||||
|
|
||||||
// 检查字段类型是否可空
|
// 检查字段类型是否可空
|
||||||
val isNullable = isNullableType(field)
|
val isNullable = isNullableType(field)
|
||||||
|
|
||||||
// 创建默认列信息
|
// 创建默认列信息
|
||||||
val defaultColumnInfo = ColumnInfo(
|
val defaultColumnInfo = ColumnInfo(
|
||||||
name = toSnakeCase(field.name),
|
name = StrUtil.toUnderlineCase(field.name),
|
||||||
type = "", // 先不设置类型
|
type = "", // 先不设置类型
|
||||||
nullable = isNullable,
|
nullable = isNullable,
|
||||||
defaultValue = "",
|
defaultValue = "",
|
||||||
@ -270,7 +327,10 @@ class SqlAnnotationMapperGenerator {
|
|||||||
processedFields.add(field.name)
|
processedFields.add(field.name)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw IllegalArgumentException("处理实体类 ${entityClass.simpleName} 的字段 ${field.name} 时出错: ${e.message}", e)
|
throw IllegalArgumentException(
|
||||||
|
"处理实体类 ${entityClass.simpleName} 的字段 ${field.name} 时出错: ${e.message}",
|
||||||
|
e
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,21 +349,6 @@ class SqlAnnotationMapperGenerator {
|
|||||||
return sqlInfo
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理表级别的索引注解
|
* 处理表级别的索引注解
|
||||||
*/
|
*/
|
||||||
@ -329,7 +374,8 @@ class SqlAnnotationMapperGenerator {
|
|||||||
// 获取索引名称
|
// 获取索引名称
|
||||||
val nameMethod = annotation.javaClass.getMethod("name")
|
val nameMethod = annotation.javaClass.getMethod("name")
|
||||||
val indexName = nameMethod.invoke(annotation) as String
|
val indexName = nameMethod.invoke(annotation) as String
|
||||||
indexInfo.name = if (indexName.isNotEmpty()) indexName else "idx_${sqlInfo.tableName}_${System.currentTimeMillis()}"
|
indexInfo.name =
|
||||||
|
if (indexName.isNotEmpty()) indexName else "idx_${sqlInfo.tableName}_${System.currentTimeMillis()}"
|
||||||
|
|
||||||
// 获取唯一性
|
// 获取唯一性
|
||||||
val uniqueMethod = annotation.javaClass.getMethod("unique")
|
val uniqueMethod = annotation.javaClass.getMethod("unique")
|
||||||
@ -364,17 +410,14 @@ class SqlAnnotationMapperGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将驼峰命名转换为蛇形命名
|
|
||||||
*/
|
|
||||||
private fun toSnakeCase(camelCase: String): String {
|
|
||||||
return camelCase.replace(Regex("([a-z])([A-Z])"), "$1_$2").toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据Java类型推断SQL类型
|
* 根据Java类型推断SQL类型
|
||||||
*/
|
*/
|
||||||
private fun inferSqlType(javaType: Class<*>, columnInfo: ColumnInfo? = null, mapper: SqlAnnotationMapper? = null): String {
|
private fun inferSqlType(
|
||||||
|
javaType: Class<*>,
|
||||||
|
columnInfo: ColumnInfo? = null,
|
||||||
|
mapper: SqlAnnotationMapper? = null
|
||||||
|
): String {
|
||||||
val sqlType = when {
|
val sqlType = when {
|
||||||
javaType == String::class.java -> {
|
javaType == String::class.java -> {
|
||||||
if (columnInfo != null) {
|
if (columnInfo != null) {
|
||||||
@ -383,6 +426,7 @@ class SqlAnnotationMapperGenerator {
|
|||||||
"VARCHAR(255)"
|
"VARCHAR(255)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
javaType == Int::class.java || javaType == Integer::class.java -> "INTEGER"
|
javaType == Int::class.java || javaType == Integer::class.java -> "INTEGER"
|
||||||
javaType == Long::class.java || javaType == java.lang.Long::class.java -> "BIGINT"
|
javaType == Long::class.java || javaType == java.lang.Long::class.java -> "BIGINT"
|
||||||
javaType == Double::class.java || javaType == java.lang.Double::class.java -> "DOUBLE PRECISION"
|
javaType == Double::class.java || javaType == java.lang.Double::class.java -> "DOUBLE PRECISION"
|
||||||
@ -424,6 +468,20 @@ class SqlAnnotationMapperGenerator {
|
|||||||
enumConstants.map { it.toString() }
|
enumConstants.map { it.toString() }
|
||||||
}
|
}
|
||||||
columnInfo.enumValues = enumValues
|
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) {
|
} catch (e: Exception) {
|
||||||
// 忽略枚举值提取失败的情况
|
// 忽略枚举值提取失败的情况
|
||||||
@ -432,6 +490,7 @@ class SqlAnnotationMapperGenerator {
|
|||||||
}
|
}
|
||||||
"VARCHAR(50)"
|
"VARCHAR(50)"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> "VARCHAR(255)"
|
else -> "VARCHAR(255)"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,7 +563,8 @@ class SqlAnnotationMapperGenerator {
|
|||||||
field.type == Double::class.java ||
|
field.type == Double::class.java ||
|
||||||
field.type == Char::class.java ||
|
field.type == Char::class.java ||
|
||||||
field.type == Byte::class.java ||
|
field.type == Byte::class.java ||
|
||||||
field.type == Short::class.java) {
|
field.type == Short::class.java
|
||||||
|
) {
|
||||||
// Kotlin基本类型不带?就不可为空
|
// Kotlin基本类型不带?就不可为空
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
} else {
|
||||||
" DEFAULT ${column.defaultValue}"
|
defaultValue = " DEFAULT '${column.defaultValue}'"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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 (")
|
||||||
|
if (allNumeric) {
|
||||||
|
// 如果全是数字,不需要加引号
|
||||||
|
sb.append(column.enumValues!!.joinToString(","))
|
||||||
|
} else {
|
||||||
|
// 否则加上引号
|
||||||
sb.append(column.enumValues!!.joinToString(",") { "'$it'" })
|
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 -> ""
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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中
|
||||||
|
if (generateDropStatements) {
|
||||||
// 生成SQL
|
// 生成SQL
|
||||||
sqlBuilder.append("drop table if exists $tableName cascade;\n")
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -543,22 +551,33 @@ class SqlMigrationGenerator {
|
|||||||
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 ->
|
columns.forEach { columnName ->
|
||||||
// 生成SQL
|
// 只有当配置为生成删除语句时才添加到SQL中
|
||||||
|
if (generateDropStatements) {
|
||||||
sqlBuilder.append("alter table $tableName drop column if exists $columnName;\n")
|
sqlBuilder.append("alter table $tableName drop column if exists $columnName;\n")
|
||||||
|
addedSql = true
|
||||||
// 添加到XML
|
|
||||||
val columnElement = doc.createElement("column")
|
|
||||||
columnElement.setAttribute("name", columnName)
|
|
||||||
dropColumnElement.appendChild(columnElement)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 无论如何都添加到XML中,以记录历史变更
|
||||||
|
val dropColumnElement = doc.createElement("dropColumn")
|
||||||
|
dropColumnElement.setAttribute("columnName", columnName)
|
||||||
|
alterTableElement.appendChild(dropColumnElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedSql) {
|
||||||
sqlBuilder.append("\n")
|
sqlBuilder.append("\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user