spring 如何在启动时加载@Cache?

How to load @Cache on startup in spring?

我正在使用 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(String isbn) 会访问缓存,即使调用 findAll() 时对应的实例已经放入缓存。 实际上它不会,因为 ehcache 会将方法 return 值缓存为 key/value 对,其中在调用方法时计算键。因此,我看不出如何匹配 findAll() 的 return 值和 getByIsbn(String) 的 return 值,因为 returned 类型不相同,而且密钥不会'永远不会匹配您的所有实例。

添加另一个 bean BookCacheInitialzer

自动装配 BookCacheInitialzer 中的当前 bean BookService

在 BookCacheInitialzer 的 PostConstruct 方法中 伪代码

然后可以做类似的事情

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());
        }
    }
}

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

您可以将所有对象加载到缓存中的一种可能方法是,如果正在使用的缓存解决方案为您提供了一种在启动时加载所有对象的方法。例如 NCache / TayzGrid 等解决方案提供缓存启动加载器功能,允许您使用可配置的缓存启动加载器在启动时使用对象加载缓存。

像以前一样使用缓存,添加一个调度程序来更新缓存,代码片段如下。

@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 将 return 作为一个参数的对象(默认情况下)。或者,在BookService中暴露putToCache方法,避免直接使用cacheManager。

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

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

来自官方 CommandLineRunner 文档,它是:

Interface used to indicate that a bean should run when it is contained within a 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));
    }
}

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

那是因为@PostConstruct 缓存某些东西还为时过早。 (至少我认为这是问题所在)

现在我在启动过程的后期使用它并且它没有问题:

@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent> {

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

}

一种避免@PostConstruct缺少参数绑定的方法是下面的代码,其优点是一旦参数被初始化就会执行:

@Bean
public Void preload(MyDAO dao) {
    dao.findAll();

    return null;
}