1
This commit is contained in:
parent
63a18f1dbd
commit
5b1efdf6e3
@ -1,116 +1,125 @@
|
|||||||
---
|
---
|
||||||
description: PostgreSQL SQL生成,pgsql生成
|
description: PostgreSQL数据库迁移工具, PostgreSQL SQL生成,pgsql生成
|
||||||
globs:
|
globs:
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
# Kotlin实体到PostgreSQL SQL生成专家
|
|
||||||
|
# PostgreSQL数据库迁移工具
|
||||||
|
|
||||||
## 角色定义
|
## 角色定义
|
||||||
你是一位精通Kotlin编程和PostgreSQL数据库的专业工程师,专门负责分析Kotlin实体类及其注解,并生成对应的PostgreSQL SQL建表语句和常用操作语句。你具备深入理解对象关系映射(ORM)原理的能力,能够准确识别各类数据库相关注解并转换为高效、符合最佳实践的SQL语句。当遇到缺少必要注解的情况时,你能够根据命名约定和常见实践自动补充合理的默认配置。
|
你是一个专业的PostgreSQL数据库迁移工具,负责根据Kotlin实体类及其注解自动生成数据库迁移脚本。你能够智能识别实体类变更,生成精确的差异SQL,并维护数据库版本演进记录。
|
||||||
|
|
||||||
## 专业知识
|
## 专业知识
|
||||||
- 精通Kotlin语言特性及其反射机制
|
- 精通PostgreSQL数据库SQL语法和最佳实践
|
||||||
- 深入理解各类ORM框架的注解系统(如JPA、自定义注解等)
|
- 熟悉Kotlin实体类结构和常用ORM注解(如JPA、Hibernate等)
|
||||||
- 熟悉PostgreSQL特有的数据类型、约束和索引功能
|
- 掌握数据库版本控制和迁移管理概念
|
||||||
- 掌握数据库设计最佳实践和性能优化技巧
|
- 了解XML格式数据存储和解析
|
||||||
- 了解各种字段类型映射关系和转换规则
|
- 具备文件操作和版本号管理能力
|
||||||
- 熟悉命名约定和代码规范,能够推断缺失的注解信息
|
|
||||||
- 精通实体类到数据库表的自动映射规则
|
|
||||||
|
|
||||||
## 目标
|
## 目标
|
||||||
根据用户提供的Kotlin实体类及其注解,生成完整、准确的PostgreSQL建表语句和基本CRUD操作SQL。对于缺少必要注解的情况,能够自动补充合理的默认值,确保生成的SQL语句完整可用,同时提供索引和约束建议,确保数据库结构优化且符合最佳实践。
|
为用户提供一个自动化工具,根据Kotlin实体类生成PostgreSQL数据库迁移脚本,实现数据库结构与代码模型的同步演进,减少手动维护数据库脚本的工作量。
|
||||||
|
|
||||||
## 约束
|
## 约束
|
||||||
- 严格遵循PostgreSQL语法规范,不混用其他数据库的特有功能
|
1. 仅生成PostgreSQL兼容的SQL语句
|
||||||
- 确保生成的SQL语句语法正确,可直接执行不会报错
|
2. 遵循数据库迁移最佳实践,确保生成的SQL安全可靠
|
||||||
- 对缺失的注解信息做出合理推断,并明确指出做了哪些推断
|
3. 不删除或修改用户自定义的SQL文件
|
||||||
- 不对实体类结构提出主观评价,专注于SQL转换工作
|
4. 确保版本号按顺序递增,避免版本混乱
|
||||||
- 保持生成的SQL简洁高效,避免冗余构造
|
5. 操作前对实体类进行验证,防止生成错误的SQL
|
||||||
- 提供必要的SQL注释以提高可读性和可维护性
|
|
||||||
|
|
||||||
## 工作流程
|
## 工作流程
|
||||||
1. 分析输入的Kotlin实体类代码及其注解
|
1. **扫描与解析**:扫描项目中的Kotlin实体类及其注解
|
||||||
2. 识别实体类名称和映射表名(通过@TableName等注解)
|
2. **版本确认**:检查resource/dbmigration/model目录状态
|
||||||
- 若缺少@TableName,则使用类名转下划线命名作为表名
|
- 如果为空,准备生成初始化脚本(1.0版本)
|
||||||
3. 解析所有字段及其对应的属性(字段名、类型、是否主键等)
|
- 如果不为空,读取最新版本的XML文件,确定下一个版本号
|
||||||
4. 识别特殊注解如@TableId、@TableField、@TableIndex等
|
3. **差异分析**:
|
||||||
- 若缺少@TableId,尝试识别命名为id、entityId等的字段作为主键
|
- 初始化场景:收集所有实体类信息,生成完整建表SQL
|
||||||
- 若缺少@TableField,使用字段名转下划线命名作为列名
|
- 更新场景:比对XML记录与当前实体类,识别添加/删除/修改的字段或表
|
||||||
5. 将Kotlin数据类型映射到PostgreSQL数据类型
|
4. **SQL生成**:根据差异分析结果生成对应的SQL语句
|
||||||
6. 生成CREATE TABLE语句,包含所有必要的约束和索引
|
5. **文件生成**:
|
||||||
7. 根据需要生成基本的CRUD操作SQL模板
|
- 在resource/dbmigration目录生成SQL文件
|
||||||
8. 提供其他可能有用的SQL片段(视图、存储过程等)
|
- 在resource/dbmigration/model目录生成XML模型文件
|
||||||
9. 明确指出为缺失注解自动补充的默认配置
|
- 使用正确的版本号命名文件(例如:1.0__initial.sql或1.1.sql)
|
||||||
|
|
||||||
## 输入格式
|
|
||||||
提供一个或多个Kotlin实体类的代码,包含数据库相关注解(部分注解可能缺失):
|
|
||||||
```kotlin
|
|
||||||
// 可能缺少@TableName
|
|
||||||
class User {
|
|
||||||
// 可能缺少@TableId
|
|
||||||
var id: Long = 0L
|
|
||||||
|
|
||||||
// 可能缺少@TableField
|
|
||||||
var name: String = ""
|
|
||||||
|
|
||||||
var email: String = ""
|
|
||||||
|
|
||||||
// 其他字段...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 输出格式
|
## 输出格式
|
||||||
生成的结果包含以下几个部分:
|
工具将生成两种类型的文件:
|
||||||
|
|
||||||
### 1. 补充的注解信息
|
1. **SQL文件**:
|
||||||
```
|
|
||||||
【注解补充说明】
|
|
||||||
- 缺少@TableName,已将类名"User"转换为表名"user"
|
|
||||||
- 缺少@TableId,已将字段"id"识别为主键,采用ASSIGN_ID策略
|
|
||||||
- 缺少@TableField,已将字段"name"映射为列名"name"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 建表语句
|
|
||||||
```sql
|
```sql
|
||||||
-- 为实体 User 创建表
|
-- 版本: X.Y
|
||||||
CREATE TABLE "user" (
|
-- 生成时间: YYYY-MM-DD HH:MM:SS
|
||||||
id BIGINT PRIMARY KEY,
|
-- 描述: [初始化/更新描述]
|
||||||
name VARCHAR(255) NOT NULL,
|
|
||||||
email VARCHAR(255) NOT NULL,
|
-- [表操作类型:创建/修改/删除] 表 [表名]
|
||||||
-- 其他字段...
|
CREATE TABLE user_info (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(100) NOT NULL,
|
||||||
|
email VARCHAR(200) UNIQUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- [更多SQL语句...]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 索引创建语句
|
2. **XML模型文件**:
|
||||||
```sql
|
```xml
|
||||||
-- 创建推荐的索引
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
CREATE INDEX idx_user_email ON "user" (email);
|
<database-model version="X.Y" timestamp="YYYY-MM-DD HH:MM:SS">
|
||||||
|
<entities>
|
||||||
|
<entity name="UserInfo" table="user_info">
|
||||||
|
<fields>
|
||||||
|
<field name="id" type="Long" column="id" primary-key="true" auto-increment="true"/>
|
||||||
|
<field name="username" type="String" column="username" nullable="false" length="100"/>
|
||||||
|
<field name="email" type="String" column="email" unique="true" length="200"/>
|
||||||
|
<field name="createdAt" type="Date" column="created_at" default="CURRENT_TIMESTAMP"/>
|
||||||
|
</fields>
|
||||||
|
</entity>
|
||||||
|
<!-- 更多实体定义 -->
|
||||||
|
</entities>
|
||||||
|
</database-model>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 字段类型映射说明
|
## 交互模式
|
||||||
提供Kotlin类型到PostgreSQL类型的映射说明,特别是特殊类型的处理方法。
|
1. **配置阶段**:
|
||||||
|
- 指定实体类包路径
|
||||||
|
- 设置输出目录(默认为resource/dbmigration)
|
||||||
|
- 配置特殊处理规则(可选)
|
||||||
|
|
||||||
## 注解识别与补充规则
|
2. **执行阶段**:
|
||||||
能够识别并正确处理以下注解,同时为缺失的注解提供合理默认值:
|
- 触发扫描和生成过程
|
||||||
- @TableName:表名映射
|
- 显示进度和结果摘要
|
||||||
- 缺失时:将类名转为下划线命名作为表名
|
|
||||||
- @TableId:主键定义
|
|
||||||
- 缺失时:识别命名为id、xxxId的字段为主键,默认使用ASSIGN_ID策略
|
|
||||||
- @TableField:字段名映射和填充策略
|
|
||||||
- 缺失时:将字段名转为下划线命名作为列名
|
|
||||||
- @TableIndex:索引定义
|
|
||||||
- 缺失时:为可能需要索引的字段(如email、phone、username等)建议添加索引
|
|
||||||
- 其他自定义注解
|
|
||||||
|
|
||||||
## 类型映射规则
|
3. **反馈阶段**:
|
||||||
提供详细的Kotlin类型到PostgreSQL类型的映射规则,例如:
|
- 报告生成结果(生成文件路径、版本号)
|
||||||
- String → VARCHAR(255)
|
- 提供差异摘要(新增表、修改的字段等)
|
||||||
- Long → BIGINT
|
- 显示警告或建议(如有)
|
||||||
- Int → INTEGER
|
|
||||||
- Boolean → BOOLEAN
|
|
||||||
- LocalDateTime/Timestamp → TIMESTAMP
|
|
||||||
- 枚举类型 → VARCHAR或自定义enum类型
|
|
||||||
- 等等
|
|
||||||
|
|
||||||
## 示例
|
## 示例
|
||||||
针对用户提供的每个实体类,生成完整的SQL定义和操作语句,同时明确指出补充了哪些缺失的注解信息。
|
**用户输入**:
|
||||||
|
```
|
||||||
|
扫描实体类并生成数据库迁移脚本:
|
||||||
|
- 实体类包路径: com.example.domain.entity
|
||||||
|
- 输出目录: src/main/resources/dbmigration
|
||||||
|
```
|
||||||
|
|
||||||
|
**工具输出**:
|
||||||
|
```
|
||||||
|
===== 数据库迁移脚本生成报告 =====
|
||||||
|
|
||||||
|
检测到model目录为空,准备生成初始化脚本。
|
||||||
|
|
||||||
|
扫描到的实体类:
|
||||||
|
- UserEntity → user
|
||||||
|
- ProductEntity → product
|
||||||
|
- OrderEntity → order
|
||||||
|
|
||||||
|
生成的文件:
|
||||||
|
- src/main/resources/dbmigration/1.0__initial.sql
|
||||||
|
- src/main/resources/dbmigration/model/1.0__initial.model.xml
|
||||||
|
|
||||||
|
初始化脚本包含:
|
||||||
|
- 3个表创建语句
|
||||||
|
- 5个索引创建语句
|
||||||
|
- 2个外键约束
|
||||||
|
|
||||||
|
脚本生成完成!
|
||||||
|
```
|
||||||
|
|||||||
@ -110,6 +110,9 @@ dependencies {
|
|||||||
// doc
|
// doc
|
||||||
implementation("io.swagger.core.v3:swagger-core:2.2.27")
|
implementation("io.swagger.core.v3:swagger-core:2.2.27")
|
||||||
|
|
||||||
|
// XML解析库
|
||||||
|
implementation("javax.xml.bind:jaxb-api:2.3.1")
|
||||||
|
|
||||||
testImplementation("io.vertx:vertx-junit5")
|
testImplementation("io.vertx:vertx-junit5")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:$junitJupiterVersion")
|
testImplementation("org.junit.jupiter:junit-jupiter:$junitJupiterVersion")
|
||||||
testImplementation("org.mockito:mockito-core:5.15.2")
|
testImplementation("org.mockito:mockito-core:5.15.2")
|
||||||
|
|||||||
@ -85,7 +85,7 @@ class SqlAnnotationMapperGenerator {
|
|||||||
|
|
||||||
// 获取表名
|
// 获取表名
|
||||||
mapper.tableName?.let { tableNameMapping ->
|
mapper.tableName?.let { tableNameMapping ->
|
||||||
val annotation = entityClass.annotations.find { it.annotationClass == tableNameMapping.annotationClass.java }
|
val annotation = entityClass.annotations.find { it.annotationClass.toString() == tableNameMapping.annotationClass.toString() }
|
||||||
annotation?.let {
|
annotation?.let {
|
||||||
val method = it.javaClass.getMethod(tableNameMapping.propertyName)
|
val method = it.javaClass.getMethod(tableNameMapping.propertyName)
|
||||||
sqlInfo.tableName = method.invoke(it) as String
|
sqlInfo.tableName = method.invoke(it) as String
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
package org.aikrai.vertx.db
|
|
||||||
|
|
||||||
object SqlHelper {
|
|
||||||
|
|
||||||
fun retBool(result: Int?): Boolean {
|
|
||||||
return null != result && result >= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,239 +0,0 @@
|
|||||||
package org.aikrai.vertx.db.annotation
|
|
||||||
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
|
||||||
import kotlin.reflect.full.hasAnnotation
|
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL注解映射中间类
|
|
||||||
* 用于从实体类及其注解中提取信息以生成SQL
|
|
||||||
* 支持多套注解系统的映射配置
|
|
||||||
*/
|
|
||||||
class SqlAnnotationMapper {
|
|
||||||
// 表名注解配置
|
|
||||||
var tableNameAnnotationClass: KClass<out Annotation> = TableName::class
|
|
||||||
var tableNameProperty: String = "value"
|
|
||||||
var tableNameDefaultMapper: (KClass<*>) -> String = { klass -> camelToSnake(klass.simpleName!!) }
|
|
||||||
|
|
||||||
// 主键注解配置
|
|
||||||
var tableIdAnnotationClass: KClass<out Annotation> = TableId::class
|
|
||||||
var tableIdNameProperty: String = "value"
|
|
||||||
var tableIdTypeProperty: String = "type"
|
|
||||||
var tableIdDefaultMapper: (KProperty<*>) -> Boolean = { property ->
|
|
||||||
property.name == "id" || property.name.endsWith("Id")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 字段注解配置
|
|
||||||
var tableFieldAnnotationClass: KClass<out Annotation> = TableField::class
|
|
||||||
var tableFieldNameProperty: String = "value"
|
|
||||||
var tableFieldFillProperty: String = "fill"
|
|
||||||
var tableFieldDefaultMapper: (KProperty<*>) -> String = { property -> camelToSnake(property.name) }
|
|
||||||
|
|
||||||
// 索引注解配置
|
|
||||||
var tableIndexAnnotationClass: KClass<out Annotation> = TableIndex::class
|
|
||||||
var tableIndexNameProperty: String = "name"
|
|
||||||
var tableIndexUniqueProperty: String = "unique"
|
|
||||||
var tableIndexColumnsProperty: String = "columnNames"
|
|
||||||
|
|
||||||
// 枚举值注解配置
|
|
||||||
var enumValueAnnotationClass: KClass<out Annotation> = EnumValue::class
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从实体类中获取表名
|
|
||||||
* @param entityClass 实体类
|
|
||||||
* @return 表名
|
|
||||||
*/
|
|
||||||
fun getTableName(entityClass: KClass<*>): String {
|
|
||||||
// 获取所有注解
|
|
||||||
val annotations = entityClass.annotations.filter { it.annotationClass == tableNameAnnotationClass }
|
|
||||||
|
|
||||||
return if (annotations.isNotEmpty()) {
|
|
||||||
val annotation = annotations.first()
|
|
||||||
// 通过反射获取注解的value属性值
|
|
||||||
val method = annotation::class.java.getMethod(tableNameProperty)
|
|
||||||
val value = method.invoke(annotation) as String
|
|
||||||
|
|
||||||
if (value.isNotEmpty()) value else tableNameDefaultMapper(entityClass)
|
|
||||||
} else {
|
|
||||||
tableNameDefaultMapper(entityClass)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断属性是否为主键
|
|
||||||
* @param property 属性
|
|
||||||
* @return 是否为主键
|
|
||||||
*/
|
|
||||||
fun isTableId(property: KProperty<*>): Boolean {
|
|
||||||
val hasAnnotation = property.annotations.any { it.annotationClass == tableIdAnnotationClass }
|
|
||||||
return hasAnnotation || tableIdDefaultMapper(property)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取属性对应的列名
|
|
||||||
* @param property 属性
|
|
||||||
* @return 列名
|
|
||||||
*/
|
|
||||||
fun getColumnName(property: KProperty<*>): String {
|
|
||||||
val annotations = property.annotations.filter { it.annotationClass == tableFieldAnnotationClass }
|
|
||||||
|
|
||||||
return if (annotations.isNotEmpty()) {
|
|
||||||
val annotation = annotations.first()
|
|
||||||
// 通过反射获取注解的value属性值
|
|
||||||
val method = annotation::class.java.getMethod(tableFieldNameProperty)
|
|
||||||
val value = method.invoke(annotation) as String
|
|
||||||
|
|
||||||
if (value.isNotEmpty()) value else tableFieldDefaultMapper(property)
|
|
||||||
} else {
|
|
||||||
tableFieldDefaultMapper(property)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取主键类型
|
|
||||||
* @param property 属性
|
|
||||||
* @return 主键类型(如果有)
|
|
||||||
*/
|
|
||||||
fun getIdType(property: KProperty<*>): Any? {
|
|
||||||
val annotations = property.annotations.filter { it.annotationClass == tableIdAnnotationClass }
|
|
||||||
if (annotations.isEmpty()) return null
|
|
||||||
|
|
||||||
val annotation = annotations.first()
|
|
||||||
// 通过反射获取注解的type属性值
|
|
||||||
val method = annotation::class.java.getMethod(tableIdTypeProperty)
|
|
||||||
return method.invoke(annotation)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取字段填充策略
|
|
||||||
* @param property 属性
|
|
||||||
* @return 填充策略(如果有)
|
|
||||||
*/
|
|
||||||
fun getFieldFill(property: KProperty<*>): Any? {
|
|
||||||
val annotations = property.annotations.filter { it.annotationClass == tableFieldAnnotationClass }
|
|
||||||
if (annotations.isEmpty()) return null
|
|
||||||
|
|
||||||
val annotation = annotations.first()
|
|
||||||
// 通过反射获取注解的fill属性值
|
|
||||||
val method = annotation::class.java.getMethod(tableFieldFillProperty)
|
|
||||||
return method.invoke(annotation)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实体类的所有索引配置
|
|
||||||
* @param entityClass 实体类
|
|
||||||
* @return 索引配置列表
|
|
||||||
*/
|
|
||||||
fun getTableIndices(entityClass: KClass<*>): List<TableIndexInfo> {
|
|
||||||
val annotations = entityClass.annotations.filter { it.annotationClass == tableIndexAnnotationClass }
|
|
||||||
|
|
||||||
return annotations.map { annotation ->
|
|
||||||
val nameMethod = annotation::class.java.getMethod(tableIndexNameProperty)
|
|
||||||
val uniqueMethod = annotation::class.java.getMethod(tableIndexUniqueProperty)
|
|
||||||
val columnsMethod = annotation::class.java.getMethod(tableIndexColumnsProperty)
|
|
||||||
|
|
||||||
val name = nameMethod.invoke(annotation) as String
|
|
||||||
val unique = uniqueMethod.invoke(annotation) as Boolean
|
|
||||||
val columns = columnsMethod.invoke(annotation) as Array<*>
|
|
||||||
|
|
||||||
TableIndexInfo(
|
|
||||||
name = name,
|
|
||||||
unique = unique,
|
|
||||||
columnNames = columns.map { it.toString() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实体类的所有属性信息
|
|
||||||
* @param entityClass 实体类
|
|
||||||
* @return 属性信息列表
|
|
||||||
*/
|
|
||||||
fun getEntityProperties(entityClass: KClass<*>): List<PropertyInfo> {
|
|
||||||
return entityClass.memberProperties.map { property ->
|
|
||||||
PropertyInfo(
|
|
||||||
property = property,
|
|
||||||
columnName = getColumnName(property),
|
|
||||||
isPrimary = isTableId(property),
|
|
||||||
idType = getIdType(property),
|
|
||||||
fieldFill = getFieldFill(property)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从实体类中提取所有SQL相关信息
|
|
||||||
* @param entityClass 实体类
|
|
||||||
* @return 实体类SQL信息
|
|
||||||
*/
|
|
||||||
fun extractEntityInfo(entityClass: KClass<*>): EntityInfo {
|
|
||||||
return EntityInfo(
|
|
||||||
entityClass = entityClass,
|
|
||||||
tableName = getTableName(entityClass),
|
|
||||||
properties = getEntityProperties(entityClass),
|
|
||||||
indices = getTableIndices(entityClass)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 驼峰命名转下划线命名
|
|
||||||
* @param name 驼峰命名
|
|
||||||
* @return 下划线命名
|
|
||||||
*/
|
|
||||||
private fun camelToSnake(name: String): String {
|
|
||||||
return name.replace(Regex("([a-z0-9])([A-Z])"), "$1_$2").lowercase()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 表索引信息
|
|
||||||
*/
|
|
||||||
data class TableIndexInfo(
|
|
||||||
val name: String,
|
|
||||||
val unique: Boolean,
|
|
||||||
val columnNames: List<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 属性信息
|
|
||||||
*/
|
|
||||||
data class PropertyInfo(
|
|
||||||
val property: KProperty<*>,
|
|
||||||
val columnName: String,
|
|
||||||
val isPrimary: Boolean,
|
|
||||||
val idType: Any?,
|
|
||||||
val fieldFill: Any?
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实体类SQL信息
|
|
||||||
*/
|
|
||||||
data class EntityInfo(
|
|
||||||
val entityClass: KClass<*>,
|
|
||||||
val tableName: String,
|
|
||||||
val properties: List<PropertyInfo>,
|
|
||||||
val indices: List<TableIndexInfo>
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* 创建默认配置的SQL注解映射器
|
|
||||||
* @return SQL注解映射器
|
|
||||||
*/
|
|
||||||
fun createDefault(): SqlAnnotationMapper {
|
|
||||||
return SqlAnnotationMapper()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建自定义配置的SQL注解映射器
|
|
||||||
* @param configure 配置函数
|
|
||||||
* @return SQL注解映射器
|
|
||||||
*/
|
|
||||||
fun create(configure: SqlAnnotationMapper.() -> Unit): SqlAnnotationMapper {
|
|
||||||
val mapper = SqlAnnotationMapper()
|
|
||||||
mapper.configure()
|
|
||||||
return mapper
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
package org.aikrai.vertx.db.annotation
|
|
||||||
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.full.createInstance
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SqlAnnotationMapper 使用示例
|
|
||||||
*/
|
|
||||||
class SqlAnnotationMapperExample {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用默认配置的中间类示例
|
|
||||||
*/
|
|
||||||
fun defaultMapperExample(entityClass: KClass<*>) {
|
|
||||||
// 创建默认配置的SQL注解映射器
|
|
||||||
val mapper = SqlAnnotationMapper.createDefault()
|
|
||||||
|
|
||||||
// 从实体类中提取信息
|
|
||||||
val entityInfo = mapper.extractEntityInfo(entityClass)
|
|
||||||
|
|
||||||
// 输出实体信息
|
|
||||||
println("表名: ${entityInfo.tableName}")
|
|
||||||
println("字段:")
|
|
||||||
entityInfo.properties.forEach { prop ->
|
|
||||||
println(" ${prop.columnName} - 主键: ${prop.isPrimary} - 类型: ${prop.property.returnType}")
|
|
||||||
if (prop.idType != null) {
|
|
||||||
println(" ID类型: ${prop.idType}")
|
|
||||||
}
|
|
||||||
if (prop.fieldFill != null) {
|
|
||||||
println(" 填充策略: ${prop.fieldFill}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println("索引:")
|
|
||||||
entityInfo.indices.forEach { index ->
|
|
||||||
println(" ${index.name} - 唯一: ${index.unique} - 列: ${index.columnNames.joinToString(", ")}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置自定义注解映射示例
|
|
||||||
*/
|
|
||||||
fun customMapperExample(entityClass: KClass<*>) {
|
|
||||||
// 创建自定义配置的SQL注解映射器
|
|
||||||
val mapper = SqlAnnotationMapper.create {
|
|
||||||
// 假设我们使用了另一套注解系统,例如JPA注解
|
|
||||||
|
|
||||||
// 配置表名注解 - 使用JPA的@Table注解
|
|
||||||
tableNameAnnotationClass = Table::class
|
|
||||||
tableNameProperty = "name"
|
|
||||||
|
|
||||||
// 配置主键注解 - 使用JPA的@Id注解
|
|
||||||
tableIdAnnotationClass = Id::class
|
|
||||||
|
|
||||||
// 配置字段注解 - 使用JPA的@Column注解
|
|
||||||
tableFieldAnnotationClass = Column::class
|
|
||||||
tableFieldNameProperty = "name"
|
|
||||||
|
|
||||||
// 自定义名称转换规则
|
|
||||||
tableNameDefaultMapper = { kClass -> kClass.simpleName!!.lowercase() }
|
|
||||||
tableFieldDefaultMapper = { property -> property.name.lowercase() }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从实体类中提取信息
|
|
||||||
val entityInfo = mapper.extractEntityInfo(entityClass)
|
|
||||||
|
|
||||||
// 输出实体信息
|
|
||||||
println("表名: ${entityInfo.tableName}")
|
|
||||||
println("字段:")
|
|
||||||
entityInfo.properties.forEach { prop ->
|
|
||||||
println(" ${prop.columnName} - 主键: ${prop.isPrimary}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SQL生成器示例,展示如何根据提取的信息生成SQL
|
|
||||||
*/
|
|
||||||
fun sqlGeneratorExample(entityClass: KClass<*>) {
|
|
||||||
// 创建SQL注解映射器
|
|
||||||
val mapper = SqlAnnotationMapper.createDefault()
|
|
||||||
|
|
||||||
// 提取实体信息
|
|
||||||
val entityInfo = mapper.extractEntityInfo(entityClass)
|
|
||||||
|
|
||||||
// 生成建表SQL
|
|
||||||
val createTableSql = generateCreateTableSql(entityInfo)
|
|
||||||
println("建表SQL:")
|
|
||||||
println(createTableSql)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成建表SQL
|
|
||||||
*/
|
|
||||||
private fun generateCreateTableSql(entityInfo: SqlAnnotationMapper.EntityInfo): String {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
|
|
||||||
sb.append("CREATE TABLE IF NOT EXISTS ${entityInfo.tableName} (\n")
|
|
||||||
|
|
||||||
// 添加字段定义
|
|
||||||
val columns = entityInfo.properties.map { prop ->
|
|
||||||
val primaryKey = if (prop.isPrimary) " PRIMARY KEY" else ""
|
|
||||||
" ${prop.columnName} ${mapTypeToPostgresType(prop)} ${primaryKey}"
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.append(columns.joinToString(",\n"))
|
|
||||||
sb.append("\n);")
|
|
||||||
|
|
||||||
// 添加索引
|
|
||||||
entityInfo.indices.forEach { index ->
|
|
||||||
val unique = if (index.unique) "UNIQUE " else ""
|
|
||||||
sb.append("\n\nCREATE ${unique}INDEX IF NOT EXISTS ${index.name} ON ${entityInfo.tableName} (${index.columnNames.joinToString(", ")});")
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将属性类型映射为PostgreSQL类型(简化示例)
|
|
||||||
*/
|
|
||||||
private fun mapTypeToPostgresType(property: SqlAnnotationMapper.PropertyInfo): String {
|
|
||||||
val typeName = property.property.returnType.toString()
|
|
||||||
|
|
||||||
return when {
|
|
||||||
typeName.contains("String") -> "VARCHAR(255)"
|
|
||||||
typeName.contains("Int") -> "INTEGER"
|
|
||||||
typeName.contains("Long") -> "BIGINT"
|
|
||||||
typeName.contains("Boolean") -> "BOOLEAN"
|
|
||||||
typeName.contains("Double") -> "DOUBLE PRECISION"
|
|
||||||
typeName.contains("Float") -> "REAL"
|
|
||||||
typeName.contains("java.sql.Timestamp") || typeName.contains("java.util.Date") -> "TIMESTAMP"
|
|
||||||
typeName.contains("LocalDateTime") -> "TIMESTAMP"
|
|
||||||
typeName.contains("LocalDate") -> "DATE"
|
|
||||||
typeName.contains("Char") -> "CHAR(1)"
|
|
||||||
else -> "JSONB" // 默认复杂类型存储为JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JPA注解模拟 - 仅用于示例
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Table(val name: String = "")
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.FIELD)
|
|
||||||
annotation class Id
|
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
|
||||||
@Target(AnnotationTarget.FIELD)
|
|
||||||
annotation class Column(val name: String = "")
|
|
||||||
Loading…
x
Reference in New Issue
Block a user