本文是对JHipster开发文档的部分翻译,供个人学习之用。
原文链接:https://jhipster.github.io/creating-an-entity/
1.介绍
一旦你创建了应用,就会希望创建实体。
对于每个实体,你需要:
- 一个数据库表
- 一个Liquibase变化集合
- 一个JPA实体
- 一个Spring Data JPA Repository
- 一个Spring MVC REST Controller,有基本的CRUD操作
- 一个Angular router,一个component和一个service
- 一个HTML页面
- 集成的测试,来验证所有都像预计的一样运行
- 性能测试,观察是否所有都顺利运行
如果已经有几个类,你可能想要他们彼此之间有关系,这就需要:
- 一个数据库的外键
- 特定的JavaScript和HTML代码来管理各种关系
实体生成器将会生成必要的文件,并且为每个实体提供一个CRUD的前后端。生成器可以用yo jhipster:entity <entityName> --[options]来调用,可选参数可以用yo jhipster:entity --help来查询,下面是一些选项:
- --table-name <table-name>:默认情况下JHipster会生成一个名为实体名字的表,如果你想要一个不一样的表名,可以在这里传递参数。
- --angular-suffix <suffix>:如果你想要所有的Angular路由都有一个自定义的后缀,可以在这里传递参数。
- --regenerate:这会重新生成一个已存在的实体。
- --skip-server:这会跳过服务器端的代码,只生成客户端的代码。
- --skip-client:这会跳过客户端的代码,只生成服务器端的代码。
2.JHipster UML和JDL Studio
如果想要创建许多实体,可以使用图形工具
- JHipster UML,让你可以使用一个UML编辑器
- JDL Studio,使用JDL创建实体和关系的线上工具
如果使用JDL Studio
- 执行yo jhipster:import-jdl your-jdl-file.jh,用import-jdl生成器从一个JDL文件生成实体
- 运行npm install -g jhipster-uml,然后jhipster-uml yourFileName.jh,用JHipster UML来代替import-jdl生成器
3.实体域
可以对每种实体添加想要的域,只需要输入域的名字和他们的类型,JHipster就会生成从Angular HTML页面到Liquibase changelog所有需要的代码和配置。
这些域不能包含所用技术的保留关键字,如MySQL和Java的关键字。
4.域类型
JHipster支持许多域类型,这种支持依赖于你的数据库端,我们只要使用Java类型来描述即可。
- String:Java字符串,默认大小取决于后端如JPA就是255,可以使用验证规则来修改
- Integer:整型数
- Long:长整型
- Float:浮点数
- Double:双精度浮点数
- BigDecimal:java.math.BigDecimal对象,用于精确的数学计算
- LocalDate:java.time.LocalDate对象,用于准确管理日期
- ZoneDateTime:java.time.ZonedDateTime对象,用于准确管理日期和时间
- Boolean:布尔值
- Enumeration:java枚举类型,选择后,生成器会问你想要枚举类型中的什么值,并且会创建一个特殊的enum类来存储
- Blob:Blob对象,用于存储一些二进制数据,选择后,生成器会问你是否想要存储一般的二进制数据,一个图像对象或一个CLOB(长文本)。图像将可以在Angular端特殊处理,他们将会显示在用户终端。
5.验证
每个域都可以进行验证,不同的域类型可以有不同的验证选项。
验证会自动生成:
- HTML页面,使用AngularJS验证机制
- Java领域对象,使用Bean验证
当领域对象用于以下场景时,将自动进行Bean验证:
- Spring MVC REST控制器(使用@Valid注解)
- Hibernate/JPA(实体在保存前自动验证)
验证信息也会用于生成更精确数据库列元数据:
- Required域将标志为non-nullable
- 有最大长度的域将有相同的列长度
验证有一些限制:
- 不支持所有的AngularJS和Bean验证选项,只有两个API都有的。
- 正则表达式在JavaScript和Java中不一样,所以设置一个后可能需要稍稍调整另一个生成的模式
- JHipster生成一般实体的单元测试,却不知道你的验证规则,你需要更新单元测试中用到的示例值,这样他们才会传递验证规则
6.实体关系
实体关系只有当存在JPA时对SQL数据库有用,如果选择了Cassandra或MongoDB,就没有用。
关系可以存在于两个实体之间,JHipster将会生成下列代码:
在生成的实体中用JPA管理关系
对存在于数据库中的关系创建正确的Liquibase changelog
生成AngularJS前端,你可以在图像用户界面进行管理
双向的一对多关系:
Owner(1) <-----> (*)Car,一个主人可以有多辆车,一辆车只能有一个主人,并且通过车可以找到主人,通过主人可以找到所有的车。
先创建Owner:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? car
? What is the type of the relationship? one-to-many
? What is the name of this relationship in the other entity? owner
再生成Car:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Owner
? What is the name of the relationship? owner
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Owner' do you want to use? id
同样在JDL中可以:
entity Owner
entity Car
relationship OneToMany {
Owner{car} to Car{owner}
}
这样在AngularJS客户界面上当选择Owner时就有一个下拉的Car
单向的多对一关系:
Owner(1) <------- (*)Car,一个主人可以有多辆车,一辆车只能有一个主人,但是通过主人不能找到车,通过车可以找到他的主人。先生成Owner
Generating relationships with other entities
? Do you want to add a relationship to another entity? No
再生成Car
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Owner
? What is the name of the relationship? owner
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Owner' do you want to use? id
JDL:
entity Owner
entity Car
relationship ManyToOne {
Car{owner} to Owner
}
这样就不能从Owner实体中添加或移除车。
单向的一对多关系:
Owner (1) ------> (*) Car,一个主人可以有多辆车,一辆车只能有一个主人,但是通过主人可以找到所有的车,通过车不能找到它的主人。
这种关系在JHipster中还不默认提供,有两种解决方法:
- 执行一个双向映射,不做修改地使用
- 执行一个双向映射,将其修改为一个单向映射:
- 移除@OneToMany注解的mappedBy属性
- 生成必要的连接表:mvn liquibase:diff来生成
JDL不支持。
两对相同实体上的两个一对多关系:
Person (1) <---owns-----> (*) Car
Person (1) <---drives----> (*) Car
先生成Person实体:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? ownedCar
? What is the type of the relationship? one-to-many
? What is the name of this relationship in the other entity? owner
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? drivedCar
? What is the type of the relationship? one-to-many
? What is the name of this relationship in the other entity? driver
再生成Car实体,使用在Person中配置的关系名称:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Person
? What is the name of the relationship? owner
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Person' do you want to use? id
...
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Person
? What is the name of the relationship? driver
? What is the type of the relationship? many-to-one
? When you display this relationship with AngularJS, which field from 'Person' do you want to use? id
JDL:
entity Person
entity Car
relationship OneToMany {
Person{ownedCar} to Car{owner}
}
relationship OneToMany {
Person{drivedCar} to Car{driver}
}
在客户端你可以在选择Person的owner和driver域时有Car的下拉列表
多对多关系:
Driver (*) <--------> (*) Car,一个司机可以开多辆车,一辆车可以有多个司机。在数据库层,意味着在Driver和Car之间有一个连接表。
对于JPA,两个实体中的一个需要管理这个关系,我们这里让Car来管理,负责添加或删除Driver。
先生成Driver:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? car
? What is the type of the relationship? many-to-many
? Is this entity the owner of the relationship? No
? What is the name of this relationship in the other entity? driver
再生成Car,是关系的所有者:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Driver
? What is the name of the relationship? driver
? What is the type of the relationship? many-to-many
? Is this entity the owner of the relationship? Yes
? When you display this relationship with AngularJS, which field from 'Driver' do you want to use? id
JDL:
entity Driver
entity Car
relationship ManyToMany {
Car{driver} to Driver{car}
}
在客户端,因为Car是所有者,可以在Car中下拉选择Driver。
一对一关系:
Driver (1) <---------->(1) Car,一辆车只有一个司机,一个司机只开一辆车。先生成Driver
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Car
? What is the name of the relationship? car
? What is the type of the relationship? one-to-one
? Is this entity the owner of the relationship? No
? What is the name of this relationship in the other entity? driver
再生成Car
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Driver
? What is the name of the relationship? driver
? What is the type of the relationship? one-to-one
? Is this entity the owner of the relationship? Yes
? What is the name of this relationship in the other entity? car
? When you display this relationship with AngularJS, which field from 'Driver' do you want to use? id
JDL:
entity Driver
entity Car
relationship OneToOne {
Car{driver} to Driver{car}
}
因为Car是关系的所有者,可以在Car中下拉选择Driver。
单向的一对一关系:
Citizen(1) <--------->(1)Passport,一个公民只能有一张护照,但是一个护照实例不能有主人。
先生成Passport:
Generating relationships with other entities
? Do you want to add a relationship to another entity? No
再生成Citizen:
Generating relationships with other entities
? Do you want to add a relationship to another entity? Yes
? What is the name of the other entity? Passport
? What is the name of the relationship? passport
? What is the type of the relationship? one-to-one
? Is this entity the owner of the relationship? Yes
? What is the name of this relationship in the other entity? citizen
? When you display this relationship with AngularJS, which field from 'Passport' do you want to use? id
JDL:
entity Citizen
entity Passport
relationship OneToOne {
Citizen{passport} to Passport
}
一个公民持有一个护照,但是护照中没有定义公民实例,在客户端,Citizen中可以下拉选择Passport。
7.数据传输类型
默认的JHipster实体不使用DTOs,但是他们是可选的
默认情况下,JHipster直接在REST终端使用领域对象——JPA实体,主要的好处就是让代码更易用、易理解、易扩展。
然而,对于复杂的用例,可能想用可以被REST终端暴露的数据转化对象(DTO)。这些对象在领域对象的顶层添加了一个额外的层,专门用于REST层。主要的好处就是可以集合多个领域对象。
DTO的使用原理:
当生成一个JHipster实体时,可以选择是否也生成DTO,如果选择:
- 生成一个DTO,映射到依赖的实体
- 集合多对一的关系,只使用ID和用于展示的域。例如一个User实体的多对一关系会将userId域和userLogin域添加到DTO中。
- 将忽略非所有者上的一对多、多对多关系,这符合实体的运作方式(他们对这些域使用@JsonIgnore注解)
- 对于所有者的多对多关系,使用其他实体的DTO并将他们放在一个Set中使用,因此,只有当其他实体也使用DTO时才能支持。
使用MapStruct映射DTO和实体:
DTO看起来很像实体,经常需要将他们想办法相互映射。
JHipster选择的方式是使用MapStruct,这是一个注解处理器,作为Java编译器的插件,将自动生成必要的映射。
这简介又有效,并且似乎不使用反射(当在mapper中用到很多时,性能很受影响)
为MapStruct配置IDE:
MapStruct需要设置为在IDE编译项目时自动运行
如果使用Maven,需要激活IDE的maven配置。
MapStruct的进一步用法:
MapStruct的mapper被配置为Spring Beans,并且支持独立注入。
建议将一个Repository注入一个mapper,这样就可以从mapper通过它的ID获取一个可控的JPA实体。
示例:
@Mapper
public abstract class CarMapper {
@Inject
private UserRepository userRepository;
@Mapping(source = "user.id", target = "userId")
@Mapping(source = "user.login", target = "userLogin")
public abstract CarDTO carToCarDTO(Car car);
@Mapping(source = "userId", target = "user")
public abstract Car carDTOToCar(CarDTO carDTO);
public User userFromId(Long id) {
if (id == null) {
return null;
}
return userRepository.findOne(id);
}
}
8.分页
目前如果使用Cassandra来创建应用是不能进行分页的。
分页使用了和GitHub API中一样的Linker header。JHipster提供对服务器和客户端特定的自定义实现。
生成实体时,JHipster提供4中分页选项:
- 不分页
- 简单分页,基于Bootstrap pager
- 完全分页系统,基于Bootstrap分页组件
- 有限滚动系统,基于有限滚动指令
9.更新已存在的实体
实体的配置保存在特定的.jhipster目录下的.json文件中。如果你再运行一次实体生成器,使用已存在的实体名称,你可以更新或重新生成这个实体。
此时有四种选项:
- 重新生成实体(可以在运行生成器时传递一个--regenerate标志来强制执行)
- 添加域和关系
- 去除域和关系
- 退出
你可能因为下列原因想要更新实体:
- 对已存在的实体添加/移除域和关系
- 重新设置实体的代码到原始状态
- 更新了JHipster,想要用新的模板来生成实体
- 更改了.json配置文件,有一个实体的新的版本
- 复制粘贴了.json文件,想要一个新的与拷贝实体很接近的实体
为了立即重新生成实体,可以使用下面的命令,如果更改了文件询问问题时,可以将--force去除
Linux/Mac:for f in `ls .jhipster`;do yo jhipster:entity ${f%.*} --force; done
Windows:for %f in (.jhipster/*) do yo jhipster:entity %~nf --force