1
This commit is contained in:
parent
d126bd5783
commit
63a18f1dbd
146
vertx-demo/src/main/kotlin/app/example/SqlAnnotationExample.kt
Normal file
146
vertx-demo/src/main/kotlin/app/example/SqlAnnotationExample.kt
Normal file
@ -0,0 +1,146 @@
|
||||
package app.example
|
||||
|
||||
import app.util.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* 表名注解示例
|
||||
*/
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class TableName(val value: String)
|
||||
|
||||
/**
|
||||
* 列名注解示例
|
||||
*/
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Column(val value: String, val type: String = "VARCHAR(255)")
|
||||
|
||||
/**
|
||||
* 主键注解示例
|
||||
*/
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Id(val value: Boolean = true)
|
||||
|
||||
/**
|
||||
* 非空注解示例
|
||||
*/
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class NotNull(val value: Boolean = true)
|
||||
|
||||
/**
|
||||
* 默认值注解示例
|
||||
*/
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class DefaultValue(val value: String)
|
||||
|
||||
/**
|
||||
* 用户实体类示例
|
||||
*/
|
||||
@TableName("tb_user")
|
||||
class User {
|
||||
@Id
|
||||
@Column("id", "SERIAL")
|
||||
@NotNull
|
||||
var id: Long = 0
|
||||
|
||||
@Column("username")
|
||||
@NotNull
|
||||
var username: String = ""
|
||||
|
||||
@Column("email")
|
||||
var email: String? = null
|
||||
|
||||
@Column("created_at", "TIMESTAMP")
|
||||
@DefaultValue("CURRENT_TIMESTAMP")
|
||||
var createdAt: String = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL注解映射示例
|
||||
*/
|
||||
class SqlAnnotationExample {
|
||||
companion object {
|
||||
/**
|
||||
* 创建注解映射器
|
||||
*/
|
||||
fun createMapper(): SqlAnnotationMapper {
|
||||
val mapper = SqlAnnotationMapper()
|
||||
|
||||
// 设置表名映射
|
||||
mapper.tableName = AnnotationMapping(
|
||||
annotationClass = TableName::class,
|
||||
propertyName = "value"
|
||||
)
|
||||
|
||||
// 设置列名映射
|
||||
mapper.addColumnMapping(ColumnMapping(
|
||||
nameMapping = AnnotationMapping(
|
||||
annotationClass = Column::class,
|
||||
propertyName = "value"
|
||||
),
|
||||
typeMapping = AnnotationMapping(
|
||||
annotationClass = Column::class,
|
||||
propertyName = "type"
|
||||
),
|
||||
nullableMapping = AnnotationMapping(
|
||||
annotationClass = NotNull::class,
|
||||
propertyName = "value"
|
||||
),
|
||||
defaultValueMapping = AnnotationMapping(
|
||||
annotationClass = DefaultValue::class,
|
||||
propertyName = "value"
|
||||
)
|
||||
))
|
||||
|
||||
// 设置主键映射
|
||||
mapper.primaryKeyMapping = AnnotationMapping(
|
||||
annotationClass = Id::class,
|
||||
propertyName = "value"
|
||||
)
|
||||
|
||||
return mapper
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试SQL生成
|
||||
*/
|
||||
fun testSqlGeneration() {
|
||||
// 创建注解映射器
|
||||
val mapper = createMapper()
|
||||
|
||||
// 生成各种SQL
|
||||
val createTableSql = SqlGenerator.generateCreateTableSql(User::class, mapper)
|
||||
val insertSql = SqlGenerator.generateInsertSql(User::class, mapper)
|
||||
val updateSql = SqlGenerator.generateUpdateSql(User::class, mapper)
|
||||
val deleteSql = SqlGenerator.generateDeleteSql(User::class, mapper)
|
||||
val selectSql = SqlGenerator.generateSelectSql(User::class, mapper)
|
||||
val selectByPkSql = SqlGenerator.generateSelectByPrimaryKeySql(User::class, mapper)
|
||||
|
||||
// 打印SQL
|
||||
println("Create Table SQL:")
|
||||
println(createTableSql)
|
||||
println("\nInsert SQL:")
|
||||
println(insertSql)
|
||||
println("\nUpdate SQL:")
|
||||
println(updateSql)
|
||||
println("\nDelete SQL:")
|
||||
println(deleteSql)
|
||||
println("\nSelect SQL:")
|
||||
println(selectSql)
|
||||
println("\nSelect By PK SQL:")
|
||||
println(selectByPkSql)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数,用于演示
|
||||
*/
|
||||
fun main() {
|
||||
SqlAnnotationExample.testSqlGeneration()
|
||||
}
|
||||
187
vertx-demo/src/main/kotlin/app/util/SqlAnnotationMapper.kt
Normal file
187
vertx-demo/src/main/kotlin/app/util/SqlAnnotationMapper.kt
Normal file
@ -0,0 +1,187 @@
|
||||
package app.util
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* SQL注解映射中间类
|
||||
* 用于记录从哪些注解获取SQL生成所需的信息
|
||||
*/
|
||||
class SqlAnnotationMapper {
|
||||
/**
|
||||
* 表名映射信息
|
||||
*/
|
||||
var tableName: AnnotationMapping? = null
|
||||
|
||||
/**
|
||||
* 列名映射信息列表
|
||||
*/
|
||||
var columnMappings: MutableList<ColumnMapping> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 主键映射信息
|
||||
*/
|
||||
var primaryKeyMapping: AnnotationMapping? = null
|
||||
|
||||
/**
|
||||
* 其他自定义映射
|
||||
*/
|
||||
var customMappings: MutableMap<String, AnnotationMapping> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* 添加一个列映射
|
||||
*/
|
||||
fun addColumnMapping(columnMapping: ColumnMapping) {
|
||||
columnMappings.add(columnMapping)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个自定义映射
|
||||
*/
|
||||
fun addCustomMapping(key: String, mapping: AnnotationMapping) {
|
||||
customMappings[key] = mapping
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注解映射类
|
||||
* 记录从哪个注解的哪个属性获取信息
|
||||
*/
|
||||
data class AnnotationMapping(
|
||||
/** 注解类 */
|
||||
val annotationClass: KClass<out Annotation>,
|
||||
/** 注解属性名 */
|
||||
val propertyName: String = "value"
|
||||
)
|
||||
|
||||
/**
|
||||
* 列映射信息
|
||||
*/
|
||||
data class ColumnMapping(
|
||||
/** 字段名称映射 */
|
||||
val nameMapping: AnnotationMapping,
|
||||
/** 字段类型映射,可选 */
|
||||
val typeMapping: AnnotationMapping? = null,
|
||||
/** 是否可为空映射,可选 */
|
||||
val nullableMapping: AnnotationMapping? = null,
|
||||
/** 默认值映射,可选 */
|
||||
val defaultValueMapping: AnnotationMapping? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* SQL注解映射生成器
|
||||
* 用于生成和使用SQL注解映射中间类
|
||||
*/
|
||||
class SqlAnnotationMapperGenerator {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* 从实体类获取SQL信息
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return SQL信息
|
||||
*/
|
||||
fun extractSqlInfo(entityClass: KClass<*>, mapper: SqlAnnotationMapper): SqlInfo {
|
||||
val sqlInfo = SqlInfo()
|
||||
|
||||
// 获取表名
|
||||
mapper.tableName?.let { tableNameMapping ->
|
||||
val annotation = entityClass.annotations.find { it.annotationClass == tableNameMapping.annotationClass.java }
|
||||
annotation?.let {
|
||||
val method = it.javaClass.getMethod(tableNameMapping.propertyName)
|
||||
sqlInfo.tableName = method.invoke(it) as String
|
||||
}
|
||||
}
|
||||
|
||||
// 获取实体类的所有字段
|
||||
entityClass.java.declaredFields.forEach { field ->
|
||||
// 获取列信息
|
||||
val columnInfo = ColumnInfo()
|
||||
|
||||
// 处理每个列映射
|
||||
mapper.columnMappings.forEach { columnMapping ->
|
||||
val nameAnnotation = field.annotations.find {
|
||||
it.annotationClass.java == columnMapping.nameMapping.annotationClass.java
|
||||
}
|
||||
|
||||
if (nameAnnotation != null) {
|
||||
val nameMethod = nameAnnotation.javaClass.getMethod(columnMapping.nameMapping.propertyName)
|
||||
columnInfo.name = nameMethod.invoke(nameAnnotation) as String? ?: field.name
|
||||
|
||||
// 处理类型映射
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sqlInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL信息类
|
||||
* 存储从实体类中提取的SQL相关信息
|
||||
*/
|
||||
data class SqlInfo(
|
||||
var tableName: String = "",
|
||||
val columns: MutableList<ColumnInfo> = mutableListOf(),
|
||||
val primaryKeys: MutableList<String> = mutableListOf()
|
||||
)
|
||||
|
||||
/**
|
||||
* 列信息类
|
||||
*/
|
||||
data class ColumnInfo(
|
||||
var name: String = "",
|
||||
var type: String = "",
|
||||
var nullable: Boolean = true,
|
||||
var defaultValue: String = "",
|
||||
var isPrimaryKey: Boolean = false
|
||||
)
|
||||
182
vertx-demo/src/main/kotlin/app/util/SqlGenerator.kt
Normal file
182
vertx-demo/src/main/kotlin/app/util/SqlGenerator.kt
Normal file
@ -0,0 +1,182 @@
|
||||
package app.util
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* PostgreSQL SQL生成工具类
|
||||
*/
|
||||
class SqlGenerator {
|
||||
companion object {
|
||||
/**
|
||||
* 生成创建表SQL
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return 创建表SQL语句
|
||||
*/
|
||||
fun generateCreateTableSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String {
|
||||
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
|
||||
val tableName = sqlInfo.tableName
|
||||
val columns = sqlInfo.columns
|
||||
|
||||
if (tableName.isEmpty() || columns.isEmpty()) {
|
||||
throw IllegalArgumentException("无法生成SQL,表名或列信息为空")
|
||||
}
|
||||
|
||||
val sb = StringBuilder()
|
||||
sb.append("CREATE TABLE IF NOT EXISTS $tableName (\n")
|
||||
|
||||
// 添加列定义
|
||||
val columnDefinitions = columns.map { column ->
|
||||
val nullable = if (column.nullable) "" else " NOT NULL"
|
||||
val defaultValue = if (column.defaultValue.isNotEmpty()) " DEFAULT ${column.defaultValue}" else ""
|
||||
" ${column.name} ${column.type}$nullable$defaultValue"
|
||||
}
|
||||
|
||||
sb.append(columnDefinitions.joinToString(",\n"))
|
||||
|
||||
// 添加主键
|
||||
if (sqlInfo.primaryKeys.isNotEmpty()) {
|
||||
sb.append(",\n PRIMARY KEY (${sqlInfo.primaryKeys.joinToString(", ")})")
|
||||
}
|
||||
|
||||
sb.append("\n);")
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成插入SQL
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return 插入SQL语句模板
|
||||
*/
|
||||
fun generateInsertSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String {
|
||||
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
|
||||
val tableName = sqlInfo.tableName
|
||||
val columns = sqlInfo.columns
|
||||
|
||||
if (tableName.isEmpty() || columns.isEmpty()) {
|
||||
throw IllegalArgumentException("无法生成SQL,表名或列信息为空")
|
||||
}
|
||||
|
||||
val columnNames = columns.map { it.name }
|
||||
val placeholders = columns.mapIndexed { index, _ -> "$$${index + 1}" }
|
||||
|
||||
return "INSERT INTO $tableName (${columnNames.joinToString(", ")}) VALUES (${placeholders.joinToString(", ")});"
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成更新SQL
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return 更新SQL语句模板
|
||||
*/
|
||||
fun generateUpdateSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String {
|
||||
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
|
||||
val tableName = sqlInfo.tableName
|
||||
val columns = sqlInfo.columns.filter { !it.isPrimaryKey }
|
||||
val primaryKeys = sqlInfo.columns.filter { it.isPrimaryKey }
|
||||
|
||||
if (tableName.isEmpty() || columns.isEmpty() || primaryKeys.isEmpty()) {
|
||||
throw IllegalArgumentException("无法生成SQL,表名、列信息或主键为空")
|
||||
}
|
||||
|
||||
val sb = StringBuilder()
|
||||
sb.append("UPDATE $tableName SET ")
|
||||
|
||||
// 设置列
|
||||
val setStatements = columns.mapIndexed { index, column ->
|
||||
"${column.name} = $$${index + 1}"
|
||||
}
|
||||
sb.append(setStatements.joinToString(", "))
|
||||
|
||||
// 添加条件
|
||||
sb.append(" WHERE ")
|
||||
val whereStatements = primaryKeys.mapIndexed { index, pk ->
|
||||
"${pk.name} = $$${columns.size + index + 1}"
|
||||
}
|
||||
sb.append(whereStatements.joinToString(" AND "))
|
||||
|
||||
sb.append(";")
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成删除SQL
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return 删除SQL语句模板
|
||||
*/
|
||||
fun generateDeleteSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String {
|
||||
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
|
||||
val tableName = sqlInfo.tableName
|
||||
val primaryKeys = sqlInfo.columns.filter { it.isPrimaryKey }
|
||||
|
||||
if (tableName.isEmpty() || primaryKeys.isEmpty()) {
|
||||
throw IllegalArgumentException("无法生成SQL,表名或主键为空")
|
||||
}
|
||||
|
||||
val sb = StringBuilder()
|
||||
sb.append("DELETE FROM $tableName WHERE ")
|
||||
|
||||
// 添加条件
|
||||
val whereStatements = primaryKeys.mapIndexed { index, pk ->
|
||||
"${pk.name} = $$${index + 1}"
|
||||
}
|
||||
sb.append(whereStatements.joinToString(" AND "))
|
||||
|
||||
sb.append(";")
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成查询SQL
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return 查询SQL语句
|
||||
*/
|
||||
fun generateSelectSql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String {
|
||||
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
|
||||
val tableName = sqlInfo.tableName
|
||||
val columns = sqlInfo.columns
|
||||
|
||||
if (tableName.isEmpty() || columns.isEmpty()) {
|
||||
throw IllegalArgumentException("无法生成SQL,表名或列信息为空")
|
||||
}
|
||||
|
||||
val columnNames = columns.map { it.name }
|
||||
|
||||
return "SELECT ${columnNames.joinToString(", ")} FROM $tableName;"
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成根据主键查询SQL
|
||||
* @param entityClass 实体类
|
||||
* @param mapper 注解映射中间类
|
||||
* @return 根据主键查询SQL语句
|
||||
*/
|
||||
fun generateSelectByPrimaryKeySql(entityClass: KClass<*>, mapper: SqlAnnotationMapper): String {
|
||||
val sqlInfo = SqlAnnotationMapperGenerator.extractSqlInfo(entityClass, mapper)
|
||||
val tableName = sqlInfo.tableName
|
||||
val columns = sqlInfo.columns
|
||||
val primaryKeys = sqlInfo.columns.filter { it.isPrimaryKey }
|
||||
|
||||
if (tableName.isEmpty() || columns.isEmpty() || primaryKeys.isEmpty()) {
|
||||
throw IllegalArgumentException("无法生成SQL,表名、列信息或主键为空")
|
||||
}
|
||||
|
||||
val columnNames = columns.map { it.name }
|
||||
|
||||
val sb = StringBuilder()
|
||||
sb.append("SELECT ${columnNames.joinToString(", ")} FROM $tableName WHERE ")
|
||||
|
||||
// 添加条件
|
||||
val whereStatements = primaryKeys.mapIndexed { index, pk ->
|
||||
"${pk.name} = $$${index + 1}"
|
||||
}
|
||||
sb.append(whereStatements.joinToString(" AND "))
|
||||
|
||||
sb.append(";")
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,63 +0,0 @@
|
||||
package app.util.sql
|
||||
|
||||
import org.aikrai.vertx.gen.SqlGen
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* SqlGen工具类使用示例
|
||||
*/
|
||||
object SqlGenExample {
|
||||
|
||||
/**
|
||||
* 示例方法
|
||||
*/
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
// 指定要扫描的包路径,例如"app.data.domain"
|
||||
val packageName = "app.data.domain"
|
||||
|
||||
// // 方式一:一步生成所有SQL
|
||||
// val sqlMap = SqlGen.generateSql(packageName)
|
||||
//
|
||||
// // 打印生成的SQL
|
||||
// println("=== 生成的SQL语句 ===")
|
||||
// sqlMap.forEach { (key: String, sql: String) ->
|
||||
// println("\n--- $key ---")
|
||||
// println(sql)
|
||||
// }
|
||||
|
||||
// 方式二:分步骤生成
|
||||
println("\n\n=== 分步骤生成SQL ===")
|
||||
val sqlGen = SqlGen()
|
||||
|
||||
// 1. 扫描实体类
|
||||
println("1. 开始扫描实体类...")
|
||||
val entities = sqlGen.scanEntities(packageName)
|
||||
println("发现 ${entities.size} 个实体类:")
|
||||
entities.forEach { entity: KClass<*> ->
|
||||
println("- ${entity.simpleName}")
|
||||
}
|
||||
|
||||
// 2. 解析实体类信息到缓存
|
||||
println("\n2. 解析实体类信息...")
|
||||
sqlGen.parseEntities(entities)
|
||||
|
||||
// 3. 生成建表SQL
|
||||
println("\n3. 生成建表SQL...")
|
||||
val createTableSql = sqlGen.generateCreateTableSql()
|
||||
createTableSql.forEach { sql: String ->
|
||||
println(sql)
|
||||
}
|
||||
|
||||
// 4. 生成CRUD SQL
|
||||
println("\n4. 生成CRUD SQL...")
|
||||
val crudSql = sqlGen.generateCrudSql()
|
||||
crudSql.forEach { (entityClass: KClass<*>, crud: SqlGen.CrudSql) ->
|
||||
println("\n--- ${entityClass.simpleName} ---")
|
||||
println("INSERT: ${crud.insertSql}")
|
||||
println("SELECT: ${crud.selectByIdSql}")
|
||||
println("UPDATE: ${crud.updateSql}")
|
||||
println("DELETE: ${crud.deleteSql}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,338 +0,0 @@
|
||||
package org.aikrai.vertx.gen
|
||||
|
||||
import org.aikrai.vertx.db.annotation.*
|
||||
import java.sql.Timestamp
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
/**
|
||||
* SQL生成工具类
|
||||
* 根据Kotlin实体类生成PostgreSQL SQL语句
|
||||
*/
|
||||
class SqlGen {
|
||||
|
||||
// 注解扫描器
|
||||
private val scanner = AnnotationScanner.create<TableName, String>(
|
||||
valueProperty = TableName::value,
|
||||
defaultValueMapper = { klass -> camelToSnake(klass.simpleName!!) }
|
||||
)
|
||||
|
||||
// 实体类缓存
|
||||
private val entityCache = mutableMapOf<KClass<*>, EntityCache>()
|
||||
|
||||
/**
|
||||
* 扫描指定包路径下的所有带有@TableName注解的类
|
||||
* @param packageName 包名,例如 "app.data.domain"
|
||||
* @return 带有@TableName注解的类列表
|
||||
*/
|
||||
fun scanEntities(packageName: String): List<KClass<*>> {
|
||||
return scanner.scanClasses(packageName)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析实体类并缓存信息
|
||||
* @param entityClasses 实体类列表
|
||||
*/
|
||||
fun parseEntities(entityClasses: List<KClass<*>>) {
|
||||
// 使用scanner解析类
|
||||
scanner.parseClasses(entityClasses)
|
||||
|
||||
// 将scanner的结果转换为entityCache
|
||||
scanner.getAnnotatedClasses().forEach { (klass, annotatedClass) ->
|
||||
val fields = mutableListOf<FieldCache>()
|
||||
val indices = mutableListOf<IndexCache>()
|
||||
|
||||
// 解析字段
|
||||
annotatedClass.properties.forEach { propertyInfo ->
|
||||
val fieldCache = parseField(propertyInfo.property as KProperty<*>)
|
||||
fields.add(fieldCache)
|
||||
}
|
||||
|
||||
// 解析索引
|
||||
val tableIndices = klass.findAnnotation<TableIndex>()
|
||||
if (tableIndices != null) {
|
||||
val indexCache = IndexCache(
|
||||
name = tableIndices.name.ifEmpty { "idx_${annotatedClass.value}_${tableIndices.columnNames.joinToString("_")}" },
|
||||
unique = tableIndices.unique,
|
||||
concurrent = tableIndices.concurrent,
|
||||
columnNames = tableIndices.columnNames.toList(),
|
||||
definition = tableIndices.definition
|
||||
)
|
||||
indices.add(indexCache)
|
||||
}
|
||||
|
||||
entityCache[klass] = EntityCache(
|
||||
entityClass = klass,
|
||||
tableName = annotatedClass.value,
|
||||
fields = fields,
|
||||
indices = indices
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析字段
|
||||
*/
|
||||
private fun parseField(property: KProperty<*>): FieldCache {
|
||||
val tableId = property.findAnnotation<TableId>()
|
||||
val tableField = property.findAnnotation<TableField>()
|
||||
|
||||
val isPrimary = tableId != null
|
||||
val idType = tableId?.type ?: IdType.NONE
|
||||
val columnName = tableField?.value?.ifEmpty { camelToSnake(property.name) } ?: camelToSnake(property.name)
|
||||
val fieldFill = tableField?.fill ?: FieldFill.DEFAULT
|
||||
|
||||
val returnType = property.returnType
|
||||
|
||||
return FieldCache(
|
||||
property = property,
|
||||
columnName = columnName,
|
||||
isPrimary = isPrimary,
|
||||
idType = idType,
|
||||
fieldFill = fieldFill,
|
||||
returnType = returnType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成所有实体类的建表SQL
|
||||
* @return 建表SQL语句列表
|
||||
*/
|
||||
fun generateCreateTableSql(): List<String> {
|
||||
return entityCache.values.map { generateCreateTableSql(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个实体生成建表SQL
|
||||
*/
|
||||
private fun generateCreateTableSql(entityCache: EntityCache): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
sb.appendLine("-- 为实体 ${entityCache.entityClass.simpleName} 创建表")
|
||||
sb.appendLine("CREATE TABLE IF NOT EXISTS \"${entityCache.tableName}\" (")
|
||||
|
||||
// 添加字段定义
|
||||
entityCache.fields.forEachIndexed { index, field ->
|
||||
sb.append(" \"${field.columnName}\" ${mapKotlinTypeToPostgresType(field)}")
|
||||
|
||||
// 主键约束
|
||||
if (field.isPrimary) {
|
||||
sb.append(" PRIMARY KEY")
|
||||
}
|
||||
|
||||
// 非空约束 (根据Kotlin类型是否为可空)
|
||||
if (!field.returnType.isMarkedNullable && !field.isPrimary) {
|
||||
sb.append(" NOT NULL")
|
||||
}
|
||||
|
||||
// 自增约束
|
||||
if (field.isPrimary && field.idType == IdType.AUTO) {
|
||||
sb.append(" GENERATED BY DEFAULT AS IDENTITY")
|
||||
}
|
||||
|
||||
// 默认值
|
||||
when (field.fieldFill) {
|
||||
FieldFill.INSERT, FieldFill.INSERT_UPDATE -> {
|
||||
if (field.returnType.classifier == Timestamp::class ||
|
||||
field.returnType.classifier == LocalDateTime::class ||
|
||||
field.returnType.classifier == LocalDate::class) {
|
||||
sb.append(" DEFAULT CURRENT_TIMESTAMP")
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (index < entityCache.fields.size - 1) {
|
||||
sb.appendLine(",")
|
||||
} else {
|
||||
sb.appendLine()
|
||||
}
|
||||
}
|
||||
|
||||
sb.appendLine(");")
|
||||
|
||||
// 添加索引
|
||||
entityCache.indices.forEach { index ->
|
||||
sb.appendLine()
|
||||
sb.append("-- 创建索引: ${index.name}")
|
||||
sb.appendLine()
|
||||
sb.append("CREATE ")
|
||||
if (index.unique) sb.append("UNIQUE ")
|
||||
sb.append("INDEX ")
|
||||
if (index.concurrent) sb.append("CONCURRENTLY ")
|
||||
sb.append("IF NOT EXISTS ${index.name} ON \"${entityCache.tableName}\" ")
|
||||
if (index.definition.isNotEmpty()) {
|
||||
sb.append(index.definition)
|
||||
} else {
|
||||
sb.append("(${index.columnNames.joinToString(", ") { "\"$it\"" }})")
|
||||
}
|
||||
sb.appendLine(";")
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成基本的CRUD SQL语句
|
||||
*/
|
||||
fun generateCrudSql(): Map<KClass<*>, CrudSql> {
|
||||
return entityCache.mapValues { (_, entityCache) ->
|
||||
val tableName = entityCache.tableName
|
||||
val fields = entityCache.fields
|
||||
|
||||
val primaryKeyField = fields.find { it.isPrimary }
|
||||
val regularFields = fields.filter { !it.isPrimary }
|
||||
|
||||
val insertFields = regularFields.filter { field ->
|
||||
when (field.fieldFill) {
|
||||
FieldFill.INSERT, FieldFill.INSERT_UPDATE -> true
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
val updateFields = regularFields.filter { field ->
|
||||
when (field.fieldFill) {
|
||||
FieldFill.UPDATE, FieldFill.INSERT_UPDATE -> true
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
val fieldList = fields.joinToString(", ") { "\"${it.columnName}\"" }
|
||||
val primaryKeyColumn = primaryKeyField?.columnName ?: "id"
|
||||
|
||||
val insertFieldList = insertFields.joinToString(", ") { "\"${it.columnName}\"" }
|
||||
val insertValuesList = insertFields.joinToString(", ") { ":${it.property.name}" }
|
||||
|
||||
val updateSetList = updateFields.joinToString(", ") { "\"${it.columnName}\" = :${it.property.name}" }
|
||||
|
||||
CrudSql(
|
||||
insertSql = "INSERT INTO \"$tableName\" ($insertFieldList) VALUES ($insertValuesList);",
|
||||
selectByIdSql = "SELECT $fieldList FROM \"$tableName\" WHERE \"$primaryKeyColumn\" = :id;",
|
||||
updateSql = "UPDATE \"$tableName\" SET $updateSetList WHERE \"$primaryKeyColumn\" = :id;",
|
||||
deleteSql = "DELETE FROM \"$tableName\" WHERE \"$primaryKeyColumn\" = :id;"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将驼峰命名转换为下划线命名
|
||||
*/
|
||||
private fun camelToSnake(name: String): String {
|
||||
return name.replace(Regex("([a-z0-9])([A-Z])"), "$1_$2").toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Kotlin类型映射为PostgreSQL类型
|
||||
*/
|
||||
private fun mapKotlinTypeToPostgresType(field: FieldCache): String {
|
||||
val type = field.returnType.classifier
|
||||
return when (type) {
|
||||
String::class -> "VARCHAR(255)"
|
||||
Int::class, Integer::class -> "INTEGER"
|
||||
Long::class -> "BIGINT"
|
||||
Double::class -> "DOUBLE PRECISION"
|
||||
Float::class -> "REAL"
|
||||
Boolean::class -> "BOOLEAN"
|
||||
Timestamp::class, java.util.Date::class -> "TIMESTAMP"
|
||||
LocalDateTime::class -> "TIMESTAMP"
|
||||
LocalDate::class -> "DATE"
|
||||
Char::class -> "CHAR(1)"
|
||||
else -> {
|
||||
// 处理枚举类型
|
||||
if (type is KClass<*> && type.java.isEnum) {
|
||||
"VARCHAR(50)"
|
||||
} else {
|
||||
"JSONB" // 默认复杂类型存储为JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体缓存类
|
||||
*/
|
||||
data class EntityCache(
|
||||
val entityClass: KClass<*>,
|
||||
val tableName: String,
|
||||
val fields: List<FieldCache>,
|
||||
val indices: List<IndexCache>
|
||||
)
|
||||
|
||||
/**
|
||||
* 字段缓存类
|
||||
*/
|
||||
data class FieldCache(
|
||||
val property: KProperty<*>,
|
||||
val columnName: String,
|
||||
val isPrimary: Boolean,
|
||||
val idType: IdType,
|
||||
val fieldFill: FieldFill,
|
||||
val returnType: kotlin.reflect.KType
|
||||
)
|
||||
|
||||
/**
|
||||
* 索引缓存类
|
||||
*/
|
||||
data class IndexCache(
|
||||
val name: String,
|
||||
val unique: Boolean,
|
||||
val concurrent: Boolean,
|
||||
val columnNames: List<String>,
|
||||
val definition: String
|
||||
)
|
||||
|
||||
/**
|
||||
* CRUD SQL语句
|
||||
*/
|
||||
data class CrudSql(
|
||||
val insertSql: String,
|
||||
val selectByIdSql: String,
|
||||
val updateSql: String,
|
||||
val deleteSql: String
|
||||
)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* 快速生成指定包下所有实体类的SQL
|
||||
*/
|
||||
fun generateSql(packageName: String): Map<String, String> {
|
||||
val sqlGen = SqlGen()
|
||||
val entities = sqlGen.scanEntities(packageName)
|
||||
sqlGen.parseEntities(entities)
|
||||
|
||||
val createTableSql = sqlGen.generateCreateTableSql()
|
||||
val crudSql = sqlGen.generateCrudSql()
|
||||
|
||||
val result = mutableMapOf<String, String>()
|
||||
|
||||
entities.forEachIndexed { index, entity ->
|
||||
val tableName = sqlGen.scanner.getAnnotatedClass(entity)?.value
|
||||
?: camelToSnake(entity.simpleName!!)
|
||||
|
||||
result["$tableName-create"] = createTableSql[index]
|
||||
|
||||
val crud = crudSql[entity]
|
||||
if (crud != null) {
|
||||
result["$tableName-insert"] = crud.insertSql
|
||||
result["$tableName-select"] = crud.selectByIdSql
|
||||
result["$tableName-update"] = crud.updateSql
|
||||
result["$tableName-delete"] = crud.deleteSql
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 将驼峰命名转换为下划线命名
|
||||
*/
|
||||
private fun camelToSnake(name: String): String {
|
||||
return name.replace(Regex("([a-z0-9])([A-Z])"), "$1_$2").toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,153 +0,0 @@
|
||||
package org.aikrai.vertx.gen
|
||||
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* SqlGen命令行工具
|
||||
*
|
||||
* 用法:
|
||||
* SqlGenCli <packageName> <outputDir>
|
||||
*
|
||||
* 参数:
|
||||
* packageName - 要扫描的包名,例如 app.data.domain
|
||||
* outputDir - SQL文件输出目录,可选,默认为 ./sql-gen
|
||||
*/
|
||||
object SqlGenCli {
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty() || args[0] == "-h" || args[0] == "--help") {
|
||||
printHelp()
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
val packageName = args[0]
|
||||
val outputDir = if (args.size > 1) args[1] else "./sql-gen"
|
||||
|
||||
println("开始从包 $packageName 生成SQL...")
|
||||
println("输出目录: $outputDir")
|
||||
|
||||
// 创建输出目录
|
||||
val outputDirFile = File(outputDir)
|
||||
if (!outputDirFile.exists()) {
|
||||
outputDirFile.mkdirs()
|
||||
println("创建输出目录: $outputDir")
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成SQL
|
||||
val sqlMap: Map<String, String> = SqlGen.generateSql(packageName)
|
||||
|
||||
// 按类型组织SQL
|
||||
val createTableSql = mutableListOf<String>()
|
||||
val insertSql = mutableListOf<String>()
|
||||
val selectSql = mutableListOf<String>()
|
||||
val updateSql = mutableListOf<String>()
|
||||
val deleteSql = mutableListOf<String>()
|
||||
|
||||
// 分类SQL语句
|
||||
sqlMap.forEach { (key: String, sql: String) ->
|
||||
when {
|
||||
key.endsWith("-create") -> createTableSql.add(sql)
|
||||
key.endsWith("-insert") -> insertSql.add(sql)
|
||||
key.endsWith("-select") -> selectSql.add(sql)
|
||||
key.endsWith("-update") -> updateSql.add(sql)
|
||||
key.endsWith("-delete") -> deleteSql.add(sql)
|
||||
else -> {} // 忽略其他类型
|
||||
}
|
||||
}
|
||||
|
||||
// 写入各类SQL文件
|
||||
File(outputDirFile, "tables.sql").writeText(createTableSql.joinToString("\n\n"))
|
||||
File(outputDirFile, "inserts.sql").writeText(insertSql.joinToString("\n\n"))
|
||||
File(outputDirFile, "selects.sql").writeText(selectSql.joinToString("\n\n"))
|
||||
File(outputDirFile, "updates.sql").writeText(updateSql.joinToString("\n\n"))
|
||||
File(outputDirFile, "deletes.sql").writeText(deleteSql.joinToString("\n\n"))
|
||||
|
||||
// 写入完整SQL文件
|
||||
val allSql = """
|
||||
|-- ====================
|
||||
|-- 建表语句
|
||||
|-- ====================
|
||||
|
|
||||
|${createTableSql.joinToString("\n\n")}
|
||||
|
|
||||
|-- ====================
|
||||
|-- 插入语句
|
||||
|-- ====================
|
||||
|
|
||||
|${insertSql.joinToString("\n\n")}
|
||||
|
|
||||
|-- ====================
|
||||
|-- 查询语句
|
||||
|-- ====================
|
||||
|
|
||||
|${selectSql.joinToString("\n\n")}
|
||||
|
|
||||
|-- ====================
|
||||
|-- 更新语句
|
||||
|-- ====================
|
||||
|
|
||||
|${updateSql.joinToString("\n\n")}
|
||||
|
|
||||
|-- ====================
|
||||
|-- 删除语句
|
||||
|-- ====================
|
||||
|
|
||||
|${deleteSql.joinToString("\n\n")}
|
||||
""".trimMargin()
|
||||
|
||||
File(outputDirFile, "all.sql").writeText(allSql)
|
||||
|
||||
// 为每个实体类生成单独的SQL文件
|
||||
val tableNames = sqlMap.keys
|
||||
.filter { k: String -> k.endsWith("-create") }
|
||||
.map { k: String -> k.substring(0, k.length - 7) }
|
||||
|
||||
tableNames.forEach { tableName: String ->
|
||||
val entitySql = """
|
||||
|-- 表结构
|
||||
|${sqlMap["$tableName-create"] ?: ""}
|
||||
|
|
||||
|-- 插入操作
|
||||
|${sqlMap["$tableName-insert"] ?: ""}
|
||||
|
|
||||
|-- 查询操作
|
||||
|${sqlMap["$tableName-select"] ?: ""}
|
||||
|
|
||||
|-- 更新操作
|
||||
|${sqlMap["$tableName-update"] ?: ""}
|
||||
|
|
||||
|-- 删除操作
|
||||
|${sqlMap["$tableName-delete"] ?: ""}
|
||||
""".trimMargin()
|
||||
|
||||
File(outputDirFile, "$tableName.sql").writeText(entitySql)
|
||||
}
|
||||
|
||||
println("SQL生成完成!")
|
||||
println("成功生成 ${tableNames.size} 个实体类的SQL脚本到目录: $outputDir")
|
||||
|
||||
} catch (e: Exception) {
|
||||
System.err.println("生成SQL时发生错误: ${e.message}")
|
||||
e.printStackTrace()
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun printHelp() {
|
||||
println("""
|
||||
|SqlGen - Kotlin实体类SQL生成工具
|
||||
|
|
||||
|用法: SqlGenCli <packageName> [outputDir]
|
||||
|
|
||||
|参数:
|
||||
| packageName 要扫描的包名,例如 app.data.domain
|
||||
| outputDir SQL文件输出目录,可选,默认为 ./sql-gen
|
||||
|
|
||||
|示例:
|
||||
| SqlGenCli app.data.domain ./sql/generated
|
||||
""".trimMargin())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user