0

What is the correct way to hide properties in a specific Page response while keeping the properties on different responses?

In my Spring Boot API, I have an entity MyEntity, a controller with 2 endpoints /myentities and /myentities/{id}.

  1. /myentities returns a Page<MyEntity> response.
  2. /myentities/{id} returns a single MyEntity object with the id {id}:

class MyEntity:

// MyEntity:
@Entity
@Table(name = "myentities")
public class MyEntity extends AuditModel {

    @Id
    @Column(name = "id", updatable=false)
    private long id;

    @Size(min = 3, max = 100)
    private String title;

    @Column(columnDefinition = "text")
    private String content;

    public MyEntity() {
        super();
    }

    public MyEntity(String title, String alias, String content) {
        super();
        this.title = title;
        this.content = content;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

}

RestController with the 2 endpoints:

// controller methods:
@RequestMapping(value = "/myentities", method = RequestMethod.GET)
public Page<MyEntity> getPagedMyEnities(
    @RequestParam(name = "after", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") Date after,
    @RequestParam(name = "before", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") Date before,
    @RequestParam(name = "title", required = false) String title,
    @PageableDefault(sort = { "createdAt" }, direction = Sort.Direction.DESC, page = 0, value = 10) Pageable pageable) {
    // ..createdAfter(),createdBefore(),titleContains() skipped..
    Specification<MyEntity> spec =
        where((createdAtAfter(after))
            .and(createdAtBefore(before))
            .and(titleContains(title));
    // MyEntityRepository extends JpaRepository<MyEntity, Long>, JpaSpecificationExecutor<MyEntity>
    return this.myEntityRepository.findAll(spec, pageable);
}

@RequestMapping(value = "/myentities/{id}", method = RequestMethod.GET)
public ResponseEntity<?> getMyEntity(@PathVariable(value = "id") long id) {
    return ResponseEntity.status(HttpStatus.OK)
            .body(this.myEntityRepository.findById(id));
}

Everything works fine, but the content property of MyEntity is a huge string slowing down the response times for /myentities a lot. I want to hide the content property for the page response now and keep including it only for the /myentities/{id} response.

I tried a few attempts, all missleading:

  1. @JsonIgnore/@JsonIgnoreProperties on private string content;

    "JsonIgnoring" the property will ignore contenton both endpoints.

  2. a) Implementing my own @JsonComponent MyEntitySerializer extends JsonSerializer<MyEntity>

    The MyEntitySerializer does simply only write those fields, that I want to have serialized in my responses. This serializer approach serializes the properties for both endpoints as well..

    b) Implementing my own @JsonComponent MyEntityPageSerializer extends JsonSerializer<Page<MyEntity>>

    The PageSerializer for MyEntity iterates through the content of the page and serializes all the properties except of content. This approach sort of works leading to Page response missing the contentproperties for its MyEntity's while still keeping the contentproperty in the single /myentities/{id} response for a single MyEntity.

    Badly, MyEntitySerializer extends Page<MyEntity> will be used for any generic Page<T> response and then throwing exceptions for Page responses any other than MyEntity.

What is the correct way to ignore JsonProperties in a specific Page response while keeping the properties on different requests?

MojioMS
  • 1,583
  • 4
  • 17
  • 42
  • 1
    Create a custom `findAll` which uses a [projection](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections). – M. Deinum Aug 05 '19 at 10:30
  • The simplest approach I can think of is creating a `MyEntityResult` class that contains only `id` and `title`. In the `getMyEntity()` method fetch the `MyEntity` object and map the two required fields to a `MyEntityResult` new object, finally returning it. – JMSilla Aug 05 '19 at 10:33

1 Answers1

0

You can not use specification in combination of projection with spring jpa, as I found out e.g. here

As a workaround, I've implemented all 8 combinations in my MyEntityRepository:

Page<MyEntityProjection> findAllByTitleContainingAndCreatedAtAfterAndCreatedAtBefore(String title, Date after, Date before, Pageable pageable);

Page<MyEntityProjection> findAllByCreatedAtAfterAndCreatedAtBefore(Date after, Date before, Pageable pageable);

Page<MyEntityProjection> findAllByTitleContainingAndCreatedAtBefore(String title, Date before, Pageable pageable);

Page<MyEntityProjection> findAllByCreatedAtBefore(Date before, Pageable pageable);

Page<MyEntityProjection> findAllByTitleContainingAndCreatedAtAfter(String title, Date after, Pageable pageable);

Page<MyEntityProjection> findAllByCreatedAtAfter(Date after, Pageable pageable);

Page<MyEntityProjection> findAllByTitleContaining(String title, Pageable pageable);

Page<MyEntityProjection> findAllProjectedBy(Pageable pageable);

And then I used a horribly nested if/else-ramification in my service layer. Another solution like fetching all properties of the entity and then mapreducing properties keeps traffic high. Having in mind the square growth implementations depending on amount of specifications, I really hope there will be a correct solution to solve specification+projection in near future.

MojioMS
  • 1,583
  • 4
  • 17
  • 42
  • Have you actually tried using a projection with a specification. That question is 2 years old (as well as the answer) and Spring Data has improved a lot since then. I would strongly suggest to try it. If all fails you can always use the `map` method on the `Page` to do the conversion in-memory. – M. Deinum Aug 06 '19 at 08:24
  • When using specification+projection, then the 'where'-statement from the specification gets omitted. Answers to that old question has a bugtracking linked with recent activity. I will keep your map method on the Page in mind for future needs with clearly more specifications! – MojioMS Aug 06 '19 at 08:27
  • 1
    Was looking at that a moment ago, apparently hasn't been fixed. Then use the `map` method on the `Page` to in-memory convert the result-type to your projection/dto (and still return a `Page`). Saves you from litering your repo with all sorts of ugly combinations, not to mention the complexity in determining which method to call. – M. Deinum Aug 06 '19 at 08:28