This commit is contained in:
AiKrai 2025-03-19 15:09:09 +08:00
parent d126bd5783
commit 63a18f1dbd
6 changed files with 515 additions and 554 deletions

View 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()
}

View 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
)

View 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()
}
}
}

View File

@ -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}")
}
}
}

View File

@ -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()
}
}
}

View File

@ -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())
}
}