找回密码
 立即注册
搜索
查看: 31|回复: 4

:fire:xbatis-ddl-auto 1.0.2 ORM DDL,最强 mybatis ORM DDL

[复制链接]

主题

0

回帖

0

积分

积分
0
发表于 3 天前 | 显示全部楼层 |阅读模式
官网 https://xbatis.cn

xbatis-ddl-auto 是一个基于 xbatis 实体元数据的轻量自动建表工具。

它复用 xbatis 的 @Table、@TableId、@TableField、@ColumnDefinition 等注解解析结果,根据实体类生成并执行数据库 DDL,提供接近 JPA ddl-auto=create/update 的使用体验,但不引入 JPA 或 Hibernate。
1.0.2 更新
增加SYNC模式,支持 列 索引 变更同步模式

功能

• 根据 xbatis 实体生成 CREATE TABLE SQL
• 表不存在时自动建表
• 表存在时可在 UPDATE 模式下自动追加新增字段
• 支持只生成 SQL,不执行数据库操作
• 通过 JDBC DatabaseMetaData 判断表、字段和索引是否存在
• 支持常见 Java 类型到数据库列类型映射
• 支持 @ColumnDefinition 配置字段长度、精度、小数位、默认值、唯一约束、非空和字段注释
• 支持类级 @Index 创建普通索引和唯一索引

安全边界

UPDATE 模式只会自动新增字段和缺失索引,不会自动执行以下高风险操作:

• 删除数据库已有字段
• 修改字段类型
• 修改字段长度
• 修改字段是否可空
• 修改默认值
• 重命名字段
• 修改或删除已有索引
• 删除或重建表


这些操作可能造成数据丢失或生产事故,建议通过人工审核 SQL 或专业迁移工具处理。
Maven

本工具 Maven 坐标:

  1. <dependency>
  2.     <groupId>cn.xbatis</groupId>
  3.     <artifactId>xbatis-ddl-auto</artifactId>
  4.     <version>1.0.1</version>
  5. </dependency>
复制代码

运行依赖 xbatis core

  1. <dependency>
  2.     <groupId>cn.xbatis</groupId>
  3.     <artifactId>xbatis-core</artifactId>
  4.     <version>1.10.6</version>
  5. </dependency>
复制代码

默认测试使用 JUnit 5 和 H2;需要真实数据库的集成测试通过 Maven profile 单独执行。
快速开始

定义 xbatis 实体

  1. import cn.xbatis.db.annotations.ColumnDefinition;
  2. import cn.xbatis.db.annotations.Table;
  3. import cn.xbatis.db.annotations.TableId;

  4. import java.math.BigDecimal;
  5. import java.time.LocalDateTime;

  6. @Table("sys_user")
  7. public class SysUser {

  8.     @TableId
  9.     private Long id;

  10.     @ColumnDefinition(length = 64, nullable = false)
  11.     private String username;

  12.     @ColumnDefinition(precision = 10, scale = 2, defaultValue = "0")
  13.     private BigDecimal balance;

  14.     private LocalDateTime createdAt;
  15. }
复制代码

执行自动建表:

  1. import db.sql.api.DbType;

  2. DDLAuto.of(DbType.MYSQL)
  3.     .add(SysUser.class)
  4.     .execute(dataSource);
复制代码
CREATE 模式

CREATE 是默认模式:

  1. DDLAuto.of(DbType.MYSQL)
  2.     .add(SysUser.class)
  3.     .execute(dataSource);
复制代码

行为:

• 表不存在:执行 CREATE TABLE
• 表已存在:跳过,不做任何变更

UPDATE 模式

UPDATE 模式用于补新增字段和缺失索引:

  1. import cn.xbatis.ddl.auto.Mode;

  2. DDLAuto.of(DbType.MYSQL)
  3.     .mode(Mode.UPDATE)
  4.     .add(SysUser.class)
  5.     .execute(dataSource);
复制代码

行为:

• 表不存在:执行 CREATE TABLE
• 表已存在:读取数据库已有列和索引,只对实体中新增的字段执行 ALTER TABLE ... ADD COLUMN ...,并创建缺失索引


重复执行 UPDATE 模式不会重复添加已存在字段或已存在索引。
只生成 SQL / 预览 SQL

不连接数据库,只生成建表 SQL:

  1. List<String> sqlList = DDLAuto.of(DbType.PGSQL)
  2.         .add(SysUser.class)
  3.         .sqlList();
复制代码

如果要按当前数据库状态预览将要执行的 SQL,可以传入 DataSource 或 Connection。该方法只读取 JDBC 元数据并生成 SQL,不会执行 DDL:

  1. List<String> sqlList = DDLAuto.of(DbType.MYSQL)
  2.         .mode(Mode.UPDATE)
  3.         .add(SysUser.class)
  4.         .sqlList(dataSource);
复制代码

行为:

• 表不存在:返回 CREATE TABLE 及附属 DDL
• 表已存在且是 CREATE 模式:返回空列表
• 表已存在且是 UPDATE 模式:只返回缺失字段的 ALTER TABLE ... ADD COLUMN ... 及附属 DDL,以及缺失索引的 CREATE INDEX


也可以使用底层构建器生成单个字段的新增列 SQL:

  1. import cn.xbatis.ddl.auto.DDLBuilder;
  2. import cn.xbatis.ddl.auto.DefaultDDLBuilder;

  3. DDLBuilder builder = new DefaultDDLBuilder();
  4. String sql = builder.addColumnSql(DbType.MYSQL, SysUser.class, "email");
复制代码

生产或准生产环境建议先通过 sqlList(dataSource) 或 sqlList(connection) 生成 SQL 并审核,再决定是否执行。
执行监听

DDL 会按 SQL 列表逐条执行。若中途失败,数据库可能已经保留前面成功执行的 DDL。可以配置执行监听器记录已执行 SQL:

  1. import cn.xbatis.ddl.auto.Mode;
  2. import cn.xbatis.ddl.auto.DDLExecutionListener;

  3. import java.util.ArrayList;
  4. import java.util.List;

  5. List<String> executedSqlLog = new ArrayList<>();

  6. DDLAuto.of(DbType.MYSQL)
  7.     .mode(Mode.UPDATE)
  8.     .executionListener(new DDLExecutionListener() {
  9.     @Override
  10.     public void afterExecute(String sql, List<String> executedSqlList) {
  11.         executedSqlLog.add(sql);
  12.     }

  13.     @Override
  14.     public void onExecuteError(String sql, SQLException exception, List<String> executedSqlList) {
  15.         // sql 为当前失败 SQL,executedSqlList 为失败前已成功执行的 SQL。
  16.     }
  17.     })
  18.     .add(SysUser.class)
  19.     .execute(dataSource);
复制代码

生产或准生产环境建议先通过 sqlList(dataSource) 或 sqlList(connection) 生成 SQL 并审核,再决定是否执行。
执行监听

DDL 会按 SQL 列表逐条执行。若中途失败,数据库可能已经保留前面成功执行的 DDL。可以配置执行监听器记录已执行 SQL:

  1. import cn.xbatis.ddl.auto.Mode;
  2. import cn.xbatis.ddl.auto.DDLExecutionListener;

  3. import java.util.ArrayList;
  4. import java.util.List;

  5. List<String> executedSqlLog = new ArrayList<>();

  6. DDLAuto.of(DbType.MYSQL)
  7.     .mode(Mode.UPDATE)
  8.     .executionListener(new DDLExecutionListener() {
  9.     @Override
  10.     public void afterExecute(String sql, List<String> executedSqlList) {
  11.         executedSqlLog.add(sql);
  12.     }

  13.     @Override
  14.     public void onExecuteError(String sql, SQLException exception, List<String> executedSqlList) {
  15.         // sql 为当前失败 SQL,executedSqlList 为失败前已成功执行的 SQL。
  16.     }
  17.     })
  18.     .add(SysUser.class)
  19.     .execute(dataSource);
复制代码

执行失败时抛出的 SQLException 消息也会包含当前失败 SQL 和失败前已执行 SQL。
支持的注解
@Table

用于解析表名和 schema:

  1. @Table("sys_user")
  2. public class SysUser {
  3. }
复制代码
@TableId

用于识别主键和数据库自增:

  1. @TableId
  2. private Long id;
复制代码

默认自增类型为 xbatis 的 IdAutoType.AUTO。 单主键会按数据库方言生成自增片段;联合主键不会为每个主键字段自动生成自增片段。

如果主键使用 IdAutoType.SQL 通过数据库序列取值,会从 sql 中解析序列名并在建表前生成 CREATE SEQUENCE:

  1. @TableId(dbType = DbType.Name.PGSQL, value = IdAutoType.SQL, sql = "select nextval('id_test_id_seq')")
  2. @TableId(dbType = DbType.Name.ORACLE, value = IdAutoType.SQL, sql = "select id_test_seq.NEXTVAL FROM dual")
  3. @TableId(dbType = DbType.Name.SQL_SERVER, value = IdAutoType.SQL, sql = "select next value for id_test_sqlserver_seq")
  4. @TableId(dbType = DbType.Name.DB2, value = IdAutoType.SQL, sql = "select next value for id_test_db2_seq from sysibm.sysdummy1")
  5. private Long id;
复制代码

当前支持解析

• PostgreSQL:nextval('sequence_name')
• Oracle / DM:sequence_name.NEXTVAL
• SQL Server / DB2:NEXT VALUE FOR sequence_name
• 其他数据库:兜底解析上述常见形式,并生成通用序列 DDL:


  1. CREATE SEQUENCE my_sequence
  2.     START WITH 1
  3.     INCREMENT BY 1;
复制代码

UPDATE 模式会读取数据库已有序列,只创建缺失序列,不重复创建。
@ColumnDefinition

用于控制建表字段定义:

  1. @ColumnDefinition(
  2.         length = 64,
  3.         nullable = false,
  4.         unique = true,
  5.         comment = "用户名"
  6. )
  7. private String username;
复制代码

常用配置:

• length:字符串长度
• precision:数值精度
• scale:小数位数
• defaultValue:数据库默认值 SQL 片段
• nullable:是否允许为空
• unique:是否唯一
• definition:字段类型片段,配置后优先替代 Java 类型到数据库类型的自动推导,length、precision、scale、defaultValue、nullable、unique、comment 等其他配置仍会继续生效
• comment:字段注释;MySQL 使用列内联 COMMENT,PostgreSQL / Oracle / DM 使用独立 COMMENT ON COLUMN,SQL Server 使用 sys.sp_addextendedproperty


当 definition 本身没有包含括号参数时,会按配置补齐长度或精度,例如 @ColumnDefinition(definition = "VARCHAR", length = 64) 会生成 VARCHAR(64);如果已经写成 VARCHAR(64),则不会再追加参数。

unique = true 目前表示单字段唯一约束:

• CREATE 模式下,多数关系型数据库使用列内联 UNIQUE


• UPDATE 模式新增字段时,SQLite 不支持 ALTER TABLE ADD COLUMN ... UNIQUE,会改为先新增字段再生成 CREATE UNIQUE INDEX
• ClickHouse 不支持传统唯一约束,配置 unique = true 时会直接抛出异常,避免生成无效 SQL
• 不支持联合唯一、部分唯一索引、命名唯一约束和已存在字段的唯一约束同步

@Index

用于在实体类上声明数据库索引:

  1. import cn.xbatis.db.IndexDirection;
  2. import cn.xbatis.db.annotations.Index;
  3. import cn.xbatis.db.annotations.IndexField;
  4. import cn.xbatis.db.annotations.Table;

  5. @Index(name = "idx_sys_user_username", fields = @IndexField(name = "username"))
  6. @Index(
  7.         name = "uk_sys_user_username_created_at",
  8.         unique = true,
  9.         fields = {
  10.                 @IndexField(name = "username"),
  11.                 @IndexField(name = "createdAt", direction = IndexDirection.DESC)
  12.         }
  13. )
  14. @Table("sys_user")
  15. public class SysUser {
  16. }
复制代码

说明:

• name:索引名;为空时按表名和列名生成稳定索引名
• unique:是否唯一索引
• fields:索引字段,name 使用实体字段名,也兼容已映射的列名
• direction:索引字段排序,支持 ASC、DESC,默认不追加排序片段
• CREATE 模式下,建表后生成 CREATE INDEX
• UPDATE 模式下,只按索引名创建数据库中缺失的索引,不修改或删除已有索引
• ClickHouse 不支持传统 CREATE INDEX 时会直接抛出异常,避免生成无效 SQL


PostgreSQL 字段注释会生成额外 SQL:

  1. COMMENT ON COLUMN sys_user.username IS '用户名';
复制代码

SQL Server 字段注释会生成扩展属性 SQL:

  1. DECLARE @schema sysname = SCHEMA_NAME();
  2. EXEC sys.sp_addextendedproperty
  3.   @name=N'MS_Description',
  4.   @value=N'用户名',
  5.   @level0type=N'SCHEMA',
  6.   @level0name=@schema,
  7.   @level1type=N'TABLE',
  8.   @level1name=N'sys_user',
  9.   @level2type=N'COLUMN',
  10.   @level2name=N'username';
复制代码
类型映射

默认类型映射包括:

• String / Character / UUID -> VARCHAR
• Integer / int -> INTEGER
• Long / long / BigInteger -> BIGINT
• Short / short -> SMALLINT
• Boolean / boolean -> BOOLEAN、MySQL 为 TINYINT(1)
• BigDecimal -> DECIMAL(precision, scale)
• Float / Double -> 浮点类型
• byte&#91;&#93; -> 二进制大字段
• LocalDate -> DATE
• LocalTime -> TIME,Oracle / DM 使用 TIMESTAMP
• LocalDateTime / Timestamp / Date -> TIMESTAMP,MySQL 使用 DATETIME,SQL Server 使用 DATETIME2
• 普通 enum -> VARCHAR(64)
• 实现 xbatis EnumSupport<T> 的枚举 -> 按 T 的类型映射,例如 EnumSupport<Integer> -> INTEGER


具体类型会根据 DbType 做方言调整。

原文链接

打赏作者

当前余额:0 Token,打赏后立即到账

主题

0

回帖

0

积分

积分
0
发表于 3 天前 | 显示全部楼层
学到了,ddl这块之前一直似懂非懂,看完这篇清晰多了。

主题

0

回帖

0

积分

积分
0
发表于 3 天前 | 显示全部楼层
执行监听 DDL 会按 SQL 列表逐条执行。
讲得挺透,fire这块就该这么理解。

主题

0

回帖

0

积分

积分
0
发表于 3 天前 | 显示全部楼层
若中途失败,数据库可能已经保留前面成功执行的 DDL。
这句信息量很大,值得单独展开讲讲。

主题

0

回帖

0

积分

积分
0
发表于 3 天前 | 显示全部楼层
排版舒服内容也硬核,这样的帖子多来点。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

{ template common/footer}