12

Issue is when using Spring cache with redis cache manager, not able to deserializer Spring Pageable response due to no default constructor

The spring boot version used is 2.1.4.RELEASE

Redis config class that uses the serializer

@Bean
public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));

    redisCacheConfiguration.usePrefix();

    return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
            .cacheDefaults(redisCacheConfiguration).build();
}

I am trying to cache spring REST API Page result response in Redis cache using Spring cache and Redis as a cache backend

@GetMapping
@Cacheable("Article_Response_Page")
public Page<Article> findAll(Pageable pageable) {
    return articleRepository.findAll(pageable);
}

I am able to see the Page<Article> getting cached as JSON in Redis cache using RedisSerializer.json() serializer but during the next call when the data is read from the cache I get the following exception

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot 
construct instance of `org.springframework.data.domain.PageImpl` (no 
Creators, like default construct, exist): cannot deserialize from Object 
value (no delegate- or property-based Creator)
at [Source: (byte[])" 
{"@class":"org.springframework.data.domain.PageImpl","content": 
["java.util.Collections$UnmodifiableRandomAccessList",[]],"pageable": 
{"@class":"org.springframework.data.domain.PageRequest","sort":{"@class":"org.springframework.data.domain.Sort","sorted":false,"unsorted":true,"empty":true},"offset":0,"pageSize":20,"pageNumber":0,"paged":true,"unpaged":false},"totalPages":0,"totalElements":0,"last":true,"size":20,"number":0,"sort":{"@class":"org.springframework.data.domain.Sort","sorted":false,"uns"[truncated 73 bytes]; line: 1, column: 54]
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.9.8.jar:2.9.8]

I tried giving a custom serializer for PageImpl then I got an exception for PageRequest implementation and Sort implementation all part of Spring 'org.springframework.data.domain' package

There must be a better way to solve this and I like to know the best approach to solve this kind of issue in spring cache

Is this a Jackson bug after moving to SPRING BOOT v2 ?

ClaudiaR
  • 3,108
  • 2
  • 13
  • 27
Tan mally
  • 572
  • 1
  • 5
  • 11

5 Answers5

5

I had the same Problem when creating a simple REST interface. The solution for me was to extend PageImpl and specify the required JsonProperties while explicitly ignoring the other ones:

@JsonIgnoreProperties(ignoreUnknown = true, value = {"pageable"})
public class RestPage<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestPage(@JsonProperty("content") List<T> content,
                     @JsonProperty("number") int page,
                     @JsonProperty("size") int size,
                     @JsonProperty("totalElements") long total) {
        super(content, PageRequest.of(page, size), total);
    }

    public RestPage(Page<T> page) {
        super(page.getContent(), page.getPageable(), page.getTotalElements());
    }
}

You can than write your Controller along the lines of:

@GetMapping
@Cacheable("Article_Response_Page")
public RestPage<Article> findAll(Pageable pageable) {
    return new RestPage<>(articleRepository.findAll(pageable));
}
attalos
  • 454
  • 5
  • 9
3

I'm using spring boot 2.6.2. Got the same error trying to make an http get request to a spring boot service that returns a Page. I just solved it by adding the property feign.autoconfiguration.jackson.enabled=true in my feign client. I'm using spring-cloud-starter-openfeign.

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-data-support

acron.bn
  • 31
  • 1
  • The property alone didn't work for me. Had to declare the modules in the Jackson configuration... https://stackoverflow.com/a/74024817/831061 – luso Oct 11 '22 at 08:03
2

You can use wrapper of PageImpl and then:

public class PageImpl<T> extends org.springframework.data.domain.PageImpl<T> {

     @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
     public PageImpl(@JsonProperty("content") List<T> content,
                @JsonProperty("number") int page,
                @JsonProperty("size") int size,
                @JsonProperty("totalElements") long total) {

        super(content, PageRequest.of(page, size), total);
    }
}
Anton
  • 61
  • 7
  • 5
    This leads to other error: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information – c.sankhala Jan 07 '22 at 08:40
1

I fixed the issue for time being by using JAVA serializer but like to know how this can be fixed with JSON value serializer turned on

RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
        .serializeValuesWith(
                RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.java()));
Tan mally
  • 572
  • 1
  • 5
  • 11
0
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new PageJacksonModule());
}
drvisor
  • 71
  • 1
  • 2