首页 > 解决方案 > 如何使用 Spring HATEAOS RepresentationModel/Dto 中的单独实体

问题描述

我正在使用 Spring Boot 2.2.7、Spring HATEAOS 1.0、Spring JPA、Spring Data REST、Hibernate。我创建了一个具有复杂模型的服务器 REST。

到目前为止,我按照 Data REST 允许的方式部署了我的 API 向世界公开实体。所以我的 DTO 等于我的实体(即使我有几个 Spring 预测)。但是,经过多次阅读,我知道我所做的并不是最佳实践,我想与保留 HATEOASEntity分开。Dto

我寻找了一些复杂的例子,但我没有发现任何对实际应用程序真正有用的东西。我知道 HATEOAS 用的不多,但是我花了很多精力在客户端上利用它。

我正在寻找一种简洁的方法来对我的数据库执行查询、获取数据并将它们转换为具有 Dto HATEOAS 能力的。

要将数据从中复制EntityDto我正在使用 Mapstructs:我想尽可能减少样板代码。

假设我有这个实体:

@Entity
@Data
@Builder
public class EntityA implements Persistable<Long>, Serializable{
     private long id;
     private String 

     //other fields
}

和这个实体

@实体

@Data
@Builder
public class EntityB implements Persistable<Long>, Serializable{
    @ToString.Exclude    
    @NotNull
    @OnDelete(action = OnDeleteAction.CASCADE)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Document document;

    //other fields    
}

我将实体存储库创建为:

@Transactional
@PreAuthorize("isAuthenticated()")
public interface ContactRepository extends JpaRepository<EntityA, Long>, ContactCustomRepository<EntityA, Long>, JpaSpecificationExecutor<EntityA> {

}

我的控制器是:

@RepositoryRestController
@PreAuthorize("isAuthenticated()")
@Log4j2
public class EntityAController {

 @GetMapping(path = "/entityA/{id:[0-9]+}")
    public ResponseEntity<?> get(@PathVariable(value = "id") long id) {
        return ResponseEntity.ok(/*assembler??*/.toModel(myservice.get(id)));
    }

我有一些缺失的部分:如何定义 Dto 以及如何创建不需要复制 Dto 中已经定义的代码的汇编程序。

我试图将 Dto 创建为:

@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@JsonRootName(value = "entityA")
@Relation(collectionRelation = "entitiesA")
@Builder
public class EntityAModel extends RepresentationModel<EntityA> implements Serializable {
     private long id;
     private String 
     //other fields
}

而关于 EntityB 我仍然更不确定:

@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@JsonRootName(value = "entityB")
@Relation(collectionRelation = "entitiesB")
@Builder
public class EntityBModel extends RepresentationModel<EntityB> implements Serializable {
     private EntityA entityA;
     //other fields
}

我创建了一个通用汇编器:

/**
 * A {@link SimpleRepresentationModelAssembler} that mixes together a Spring web controller and a
 * {@link LinkRelationProvider} to build links upon a certain strategy.
 *
 * @author Greg Turnquist
 */
public class SimpleIdentifiableRepresentationModelAssembler<T> implements SimpleRepresentationModelAssembler<T> {

    /**
     * The Spring MVC class for the object from which links will be built.
     */
    private final Class<?> controllerClass;

    /**
     * A {@link LinkRelationProvider} to look up names of links as options for resource paths.
     */
    @Getter
    private final LinkRelationProvider relProvider;

    /**
     * A {@link Class} depicting the object's type.
     */
    @Getter
    private final Class<?> resourceType;

    /**
     * Default base path as empty.
     */
    @Getter
    @Setter
    private String basePath = "";

    /**
     * Default a assembler based on Spring MVC controller, resource type, and {@link LinkRelationProvider}. With this
     * combination of information, resources can be defined.
     *
     * @param controllerClass - Spring MVC controller to base links off of
     * @param relProvider
     * @see #setBasePath(String) to adjust base path to something like "/api"/
     */
    public SimpleIdentifiableRepresentationModelAssembler(Class<?> controllerClass, LinkRelationProvider relProvider) {

        this.controllerClass = controllerClass;
        this.relProvider = relProvider;

        // Find the "T" type contained in "T extends Identifiable<?>", e.g.
        // SimpleIdentifiableRepresentationModelAssembler<User> -> User
        this.resourceType = GenericTypeResolver.resolveTypeArgument(this.getClass(),
                SimpleIdentifiableRepresentationModelAssembler.class);
    }

    /**
     * Alternate constructor that falls back to {@link EvoInflectorLinkRelationProvider}.
     *
     * @param controllerClass
     */
    public SimpleIdentifiableRepresentationModelAssembler(Class<?> controllerClass) {
        this(controllerClass, new EvoInflectorLinkRelationProvider());
    }

    /**
     * Add single item self link based on the object and link back to aggregate root of the {@literal T} domain type using
     * {@link LinkRelationProvider#getCollectionResourceRelFor(Class)}}.
     *
     * @param resource
     */
    @Override
    public void addLinks(EntityModel<T> resource) {

        resource.add(getCollectionLinkBuilder().slash(getId(resource)).withSelfRel());
        resource.add(getCollectionLinkBuilder().withRel(this.relProvider.getCollectionResourceRelFor(this.resourceType)));
    }

    private Object getId(EntityModel<T> resource) {

        Field id = ReflectionUtils.findField(this.resourceType, "id");
        ReflectionUtils.makeAccessible(id);

        return ReflectionUtils.getField(id, resource.getContent());
    }

    /**
     * Add a self link to the aggregate root.
     *
     * @param resources
     */
    @Override
    public void addLinks(CollectionModel<EntityModel<T>> resources) {
        resources.add(getCollectionLinkBuilder().withSelfRel());
    }

    /**
     * Build up a URI for the collection using the Spring web controller followed by the resource type transformed by the
     * {@link LinkRelationProvider}. Assumption is that an {@literal EmployeeController} serving up {@literal Employee}
     * objects will be serving resources at {@code /employees} and {@code /employees/1}. If this is not the case, simply
     * override this method in your concrete instance, or resort to overriding {@link #addLinks(EntityModel)} and
     * {@link #addLinks(CollectionModel)} where you have full control over exactly what links are put in the individual
     * and collection resources.
     *
     * @return
     */
    protected LinkBuilder getCollectionLinkBuilder() {

        WebMvcLinkBuilder linkBuilder = linkTo(this.controllerClass);

        for (String pathComponent : (getPrefix() + this.relProvider.getCollectionResourceRelFor(this.resourceType))
                .split("/")) {
            if (!pathComponent.isEmpty()) {
                linkBuilder = linkBuilder.slash(pathComponent);
            }
        }

        return linkBuilder;
    }

    /**
     * Provide opportunity to override the base path for the URI.
     */
    private String getPrefix() {
        return getBasePath().isEmpty() ? "" : getBasePath() + "/";
    }
}

我希望为每个模型创建一个简单的特定汇编程序,如下所示:

@Component
public
class EntityAModelAssembler extends SimpleIdentifiableRepresentationModelAssembler<EntityAModel> {

    DocumentFullModelAssembler() {
        super(EntityAController.class);
    }
}

我省略了 mapStructs 映射器,因为我仍然不确定谁应该负责将 Entity 转换为 Dto,特别是当我想创建一个时如何做到这一点RepresentationModel,例如,它携带两个不同Entitys 的组合(即避免多个 HTTP 请求)。

标签: javaspringspring-bootspring-hateoas

解决方案


推荐阅读