【问题标题】:How to load @Cache on startup in spring?如何在春季启动时加载@Cache?
【发布时间】: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 时如何显式填充缓存?

【问题讨论】:

标签: java spring spring-cache


【解决方案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) 来执行缓存填充任务。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-05
        • 2017-09-11
        • 2015-04-18
        • 2017-06-24
        • 2015-08-22
        相关资源
        最近更新 更多