由于想给 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
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中设置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) +
'}';
}
}
关于序列化工具主要有:fastJson,Gson,JackJson。
我在配置redis对value序列化使用的是JackJson,具体序列化的注意点在这篇博客点我。对比有get方法的属性,三种处理工具所表现出的状态。如果把pageFromJpa属性实现get方法,那么JackJson会把pageFromJpa一并序列化,那么又会出现文章开头所说问题。
只需要把重写分页类中的Page对象的set get 方法去掉就行。