配置类
/**
* @description: Spring的缓存配置!!!
*/
@Slf4j
@Configuration
@EnableCaching
public class MyCacheConfig extends CachingConfigurerSupport {
/** 人员每日打卡的缓存名称 */
public static final String CACHE_NAME_PERSON_DAILY_GROUP= "PERSON_DAILY_GROUP";
@Resource
private RedisConnectionFactory redisConnectionFactory;
private CacheManager cacheManager;
@Override
@Bean
public CacheManager cacheManager() {
final RedisCacheManager redisCacheManager = new RedisCacheManager(
// 创建无锁的RedisCacheWriter对象
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默认的缓存配置,未配置的 cacheName 会使用这个
this.createCacheConfiguration(30 * 60),
// 针对特定cacheName的缓存配置
this.initialCacheConfigurations()
);
this.cacheManager = redisCacheManager;
return redisCacheManager;
}
/**
* 创建缓存配置对象
* @param seconds 缓存的ttl,单位为秒。0表示永久缓存
* @return 缓存配置对象
*/
private RedisCacheConfiguration createCacheConfiguration(Integer seconds) {
// 使用Jackson2JsonRedisSerialize 替换默认序列化(JdkSerializationRedisSerializer)
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// 下面2行解决jackson2无法反序列化LocalDateTime的问题
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
jackson2JsonRedisSerializer.setObjectMapper(om);
// 缓存key前缀生成规则
CacheKeyPrefix cacheKeyPrefix = (String cacheName) -> cacheName + SysConstants.COLON;
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.entryTtl(Duration.ofSeconds(seconds))
.computePrefixWith(cacheKeyPrefix);
return redisCacheConfiguration;
}
private Map<String, RedisCacheConfiguration> initialCacheConfigurations() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
// key:cacheName,value:缓存配置
redisCacheConfigurationMap.put(CACHE_NAME_PERSON_DAILY_GROUP, this.createCacheConfiguration(24*60*60));
return redisCacheConfigurationMap;
}
@Override
@Bean
public KeyGenerator keyGenerator() {
return (o, method, objects) -> {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName()).append(".");
sb.append(method.getName()).append(".");
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
};
}
@Override
@Bean
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(this.cacheManager);
}
@Override
@Bean
public CacheErrorHandler errorHandler() {
// 用于捕获从Cache中进行CRUD时的异常的回调处理器。
return new SimpleCacheErrorHandler();
}
}
若要确保该配置类一定能被扫描到,可以不使用
@Configuration
方式,而是在/resources/META-INF/spring.factories
文件中配置org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xx.common.yy.config.MyCacheConfig
使用
/**
* 人员每日打卡 服务实现类
* @author panxili
* @since 2022-08-11
*/
@Slf4j
@Service
@CacheConfig(cacheNames = CACHE_NAME_PERSON_DAILY_GROUP)
public class PersonDailyCheckService {
@Cacheable(key = "T(String).join('-'', #queryForm.day, #queryForm.personId, #queryForm.shopOrgId, #queryForm.positionId)"
, condition = "#queryForm != null && #queryForm.day != null && T(cn.hutool.core.text.CharSequenceUtil).isAllNotEmpty(#queryForm.personId, #queryForm.shopOrgId, #queryForm.positionId)")
public List<PersonDailyCheckModel> queryList(PersonDailyCheckQueryForm queryForm) {
log.info("com.xx.service.attendance.service.PersonDailyCheckService.queryList() run...");
return personDailyCheckMapper.queryList(queryForm);
}
从数据库中查到数据后,序列化到缓存时报错:
org.springframework.expression.spel.SpelEvaluationException: EL1029E: A problem occurred when trying to execute method 'join' on object of type 'java.lang.Class': 'Problem invoking method: public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])'
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:145)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:54)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:390)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:90)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:109)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:265)
at org.springframework.cache.interceptor.CacheOperationExpressionEvaluator.key(CacheOperationExpressionEvaluator.java:104)
at org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext.generateKey(CacheAspectSupport.java:778)
at org.springframework.cache.interceptor.CacheAspectSupport.generateKey(CacheAspectSupport.java:575)
......
Caused by: org.springframework.expression.AccessException: Problem invoking method: public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:134)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:138)
... 98 common frames omitted
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.time.LocalDate to java.lang.CharSequence
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:75)
at org.springframework.expression.spel.support.ReflectionHelper.convertArguments(ReflectionHelper.java:307)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:123)
... 99 common frames omitted
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.time.LocalDate] to type [java.lang.CharSequence]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:321)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:194)
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:70)
... 101 common frames omitted
将#queryForm.day
改为#queryForm.day.toString()
:
@Cacheable(key = "T(String).join('-'', #queryForm.day.toString(), #queryForm.personId, #queryForm.shopOrgId, #queryForm.positionId)"
, condition = "#queryForm != null && #queryForm.day != null && T(cn.hutool.core.text.CharSequenceUtil).isAllNotEmpty(#queryForm.personId, #queryForm.shopOrgId, #queryForm.positionId)")
从缓存中查到数据后,反序列化时报错:
org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Unrecognized field "pmOffCheckStatusTimely" (class com.xx.service.attendance.model.PersonDailyCheckModel), not marked as ignorable (28 known properties: "pmOffCheckStatus", "shopOrgId", "amOnCheckTime", "storeLongitude", "shiftStr", "personId", "areaOrgId", "pmOffDutyTime", "needCheckInfoTimelyList", "pmOnCheckTime", "id", "day", "pmOnDutyTime", "companyOrgId", "pmOffCheckTime", "positionId", "createTime", "bigRegionOrgId", "storeLatitude", "amOnCheckStatus", "week", "totalCheckStatus", "checkRecordList", "salesDepartmentId", "groupId", "pmOnCheckStatus", "amOnDutyTime", "updateTime"])
at [Source: (byte[])"["java.util.ArrayList",[["com.xx.service.attendance.model.PersonDailyCheckModel",{"id":61,"day":"2022-08-26","week":"FRIDAY","personId":"3fda4ad023e74177afb506c621bf9443","companyOrgId":"003539","salesDepartmentId":"003543","bigRegionOrgId":"003546","areaOrgId":"003547","shopOrgId":"003763","storeLongitude":"121.5517810","storeLatitude":"24.9951282","positionId":"0003","groupId":2,"amOnDutyTime":"09:30:00","amOnCheckStatus":"LATE","amOnCheckTime":"2022-08-26T10:08:12","pmOnDutyTime":"14:00:00"[truncated 1118 bytes]; line: 1, column: 822] (through reference chain: java.util.ArrayList[0]->com.xx.service.attendance.model.PersonDailyCheckModel["pmOffCheckStatusTimely"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "pmOffCheckStatusTimely" (class com.xx.service.attendance.model.PersonDailyCheckModel), not marked as ignorable (28 known properties: "pmOffCheckStatus", "shopOrgId", "amOnCheckTime", "storeLongitude", "shiftStr", "personId", "areaOrgId", "pmOffDutyTime", "needCheckInfoTimelyList", "pmOnCheckTime", "id", "day", "pmOnDutyTime", "companyOrgId", "pmOffCheckTime", "positionId", "createTime", "bigRegionOrgId", "storeLatitude", "amOnCheckStatus", "week", "totalCheckStatus", "checkRecordList", "salesDepartmentId", "groupId", "pmOnCheckStatus", "amOnDutyTime", "updateTime"])
at [Source: (byte[])"["java.util.ArrayList",[["com.xx.service.attendance.model.PersonDailyCheckModel",{"id":61,"day":"2022-08-26","week":"FRIDAY","personId":"3fda4ad023e74177afb506c621bf9443","companyOrgId":"003539","salesDepartmentId":"003543","bigRegionOrgId":"003546","areaOrgId":"003547","shopOrgId":"003763","storeLongitude":"121.5517810","storeLatitude":"24.9951282","positionId":"0003","groupId":2,"amOnDutyTime":"09:30:00","amOnCheckStatus":"LATE","amOnCheckTime":"2022-08-26T10:08:12","pmOnDutyTime":"14:00:00"[truncated 1118 bytes]; line: 1, column: 822] (through reference chain: java.util.ArrayList[0]->com.xx.service.attendance.model.PersonDailyCheckModel["pmOffCheckStatusTimely"])
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:75)
at org.springframework.data.redis.serializer.DefaultRedisElementReader.read(DefaultRedisElementReader.java:48)
at org.springframework.data.redis.serializer.RedisSerializationContext$SerializationPair.read(RedisSerializationContext.java:226)
at org.springframework.data.redis.cache.RedisCache.deserializeCacheValue(RedisCache.java:254)
at org.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:88)
at org.springframework.cache.support.AbstractValueAdaptingCache.get(AbstractValueAdaptingCache.java:58)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:73)
......
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "pmOffCheckStatusTimely" (class com.xx.service.attendance.model.PersonDailyCheckModel), not marked as ignorable (28 known properties: "pmOffCheckStatus", "shopOrgId", "amOnCheckTime", "storeLongitude", "shiftStr", "personId", "areaOrgId", "pmOffDutyTime", "needCheckInfoTimelyList", "pmOnCheckTime", "id", "day", "pmOnDutyTime", "companyOrgId", "pmOffCheckTime", "positionId", "createTime", "bigRegionOrgId", "storeLatitude", "amOnCheckStatus", "week", "totalCheckStatus", "checkRecordList", "salesDepartmentId", "groupId", "pmOnCheckStatus", "amOnDutyTime", "updateTime"])
at [Source: (byte[])"["java.util.ArrayList",[["com.xx.service.attendance.model.PersonDailyCheckModel",{"id":61,"day":"2022-08-26","week":"FRIDAY","personId":"3fda4ad023e74177afb506c621bf9443","companyOrgId":"003539","salesDepartmentId":"003543","bigRegionOrgId":"003546","areaOrgId":"003547","shopOrgId":"003763","storeLongitude":"121.5517810","storeLatitude":"24.9951282","positionId":"0003","groupId":2,"amOnDutyTime":"09:30:00","amOnCheckStatus":"LATE","amOnCheckTime":"2022-08-26T10:08:12","pmOnDutyTime":"14:00:00"[truncated 1118 bytes]; line: 1, column: 822] (through reference chain: java.util.ArrayList[0]->com.xx.service.attendance.model.PersonDailyCheckModel["pmOffCheckStatusTimely"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
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:712)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
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:712)
at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:68)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3129)
at org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer.deserialize(Jackson2JsonRedisSerializer.java:73)
... 97 common frames omitted
原来是因为实体类中有getPmOffCheckStatusTimely()
方法,在序列化时会调用该方法,然后缓存的json中会有pmOffCheckStatusTimely
字段。后来反序列化时,在实体类中找不到pmOffCheckStatusTimely
属性,所以就报错了!
实体类
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(description = "人员每日打卡")
public class PersonDailyCheckModel implements Serializable {
@ApiModelProperty(value = "下午下班应该打卡时间;若无需打卡,则为null")
private LocalTime pmOffDutyTime;
/**
* 获取下午下班打卡状态(实时)
* @return
*/
@Nullable
public CheckStatusEnum getPmOffCheckStatusTimely() {
if (getPmOffDutyTime() == null) {
return null;
}
final CheckStatusEnum pmOffCheckStatus = this.getPmOffCheckStatus();
if (pmOffCheckStatus != null) {
return pmOffCheckStatus;
}
final LocalDate day = getDay();
if (LocalDateTime.now().compareTo(day.plusDays(ONE).atTime(AM_ON_CHECK_START)) >= ZERO) {
return CheckStatusEnum.FORGOT;
}
return null;
}
}