CHARLIE SAYS

查理如是说
DATE 2026-08-24
THEME
SERIES / JHIPSTER / P-221 · JHipster 项目开发

JHipster 开发 06:实体代码生成深潜——JDL 从入门到驯服

JDL 是 JHipster 的灵魂:用一份声明式文本描述领域模型,生成器据此产出前后端全链路代码。这篇是我们团队多年 JDL 实战的完整沉淀——语法、选项取舍、增量更新机制、生成产物清单,最后用一个订单系统建模收尾。

JDL 是什么

JDL(JHipster Domain Language)是一门小 DSL,覆盖实体、关系、枚举、选项四类声明。它有两个不可替代的价值:

  1. 可评审:领域模型是纯文本,pull request 里 diff 一目了然,比”在数据库里改了表再通知大家”文明一个量级;
  2. 可持续再生:建模文件与 .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 类型用途
StringString常规字符串
Integer / Long / Float / Double / BigDecimal对应包装类型数值(金额用 BigDecimal)
BooleanBoolean开关
LocalDate / Instant / ZonedDateTimeJSR-310 时间生日用 LocalDate,时间戳用 Instant
Enum自定义枚举状态、类别
Blob / ImageBlob / TextBlobbyte[] / String文件、图片、长文本

每种类型有配套校验注解,生成时会同时落到后端(Bean Validation 注解)与前端(表单校验规则):

  • 通用:requiredunique(生成唯一约束与 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 不做行为建模。

选项取值效果与我们的建议
dtomapstruct / 否一律开。REST 层走 DTO + MapStruct,避免实体直出(懒加载与敏感字段双雷)
serviceserviceClass / serviceImpl / 否业务系统一律 serviceClassserviceImpl 生成接口+实现两件套,只有需要多实现时才有意义
paginatepagination / infinite-scroll / 否列表页必开 pagination(生成 Spring Data 分页 + 前端分页组件);移动端流式加载用 infinite-scroll
searchelasticsearch / 否需要全文检索的实体才开,会引入 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 新旧定义决定生成什么。两个实践纪律:

  1. .jhipster/app.jdl 必须提交版本库;
  2. 不要手改生成代码后又被增量生成覆盖——自定义业务逻辑写在生成类的既定扩展点里(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 一把生成。两个建模取舍值得注意:金额永远 BigDecimalOrderItem 独立成实体而不是塞 JSON 进订单表——关系型数据交给数据库,将来报表和对账都会感谢这个决定。

小结

  • JDL 四类声明:实体(类型+校验注解)、关系(OneToMany/ManyToMany/OneToOne)、枚举、选项(dto/service/paginate/search)。
  • dto * with mapstruct + service * with serviceClass + 列表 paginate 是业务系统的默认三件套。
  • 增量更新靠 .jhipster/*.json diff,建模文件必须进版本库;自定义逻辑写生成类的扩展点内,别硬改结构。
  • 生成产物横跨后端分层、前端页面、Liquibase、i18n、测试——改一处模型,全链路同步。

系列导航

← 集合源码 006:HashSet & HashMap 源码解析 目录 网络协议 006:UDP 协议详解 →
← 返回文章列表