JDL 是 JHipster 的灵魂:用一份声明式文本描述领域模型,生成器据此产出前后端全链路代码。这篇是我们团队多年 JDL 实战的完整沉淀——语法、选项取舍、增量更新机制、生成产物清单,最后用一个订单系统建模收尾。
JDL 是什么
JDL(JHipster Domain Language)是一门小 DSL,覆盖实体、关系、枚举、选项四类声明。它有两个不可替代的价值:
- 可评审:领域模型是纯文本,pull request 里 diff 一目了然,比”在数据库里改了表再通知大家”文明一个量级;
- 可持续再生:建模文件与
.jhipster/*.json实体定义进版本库,任何人任何时候都能从模型重建全栈代码。
实体与字段类型
entity Product {
name String required minlength(2) maxlength(120)
sku String required unique
price BigDecimal required min(0)
weight Double
stock Integer required min(0)
active Boolean
releasedOn LocalDate
createdAt Instant required
cover ImageBlob
description TextBlob
}
支持的常用字段类型:
| JDL 类型 | Java 类型 | 用途 |
|---|---|---|
| String | String | 常规字符串 |
| Integer / Long / Float / Double / BigDecimal | 对应包装类型 | 数值(金额用 BigDecimal) |
| Boolean | Boolean | 开关 |
| LocalDate / Instant / ZonedDateTime | JSR-310 时间 | 生日用 LocalDate,时间戳用 Instant |
| Enum | 自定义枚举 | 状态、类别 |
| Blob / ImageBlob / TextBlob | byte[] / String | 文件、图片、长文本 |
每种类型有配套校验注解,生成时会同时落到后端(Bean Validation 注解)与前端(表单校验规则):
- 通用:
required、unique(生成唯一约束与 Liquibase 索引) - String:
minlength(n)、maxlength(n)、pattern(/regex/) - 数值:
min(n)、max(n)
关系:三种形态
relationship OneToOne {
Customer{user} to User{customer} // 单向/双向由花号内是否声明决定
}
relationship OneToMany {
Customer{order} to Order{customer(name required)} // 显示字段与必填可在此指定
}
relationship ManyToMany {
Order{product} to Product{order}
}
要点:
- OneToMany:JPA 里的天然形态,拥有方在多的一侧(Order 持有 customer_id 外键);
- ManyToMany:自动生成中间表(如
rel_order__product),Liquibase 同步建表; - OneToOne:主侧持外键,可选
injectField控制注入方向; - 花括号里声明的是在对方实体上注入的字段名,不写就是单向;
- 关系字段默认懒加载,DTO 里按需带出(避免序列化炸雷)。
枚举
enum OrderStatus {
PENDING, PAID, SHIPPED, COMPLETED, CANCELLED
}
entity Order {
status OrderStatus required
}
生成物覆盖全链路:Java enum(含 i18n key)、Liquibase 列、前端 TS enum 加翻译条目。状态机流转逻辑则留在 Service 里手写——JDL 不做行为建模。
四个关键选项:dto / service / paginate / search
| 选项 | 取值 | 效果与我们的建议 |
|---|---|---|
dto | mapstruct / 否 | 一律开。REST 层走 DTO + MapStruct,避免实体直出(懒加载与敏感字段双雷) |
service | serviceClass / serviceImpl / 否 | 业务系统一律 serviceClass;serviceImpl 生成接口+实现两件套,只有需要多实现时才有意义 |
paginate | pagination / infinite-scroll / 否 | 列表页必开 pagination(生成 Spring Data 分页 + 前端分页组件);移动端流式加载用 infinite-scroll |
search | elasticsearch / 否 | 需要全文检索的实体才开,会引入 Elasticsearch 依赖 |
示例:
dto * with mapstruct
service * with serviceClass
paginate Customer, Order, Product with pagination
search Product with elasticsearch
* 表示应用到全部实体。还可以 all except Foo 这种组合。
JDL Studio 与建模流程
JDL Studio 是官方在线编辑器,左边写 JDL 右边实时出 ER 图,导出 .jhipster 文件。我们的流程:领域讨论时在 JDL Studio 里现场改图达成一致 → 存入仓库 app.jdl → pull request 评审 → 合并后生成。
增量更新与 .jhipster/*.json
第一次 jhipster jdl app.jdl 生成全部;此后模型变更(加字段、改校验、加关系)不需要从零再来,两条路:
# 路线一:改 app.jdl 后重跑(只会更新受影响的实体)
jhipster jdl app.jdl
# 路线二:单个实体细粒度再生成
jhipster entity Customer
jhipster entity Customer --single-entity # 只再生该实体,不连带关系对方
每生成一个实体,.jhipster/Customer.json 会记录它的完整定义(字段、校验、关系、选项、changelog 名)。这份文件是增量更新的真源:生成器靠 diff 新旧定义决定生成什么。两个实践纪律:
.jhipster/与app.jdl必须提交版本库;- 不要手改生成代码后又被增量生成覆盖——自定义业务逻辑写在生成类的既定扩展点里(Service 方法体内),签名与结构别动;确实要改结构的,先改 JDL 再生成,而不是手改代码。
增量更新时生成器会询问”是否重新生成覆盖”,未提交的手改生成文件有被冲掉的风险——所以生成前保持工作区干净、生成后立刻 review diff 再提交。
生成产物清单
一个实体(以 dto/serviceClass/pagination 全开为例)会产出:
后端
├── domain/Customer.java # 实体 + 校验注解 + 审计字段
├── domain/enums/… # 枚举(若有)
├── repository/CustomerRepository.java # 含分页/排序/JPA 规范查询支持
├── service/CustomerService.java # 业务服务
├── service/dto/CustomerDTO.java
├── service/mapper/CustomerMapper.java # MapStruct
├── web/rest/CustomerResource.java # CRUD REST + 分页端点
├── config/liquibase/changelog/
│ └── 20260824000000_added_entity_Customer.xml # 建表 + 索引 + 关系外键
前端
├── entities/customer/customer.model.ts # 类型与枚举
├── entities/customer/customer.service.ts # REST 客户端
├── entities/customer/customer.routes.ts # 路由 + 权限守卫
├── entities/customer/list/customer.component.* # 列表(分页)
├── entities/customer/detail/… update/… delete/… # 详情/编辑/删除
├── i18n/zh-cn/customer.json 等 # 全语言条目
测试
├── CustomerRepositoryIT / CustomerServiceIT / CustomerResourceIT
├── web/rest 的技术测试基类更新
└── (前端测试,取决于所选测试器)
Liquibase changelog 与 master.xml 的挂接第 7 篇展开。
实战:订单系统建模
把前面所有要素拼起来。需求:客户(带等级)下订单,订单含多条明细,明细关联商品;订单有状态流转;商品要全文搜索。
application {
config {
applicationType monolith
baseName shop
authenticationType jwt
databaseType sql
prodDatabaseType postgresql
devDatabaseType h2disk
cacheProvider hazelcast
buildTool maven
clientFramework angular
}
}
entity Customer {
name String required minlength(2) maxlength(100)
email String required pattern(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)
level CustomerLevel required
creditLimit BigDecimal min(0)
}
enum CustomerLevel { BASIC, VIP, SVIP }
entity Product {
name String required maxlength(200)
sku String required unique
price BigDecimal required min(0)
stock Integer required min(0)
description TextBlob
}
entity ProductOrder {
orderNo String required unique
status OrderStatus required
placedDate Instant required
shipAddress String required maxlength(500)
}
enum OrderStatus { PENDING, PAID, SHIPPED, COMPLETED, CANCELLED }
entity OrderItem {
quantity Integer required min(1)
unitPrice BigDecimal required min(0)
}
relationship ManyToOne {
ProductOrder{customer(email required)} to Customer
}
relationship OneToMany {
Customer{order} to ProductOrder
ProductOrder{item} to OrderItem{order(required)}
OrderItem{product(name required)} to Product
}
dto * with mapstruct
service * with serviceClass
paginate Customer, Product, ProductOrder with pagination
search Product with elasticsearch
jhipster jdl order.jdl 一把生成。两个建模取舍值得注意:金额永远 BigDecimal;OrderItem 独立成实体而不是塞 JSON 进订单表——关系型数据交给数据库,将来报表和对账都会感谢这个决定。
小结
- JDL 四类声明:实体(类型+校验注解)、关系(OneToMany/ManyToMany/OneToOne)、枚举、选项(dto/service/paginate/search)。
dto * with mapstruct+service * with serviceClass+ 列表paginate是业务系统的默认三件套。- 增量更新靠
.jhipster/*.jsondiff,建模文件必须进版本库;自定义逻辑写生成类的扩展点内,别硬改结构。 - 生成产物横跨后端分层、前端页面、Liquibase、i18n、测试——改一处模型,全链路同步。
系列导航
- 上一篇:微服务 vs 单体——怎么选
- 下一篇:数据库迁移——Liquibase 不是可选项
- 延伸阅读:环境搭建与第一个应用 · 开发工作流——热重载与前后端联调