启动服务时按实体自动建表,注解零改动复用

image

实体驱动建表听起来不新鲜——Hibernate 的 ddl-auto、不少脚手架都能干。真正卡住的人,多半不是"会不会生成 CREATE TABLE",而是:

  • 项目根本不在 Hibernate / JPA Provider 体系里,却已经有一套带 @Entity 的类;
  • 只要 DDL 文本 给 DBA,不想应用一启动就改库;
  • 同一套实体要出 MySQL、PostgreSQL、达梦、Oracle,乃至 Hive / ClickHouse 好几份,注释和自增写法全不一样。

jkit-sql 的切入点是:从实体扫出模型,按方言生成 DDL(以及增删改查);需要连库执行时,再交给 jkit-sql-auto。它认 jkit 自己的注解,也认 MyBatis-Plus、以及 javax.persistence / jakarta.persistence 里的标准映射注解——没有对这些 API 的编译依赖


一、先澄清:@Entity 不是"Hibernate 专属"

文里常说"JPA 注解",容易让人以为必须上 Hibernate / EclipseLink 全套。更准确的说法是:

@Entity / @Table / @Column / @Id / @GeneratedValue 等定义在 Jakarta Persistence API(历史包名 javax.persistence,现为 jakarta.persistence)里,也就是大家说的 persistence-api。Hibernate、EclipseLink 是它的实现(Provider);Spring Data JPA、各种脚手架只是在用这套 API。

所以你会看到三种很常见、却容易被混为一谈的情况:

  1. 有 persistence-api 注解,但没有 JPA Provider——类上挂着 @Entity,持久化却走 MyBatis / JdbcTemplate;
  2. 只有 MyBatis-Plus 注解——@TableName / @TableId,和 persistence-api 无关;
  3. 两边都有——存量实体两套注解混用,迁移期最麻烦。

jkit 的做法是:@Entity / @Table / @Column / @Id 等按全限定名javax.persistence.*jakarta.persistence.*;MyBatis-Plus 同理认其包名下的注解。只有表/列注释那类 Comment 注解,才按简单类名匹配(不绑包名)。全程无编译依赖——不引入 Hibernate / Spring Data / MyBatis-Plus。你不用为了"能 ddl-auto"硬塞一个 Provider,也不用为了生成 DDL 把实体改写成 jkit 注解。


二、为什么还要单独搞"生成建表 SQL / 自动建表"

2ca0bd30-c64b-4946-92be-34705bc31716_0下面这些痛点,比"再写一遍 CREATE TABLE"更常见:

1. 只要脚本,不要启动改库

测试库可以自动建;生产往往要求:生成 DDL → DBA 评审 → 进 Flyway / Liquibase。很多工具绑死了"启动时连库执行",缺一截"只出文本"的能力。

2. 同一套实体,多套库

信创迁移、双写验证、本地 MySQL + 预发达梦,都要同一模型、不同方言的注释 / 自增 / 序列。手写两份 DDL 很快漂移。

3. 开发环境想快,生产环境要稳

本地希望改实体就出表;生产只允许"追加列",禁止默默改类型、删列。缺"只追加不收缩 + dryRun"时,自动建表不敢开。

4. 集成测试要临时库表

Testcontainers / 嵌入式库每次拉起都要 schema。从实体生成 DDL,比维护一份永远过期的 schema.sql 省心。

5. 微服务 / 多模块实体分散

实体散落在多个 jar,建表顺序还要顾外键。需要按包扫描,并按外键依赖排序后再出脚本。

6. 注释和命名要给 DBA 看

中文表注释、列注释、索引名长度(经典 Oracle 30 字符)——生成器如果不管,评审轮次会明显变多。

7. 已经有注解,不想为了建表换栈

类上已是 persistence-api 或 MP 注解。为了 ddl-auto 引入 Hibernate,或把注解全部改成另一套,成本都高于"读现有注解生成 SQL"。

这些场景里,你要的通常是两截能力拆开:SqlEntities 负责生成;sql-auto 负责(可选地)执行。

为什么还要单独搞自动建表


三、注解兼容性:一张表说清

来源典型注解说明
jkit 原生@SqlTable / @SqlId / @SqlColumn / @SqlGenerated无第三方语义依赖
persistence-api(javax / jakarta@Entity / @Table / @Column / @Id / @GeneratedValue按全限定名识别,无编译依赖
MyBatis-Plus@TableName / @TableId / @TableField同上;exist=false 会跳过

另外还认:任意简单名为 Comment 的注释注解、Hibernate @ColumnDefault(有则读,无则忽略),以及 persistence-api 的 @Index(含 @Table(indexes=…))、@Enumerated / @Embedded 等(详见文档)。List / Set / @OneToMany 默认不建列。

三套注解同一套扫描

原生注解示例:

import com.alianga.jkit.sql.entity.SqlEntities;
import com.alianga.jkit.sql.entity.SqlTable;
import com.alianga.jkit.sql.entity.SqlId;
import com.alianga.jkit.sql.entity.SqlGenerated;
import com.alianga.jkit.sql.entity.SqlColumn;
import com.alianga.jkit.sql.SqlDialect;

@SqlTable(name = "demo_user")
public class DemoUser {
    @SqlId @SqlGenerated Long id;
    @SqlColumn(name = "user_name", length = 32, nullable = false) String name;
    Integer age;
}

String ddl = SqlEntities.createTable(DemoUser.class, SqlDialect.POSTGRES);
// CREATE TABLE demo_user (id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ...)

存量 persistence-api 实体则不必改注解,直接:

// 类上已是 jakarta.persistence.Entity / Table / Id / Column …
String ddl = SqlEntities.createTable(OrderEntity.class, SqlDialect.MYSQL);
List<Class<?>> all = SqlEntities.scan("com.example.entity");
String batch = SqlEntities.createTables(all, SqlDialect.DAMENG);

四、一套实体,多方言 DDL 形态自适应

注释、自增、序列的写法因库而异,生成器要一次处理:

@SqlTable(name = "t_member", comment = "会员表")
class Member {
    @SqlId Long id;
    @SqlColumn(comment = "昵称") String nick;
}

SqlEntities.createTable(Member.class, SqlDialect.MYSQL);
// MySQL:内联 COMMENT

SqlEntities.createTable(Member.class, SqlDialect.POSTGRES);
// PG:CREATE TABLE + COMMENT ON TABLE/COLUMN

SqlDialect 目前有 13 个一等方言MYSQLPOSTGRESORACLEORACLE12SQLSERVERANSIH2DB2SQLITEHIVECLICKHOUSEPRESTODAMENG。另有一批别名可直接 fromName(...)——例如 tidb / mariadb / gbase / doris / starrocks→MySQL,opengauss / gaussdb / kingbase→Postgres,dm→达梦,trino→Presto——信创和衍生库不必各写一套实体。

自增形态差异大的,先看这几类:

方言自增形态(差异明显)
MySQL(含 TiDB / MariaDB / GBase 等别名)AUTO_INCREMENT
PostgreSQL(含 openGauss / GaussDB 等别名)GENERATED … AS IDENTITY
Oracle ≤11gSEQUENCE + TRIGGER(附录输出)
Oracle 12c+GENERATED … AS IDENTITY
达梦(dm列上 IDENTITY
SQL ServerIDENTITY(1,1)

其余一等方言(ANSI / H2 / DB2 / SQLITE / HIVE / CLICKHOUSE / PRESTO 等)建表走同一套 SqlDialect类型、引号、注释按方言适配,不必再维护多份实体。注释写法也会跟着变:MySQL 常内联 COMMENT,PostgreSQL / Oracle 一路多是 COMMENT ON,SQL Server 走扩展属性——生成器一并处理。

边界也要管:字符串 / UUID 主键不应生成 IDENTITY;经典 Oracle 索引名超长要截断。这些是手写多方言 DDL 时最容易漏的点。

一套实体多方言 DDL


五、jkit-sql-auto:可选的"启动时连库"

SqlEntities 只生成 SQL。要对比元数据并执行,用 jkit-sql-auto

SqlAuto.run(SqlAutoOptions.defaults()
        .url("jdbc:mysql://localhost:3306/shop")
        .username("root").password("secret")
        .packages("com.example.entity")
        .mode(SqlAutoMode.UPDATE));

Spring Boot 可加 starter:

jkit:
  sql:
    auto:
      enabled: true
      mode: update
      packages: com.example.entity
      show-sql: true

jkit-sql-auto-spring-boot-2 / -3 分别对应 Boot 2 / Boot 3。)

常见用法拆开选:

目标做法
只给 DBA 脚本SqlEntities.createTable / createTables,不引 auto
本地 / 测试自动对齐auto + UPDATE,可开 show-sql
先看再执行dryRun(true),打印 plan.sql()
生产受控变更生成文本 → 写入 Flyway;生产勿与 auto UPDATE 双开

d84b5af6-f608-44b4-abef-40dd761e5ef6_0


六、生产默认 UPDATE:只追加不收缩

SqlAutoModeNONE / VALIDATE / UPDATE(默认) / CREATE / CREATE_DROP

  • UPDATE 默认只追加:新列 ALTER TABLE … ADD不删列、默认不改类型、不改主键
  • 改类型需显式 alter-column: true
  • 删多余列需显式 drop-extra-columns: true
  • dryRun(true) 只出计划不执行。
SqlAutoPlan plan = SqlAuto.run(options.dryRun(true));
System.out.println(plan.sql());

自动建表最怕"自作主张弄丢数据"。破坏性操作默认关、显式开,是底线。

10 信息图:实体驱动建表全流程


七、和 Hibernate ddl-auto、Flyway 怎么分工

  • 已有 Hibernate + ddl-auto:开发期可以继续用;jkit 更适合"多方言脚本 / 无 Provider 的工程 / 只生成不执行"。
  • MyBatis / 裸 JDBC / 只有 persistence-api 注解:不必为了建表引入 Provider;用 SqlEntities + 可选 sql-auto。
  • Flyway / Liquibase:管版本化变更与协作;jkit 管"从实体推导结构"。常见组合是开发/测试用 auto,上线前 dryRuncreateTable 产出交给迁移脚本。两套同时在生产改表,容易冲突。

原则就一句:代码即 schema 的阶段用实体驱动提效;变更必须受控的阶段用版本化脚本。

建议试用顺序:① createTable 只生成看差异 → ② 测试库 dryRun → ③ 再开 update(只追加)→ ④ 产物进评审,DBA 知情。


定位没变:SQL 文本与 AST、实体与 DDL 之间的轻量工具层——不绑架框架,不替代你的 ORM,把"出 DDL / 对齐表结构"这类脏活收拢。

你那边是更需要"只生成脚本",还是"测试环境自动建、生产走 Flyway"?评论区可以说一下栈(MP / persistence-api / 多库)。

文档:https://jkit.alianga.com/ · 源码:jkit

Q.E.D.


寻门而入,破门而出