在本章中,我们将看到如何使用业务实体和模型。
使用命令行生成实体
> jhipster entity Invoice
使用JHipster域语言进行实体建模
JHipster Domain Language (JDL)用于为JHipster应用程序创建域模型。它提供了一个简单且用户友好的DSL来描述实体及其关系(仅适用于SQL数据库)。
JDL是为应用程序创建实体的推荐方法,它可以替代JHipster提供的实体生成器,在创建大量实体时,可能很难使用JHipster提供的实体生成器。JDL通常写在一个或多个扩展名为.jh的文件中。
使用JDL进行实体建模
实体声明语法
entity <entity name> ([<table name>]) {
<field name> <type> [<validation>*]
}
<entity name>
是实体的名称,将用于类名和表名。可以使用可选的<Table name>
参数重写表名。<field name>
是您希望用于实体的字段(属性)的名称,<type>
是字段类型,如String、Integer等。
请参考JHipster 域语言 (JDL),查看所有支持类型。
- ID字段将自动创建,因此不需要在JDL中指定。
<validation>
是可选的,可以根据字段类型支持的验证指定一个或多个字段的<validation>
。对于max length和pattern等验证,可以在大括号中指定值。
示例如下:
entity Customer {
/** Name field */
name String required,
age Integer,
address String maxlength(100) pattern(/[a-Z0-9]+/)
}
枚举也可以使用以下语法声明:
enum <enum name> {
<VALUE>*
}
示例如下:
enum Language {
ENGLISH, DUTCH, FRENCH
}
关系管理
实体之间的关系可以使用以下语法声明:
relationship <type> {
<from entity>[{<relationship name>[(<display field>)] <validation>*}]
to
<to entity>[{<relationship name>[(<display field>)] <validation>*}]
}
<type>
是来自OneToMany、ManyToOne、OneToOne或ManyToMany的类型,它声明了<from entity>
and<to entity>
之间的关系类型。<from entity>
是关系或源的所有者实体的名称。<to entity>
是关系的目标。<relationship name>
是可选的,可用于指定要在域对象中为关系创建的字段名。<display field>
可以在大括号中指定来控制要在生成的web页面的下拉菜单中显示的实体的字段,默认情况下将使用ID字段。<validation>
可以在<from entity>
或<to entity>
上指定,并且是可选的。目前,只支持required。- 同一类型的多个关系可以在同一个块中声明,用逗号分隔。
示例如下:
entity Book
entity Author
entity Tag
relationship OneToMany {
Author{book} to Book{writer(name) required},
Book{tag} to Tag
}
DTO、服务和分页选项
JDL还允许我们轻松地声明与实体相关的选项。目前支持的选项有:
- service :默认情况下,JHipster生成直接调用实体存储库的REST资源类。这是最简单的选择,但在实际场景中,我们可能需要一个服务层来处理业务逻辑。该选项允许我们使用简单的Spring服务bean类或传统的服务bean接口和实现创建服务层。可能的值是serviceClass和serviceImpl。
- dto :默认情况下,域对象直接用于创建的REST端点,这在某些情况下可能不太理想,您可能希望使用中介数据传输对象(DTO)来进行更多的控制。JHipster允许我们使用Mapstruct生成DTO层,这是一个自动生成DTO类的注释预处理器库。建议在使用DTO时使用服务层。一个可能的值是mapstruct。更多细节请参考https://www.jhipster.tech/using-dtos。
- filter :该选项允许我们为实体启用基于JPA的过滤功能。这只在使用服务层时才有效。更多细节请参考http://www.jhipster.tech/entities-filtering。
- paginate :这个选项允许我们为实体启用分页。这将在资源层上启用分页,并在客户端实现分页选项。可能的值是分页器、分页和无限滚动。
- noFluentMethod :这允许我们为生成的实体域对象禁用Fluent API样式设置器。
- skipClient/skipServer :这些选项允许我们在生成期间跳过客户端代码或服务器端代码。
- angularSuffix :该选项允许我们在前端代码中为文件夹和类名指定后缀。
选项声明的一般语法:
< option > <ENTITIES | * | all> [with <VALUE>] [except <ENTITIES>]。
示例如下:
entity A
entity B
...
entity Z
dto * with mapstruct
service A with serviceImpl
service B with serviceClass
paginate * with pagination except B, C
paginate B, C with infinite-scroll
filter A, B
JDL Studio
可使用JDL Studio来创建我们的JDL文件。它是由JHipster团队构建的一个在线web应用程序,用于在可视化编辑器中创建JDL文件。该工具显示了创建实体模型的可视化表示,并允许您导入/导出JDL和捕获图像快照:
使用JHipster生成实体
> cd my-shop
> jhipster import-jdl shop.jh
生成代码
以Project实体为例:
.jhipster文件夹
将会在根目录下生成一个.jhipster
文件夹,查看project.json
。
{
"name": "Product",
"fields": [
{
"fieldName": "name",
"fieldType": "String",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "description",
"fieldType": "String"
},
{
"fieldName": "price",
"fieldType": "BigDecimal",
"fieldValidateRules": [
"required",
"min"
],
"fieldValidateRulesMin": 0
},
{
"fieldName": "size",
"fieldType": "Size",
"fieldValues": "S,M,L,XL,XXL",
"fieldValidateRules": [
"required"
]
},
{
"fieldName": "image",
"fieldType": "byte[]",
"fieldTypeBlobContent": "image"
}
],
"relationships": [
{
"relationshipType": "many-to-one",
"otherEntityName": "productCategory",
"otherEntityRelationshipName": "product",
"relationshipName": "productCategory",
"otherEntityField": "name"
}
],
"changelogDate": "20190415054558",
"javadoc": "shop",
"entityTableName": "product",
"dto": "no",
"pagination": "pagination",
"service": "serviceClass",
"jpaMetamodelFiltering": false,
"fluentMethods": true,
"clientRootFolder": "",
"applications": "*"
}
服务器端源代码
实体类
在src/main/java/com/mycompany/myapp/domain文件加下,将会生成Project.java。
实体类定义字段和关系。
//Swagger使用这个注释在端点中使用实体时显示有用的文档:
@ApiModel(description = "shop")
//这些JPA注释将POJO声明为一个实体并将其映射到一个SQL表:
@Entity
@Table(name = "product")
//是Hibernate注释,它允许我们为这个实体启用二级缓存。在我们使用Hazelcast的情况下:
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
//id字段是特殊的,它被映射为一个生成的值字段。根据数据库的不同,这个字段将使用Hibernate提供的本地生成技术或序列。
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
private Long id;
@NotNull
@Column(name = "name", nullable = false)
private String name;
@Column(name = "description")
private String description;
//JPA注释用于将列映射到字段,还可以用于声明字段的属性,如可空、精度、比例、惟一等等:
@NotNull
@DecimalMin(value = "0")
@Column(name = "price", precision = 10, scale = 2, nullable = false)
private BigDecimal price;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "jhi_size", nullable = false)
private Size size;
//是Bean验证注释,支持对字段进行验证
@Lob
@Column(name = "image")
private byte[] image;
@Column(name = "image_content_type")
private String imageContentType;
//枚举数据
@ManyToOne
@JsonIgnoreProperties("products")
private ProductCategory productCategory;
//setter、getter
}
对应的表定义和约束使用Liquibase创建,可以在src/main/resources/config/Liquibase/changelog
中找到,文件名<timestamp>_added_entity_Product
和<timestamp>_added_entity_constraints_Product.xml
,当我们重新加载或启动应用程序时,它会自动应用到数据库。
实体的存储库接口
在src/main/java/com/mycompany/myapp/repository文件夹中,您将找到实体存储库服务。打开ProductRepository.java:
@SuppressWarnings("unused")
@Repository
public interface ProjecyRepository extends JpaRepository<Projecy, Long> {
}
repository服务只是一个扩展JpaRepository类的空接口。因为它是一个Spring数据存储库,所以实现是自动创建的,允许我们使用这个简单的接口声明执行所有CRUD操作。
实体的服务类
由于我们选择为实体生成服务类,让我们来看一个在src/main/java/com/mycompany/myapp/service文件夹中,将找到实体存储库服务。打开ProductService.java:
@Service
@Transactional
public class ProductService {
private final Logger log = LoggerFactory.getLogger(ProductService.class);
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
...
}
该服务使用构造函数注入来获取依赖项,这些依赖项在bean实例化期间由Spring自动注入。该服务还被标记为@Transactional,以启用数据访问的事务管理。该服务定义了CRUD操作方法。例如,findAll方法在向其添加只读事务规则时调用等效的存储库方法。您可以看到该方法已经支持分页并将结果作为Page返回。页面和可分页对象由Spring提供,让我们轻松控制分页:
@Transactional(readOnly = true)
public Page<Product> findAll(Pageable pageable) {
log.debug("Request to get all Products");
return productRepository.findAll(pageable);
}
实体的资源类
在src/main/java/com/mycompany/myapp/web/rest文件夹中,您将找到实体资源服务。打开ProductResource.java:
@RestController
@RequestMapping("/api")
public class ProductResource {
···
}
资源充当控制器层,在我们的示例中,它服务于客户端代码使用的其余端点。端点有一个到“/api”的基本映射:
@PostMapping("/products")
public ResponseEntity<Product> createProduct(@Valid @RequestBody Product product) throws URISyntaxException {
log.debug("REST request to save Product : {}", product);
if (product.getId() != null) {
throw new BadRequestAlertException("A new product cannot already have an ID", ENTITY_NAME, "idexists");
}
Product result = productService.save(product);
return ResponseEntity.created(new URI("/api/products/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
这里所有的CRUD操作都有等价的映射方法。
客户端源代码
实体的客户端资源在src/main/webapp/app/entities文件夹中创建。让我们看一下为Product文件夹中的Product实体创建的代码。
实体的TypeScript模型类
让我们看看在src\main\webapp\app\shared\model\product.model.ts中生成的TypeScript模型。这直接映射到域对象:
import { IProductCategory } from 'app/shared/model/product-category.model';
export const enum Size {
S = 'S',
M = 'M',
L = 'L',
XL = 'XL',
XXL = 'XXL'
}
export interface IProduct {
id?: number;
name?: string;
description?: string;
price?: number;
size?: Size;
imageContentType?: string;
image?: any;
productCategory?: IProductCategory;
}
export class Product implements IProduct {
constructor(
public id?: number,
public name?: string,
public description?: string,
public price?: number,
public size?: Size,
public imageContentType?: string,
public image?: any,
public productCategory?: IProductCategory
) {}
}
实体的Angular服务
ProductService是一个Angular服务,它与我们的REST端点交互,并在src\main\webapp\app\entities\product\product.service.ts中创建:
@Injectable({ providedIn: 'root' })
export class ProductService {
public resourceUrl = SERVER_API_URL + 'api/products';
constructor(protected http: HttpClient) {}
create(product: IProduct): Observable<EntityResponseType> {
return this.http.post<IProduct>(this.resourceUrl, product, { observe: 'response' });
}
···
}
正如您所看到的,服务有一个构造函数,按照与服务器端代码类似的模式注入依赖项。有一些方法将所有CRUD操作映射到后端REST资源。HTTP调用使用RxJS Observables提供异步流API。
实体的Angular组件
对于一个实体,在组件中使用的四个文件和四个HTML文件中生成了六个组件类。
在product.component.ts中定义的ProductComponent处理主清单屏幕。它使用product.component.html作为模板。组件管理视图及其操作。它还调用多个服务来获取数据并执行其他操作,如警报和事件广播:
@Component({
selector: 'jhi-product',
templateUrl: './product.component.html'
})
export class ProductComponent implements OnInit, OnDestroy {
···
}
实体的Angular路由
我们需要一个路由声明,以便访问实体页面。这在product.route.ts中声明。
{
path: ':id/view',
component: ProductDetailComponent,
resolve: {
product: ProductResolve
},
data: {
authorities: ['ROLE_USER'],
pageTitle: 'shopApp.product.home.title'
},
canActivate: [UserRouteAccessService]
}
data属性用于将元数据(如允许的角色和页面标题)传递给组件。canActivate属性中定义的UserRouteAccessService决定用户是否具有查看页面的授权,并使用权威元数据和身份验证细节进行验证。具有弹出框的路由,声明outlet: ‘popup’属性。
实体的Angular模块
最后,我们有一个实体模块。Angular模块可以用来整合一个实体的所有组件、指令、管道和服务,这样它们就可以很容易地导入到其他模块中。ShopProductModule模块定义在product.module.ts中:
@NgModule({
imports: [ShopSharedModule, RouterModule.forChild(ENTITY_STATES)],
declarations: [
ProductComponent,
ProductDetailComponent,
ProductUpdateComponent,
ProductDeleteDialogComponent,
ProductDeletePopupComponent
],
entryComponents: [ProductComponent, ProductUpdateComponent, ProductDeleteDialogComponent, ProductDeletePopupComponent],
providers: [{ provide: JhiLanguageService, useClass: JhiLanguageService }],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class ShopProductModule {
constructor(private languageService: JhiLanguageService, private languageHelper: JhiLanguageHelper) {
this.languageHelper.language.subscribe((languageKey: string) => {
if (languageKey !== undefined) {
this.languageService.changeLanguage(languageKey);
}
});
}
}
模块声明组件并注册它提供的服务。模块还导入共享模块,以便访问共享服务和组件。模块由src/main/webapp/app/entities/entity.module.ts中定义的ShopEntityModule导入。
提交Git(可选择行执行)
> git add --all
> git commit -am "generated shop entity model"