如何在春季启动时加载@Cache?
Posted
技术标签:
【中文标题】如何在春季启动时加载@Cache?【英文标题】:How to load @Cache on startup in spring? 【发布时间】:2015-03-12 12:06:38 【问题描述】:我正在使用 spring-cache 来改进数据库查询,效果如下:
@Bean
public CacheManager cacheManager()
return new ConcurrentMapCacheManager("books");
@Cacheable("books")
public Book getByIsbn(String isbn)
return dao.findByIsbn(isbn);
但现在我想在启动时预填充完整的图书缓存。这意味着我想调用dao.findAll()
并将所有值放入缓存中。此例程应仅定期安排。
但是在使用@Cacheable
时如何显式填充缓存?
【问题讨论】:
***.com/questions/53030289/… 【参考方案1】:如果您要求在启动时将所有 Book 实例都保存在内存中,那么您应该自己将它们存储在某个缓冲区中。 使用 findAll() 方法将它们放入缓存意味着您必须使用 @Cacheable 注释 findAll()。然后你必须在启动时调用 findAll() 。 但这并不意味着调用 getByIsbn(String isbn) 会访问缓存,即使调用 findAll() 时已将相应的实例放入缓存。 实际上不会,因为 ehcache 会将方法返回值缓存为键/值对,其中键是在调用方法时计算的。因此,我看不出如何匹配 findAll() 的返回值和 getByIsbn(String) 的返回值,因为返回的类型不一样,而且 key 不会永远匹配所有实例。
【讨论】:
【参考方案2】:添加另一个 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());
【讨论】:
是的,这将是一个选项,但对性能非常不利,因为我在每个条目的启动过程中都会多次访问数据库。而且,它在某种程度上是多余的,因为我已经拥有了findAll()
的所有书籍。所以我正在寻找一种方法将这些书籍放入缓存中,而无需再次往返 db。
然后可以做类似的事情
相应地编辑了解决方案。
事实上,我并没有真正理解将 book
保存到 service.books
的目的,除了不了解 searchBook
方法的实用性...请在您的示例中更加明确.【参考方案3】:
正如 Olivier 所指定的,由于 spring 将函数的输出缓存为单个对象,因此将 @cacheable 表示法与 findAll 一起使用将不允许您加载缓存中的所有对象,以便以后可以单独访问它们。
您可以在缓存中加载所有对象的一种可能方法是,如果正在使用的缓存解决方案为您提供了一种在启动时加载所有对象的方法。例如,NCache / TayzGrid 等解决方案提供了缓存启动加载器功能,允许您在启动时使用可配置的缓存启动加载器加载对象。
【讨论】:
【参考方案4】:像以前一样使用缓存,添加一个更新缓存的调度器,代码sn -p在下面。
@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;
【讨论】:
BookService
是什么,我在 OP 的问题中没有看到任何提及。谁打电话给putToCache
?【参考方案5】:
一个选项是使用CommandLineRunner
在启动时填充缓存。
从 CommandLineRunner 官方文档来看,它是一个:
当 bean 包含在 SpringApplication 中时,该接口用于指示 bean 应该运行。
因此,我们只需要检索所有可用书籍的列表,然后使用 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));
【讨论】:
【参考方案6】:我在使用@PostConstruct 时遇到了以下问题: - 即使调用了我想要缓存的方法,在从 swagger 调用它之后,它仍然没有使用缓存的值。只有在再次调用它之后。
那是因为@PostConstruct 缓存某些东西还为时过早。 (至少我认为这是问题所在)
现在我在启动过程的后期使用它,它可以正常工作:
@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent>
@Override
public void onApplicationEvent(ApplicationReadyEvent event)
//call service method
【讨论】:
使用了类似的方法。 @EventListener 和 ContextRefreshedEvent 为我们解决了问题。【参考方案7】:避免@PostConstruct
缺少参数绑定的一种方法是下面的代码,其优点是一旦参数初始化就会执行:
@Bean
public Void preload(MyDAO dao)
dao.findAll();
return null;
【讨论】:
您是否愿意改进您的示例,因为因此,我不明白调用sched.list()
(它做了什么......?)以及在没有调用dao.findAll()
实际使用它的结果...?!
当然!此响应是对 Loki 接受响应的微小改进,因为您不需要类实用程序类 (CacheScheduler) 来执行缓存填充任务。以上是关于如何在春季启动时加载@Cache?的主要内容,如果未能解决你的问题,请参考以下文章