我为我的存储库定义了自定义控制器。例如,一个看起来像这样
@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了很多,你可以手动添加链接,但这似乎是一个更清晰的方式。谁能理解,我做错了什么?
对于类似问题,“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);
}
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());
}
}