我们使用Redis缓存在应用程序的缓存中存储数据。我们直接使用@Cacheable来允许缓存,并使用Redis来缓存。下面是配置
Redis配置-
@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。实体。CompanyEntity”,“createdTs”:1693922698604,“id”:100000000002,“名称”:“ABC”,“description”:“ABC操作”,“活动”:true,“EmployeeEntities”:[“org. hibernate 收藏。内部的PersistentBag”,[{"@class”:”com。abc。实体。EmployeeEntity”,“createdTs”:1693922698604,“ID”:10000000002,“删除实体”:{"@class”:”com。abc。EmployeeLevel”,“levelId”:100000000000,“名称”:“H1”,“active”:真}}]]}”
但是,当我们尝试第二次执行查询时,它仍然进入该高速缓存方法,并具有以下日志-
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模式进行缓存,整个集合也存在于缓存中。但它仍然进入缓存方法并从db获取值。我们如何防止它并直接从缓存中使用它。
UPDATE如果我们使用LAZY加载,集合对象不会被缓存,并且为null。但是我们需要缓存的集合,因为方法不会按顺序调用,缓存的方法稍后将返回null。
2条答案
按热度按时间isr3a4wc1#
找到所需的答案here。我的缓存集合引用未正确反序列化。在应用所需的更改之后,我能够成功地从Redis缓存中反序列化缓存的集合对象。
现有Redis配置中的更改-
作为修复的一部分,添加了两个新类-
}
和
xpcnnkqh2#
JsonMappingException
表示Jackson尝试反序列化Hibernate代理对象,但由于反序列化期间Hibernate会话不可用而无法执行此操作。因此,您需要确保
employeeEntities
集合在序列化之前正确初始化为非代理状态,以便Jackson正确地从该高速缓存中反序列化CompanyEntity
对象 * 而不需要 * Hibernate会话。您可以通过调整服务方法来确保集合的正确初始化,以便在缓存
CompanyEntity
之前强制初始化employeeEntities
集合!这样,
employeeEntities
集合就从Hibernate代理转换为常规Java集合。这应该有助于避免在该高速缓存进行反序列化时遇到的JsonMappingException
问题。这假设您使用的是
FetchType.EAGER
,这意味着当您获取CompanyEntity
时,employeeEntities
集合将自动加载。如果问题仍然存在,您可以检查分离实体是否有帮助:
从Hibernate会话中分离实体会将其转换为普通的POJO。
请注意,要获得
EntityManager
,您需要将其注入到服务类中,并且您应该确保在分离实体之前正确初始化稍后将访问的所有关系和属性。另一种方法,避免直接缓存Hibernate托管实体或确保Hibernate代理不被序列化,是使用DTO(数据传输对象)将持久化模型与应用程序逻辑中使用的对象分离。
CompanyEntity
类对应的DTO类。CompanyEntity
示例Map到DTO示例。在您的服务类中,它看起来像这样:
在此方法中,您将使用
ModelMapper
或其他Map框架将实体Map到DTO。该DTO将被缓存,避免了您遇到的Hibernate代理问题。请记住为
EmployeeEntity
和作为对象图一部分的任何其他实体创建相应的DTO。这种方法需要创建额外的类并修改服务逻辑,但它将在Hibernate实体和缓存内容之间创建一个清晰的分离,这有助于避免类似这样的问题。