关于SpringBoot-SpringDataJpa+redis 对分页功能解决反序列化时PageImpl没有无参构造器的问题

问题描述

由于想给 findAll(Pageable pageable)加上spring的缓存(spring cache),第一遍查询正常,但是在第二遍以后,从缓存中读取时,无法反序列化,提示没有无参构造

问题出现的原因:

从缓存读取时,JackJson无法反序列化Page对象,因为它没有无参构造器

错误日志:

org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Can not construct instance of org.springframework.data.domain.PageImpl: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@4c12c893; line: 1, column: 98] (through reference chain: com.zcl.tmall.util.Page4Navigator[“pageFromJPA”]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.data.domain.PageImpl: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@4c12c893; line: 1, column: 98] (through reference chain: com.zcl.tmall.util.Page4Navigator[“pageFromJPA”])
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:73)
at org.springframework.data.redis.cache.RedisCache C a c h e V a l u e A c c e s s o r . d e s e r i a l i z e I f N e c e s s a r y ( R e d i s C a c h e . j a v a : 477 ) a t o r g . s p r i n g f r a m e w o r k . d a t a . r e d i s . c a c h e . R e d i s C a c h e . l o o k u p ( R e d i s C a c h e . j a v a : 323 ) a t o r g . s p r i n g f r a m e w o r k . d a t a . r e d i s . c a c h e . R e d i s C a c h e . g e t ( R e d i s C a c h e . j a v a : 184 ) a t o r g . s p r i n g f r a m e w o r k . d a t a . r e d i s . c a c h e . R e d i s C a c h e . g e t ( R e d i s C a c h e . j a v a : 133 ) a t o r g . s p r i n g f r a m e w o r k . c a c h e . i n t e r c e p t o r . A b s t r a c t C a c h e I n v o k e r . d o G e t ( A b s t r a c t C a c h e I n v o k e r . j a v a : 71 ) a t o r g . s p r i n g f r a m e w o r k . c a c h e . i n t e r c e p t o r . C a c h e A s p e c t S u p p o r t . f i n d I n C a c h e s ( C a c h e A s p e c t S u p p o r t . j a v a : 536 ) a t o r g . s p r i n g f r a m e w o r k . c a c h e . i n t e r c e p t o r . C a c h e A s p e c t S u p p o r t . f i n d C a c h e d I t e m ( C a c h e A s p e c t S u p p o r t . j a v a : 502 ) a t o r g . s p r i n g f r a m e w o r k . c a c h e . i n t e r c e p t o r . C a c h e A s p e c t S u p p o r t . e x e c u t e ( C a c h e A s p e c t S u p p o r t . j a v a : 388 ) a t o r g . s p r i n g f r a m e w o r k . c a c h e . i n t e r c e p t o r . C a c h e A s p e c t S u p p o r t . e x e c u t e ( C a c h e A s p e c t S u p p o r t . j a v a : 326 ) a t o r g . s p r i n g f r a m e w o r k . c a c h e . i n t e r c e p t o r . C a c h e I n t e r c e p t o r . i n v o k e ( C a c h e I n t e r c e p t o r . j a v a : 61 ) a t o r g . s p r i n g f r a m e w o r k . a o p . f r a m e w o r k . R e f l e c t i v e M e t h o d I n v o c a t i o n . p r o c e e d ( R e f l e c t i v e M e t h o d I n v o c a t i o n . j a v a : 179 ) a t o r g . s p r i n g f r a m e w o r k . a o p . f r a m e w o r k . C g l i b A o p P r o x y CacheValueAccessor.deserializeIfNecessary(RedisCache.java:477) at org.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:323) at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:184) at org.springframework.data.redis.cache.RedisCache.get(RedisCache.java:133) at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:71) at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:536) at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:502) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:388) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:326) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy CacheValueAccessor.deserializeIfNecessary(RedisCache.java:477)atorg.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:323)atorg.springframework.data.redis.cache.RedisCache.get(RedisCache.java:184)atorg.springframework.data.redis.cache.RedisCache.get(RedisCache.java:133)atorg.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:71)atorg.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:536)atorg.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:502)atorg.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:388)atorg.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:326)atorg.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)atorg.springframework.aop.framework.CglibAopProxyDynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
at com.zcl.tmall.service.CategoryService E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB5ece0b1f.list()
at com.zcl.tmall.web.CategoryController.list(CategoryController.java:33)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol C o n n e c t i o n H a n d l e r . p r o c e s s ( A b s t r a c t P r o t o c o l . j a v a : 868 ) a t o r g . a p a c h e . t o m c a t . u t i l . n e t . N i o E n d p o i n t ConnectionHandler.process(AbstractProtocol.java:868) at org.apache.tomcat.util.net.NioEndpoint ConnectionHandler.process(AbstractProtocol.java:868)atorg.apache.tomcat.util.net.NioEndpointSocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor W o r k e r . r u n ( T h r e a d P o o l E x e c u t o r . j a v a : 624 ) a t o r g . a p a c h e . t o m c a t . u t i l . t h r e a d s . T a s k T h r e a d Worker.run(ThreadPoolExecutor.java:624) at org.apache.tomcat.util.threads.TaskThread Worker.run(ThreadPoolExecutor.java:624)atorg.apache.tomcat.util.threads.TaskThreadWrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.data.domain.PageImpl: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: [B@4c12c893; line: 1, column: 98] (through reference chain: com.zcl.tmall.util.Page4Navigator[“pageFromJPA”])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1206)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:116)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromObject(AsArrayTypeDeserializer.java:61)
at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:209)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:502)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:104)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:116)
at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71)
at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer$Vanilla.deserializeWithType(UntypedObjectDeserializer.java:553)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:63)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3814)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2983)
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:71)
… 68 more

配置redis

package com.zcl.tmall.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.domain.PageImpl;
/**
 * Author:markusZhang
 * VM Args:
 * Date:Create in 2020/1/31 21:18
 */
@Configuration
//redis缓存配置类
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate){
        RedisSerializer stringSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        CacheManager cacheManager = new RedisCacheManager(redisTemplate);
        return cacheManager;
    }
}

对CacheConfig中的Jackson2JsonSerializer解释

CacheConfig中设置redis缓存value的序列化方法Jackson2JsonSerializer,它要求所有被序列化的对象都必须要有无参构造器,而恰巧jpa中的PageImpl就没有无参构造器,这就导致了文章开头所提到的问题,所以呢,我们需要把jpa中的Page对象封装起来,重写一个分页类,再定义一个无参构造器。

重写分页

package com.zcl.tmall.util;

import org.springframework.data.domain.Page;
import java.util.Arrays;
import java.util.List;

/**
 * Author:markusZhang
 * VM Args:
 * Date:Create in 2020/1/26 15:17
 */
public class Page4Navigator<T> {
    //private static final long serialVersionUID = -3720998571176536865L;
    //springBoot jpa传出来的分页对象 Page4Navigator 类就是对它进行封装以达到扩展的效果
    Page<T> pageFromJPA;
    //分页的时候,如果总页数比较多,那么显示出来的分页超链一个有几个。比如如果分页出来的超链是这样的:{1,2,3},那么navigatePages=3
    private int navigatePages;
    //总页数
    private int totalPages;
    //第几页(默认是0)
    private int number;
    //总共有多少条数据
    private long totalElements;
    //一页最多有多少条数据
    private int size;
    //当前页有多少条数据
    private int numberOfElements;
    //数据集合
    private List<T> content;
    //是否有数据
    private boolean HasContent;
    //是否是首页
    private boolean first;
    //是否是尾页
    private boolean last;
    //是否有下一页
    private boolean HasNext;
    //是否有上一页
    private boolean HasPrevious;
    //分页的时候,如果总页数比较多,那么显示出来的分页超链一个有几个。比如如果分页出来的超链是这样的:[1 2 3],那么navigatepageNums就是这个数组[1 2 3] 这样便于前端展示
    private int []navigatepageNums;
    public Page4Navigator(){
        //这个空的分页是为了Redis从json格式转换Page4Navigator对象专门提供的
    }
    public Page4Navigator(Page<T> pageFromJPA,int navigatePages){
        this.pageFromJPA = pageFromJPA;
        this.navigatePages = navigatePages;
        totalPages = pageFromJPA.getTotalPages();
        number = pageFromJPA.getNumber();
        totalElements = pageFromJPA.getTotalElements();
        size = pageFromJPA.getSize();
        numberOfElements = pageFromJPA.getNumberOfElements();
        content = pageFromJPA.getContent();
        HasContent = pageFromJPA.hasContent();
        first = pageFromJPA.isFirst();
        last = pageFromJPA.isLast();
        HasNext = pageFromJPA.hasNext();
        HasContent = pageFromJPA.hasContent();
        HasPrevious = pageFromJPA.hasPrevious();
        calcNavigatepageNums();
    }

    private void calcNavigatepageNums() {
        int navigatepageNums[];
        int totalPages = getTotalPages();
        int num = getNumber();
        //当总页数小于或等于导航页码时
        if(totalPages <= navigatePages){
            navigatepageNums = new int[totalPages];
            for(int i=0;i<totalPages;i++){
                navigatepageNums[i] = i+1;
            }
        }else{//当总页数大于导航页码时
            navigatepageNums = new int[navigatePages];
            int startNum = num-navigatePages/2;
            int endNum = num+navigatePages/2;
            if(startNum<1){
                startNum=1;
                //(最前navigatePages页
                for(int i=0;i<navigatePages;i++){
                    navigatepageNums[i] = startNum++;
                }
            }else if (endNum>totalPages){
                endNum = totalPages;
                //(最后navigatePages页
                for(int i=navigatePages-1;i>=0;i--){
                    navigatepageNums[i] = endNum--;
                }
            }else{
                //所有中间页
                for(int i=0;i<navigatePages;i++){
                    navigatepageNums[i] = startNum++;
                }
            }
        }
        this.navigatepageNums = navigatepageNums;
    }

    public int getNavigatePages() {
        return navigatePages;
    }

    public void setNavigatePages(int navigatePages) {
        this.navigatePages = navigatePages;
    }

    public int getTotalPages() {
        return totalPages;
    }

    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public long getTotalElements() {
        return totalElements;
    }

    public void setTotalElements(long totalElements) {
        this.totalElements = totalElements;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public int getNumberOfElements() {
        return numberOfElements;
    }

    public void setNumberOfElements(int numberOfElements) {
        this.numberOfElements = numberOfElements;
    }

    public List<T> getContent() {
        return content;
    }

    public void setContent(List<T> content) {
        this.content = content;
    }

    public boolean isHasContent() {
        return HasContent;
    }

    public void setHasContent(boolean hasContent) {
        HasContent = hasContent;
    }

    public boolean isFirst() {
        return first;
    }

    public void setFirst(boolean first) {
        this.first = first;
    }

    public boolean isLast() {
        return last;
    }

    public void setLast(boolean last) {
        this.last = last;
    }

    public boolean isHasNext() {
        return HasNext;
    }

    public void setHasNext(boolean hasNext) {
        HasNext = hasNext;
    }

    public boolean isHasPrevious() {
        return HasPrevious;
    }

    public void setHasPrevious(boolean hasPrevious) {
        HasPrevious = hasPrevious;
    }

    public int[] getNavigatepageNum() {
        return navigatepageNums;
    }

    public void setNavigatepageNum(int[] navigatepageNums) {
        this.navigatepageNums = navigatepageNums;
    }

    @Override
    public String toString() {
        return "Page4Navigator{" +
                "pageFromJPA=" + pageFromJPA +
                ", navigatePages=" + navigatePages +
                ", totalPages=" + totalPages +
                ", number=" + number +
                ", totalElements=" + totalElements +
                ", size=" + size +
                ", numberOfElements=" + numberOfElements +
                ", content=" + content +
                ", isHasContent=" + HasContent +
                ", first=" + first +
                ", last=" + last +
                ", isHasNext=" + HasNext +
                ", isHasPrevious=" + HasPrevious +
                ", navigatepageNums=" + Arrays.toString(navigatepageNums) +
                '}';
    }
}

关于重写类中pageFromJpa属性没有进行getter setter的解释

关于序列化工具主要有:fastJson,Gson,JackJson。
我在配置redis对value序列化使用的是JackJson,具体序列化的注意点在这篇博客点我。对比有get方法的属性,三种处理工具所表现出的状态。如果把pageFromJpa属性实现get方法,那么JackJson会把pageFromJpa一并序列化,那么又会出现文章开头所说问题。

解决问题

只需要把重写分页类中的Page对象的set get 方法去掉就行。

你可能感兴趣的:(usually,java,redis,spring)