繁体   English   中英

如何在春季启动时加载@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时显式填充缓存?

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

@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;
}

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

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

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

@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent> {

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

}

一个选项是使用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));
    }
}

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

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

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

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

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

    return null;
}

添加另一个 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());
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM