Hibernate/JPA 提供 `hbm2ddl.auto` 自動建表,MyBatis 官方無此功能。本文介紹一套**基於模型註解的自動建表/遷移框架**,已開源:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String name() default ""; // 表名,默認類名小寫
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name() default ""; // 列名,默認字段名
String type() default "varchar(255)"; // MySQL 類型
boolean primaryKey() default false; // 是否主鍵
boolean autoIncrement() default false; // 自增
boolean nullable() default true; // 允許 NULL
String defaultValue() default ""; // 默認值
String comment() default ""; // 註釋
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LengthCount {
int value(); // 參數個數:0=無參數, 1=單參數(varchar), 2=雙參數(decimal)
}
public class SysMysqlColumns {
// 類型 -> LengthCount 參數個數
@LengthCount(0) static final String BIGINT = "bigint";
@LengthCount(0) static final String DATETIME = "datetime";
@LengthCount(1) static final String VARCHAR = "varchar";
@LengthCount(2) static final String DECIMAL = "decimal";
// ... 更多類型
}
@Table(name = "t_user")
public class User {
@Column(name = "id", type = "bigint", primaryKey = true, autoIncrement = true)
private Long id;
@Column(name = "username", type = "varchar", nullable = false)
@LengthCount(1) // varchar 需 1 個長度參數
private String username;
@Column(name = "balance", type = "decimal")
@LengthCount(2) // decimal 需 2 個參數 (精度,標度)
private BigDecimal balance;
@Column(name = "created_at", type = "datetime")
private LocalDateTime createdAt;
// getter/setter...
}
# model-store-frontend/resources/config/autoCreateTable.properties mybatis.table.auto=update # create | update mybatis.model.pack=com.example.model # 掃描包路徑
@Component
public class AutoCreateTableRunner implements CommandLineRunner {
@Autowired SysMysqlCreateTableManager manager;
@Override public void run(String... args) {
manager.createMysqlTable(); // 應用啟動時執行
}
}
public void createMode() {
for (Class> entityClass : scanEntities()) {
Table tableAnn = entityClass.getAnnotation(Table.class);
String tableName = tableAnn.name().isEmpty() ?
toUnderscoreCase(entityClass.getSimpleName()) : tableAnn.name();
// 1. Drop 表
jdbcTemplate.execute("DROP TABLE IF EXISTS " + tableName);
// 2. 生成 CREATE TABLE SQL
String ddl = buildCreateDDL(entityClass, tableName);
jdbcTemplate.execute(ddl);
}
}
public void updateMode() {
for (Class> entityClass : scanEntities()) {
String tableName = getTableName(entityClass);
// 1. 獲取數據庫現有結構
List dbCols = getTableColumns(tableName);
Map dbColMap = dbCols.stream()
.collect(Collectors.toMap(ColumnMeta::getName, c -> c));
// 2. 解析實體定義結構
List entityCols = parseEntityColumns(entityClass);
// 3. 比對差異
for (ColumnMeta ec : entityCols) {
ColumnMeta dc = dbColMap.get(ec.getName());
if (dc == null) {
// 新增列
execute("ALTER TABLE " + tableName + " ADD COLUMN " + toColumnDef(ec));
} else if (!columnEquals(dc, ec)) {
// 修改列類型/屬性 (MySQL 8.0+ 支援 INSTANT ADD/DROP)
execute("ALTER TABLE " + tableName + " MODIFY COLUMN " + toColumnDef(ec));
}
}
// 4. 處理實體中刪除的列 (可選,默認不自動刪除防誤刪)
// for (ColumnMeta dc : dbCols) { if (!entityHas(dc)) execute("DROP COLUMN"); }
}
}
| Java 類型 | MySQL 類型 | LengthCount | 示例 |
|---|---|---|---|
| Long/Integer | BIGINT/INT | 0 | `id BIGINT PRIMARY KEY AUTO_INCREMENT` |
| String | VARCHAR(255) | 1 | `name VARCHAR(100)` |
| BigDecimal | DECIMAL(19,2) | 2 | `amount DECIMAL(10,2)` |
| LocalDateTime | DATETIME | 0 | `created_at DATETIME` |
| Boolean | TINYINT(1) | 0 | `active TINYINT(1) DEFAULT 1` |
parent (pom) ├── model-store-repo (jar) # 核心註解、掃描、DDL 生成 ├── model-store-web (war) # 依賴 repo,Web 入口、Controller ├── model-store-frontend (war) # 依賴 web,前台業務 └── model-store-mobile (war) # 依賴 web,移動端業務 # 安裝順序 mvn clean install -pl model-store-repo -am mvn clean install -pl model-store-web -am # 然後啟動 frontend/mobile
| 優點 | 缺點/風險 |
|---|---|
| 開發期極速迭代,無需手寫 SQL | 生產環境**嚴禁**使用 create 模式 |
| 模型即文檔,結構可視化 | update 模式無法處理複雜遷移 (重命名、分表、數據轉換) |
| 統一類型映射,減少手寫錯誤 | MySQL 特有語法 (分區、外鍵、索引優化) 需手工補充 |
| 適合微服務獨立部署、頻繁變更 | 團隊協作需規範:誰負責遷移腳本、如何審核 |
**不建議生產環境自動 DDL**。推薦: