Java 如何在春季启动时加载@Cache?

Java 如何在春季启动时加载@Cache?,java,spring,spring-cache,Java,Spring,Spring Cache,我使用spring cache改进数据库查询,其工作原理如下: @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("books"); } @Cacheable("books") public Book getByIsbn(String isbn) { return dao.findByIsbn(isbn); } 但是现在我想在启动时预填充完整的图书缓存。这意味着我要调

我使用spring cache改进数据库查询,其工作原理如下:

@Bean
public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("books");
}

@Cacheable("books")
public Book getByIsbn(String isbn) {
    return dao.findByIsbn(isbn);
}
但是现在我想在启动时预填充完整的图书缓存。这意味着我要调用
dao.findAll()
,并将所有值放入缓存。该例行程序不应仅定期安排


但是,当使用
@Cacheable
时,如何显式填充缓存呢?

如果在启动时将Book的所有实例都存储在内存中是您的要求,那么您应该自己将它们存储在某个缓冲区中。 使用findAll()方法将它们放入缓存意味着必须使用@Cacheable注释findAll()。然后,您必须在启动时调用findAll()。 但这并不意味着调用getByIsbn(字符串isbn)将访问缓存,即使在调用findAll()时已将相应实例放入缓存中。
实际上不会,因为ehcache会将方法返回值缓存为键/值对,其中在调用方法时计算键。因此,我不知道如何匹配findAll()的返回值和getByIsbn(String)的返回值,因为返回的类型不相同,而且key永远不会匹配所有实例。

添加另一个bean BookCacheInitializer

在BookCacheInitialzer中自动连接当前bean BookService

BookCacheInitializer的后构造方法中 伪码


然后我们可以做一些类似的事情

class BookService {
    @Cacheable("books")
    public Book getByIsbn(String isbn) {
        return dao.findByIsbn(isbn);
    }
    
    public List<Book> books;

    @Cacheable("books")
    public Book getByIsbnFromExistngBooks(String isbn) {
        return searchBook(isbn, books);
    }
}

class BookCacheInitialzer {

    @Autowired
    BookService  service;

    @PostConstruct
    public void initialize() {
        books = dao.findAll();
        service.books = books;
        for(Book book:books) {
            service.getByIsbnFromExistngBooks(book.getIsbn());
        }
    }
}
类图书服务{
@可缓存(“书籍”)
公共图书getByIsbn(字符串isbn){
返回dao.findByIsbn(isbn);
}
公开书目;
@可缓存(“书籍”)
公共图书从现有图书中获取isbn(字符串isbn){
返回搜索簿(isbn,图书);
}
}
类BookCacheInitializer{
@自动连线
图书服务;
@施工后
公共无效初始化(){
books=dao.findAll();
服务。书籍=书籍;
用于(书籍:书籍){
service.getByISBNfromExistingBooks(book.getIsbn());
}
}
}

正如Olivier所指定的,由于spring将函数的输出缓存为单个对象,因此使用@cacheable notation和findAll将不允许您加载缓存中的所有对象,以便以后可以单独访问它们


若正在使用的缓存解决方案为您提供了一种在启动时加载所有对象的方法,则可以加载缓存中的所有对象。例如,像/这样的解决方案提供了缓存启动加载器功能,允许您在启动时使用可配置的缓存启动加载器加载对象的缓存。

只需像以前一样使用缓存,添加计划程序以更新缓存,下面是代码段

@Service
public class CacheScheduler {
    @Autowired
    BookDao bookDao;
    @Autowired
    CacheManager cacheManager;

    @PostConstruct
    public void init() {
        update();
        scheduleUpdateAsync();
    }

    public void update() {
        for (Book book : bookDao.findAll()) {
            cacheManager.getCache("books").put(book.getIsbn(), book);
        }
    }
}
确保
KeyGenerator
将返回一个参数的对象(默认)。或者,在
BookService
中公开
putToCache
方法,以避免直接使用cacheManager

@CachePut(value = "books", key = "#book.isbn")
public Book putToCache(Book book) {
    return book;
}

一个选项是使用
CommandLineRunner
在启动时填充缓存

根据CommandLineRunner官方文档,它是:

接口,用于指示bean在包含在SpringApplication中时应运行

因此,我们只需要检索所有可用书籍的列表,然后使用
CacheManager
,填充书籍缓存

@Component
public class ApplicationRunner implements CommandLineRunner {
    @Autowired
    private BookDao dao;

    @Autowired
    private CacheManager cacheManager;

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("books");
    }

    @Override
    public void run(String... args) throws Exception {

        List<Book> results = dao.findAll();

        results.forEach(book -> 
            cacheManager.getCache("books").put(book.getId(), book));
    }
}
@组件
公共类ApplicationRunner实现CommandLineRunner{
@自动连线
私人书道;
@自动连线
专用缓存管理器缓存管理器;
@豆子
公共缓存管理器缓存管理器(){
返回新的ConcurrentMapCacheManager(“书籍”);
}
@凌驾
公共无效运行(字符串…参数)引发异常{
List results=dao.findAll();
结果。forEach(书籍->
cacheManager.getCache(“books”).put(book.getId(),book));
}
}

我在使用@PostConstruct时遇到以下问题: -即使调用了我想要缓存的方法,但在从swagger调用它之后,它仍然没有使用缓存的值。只是在再次打电话之后

这是因为@PostConstruct对于缓存某些内容来说太早了。(至少我认为这是问题所在)

现在,我在启动过程的后期使用它,它可以正常工作:

@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
       //call service method
    }

}
@组件
公共类CacheInit实现ApplicationListener{
@凌驾
ApplicationEvent上的公共无效(ApplicationReadyEvent事件){
//呼叫服务方法
}
}

避免
@PostConstruct
缺少参数绑定的一种方法是使用以下代码,其优点是参数初始化后即可执行:

@Bean
公共空间预加载(MyDAO){
dao.findAll();
返回null;
}

是的,这将是一个选项,但对性能非常不利,因为在启动过程中,我在每个条目上都会敲DB n次。而且,它在某种程度上是多余的,因为我已经把我所有的书都放在
findAll()
上了。因此,我正在寻找一种方法将这些书籍放入缓存,而无需再次往返数据库。然后可以做一些类似于相应地提交soln的事情。事实上,我并不真正理解将
书籍
保存到
服务中的目的。书籍
,除了不了解
搜索书籍
方法的实用性之外。。。请在你的例子中更加明确。什么是
BookService
,我在OP的问题中没有看到任何提及。谁调用
putToCache
?使用了类似的方法@使用ContextRefreshedEvent的EventListener为我们实现了这一点。您能改进一下您的示例吗,因为这样,我就不明白调用
sched.list()
(它的作用是…?)以及调用
dao.findAll()
而不实际使用其结果。。。?!当然此响应是对Loki已接受响应的一个小改进,因为您不需要类实用程序类(CacheScheduler)来执行缓存填充任务。