我们在应用程序中使用Redis缓存来存储数据。我们直接使用@Cacheable来允许缓存,并在底层使用Redis进行缓存。以下是配置信息:
服务类别 -
公司级别 -
一旦我们运行服务,缓存也会被正确地处理。一旦我们发出查询,我们会在缓存中得到以下记录 -
{"@class":"com.abc.entity.CompanyEntity","createdTs":1693922698604,"id":100000000002,"name":"ABC","description":"ABC Operations","active":true,"EmployeeEntities":["org.hibernate.collection.internal.PersistentBag",[{"@class":"com.abc.entity.EmployeeEntity","createdTs":1693922698604,"Id":100000000002,"EmployeeEntity":{"@class":"com.abc.EmployeeLevel","levelId":100000000000,"name":"H1","active":true}}]]}
我从各种SO答案中了解到,这是由于代理子对象的会话不可用所致。但我们正在使用EAGER模式进行缓存,并且整个集合也存在于缓存中。但它仍然进入缓存方法并从数据库获取值。我们如何防止这种情况并直接从缓存中使用它。
更新: 如果我们使用LAZY加载,集合对象将不会被缓存并且为null。但我们需要缓存的集合,因为方法不会按顺序调用,缓存方法稍后将返回null。
@Configuration
@EnableCaching
@RequiredArgsConstructor
public class RedisConfig implements CachingConfigurer {
@Value("${spring.cache.redis.time-to-live}")
Long redisTTL;
@Bean
public RedisCacheConfiguration cacheConfiguration(ObjectMapper objectMapper) {
objectMapper = objectMapper.copy();
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.registerModules(new JavaTimeModule(), new Hibernate5Module())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofDays(redisTTL))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
}
@Bean
public RedissonClient reddison(@Value("${spring.redis.host}") final String redisHost,
@Value("${spring.redis.port}") final int redisPort,
@Value("${spring.redis.cluster.nodes}") final String clusterAddress,
@Value("${spring.redis.use-cluster}") final boolean useCluster,
@Value("${spring.redis.timeout}") final int timeout) {
Config config = new Config();
if (useCluster) {
config.useClusterServers().addNodeAddress(clusterAddress).setTimeout(timeout);
} else {
config.useSingleServer().setAddress(String.format("redis://%s:%d", redisHost, redisPort)).setTimeout(timeout);
}
return Redisson.create(config);
}
@Bean
public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redissonClient) {
return new RedissonConnectionFactory(redissonClient);
}
@Bean
public RedisCacheManager cacheManager(RedissonClient redissonClient, ObjectMapper objectMapper) {
this.redissonConnectionFactory(redissonClient).getConnection().flushDb();
RedisCacheManager redisCacheManager= RedisCacheManager.builder(this.redissonConnectionFactory(redissonClient))
.cacheDefaults(this.cacheConfiguration(objectMapper))
.build();
redisCacheManager.setTransactionAware(true);
return redisCacheManager;
}
@Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
@Slf4j
public static class RedisCacheErrorHandler implements CacheErrorHandler {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.info("Unable to get from cache " + cache.getName() + " : " + exception.getMessage());
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.info("Unable to put into cache " + cache.getName() + " : " + exception.getMessage());
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.info("Unable to evict from cache " + cache.getName() + " : " + exception.getMessage());
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.info("Unable to clean cache " + cache.getName() + " : " + exception.getMessage());
}
}
}
服务类别 -
@Service
@AllArgsConstructor
@Transactional
public class CompanyServiceImpl implements CompanyService {
private final CompanyRepository companyRepository;
@Cacheable(key = "#companyName", value = COMPANY_CACHE_NAME, cacheManager = "cacheManager")
public Optional<CompanyEntity> findByName(String companyName) {
return companyRepository.findByName(companyName);
}
}
公司级别 -
@Entity
@Jacksonized
@AllArgsConstructor
@NoArgsConstructor
public class CompanyEntity {
@Id
private Long id;
@ToString.Exclude
@OneToMany(mappedBy = "comapnyENtity", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<EmployeeEntity> employeeEntities;
}
一旦我们运行服务,缓存也会被正确地处理。一旦我们发出查询,我们会在缓存中得到以下记录 -
> get Company::ABC
{"@class":"com.abc.entity.CompanyEntity","createdTs":1693922698604,"id":100000000002,"name":"ABC","description":"ABC Operations","active":true,"EmployeeEntities":["org.hibernate.collection.internal.PersistentBag",[{"@class":"com.abc.entity.EmployeeEntity","createdTs":1693922698604,"Id":100000000002,"EmployeeEntity":{"@class":"com.abc.EmployeeLevel","levelId":100000000000,"name":"H1","active":true}}]]}
但是当我们尝试第二次执行查询时,它仍然进入缓存方法,并记录如下日志 -
Unable to get from cache Company : Could not read JSON: failed to lazily initialize a
collection, could not initialize proxy - no Session (through reference chain:
com.abc.entity.CompanyEntity$CompanyEntityBuilder["employeeEntities"]); nested exception
is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a c
collection, could not initialize proxy - no Session (through reference chain:
com.abc.entity.CompanyEntity$CompanyEntityBuilder["employeeEntities"])
我从各种SO答案中了解到,这是由于代理子对象的会话不可用所致。但我们正在使用EAGER模式进行缓存,并且整个集合也存在于缓存中。但它仍然进入缓存方法并从数据库获取值。我们如何防止这种情况并直接从缓存中使用它。
更新: 如果我们使用LAZY加载,集合对象将不会被缓存并且为null。但我们需要缓存的集合,因为方法不会按顺序调用,缓存方法稍后将返回null。