当前位置: 代码网 > it编程>编程语言>Java > SpringBoot启动时将数据库数据预加载到Redis缓存的几种实现方案

SpringBoot启动时将数据库数据预加载到Redis缓存的几种实现方案

2025年09月29日 Java 我要评论
引言在实际项目开发中,我们经常需要在应用启动时将一些固定的、频繁访问的数据从数据库预加载到 redis 缓存中,以提高系统性能。本文将介绍几种实现方案。方案一:使用 @postconstruct 注解

引言

在实际项目开发中,我们经常需要在应用启动时将一些固定的、频繁访问的数据从数据库预加载到 redis 缓存中,以提高系统性能。本文将介绍几种实现方案。

方案一:使用 @postconstruct 注解

这是最简单直接的方式,在 spring bean 初始化完成后执行预加载逻辑。

@component
@slf4j
public class cachepreloader {
    
    @autowired
    private userservice userservice;
    
    @autowired
    private redistemplate<string, object> redistemplate;
    
    @postconstruct
    public void preloadcache() {
        log.info("开始预加载缓存数据...");
        
        try {
            // 加载用户基础信息
            list<user> users = userservice.getallactiveusers();
            for (user user : users) {
                string key = "user:" + user.getid();
                redistemplate.opsforvalue().set(key, user, duration.ofhours(24));
            }
            
            // 加载系统配置
            list<systemconfig> configs = systemconfigservice.getallconfigs();
            for (systemconfig config : configs) {
                string key = "config:" + config.getkey();
                redistemplate.opsforvalue().set(key, config.getvalue(), duration.ofdays(7));
            }
            
            log.info("缓存预加载完成,共加载 {} 条用户数据,{} 条配置数据", 
                    users.size(), configs.size());
                    
        } catch (exception e) {
            log.error("缓存预加载失败", e);
        }
    }
}

方案二:实现 applicationrunner 接口

applicationrunner 在应用启动完成后执行,可以获取命令行参数,更适合复杂的初始化逻辑。

@component
@order(1) // 设置执行顺序
@slf4j
public class cacheapplicationrunner implements applicationrunner {
    
    @autowired
    private datapreloadservice datapreloadservice;
    
    @override
    public void run(applicationarguments args) throws exception {
        log.info("applicationrunner 开始执行缓存预加载...");
        
        // 检查是否需要预加载
        if (args.containsoption("skip-cache-preload")) {
            log.info("跳过缓存预加载");
            return;
        }
        
        datapreloadservice.preloadallcache();
        log.info("applicationrunner 缓存预加载完成");
    }
}

方案三:监听 applicationreadyevent 事件

这种方式在应用完全启动就绪后执行,是最安全的预加载时机。

@component
@slf4j
public class cacheeventlistener {
    
    @autowired
    private redistemplate<string, object> redistemplate;
    
    @autowired
    private productservice productservice;
    
    @eventlistener
    public void handleapplicationready(applicationreadyevent event) {
        log.info("应用启动完成,开始预加载缓存...");
        
        // 异步执行预加载,避免阻塞启动
        completablefuture.runasync(() -> {
            try {
                preloadproductcache();
                preloadcategorycache();
            } catch (exception e) {
                log.error("异步预加载缓存失败", e);
            }
        });
    }
    
    private void preloadproductcache() {
        list<product> hotproducts = productservice.gethotproducts();
        hotproducts.foreach(product -> {
            string key = "hot_product:" + product.getid();
            redistemplate.opsforvalue().set(key, product, duration.ofhours(12));
        });
        log.info("热门商品缓存预加载完成,共 {} 条", hotproducts.size());
    }
    
    private void preloadcategorycache() {
        list<category> categories = productservice.getallcategories();
        redistemplate.opsforvalue().set("all_categories", categories, duration.ofdays(1));
        log.info("商品分类缓存预加载完成");
    }
}

方案四:创建专门的预加载服务

将预加载逻辑封装成独立的服务,便于管理和测试。

@service
@slf4j
public class datapreloadservice {
    
    @autowired
    private redistemplate<string, object> redistemplate;
    
    @autowired
    private dictservice dictservice;
    
    @autowired
    private regionservice regionservice;
    
    /**
     * 预加载所有缓存数据
     */
    public void preloadallcache() {
        long starttime = system.currenttimemillis();
        
        try {
            // 并行加载多种数据
            completablefuture<void> dictfuture = completablefuture.runasync(this::preloaddictcache);
            completablefuture<void> regionfuture = completablefuture.runasync(this::preloadregioncache);
            
            // 等待所有任务完成
            completablefuture.allof(dictfuture, regionfuture).join();
            
            long endtime = system.currenttimemillis();
            log.info("所有缓存预加载完成,耗时: {} ms", endtime - starttime);
            
        } catch (exception e) {
            log.error("缓存预加载过程中发生异常", e);
        }
    }
    
    /**
     * 预加载数据字典
     */
    private void preloaddictcache() {
        try {
            map<string, list<dictitem>> dictmap = dictservice.getalldictitems();
            dictmap.foreach((dicttype, items) -> {
                string key = "dict:" + dicttype;
                redistemplate.opsforvalue().set(key, items, duration.ofdays(30));
            });
            log.info("数据字典缓存预加载完成,共 {} 种类型", dictmap.size());
        } catch (exception e) {
            log.error("数据字典缓存预加载失败", e);
        }
    }
    
    /**
     * 预加载地区数据
     */
    private void preloadregioncache() {
        try {
            list<region> regions = regionservice.getallregions();
            redistemplate.opsforvalue().set("all_regions", regions, duration.ofdays(30));
            
            // 按层级缓存
            map<integer, list<region>> regionsbylevel = regions.stream()
                .collect(collectors.groupingby(region::getlevel));
            
            regionsbylevel.foreach((level, levelregions) -> {
                string key = "regions_level:" + level;
                redistemplate.opsforvalue().set(key, levelregions, duration.ofdays(30));
            });
            
            log.info("地区数据缓存预加载完成,共 {} 条记录", regions.size());
        } catch (exception e) {
            log.error("地区数据缓存预加载失败", e);
        }
    }
}

配置文件控制

application.yml 中添加配置项,控制预加载行为:

app:
  cache:
    preload:
      enabled: true
      async: true
      timeout: 30000  # 超时时间(毫秒)
      batch-size: 1000  # 批处理大小

对应的配置类:

@configurationproperties(prefix = "app.cache.preload")
@data
public class cachepreloadproperties {
    private boolean enabled = true;
    private boolean async = true;
    private long timeout = 30000;
    private int batchsize = 1000;
}

最佳实践建议

1. 异常处理

预加载过程中的异常不应该影响应用启动:

@postconstruct
public void preloadcache() {
    try {
        // 预加载逻辑
    } catch (exception e) {
        log.error("缓存预加载失败,但不影响应用启动", e);
        // 可以发送告警通知
    }
}

2. 分批处理

对于大量数据,应该分批处理避免内存溢出:

private void preloadlargedataset() {
    int pagesize = cachepreloadproperties.getbatchsize();
    int pagenum = 0;
    
    while (true) {
        list<dataentity> datalist = dataservice.getdatabypage(pagenum, pagesize);
        if (datalist.isempty()) {
            break;
        }
        
        // 批量写入 redis
        datalist.foreach(data -> {
            string key = "data:" + data.getid();
            redistemplate.opsforvalue().set(key, data, duration.ofhours(6));
        });
        
        pagenum++;
        log.info("已预加载第 {} 批数据,本批 {} 条", pagenum, datalist.size());
    }
}

3. 监控和告警

添加预加载状态监控:

@component
public class cachepreloadmonitor {
    
    private final meterregistry meterregistry;
    private final counter preloadsuccesscounter;
    private final counter preloadfailurecounter;
    
    public cachepreloadmonitor(meterregistry meterregistry) {
        this.meterregistry = meterregistry;
        this.preloadsuccesscounter = counter.builder("cache.preload.success").register(meterregistry);
        this.preloadfailurecounter = counter.builder("cache.preload.failure").register(meterregistry);
    }
    
    public void recordsuccess(string cachetype, long count) {
        preloadsuccesscounter.increment();
        gauge.builder("cache.preload.count")
            .tag("type", cachetype)
            .register(meterregistry, count, number::doublevalue);
    }
    
    public void recordfailure(string cachetype) {
        preloadfailurecounter.increment();
    }
}

总结

选择合适的预加载方案需要考虑以下因素:

  • @postconstruct: 适合简单的预加载逻辑
  • applicationrunner: 适合需要命令行参数的场景
  • applicationreadyevent: 最安全的预加载时机
  • 专门的服务: 适合复杂的预加载需求

无论选择哪种方案,都要注意异常处理、性能优化和监控告警,确保预加载过程不会影响应用的正常启动和运行。

以上就是springboot启动时将数据库数据预加载到redis缓存的几种实现方案的详细内容,更多关于springboot数据加载到redis缓存的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com