使用JHipster构建单一的Web应用程序-实体建模

在本章中,我们将看到如何使用业务实体和模型。

使用命令行生成实体

> 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"

链接与资源

jhipster学习网

Avatar
Tgenkidu Cy
程序员呀哈哈哈

目前专注于 java 开发。

相关

下一页
上一页