首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 自動建表與表結構同步:基於註解的模型驅動開發實戰

     編輯:Java 相關知識

背景與動機

Hibernate/JPA 提供 `hbm2ddl.auto` 自動建表,MyBatis 官方無此功能。本文介紹一套**基於模型註解的自動建表/遷移框架**,已開源:

🔗 Mybatis_BuildTable_V0.2

架構設計

  • **技術棧**:Spring MVC + MyBatis + Maven 多模塊
  • **核心模塊**:`model-store-repo` (註解定義、掃描、DDL 生成)
  • **支援資料庫**:MySQL (可擴展)
  • **兩種模式**:`create` (重建) / `update` (增量同步,保留數據)

核心註解體系

1. @Table - 實體映射表

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String name() default ""; // 表名,默認類名小寫
}

2. @Column - 字段映射列

@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 "";         // 註釋
}

3. 類型長度標記 @LengthCount

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LengthCount {
    int value(); // 參數個數:0=無參數, 1=單參數(varchar), 2=雙參數(decimal)
}

4. 類型註冊表 SysMysqlColumns

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  # 掃描包路徑

核心邏輯:SysMysqlCreateTableManagerImpl

啟動入口

@Component
public class AutoCreateTableRunner implements CommandLineRunner {
    @Autowired SysMysqlCreateTableManager manager;
    @Override public void run(String... args) {
        manager.createMysqlTable(); // 應用啟動時執行
    }
}

create 模式 (重建)

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);
    }
}

update 模式 (增量同步,核心難點)

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

類型映射與 DDL 生成細節

Java 類型MySQL 類型LengthCount示例
Long/IntegerBIGINT/INT0`id BIGINT PRIMARY KEY AUTO_INCREMENT`
StringVARCHAR(255)1`name VARCHAR(100)`
BigDecimalDECIMAL(19,2)2`amount DECIMAL(10,2)`
LocalDateTimeDATETIME0`created_at DATETIME`
BooleanTINYINT(1)0`active TINYINT(1) DEFAULT 1`

Maven 多模塊構建順序

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**。推薦:

  1. 開發環境使用 `update` 模式快速同步
  2. 提交代碼時,**人工提煉** 生成的 ALTER 為遷移腳本 (V1.1.0__add_user_balance.sql)
  3. 使用 **Flyway / Liquibase** 管理版本化遷移
  4. 生產部署管道自動執行遷移腳本,審核可回滾

擴展方向

  • 支援 PostgreSQL、Oracle、SQL Server 方言
  • 索引、外鍵、唯一約束註解支援
  • 基於 Liquibase Diff 自動生成遷移腳本
  • 圖形化模型設計器雙向同步代碼
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved