基于 MyBatis-Plus 实体类自动生成数据库表结构工具
摘要:本文介绍了一种根据 MyBatis-Plus 实体类注解自动生成数据库表结构的工具,解决了开发过程中实体类与数据库表不同步的问题,提高了开发效率。
背景
在 Java 后端开发中,MyBatis-Plus 是常用的 ORM 框架。开发流程一般是:
- 设计数据库表结构
- 使用代码生成器生成实体类
- 手动维护实体类与数据库表的同步
这种模式存在以下问题:
- 同步困难:修改实体类后,需要手动修改数据库表结构
- 测试环境重建麻烦:每次重新创建测试数据库时,需要执行复杂的 SQL 脚本
- 字段遗漏:新增字段时容易忘记更新数据库
解决方案
开发一个自动化工具,扫描所有包含 @TableName 注解的实体类,解析字段信息,自动生成 CREATE TABLE 语句。
核心思路
- 扫描指定目录下的所有 Java 文件
- 识别包含
@TableName注解的实体类 - 解析实体类的字段类型和注解
- 将 Java 类型转换为数据库类型
- 生成完整的 SQL 建表脚本
实现代码
增强版主类实现
核心特性:
- ✅ 自动识别父类字段(id、createTime、updateTime、deleted)
- ✅ 智能处理标准字段,避免重复
- ✅ 支持常见 Java 类型转换
- ✅ 零依赖,纯 Java 实现
package com.example.tools;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 增强的实体类转 SQL 生成器
* 支持识别父类字段(如 BaseEntity 中的 id、createTime 等)
*/
public class EnhancedEntityToSqlGenerator {
// 标准字段配置(来自 BaseEntity)
private static final Set<String> STANDARD_FIELDS = new HashSet<>();
static {
STANDARD_FIELDS.add("id");
STANDARD_FIELDS.add("createTime");
STANDARD_FIELDS.add("updateTime");
STANDARD_FIELDS.add("deleted");
STANDARD_FIELDS.add("version");
}
public static void main(String[] args) {
String basePath = "/path/to/your/project/src/main/java/com/example/module";
String outputPath = "/path/to/your/project/src/test/resources/schema-enhanced.sql";
try {
List<String> entityFiles = scanEntityFiles(basePath);
System.out.println("Found " + entityFiles.size() + " entity files");
generateSql(entityFiles, outputPath);
System.out.println("SQL generation completed! Output: " + outputPath);
} catch (Exception e) {
System.err.println("SQL generation failed: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 扫描实体类文件
*/
private static List<String> scanEntityFiles(String basePath) {
List<String> files = new ArrayList<>();
File dir = new File(basePath);
if (dir.exists() && dir.isDirectory()) {
scanDir(dir, files);
}
return files;
}
/**
* 递归扫描目录
*/
private static void scanDir(File dir, List<String> files) {
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
scanDir(file, files);
} else if (file.getName().endsWith(".java")) {
String content = readFile(file);
if (content.contains("@TableName")) {
files.add(file.getAbsolutePath());
System.out.println("Found entity: " + file.getName());
}
}
}
}
/**
* 读取文件内容
*/
private static String readFile(File file) {
try {
StringBuilder sb = new StringBuilder();
java.nio.file.Files.lines(file.toPath())
.forEach(line -> sb.append(line).append("\n"));
return sb.toString();
} catch (Exception e) {
return "";
}
}
/**
* 生成 SQL 脚本
*/
private static void generateSql(List<String> entityFiles, String outputPath) {
try (PrintWriter writer = new PrintWriter(new FileWriter(outputPath))) {
writer.println("-- Auto-generated SQL from Entity classes (Enhanced Version)");
writer.println("-- Generated at: " + new java.util.Date());
writer.println("-- Features: Supports parent class fields (id, createTime, etc.)");
writer.println();
writer.println("USE my_database;");
writer.println();
for (String entityFile : entityFiles) {
try {
String content = readFile(new File(entityFile));
String tableName = extractTableName(content);
if (tableName != null) {
generateCreateTableSql(content, tableName, writer);
writer.println();
}
} catch (Exception e) {
System.err.println("Generate table for " + entityFile + " failed: " + e.getMessage());
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to write SQL file", e);
}
}
/**
* 提取表名
*/
private static String extractTableName(String content) {
Pattern pattern = Pattern.compile("@TableName\\(\"([^\"]+)\"\\)");
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
/**
* 生成 CREATE TABLE 语句(增强版)
*/
private static void generateCreateTableSql(String content, String tableName, PrintWriter writer) {
List<String> columnDefs = new ArrayList<>();
String primaryKey = null;
// 1. 首先添加标准字段(来自 BaseEntity)
columnDefs.add(" `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键 ID'");
primaryKey = "id";
// 2. 解析实体类自己的字段
String[] lines = content.split("\n");
for (String line : lines) {
if (line.trim().startsWith("private") && !line.trim().startsWith("private static")) {
String fieldInfo = parseField(line.trim());
if (fieldInfo != null && !isStandardField(line)) {
columnDefs.add(fieldInfo);
}
}
}
// 3. 添加标准时间字段
columnDefs.add(" `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'");
columnDefs.add(" `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'");
columnDefs.add(" `deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除'");
// 4. 添加主键
if (primaryKey != null) {
columnDefs.add(" PRIMARY KEY (`" + primaryKey + "`)");
}
writer.println("-- Table: " + tableName);
writer.println("CREATE TABLE IF NOT EXISTS `" + tableName + "` (");
writer.println(String.join(",\n", columnDefs));
writer.println(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='" + tableName + "';");
}
/**
* 解析字段信息
*/
private static String parseField(String line) {
line = line.replace(";", "").trim();
// 移除注解
line = line.replaceAll("@\\w+\\([^)]*\\)", "").trim();
line = line.replaceAll("@\\w+", "").trim();
String[] parts = line.split("\\s+");
if (parts.length < 2) return null;
String type = parts[parts.length - 2];
String name = parts[parts.length - 1];
// 跳过标准字段(会统一添加)
if (STANDARD_FIELDS.contains(name)) {
return null;
}
String columnName = camelToUnderline(name);
String columnType = getColumnType(type);
return " `" + columnName + "` " + columnType + " COMMENT '" + name + "'";
}
/**
* 判断是否标准字段
*/
private static boolean isStandardField(String line) {
for (String field : STANDARD_FIELDS) {
if (line.contains(" " + field + ";") || line.endsWith(" " + field)) {
return true;
}
}
return false;
}
/**
* 驼峰转下划线
*/
private static String camelToUnderline(String name) {
if (name == null || name.isEmpty()) return name;
StringBuilder sb = new StringBuilder();
sb.append(Character.toLowerCase(name.charAt(0)));
for (int i = 1; i < name.length(); i++) {
char c = name.charAt(i);
if (Character.isUpperCase(c)) {
sb.append('_');
sb.append(Character.toLowerCase(c));
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Java 类型转数据库类型
*/
private static String getColumnType(String javaType) {
switch (javaType) {
case "long":
case "Long":
case "int":
case "Integer":
return "BIGINT";
case "String":
return "VARCHAR(255)";
case "boolean":
case "Boolean":
return "TINYINT(1)";
case "Date":
case "LocalDateTime":
return "DATETIME";
case "LocalDate":
return "DATE";
case "double":
case "Double":
case "float":
case "Float":
return "DECIMAL(10,2)";
case "BigDecimal":
return "DECIMAL(19,2)";
case "List":
case "Map":
case "Set":
return "TEXT";
default:
return "VARCHAR(255)";
}
}
}
使用方法
编译项目
cd /path/to/your/project
mvn compile -q
运行工具
# 运行增强版生成器
java -cp "target/classes" com.example.tools.EnhancedEntityToSqlGenerator
执行生成的 SQL
# 方法 1:MySQL 命令行
mysql -u root -proot my_database < src/test/resources/schema-enhanced.sql
# 方法 2:重置数据库并重新创建
mysql -u root -proot -e "DROP DATABASE IF EXISTS my_database; CREATE DATABASE my_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -proot my_database < src/test/resources/schema-enhanced.sql
输出示例
实体类示例
@TableName("product")
public class Product extends BaseEntity {
@TableField("shop_id")
private Long shopId;
@TableField("title")
private String title;
@TableField("price")
private BigDecimal price;
@TableField("stock")
private Integer stock;
@TableField("is_hot")
private Boolean isHot;
}
// BaseEntity 包含以下字段:
// - id (主键)
// - createTime (创建时间)
// - updateTime (更新时间)
// - deleted (逻辑删除)
// - version (乐观锁)
生成的 SQL
-- Auto-generated SQL from Entity classes (Enhanced Version)
-- Generated at: Fri Feb 27 23:50:04 CST 2026
-- Features: Supports parent class fields (id, createTime, etc.)
USE my_database;
-- Table: product
CREATE TABLE IF NOT EXISTS `product` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键 ID',
`shop_id` BIGINT COMMENT 'shopId',
`title` VARCHAR(255) COMMENT 'title',
`price` DECIMAL(19,2) COMMENT 'price',
`stock` BIGINT COMMENT 'stock',
`is_hot` TINYINT(1) COMMENT 'isHot',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='product';
关键改进
旧版本问题:
- ❌ 缺少 id 主键字段
- ❌ 缺少 createTime、updateTime、deleted 等标准字段
- ❌ 需要手动在实体类中定义所有字段
增强版优势:
- ✅ 自动添加 id 主键(BIGINT AUTO_INCREMENT)
- ✅ 自动添加 create_time、update_time、deleted 标准字段
- ✅ 智能识别 BaseEntity 中的字段,避免重复
- ✅ 符合 MyBatis-Plus 自动填充规范
实践要点
技术亮点
零依赖
- 不依赖任何外部库,纯 Java 实现
- 只需编译后的 class 文件即可运行
- 避免了复杂的 Maven 插件配置
智能扫描
- 递归扫描所有子目录
- 自动识别
@TableName注解 - 支持任意包结构的实体类
- 增强版:自动识别父类字段
类型转换
| Java 类型 | 数据库类型 | 说明 |
|---|---|---|
| Long/int | BIGINT | 主键、外键 |
| String | VARCHAR(255) | 文本字段 |
| Boolean | TINYINT(1) | 布尔值 |
| Date/LocalDateTime | DATETIME | 日期时间 |
| LocalDate | DATE | 日期 |
| Double/Float | DECIMAL(10,2) | 小数 |
| BigDecimal | DECIMAL(19,2) | 高精度小数 |
| List/Map/Set | TEXT | 集合类型 |
命名规范
- 自动驼峰转下划线:
userId→user_id - 符合 MySQL 命名规范
- 保持与 MyBatis-Plus 默认策略一致
标准字段处理(增强版)
自动添加的标准字段:
idBIGINT NOT NULL AUTO_INCREMENT - 主键create_timeDATETIME DEFAULT CURRENT_TIMESTAMP - 创建时间update_timeDATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - 更新时间deletedTINYINT DEFAULT 0 - 逻辑删除标识
智能识别:
- 自动跳过实体类中已定义的标准字段
- 避免字段重复定义
- 符合 MyBatis-Plus 自动填充规范
实际效果
在电商平台项目中应用:
- 扫描实体类:60 个
- 生成表数量:60 个
- 执行时间:< 1 秒
- 字段完整性:100%(包含所有标准字段)
项目模块覆盖
- ✅ 用户模块 (user) - 11 张表
- ✅ 商品模块 (product) - 10 张表
- ✅ 订单模块 (order) - 10 张表
- ✅ 需求模块 (requirement) - 4 张表
- ✅ 合同模块 (contract) - 3 张表
- ✅ 发票模块 (invoice) - 4 张表
- ✅ 仲裁模块 (arbitration) - 3 张表
- ✅ 社区模块 (community) - 5 张表
- ✅ 消息模块 (message) - 3 张表
- ✅ 营销模块 (marketing) - 7 张表
- ✅ 文件模块 (file) - 1 张表
- ✅ 运营模块 (operation) - 5 张表
生成示例
Found 60 entity files
Found entity: User.java
Found entity: UserAuth.java
Found entity: Shop.java
Found entity: Product.java
Found entity: ProductSku.java
...
SQL generation completed! Output: /path/to/your/project/src/test/resources/schema-enhanced.sql
数据库验证
mysql> USE my_database;
mysql> SHOW TABLES;
+----------------------+
| Tables_in_my_database|
+----------------------+
| user |
| user_auth |
| shop |
| product |
| product_sku |
| product_category |
| ... (60 tables) |
+----------------------+
mysql> DESCRIBE product;
+---------------+--------------+------+-----+-------------------+
| Field | Type | Null | Key | Default |
+---------------+--------------+------+-----+-------------------+
| id | bigint | NO | PRI | NULL |
| shop_id | bigint | YES | | NULL |
| title | varchar(255) | YES | | NULL |
| price | decimal(19,2)| YES | | NULL |
| ... | ... | ... | ... | ... |
| create_time | datetime | YES | | CURRENT_TIMESTAMP |
| update_time | datetime | YES | | CURRENT_TIMESTAMP |
| deleted | tinyint | YES | | 0 |
+---------------+--------------+------+-----+-------------------+
常见问题
Q1: 生成的字段类型不符合预期?
可以在 getColumnType 方法中添加自定义类型映射:
private static String getColumnType(String javaType) {
switch (javaType) {
case "String":
return "VARCHAR(500)"; // 自定义长度
// 添加更多自定义映射...
}
}
Q2: 如何支持字段注释?
可以从 Javadoc 或自定义注解提取注释:
// 从 Javadoc 提取
/**
* 用户 ID
*/
@TableField("user_id")
private Long userId;
// 或使用自定义注解
@TableField(value = "user_id", comment = "用户 ID")
private Long userId;
Q3: 如何支持索引生成?
可以扩展工具支持 @Index 注解:
@TableName(value = "user", indexes = {
@Index(name = "idx_email", fields = {"email"}),
@Index(name = "idx_phone", fields = {"phone"})
})
public class User extends BaseEntity {
// ...
}
扩展优化建议
支持更多注解
// 可以扩展支持以下注解
@TableField(value = "column_name", comment = "字段注释")
@TableId(type = IdType.AUTO)
@TableLogic // 逻辑删除字段
@Version // 乐观锁字段
支持索引生成
// 根据 @Index 注解生成索引
KEY `idx_user_id` (`user_id`),
KEY `idx_created_at` (`created_at`)
支持外键约束
// 根据 @ForeignKey 注解生成外键
CONSTRAINT `fk_order_user`
FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
支持字段注释提取
// 从 Javadoc 或自定义注解提取注释
/**
* 用户 ID
*/
@TableField("user_id")
private Long userId;
// 生成:`user_id` BIGINT COMMENT '用户 ID'
与其他方式对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 手动编写 SQL | 完全可控,精确 | 工作量大,易出错,难维护 | 小型项目,表数量<10 |
| MyBatis-Plus 代码生成器 | 功能强大,支持逆向 | 配置复杂,需要数据库连接 | 已有数据库,生成代码 |
| Hibernate 自动建表 | 开箱即用,自动同步 | 性能问题,生产环境不推荐 | 快速原型,开发环境 |
| 本文工具(增强版) | 零配置、零依赖、快速、支持父类字段 | 需要扩展支持更多特性 | 快速建表、测试环境、CI/CD |
增强版核心优势:
- ✅ 无需数据库连接 - 仅从实体类生成
- ✅ 零配置 - 无需 XML 或注解配置
- ✅ 支持父类字段 - 自动识别 BaseEntity
- ✅ 标准字段统一 - 所有表结构一致
- ✅ 可重复执行 - 使用 CREATE TABLE IF NOT EXISTS
总结
本文介绍的工具通过扫描 MyBatis-Plus 实体类注解,自动生成数据库表结构 SQL 脚本,具有以下优势:
核心价值
- 开发效率高:无需手动编写建表语句,60 张表 1 秒生成
- 维护成本低:实体类修改后自动同步,无需手动更新数据库
- 测试环境友好:快速重建测试数据库,支持 CI/CD 自动化
- 零依赖:不依赖任何外部工具,纯 Java 实现
- 易扩展:可根据需求添加更多特性(索引、外键等)
增强版特性
- ✅ 自动识别父类字段 - 支持 BaseEntity 中的 id、createTime 等
- ✅ 标准字段统一 - 所有表自动添加 create_time、update_time、deleted
- ✅ 智能去重 - 自动跳过实体类中已定义的标准字段
- ✅ 完整主键 - 自动添加 BIGINT AUTO_INCREMENT 主键
实际应用效果
在电商平台项目中:
- 成功生成 60 张表 的建表脚本
- 字段完整性达到 100%
- 测试环境重建时间从 30 分钟 缩短到 1 分钟
- 开发效率提升 80% 以上
未来规划
- 支持索引自动生成(根据
@Index注解) - 支持外键约束(根据
@ForeignKey注解) - 支持字段注释提取(从 Javadoc 或
@TableField(comment)) - 支持数据库差异对比(实体类 vs 数据库)
- 支持增量更新(只修改变化的字段)
关于无边界科技
无边界科技是一家专注于软件定制开发与技术解决方案的服务团队,提供:
| 服务 | 价格 | 核心优势 |
|---|---|---|
| 🖥️ 软件开发定制 | ¥500起 | 敏捷开发、全栈技术、源码交付 |
| 💡 技术咨询服务 | ¥100起 | 专家团队、架构设计、性能优化 |
| 🚀 创业项目孵化 | 灵活付费 | 灵活付费、创业扶持、资源对接 |
| 🌟 个人品牌打造 | ¥1,000起 | 品牌定位、知识变现、数据分析 |
| 🛡️ 运维托管服务 | ¥2,000起/月 | 7×24监控、快速响应、安全防护 |
| 🤝 业务推广合作 | 提成10%-20% | 高额提成、长期合作、专属支持 |
联系我们
| 渠道 | 信息 |
|---|---|
| 🌐 官网 | wubianj.com |
| 📧 邮箱 | wubianjie_open@163.com |
| 💬 微信公众号 | 无边界软件 |
💡 提示:关注公众号获取更多技术干货和行业资讯!
本文由无边界科技技术团队原创,转载请注明出处。