使用spring RepositoryRestController获取_links

问题描述 投票:1回答:2

我为我的存储库定义了自定义控制器。例如,一个看起来像这样

@RequestMapping(method = RequestMethod.GET, value = "/myEntities")
public ResponseEntity<?> get() {
    ...TO SOME STUFF
    MyEntity myEntity= myEntityRepository.findById(1);
    return ResponseEntity.ok(new Resource<>(myEntity));
}

这将返回一个JSON格式数据,其中包含一个_links部分,我可以在其中获取实体的href。现在,如果我想返回一个属于所有资源的实体数组,我就会陷入困境。

到目前为止我尝试了什么:

1.

@RequestMapping(method = RequestMethod.GET, value = "/myEntities")
public ResponseEntity<?> get() {
    ...TO SOME STUFF
    List<MyEntity> myEntityList= myEntityRepository.findAll(1);
    return ResponseEntity.ok(new Resources<>(myEntityList));
} 

2.

@RequestMapping(method = RequestMethod.GET, value = "/myEntities")
public ResponseEntity<?> get() {
    ...TO SOME STUFF
    List<MyEntity> myEntityList= myEntityRepository.findAll();
    List<Resource<MyEntity>> resources = new ArrayList<>();
    myEntityList.forEach(me -> {
        resources.add(new Resource<>(me));
    })
    return ResponseEntity.ok(resources);
} 

选项1.和2.不要在结果中添加_links,我不明白为什么。我已经google了很多,你可以手动添加链接,但这似乎是一个更清晰的方式。谁能理解,我做错了什么?

spring spring-data spring-data-jpa
2个回答
1
投票

对于类似问题,“adding association links to spring data rest custom exposed method”和“Enable HAL serialization in Spring Boot for custom controller method”有答案。为了解决类似的问题,我尝试了一些与这些答案略有不同的东西,并且工作正常。我希望你的问题得到解决。

它遵循我如何解决我的具体情况:

    @PostMapping(value = "/myEntities/searchWithParams"
        , consumes = MediaType.APPLICATION_JSON_VALUE
        , produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> searchWithParams(@RequestBody SearchParamsDtoClass  params, PersistentEntityResourceAssembler assembler)
{
    List<MyEntity> entities = myEntityService.searchWithParams(params);
    List<PersistentEntityResource> resourcesList = new ArrayList<PersistentEntityResource>();

    for (MyEntity entity: entities) {
        PersistentEntityResource resource = assembler.toResource(entity);
        resourcesList.add(resource);
    }

    Resources<PersistentEntityResource> resources = new Resources(resourcesList);

    return ResponseEntity.ok(resources);
}

0
投票

Resources构造函数接受嵌入内容的集合,这与链接集合不同。您必须手动添加链接。

Resources resources = new Resources();
resources.add(myEntityRepository
    .findAll()
    .stream()
    .map(entry -> convertToLink(entry)
    .collect(Collectors.toList()));
return ResponseEntity.ok(resources);

以下是一个示例,其中包含符合HAL格式的嵌入内容和分页。

import static java.util.Objects.*;
import static java.util.Optional.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.hateoas.MediaTypes.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import static org.springframework.http.HttpStatus.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.web.bind.annotation.RequestMethod.*;

import java.util.ArrayList;

import javax.inject.Inject;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.hateoas.Link;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.codahale.metrics.annotation.Timed;

@RestController
@RequestMapping("/catalog/users")
public class UserResource {

    private final @NotNull UserLocator locator;
    private final @NotNull ElasticsearchOperations elasticsearchTemplate;

    @Inject
    public UserResource(
        final @NotNull UserLocator locator,
        final @NotNull ElasticsearchOperations elasticsearchTemplate
    ) {
        this.locator = requireNonNull(locator, "locator cannot be null");
        this.elasticsearchTemplate = requireNonNull(elasticsearchTemplate, "elasticsearchTemplate cannot be null");
    }

    /**
    * GET  /users : get all the users.
    *
    * @return the ResponseEntity with status 200 (OK) and the list of users in body
    */
    @Timed
    @RequestMapping(method = GET, produces = { HAL_JSON_VALUE, APPLICATION_JSON_VALUE })
    public ResponseEntity<Representations<User>> allUsers(
        @RequestParam(name = "page", required = false, defaultValue =  "0") @Min(0) int page,
        @RequestParam(name = "size", required = false, defaultValue = "25") @Min(1) int size,
        @RequestParam(name = "like", required = false) String like
    ) {
        final PageRequest pageRequest = new PageRequest(page, size, Sort.Direction.ASC, "surname.raw", "givenName.raw");
        final Page<User> entries = elasticsearchTemplate.queryForPage(
            new NativeSearchQueryBuilder()
                .withQuery(startingWith(like))
                .withPageable(pageRequest)
                .build(),
            User.class
        );

        final ArrayList<Link> links = new ArrayList<>();
        links.add(linkTo(UserResource.class).withSelfRel());

        if (!entries.isFirst()) {
            links.add(linkTo(methodOn(UserResource.class).allUsers(0, size, like)).withRel("first"));
        }
        if (!entries.isLast()) {
            links.add(linkTo(methodOn(UserResource.class).allUsers(entries.getTotalPages() - 1, size, like)).withRel("last"));
        }
        if (entries.hasNext()) {
            links.add(linkTo(methodOn(UserResource.class).allUsers(entries.nextPageable().getPageNumber(), size, like)).withRel("next"));
        }
        if (entries.hasPrevious()) {
            links.add(linkTo(methodOn(UserResource.class).allUsers(entries.previousPageable().getPageNumber(), size, like)).withRel("prev"));
        }

        final Representations<User> resourceList = new Representations<>(entries, Representations.extractMetadata(entries), links);
        return ResponseEntity.ok(resourceList);
    }

    private QueryBuilder startingWith(String like) {
        return isNull(like) ? null : matchPhrasePrefixQuery("_all", like);
    }

    /**
    * GET  /users/:identifier : get the "identifier" user.
    *
    * @param identifier the identifier of the role to retrieve
    * @return the ResponseEntity with status 200 (OK) and with body the role, or with status 404 (Not Found) or with 410 (Gone)
    */
    @Timed
    @RequestMapping(value = "/{identifier}", method = GET, produces = APPLICATION_JSON_VALUE)
    public ResponseEntity<UserRepresentation> aUser(
        @PathVariable("identifier") @NotNull String identifier
    ) {
        return ofNullable(this.locator.findOne(identifier))
            .map(user -> toRepresentation(user))
            .map(ResponseEntity::ok)
            .orElse(notFound())
            ;
    }

    private @NotNull UserRepresentation toRepresentation(final @NotNull User role) {
        final String id = role.getIdentifier();
        final Link self = linkTo(methodOn(UserResource.class).aUser(id)).withSelfRel().expand(id);
        final Link collection = linkTo(UserResource.class).withRel("collection");
        return new UserRepresentation(role, self, collection);
    }

    protected final @NotNull <U> ResponseEntity<U> notFound() {
        return new ResponseEntity<>(NOT_FOUND);
    }

}

import java.util.Optional;

import org.springframework.hateoas.Link;
import org.springframework.hateoas.core.Relation;

@Relation(value = "item", collectionRelation = "items")
public class UserRepresentation extends Representation<User> {

    public UserRepresentation(User content, Iterable<Optional<Link>> links) {
        super(content, links);
    }

    public UserRepresentation(User content, Link... links) {
        super(content, links);
    }

    @SafeVarargs
    public UserRepresentation(User content, Optional<Link>... links) {
        super(content, links);
    }

    public UserRepresentation(User content) {
        super(content);
    }

}

import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.Objects.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Representation<T> extends Resource<T> {
    private final Map<String, ResourceSupport> embedded = new HashMap<>();

    public Representation(final @NotNull T content) {
        super(content, emptyList());
    }

    public Representation(final @NotNull T content, final @NotNull Link... links) {
        super(content, links);
    }

    @SafeVarargs
    public Representation(final @NotNull T content, final @NotNull Optional<Link>... links) {
        this(content, (Iterable<Optional<Link>>) asList(links));
    }

    public Representation(final @NotNull T content, final @NotNull Iterable<Optional<Link>> links) {
        super(content, emptyList());
        asStream(links).forEach(this::add);
    }

    public void add(final @NotNull Optional<Link> link) {
        if (null != link && link.isPresent()) super.add(link.get());
    }

    @JsonProperty("_embedded")
    @JsonInclude(Include.NON_EMPTY)
    public final @NotNull Map<String, ResourceSupport> getEmbedded() {
        return this.embedded;
    }

    /**
    * @param rel must not be {@literal null} or empty.
    * @param resource the resource to embed
    */
    public final void embed(final @NotNull @Size(min=1) String rel, final ResourceSupport resource) {
        requireNonNull(rel, "rel cannot be null");
        if (rel.trim().isEmpty()) {
            throw new IllegalArgumentException("rel cannot be empty");
        }
        if (null != resource) {
            this.embedded.put(rel, resource);
        }
    }  

}

import javax.validation.constraints.NotNull;

import org.springframework.data.domain.Page;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.core.Relation;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;

@Relation(collectionRelation = "items")
public class Representations<T> extends Resources<T> {

    @JsonUnwrapped
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private final PageMetadata metadata;

    public Representations(Iterable<T> content) {
        super(content);
        this.metadata = null;
    }

    public Representations(Iterable<T> content, PageMetadata metadata) {
        super(content);
        this.metadata = metadata;
    }

    public Representations(Iterable<T> content, Iterable<Link> links) {
        super(content, links);
        this.metadata = null;
    }

    public Representations(Iterable<T> content, PageMetadata metadata, Iterable<Link> links) {
        super(content, links);
        this.metadata = metadata;
    }

    public Representations(Iterable<T> content, Link... links) {
        super(content, links);
        this.metadata = null;
    }

    public Representations(Iterable<T> content, PageMetadata metadata, Link... links) {
        super(content, links);
        this.metadata = metadata;
    }

    /**
    * Returns the pagination metadata.
    * 
    * @return the metadata
    */
    @JsonProperty("page")
    public PageMetadata getMetadata() {
        return metadata;
    }

    public static <U> PageMetadata extractMetadata(final @NotNull Page<U> page) {
        return new PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.