Spring boot HATEOAS автоматически не добавляет ссылки на связанные ресурсы

Ссылки на ресурсы не предоставляются автоматически при использовании HATEOAS для извлечения коллекций ресурсов.

При получении коллекции ThreadResource с помощью '/forum/threads' ответ будет таким:

{
  "_embedded": {
    "threadList": [
      {
        "posts": [
          {
            "postText": "This text represents a major breakthrough in textual technology.",
            "thread": null,
            "comments": [],
            "thisId": 1
          },
          {
            "postText": "This text represents a major breakthrough in textual technology.",
            "thread": null,
            "comments": [],
            "thisId": 2
          }
        ],
        "createdBy": "admin",
        "updatedBy": null,
        "thisId": 1
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/forum/threads?page=0&size=10"
    }
  },
  "page": {
    "size": 10,
    "totalElements": 1,
    "totalPages": 1,
    "number": 0
  }
}

Я ожидал массив сообщений JSON (вместо ссылок на связанную коллекцию сообщений), как показано ниже:

{
  "_embedded": {
    "threadList": [
      {
        "createdBy": "admin",
        "updatedBy": null,
        "thisId": 1,
        "_links": {
          "posts": {
            "href": "http://localhost:8080/forum/threads/1/posts"
            }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/forum/threads?page=0&size=10"
    }
  },
  "page": {
    "size": 10,
    "totalElements": 1,
    "totalPages": 1,
    "number": 0
  }
}

Я мог бы вручную создавать и добавлять ссылки в классах реализации ResourceProcessor и исключать отображение коллекции с помощью @JsonIgnore, но раньше мне никогда не приходилось этого делать. Что я делаю неправильно?

Соответствующие классы представлены ниже. Заранее спасибо!

@Entity
public class Thread {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany
    private List<Post> posts;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_by")
    private String updatedBy;

    public Thread() { }

    @PrePersist
    public void prePersist() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        posts = new ArrayList<>();
        createdBy = auth.getName();
    }

    @PreUpdate
    public void preUpdate() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        updatedBy = auth.getName();
    }


    public void submitPost(Post newPost) {
        posts.add(newPost);
    }

    public Long getThisId() {
        return id;
    }

    public List<Post> getPosts() {
        return posts;
    }

    public void setPosts(List<Post> posts) {
        this.posts = posts;
    }

    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public String getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(String updatedBy) {
        this.updatedBy = updatedBy;
    }

@Entity
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String postText;

    @ManyToOne(fetch = FetchType.LAZY)
    private Thread thread;

    @OneToMany
    private List<Comment> comments;

    public Post() { }
}

public class ThreadResource extends ResourceSupport {

    private List<PostResource> postResources;

    private String createdBy;

    private String updatedBy;

    public ThreadResource() {
    }
}

public class PostResource extends ResourceSupport {

    private String postText;

    private ThreadResource threadResource;

    private List<CommentResource> commentResources;

    public PostResource() { }

@Component
public class PostResourceAssembler extends ResourceAssemblerSupport<Post, PostResource> {

    public PostResourceAssembler() {
        super(PostController.class, PostResource.class);
    }

    @Override
    public PostResource toResource(Post entity) {
        PostResource resource = super.createResourceWithId(entity.getThisId(), entity);
        resource.setPostText(entity.getPostText());
        return resource;
    }
}

@Component
public class ThreadResourceAssembler extends ResourceAssemblerSupport<Thread, ThreadResource> {

    private PostResourceAssembler postResourceAssembler;

    public ThreadResourceAssembler(PostResourceAssembler postResourceAssembler) {
        super(ThreadController.class, ThreadResource.class);
        this.postResourceAssembler = postResourceAssembler;
    }

    @Override
    public ThreadResource toResource(Thread entity) {
        ThreadResource resource = super.createResourceWithId(entity.getThisId(), entity);
        List<Post> posts = entity.getPosts();
        List<PostResource> postResources = new ArrayList<>();
        posts.forEach((post) -> postResources.add(postResourceAssembler.toResource(post)));
        resource.setPostResources(postResources);
        return resource;
    }
}

@RestController
public class PostController {

    private PostService postService;

    @Autowired
    public PostController(PostService postService) {
    this.postService = postService;
    }

    @GetMapping("/forum/threads/{threadId}/posts/{postId}")
    public ResponseEntity<Resource<Post>> getPost(@PathVariable long threadId, @PathVariable long postId) {
        Post post = postService.fetchPost(postId)
                .orElseThrow(() -> new EntityNotFoundException("not found thread " + postId));
        Link selfLink = linkTo(PostController.class).slash(postId).withSelfRel();
        post.add(selfLink);
        return ResponseEntity.ok(new Resource<>(post));
    }

    @GetMapping
    public ResponseEntity<PagedResources<Resource<Post>>> getPosts(PagedResourcesAssembler<Post> pagedResourcesAssembler) {
        Pageable pageable = new PageRequest(0, 10);
        Page<Post> posts = postService.fetchAllPosts(pageable);
        PagedResources<Resource<Post>> resources = pagedResourcesAssembler.toResource(posts);
        return ResponseEntity.ok(resources);
    }

    @PostMapping("/forum/threads/{threadId}/posts")
    public HttpEntity<?> submitPost(@PathVariable long threadId) throws URISyntaxException {
        Post post = postService.submitPost(threadId, new Post());
        if (post != null) {
            Link selfLink = linkTo(methodOn(PostController.class).submitPost(threadId)).slash(post.getThisId()).withSelfRel();
            post.add(selfLink);
            return ResponseEntity.created(new URI(selfLink.getHref())).build();
        }
        return ResponseEntity.status(500).build();
    }
}

@RestController
public class ThreadController {

    private ThreadService threadService;

    private ThreadResourceAssembler threadResourceAssembler;

    @Autowired
    public ThreadController(ThreadService threadService,
                            ThreadResourceAssembler threadResourceAssembler) {
        this.threadService = threadService;
        this.threadResourceAssembler = threadResourceAssembler;
    }

    @GetMapping("/forum/threads/{threadId}")
    public ResponseEntity<ThreadResource> getThread(@PathVariable long threadId) {
        Thread thread = threadService.fetchThread(threadId)
                .orElseThrow(() -> new EntityNotFoundException("not found thread " + threadId));

        ThreadResource threadResource = threadResourceAssembler.toResource(thread);
        return ResponseEntity.ok(threadResource);
    }

    @GetMapping("/forum/threads")
    public ResponseEntity<PagedResources<Resource<ThreadResource>>> getThreads(PagedResourcesAssembler pagedResourcesAssembler) {
        Pageable pageable = new PageRequest(0, 10);
        Page<Thread> threads = threadService.fetchAllThreads(pageable);
        PagedResources pagedResources = pagedResourcesAssembler.toResource(threads);
        return ResponseEntity.ok(pagedResources);
    }

    @PostMapping("/forum/threads")
    public HttpEntity<?> createThread() {
        Thread thread = threadService.createThread();
        return ResponseEntity.ok(thread);
    }

    @DeleteMapping("/forum/threads/{threadId}")
    public HttpEntity<?> deleteThread(@PathVariable long threadId) {
        Thread thread = threadService.fetchThread(threadId)
                .orElseThrow(() -> new EntityNotFoundException("not found thread" + threadId));
        threadService.closeThread(thread);
        return ResponseEntity.ok().build();
    }
}

Я не видел создания ссылок в вашем коде при возврате ресурсов!

Abdelghani Roussi 10.03.2019 20:02

Это правда, да, потому что на данном этапе мне не нужны пользовательские ссылки. В прошлом я разрешал весенней загрузке создавать для меня ссылки на связанные ресурсы. По какой-то причине я не могу заставить его работать сейчас. У меня возникает соблазн просто добавить их вручную, но я не должен этого делать.

apostrophedottilde 10.03.2019 20:05

Поведение, которое вы получили, абсолютно нормальное, вы добавляете список PostResource внутри своего ThreadResource класса; что означает, что он будет вычисляться при создании вашего списка threadResources. Если вы хотите, чтобы внутри threadList размещались только ссылки, вы должны добавить их вручную.

Abdelghani Roussi 10.03.2019 20:11

взгляните на этот сообщение

Abdelghani Roussi 10.03.2019 20:12

Привет, спасибо, я уже погуглил и увидел этот пост. Я думаю, когда аннотация @RepositoryRestController была доступна (может быть, в весенней загрузке 1? В любом случае, сейчас она недоступна), многие ссылки можно было сгенерировать автоматически. В этом была магия. Спасибо, в любом случае.

apostrophedottilde 10.03.2019 20:16
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
193
0

Другие вопросы по теме