You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate二级缓存每次调用均执行put操作且耗时过长的原因排查

问题分析与解决方案

问题背景

基于Spring Boot 2.7.3搭建的应用,采用Hibernate ORM、Hazelcast作为二级缓存提供商、MySQL作为数据库,目标是通过二级缓存加速findAll批量查询(开发环境数据库存有5012条实体数据)。实际运行中发现:首次调用findAll时,所有实体均从数据库加载并写入二级缓存;但后续调用仅能命中30条缓存条目,剩余4982条需重新执行写入操作,该过程耗时极长,导致系统性能反而不如关闭二级缓存时。

相关配置信息

application.yml 核心配置

properties:
  hibernate.jdbc.time_zone: UTC
  hibernate.id.new_generator_mappings: true
  hibernate.connection.provider_disables_autocommit: true
  hibernate.cache.use_second_level_cache: true
  hibernate.cache.use_query_cache: false
  hibernate.generate_statistics: false
  # modify batch size as necessary
  hibernate.jdbc.batch_size: 25
  hibernate.order_inserts: true
  hibernate.order_updates: true
  hibernate.query.fail_on_pagination_over_collection_fetch: true
  hibernate.query.in_clause_parameter_padding: true
  hibernate.cache.region.factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory

实体类缓存配置

实体类已添加@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)注解,启用二级缓存支持。

Hazelcast 缓存配置

@Bean
public Config hazelcastConfig() {
 
    Config config = new Config();
    config.setInstanceName("placeholder");

    MapConfig mapConfig = new MapConfig("default");
    mapConfig.setBackupCount(1);
    mapConfig.getEvictionConfig().setEvictionPolicy(EvictionPolicy.LRU);
    mapConfig.getEvictionConfig().setMaxSizePolicy(MaxSizePolicy.USED_HEAP_SIZE);
    mapConfig.setTimeToLiveSeconds(3600);
    config.addMapConfig(mapConfig);

    return Hazelcast.newHazelcastInstance(config);
}

findAll 方法实现

public List<SinkSourceAddDTO> findAll() {
    return HouseRepository.findAll()
   .stream().map(HouseAddMapper::toDto)
   .collect(Collectors.toCollection(LinkedList::new));
}

会话指标详情

首次调用指标

Session Metrics {
    1968100 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    41430100 nanoseconds spent preparing 32 JDBC statements;
    52658900 nanoseconds spent executing 32 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    2712459300 nanoseconds spent performing 5012 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    8894900 nanoseconds spent performing 30 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    97100 nanoseconds spent executing 2 partial-flushes (flushing a total of 0 entities and 0 collections)
} 

首次调用完成5012条实体的缓存写入操作。

第二次及后续调用指标

Session Metrics {
    290100 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    111100 nanoseconds spent preparing 2 JDBC statements;
    14781000 nanoseconds spent executing 2 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    1450759900 nanoseconds spent performing 4982 L2C puts;
    9978700 nanoseconds spent performing 30 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    11100 nanoseconds spent executing 2 partial-flushes (flushing a total of 0 entities and 0 collections)
} 

仅命中30条缓存条目,剩余4982条需重新写入缓存,耗时约1.4秒。

已尝试无效方案

  • 切换缓存提供商至EhCache
  • 增大缓存容量上限
  • 延长缓存TTL(Time To Live)时间
  • 更换缓存淘汰策略(如从LRU改为LFU)

针对性解决方案

1. 启用查询缓存

Hibernate二级缓存仅缓存单个实体实例,findAll这类批量查询的结果集默认不会被缓存。每次调用findAll都会重新加载所有实体,进而触发大量缓存写入操作。需开启查询缓存:

  • 修改application.yml,设置hibernate.cache.use_query_cache: true
  • 在Repository的findAll方法上添加@Cacheable注解,或在HQL查询中显式调用setCacheable(true)

2. 修正实体的equals/hashCode实现

Hibernate依赖实体的equals和hashCode方法生成缓存键。若这两个方法实现不当(比如基于非主键字段),会导致缓存键不一致,无法命中已有缓存。确保这两个方法基于实体主键实现。

3. 为实体配置专属缓存区域

当前使用默认缓存区域,可能因通用策略不匹配导致缓存失效。为目标实体单独配置缓存区域:

@Bean
public Config hazelcastConfig() {
    Config config = new Config();
    config.setInstanceName("placeholder");

    // 为House实体配置专属缓存区域
    MapConfig houseMapConfig = new MapConfig("com.yourpackage.House");
    houseMapConfig.setBackupCount(1);
    houseMapConfig.getEvictionConfig().setEvictionPolicy(EvictionPolicy.LRU);
    houseMapConfig.getEvictionConfig().setMaxSizePolicy(MaxSizePolicy.USED_HEAP_PERCENTAGE);
    houseMapConfig.setTimeToLiveSeconds(3600);
    // 临时测试可禁用TTL,排查是否为过期导致的缓存失效
    // houseMapConfig.setTimeToLiveSeconds(0);
    config.addMapConfig(houseMapConfig);

    MapConfig defaultMapConfig = new MapConfig("default");
    defaultMapConfig.setBackupCount(1);
    defaultMapConfig.getEvictionConfig().setEvictionPolicy(EvictionPolicy.LRU);
    defaultMapConfig.getEvictionConfig().setMaxSizePolicy(MaxSizePolicy.USED_HEAP_SIZE);
    defaultMapConfig.setTimeToLiveSeconds(3600);
    config.addMapConfig(defaultMapConfig);

    return Hazelcast.newHazelcastInstance(config);
}

4. 确保查询运行在只读事务中

READ_WRITE缓存策略下,Hibernate会在事务提交后更新缓存。若事务边界处理不当,可能导致缓存状态不一致。为findAll方法添加只读事务注解:

@Transactional(readOnly = true)
public List<SinkSourceAddDTO> findAll() {
    return HouseRepository.findAll()
   .stream().map(HouseAddMapper::toDto)
   .collect(Collectors.toCollection(LinkedList::new));
}

5. 验证实体序列化正确性

Hazelcast默认使用Java序列化,若实体或其关联对象未实现Serializable接口,会导致缓存条目无法正确存储或读取。确保所有需要缓存的实体及关联对象均实现Serializable接口。

内容的提问来源于stack exchange,提问作者Furkan Calik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 19:02:32