16

我正在使用 spring-cache 来改进数据库查询,效果如下:

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

@Cacheable("books")
public Book getByIsbn(String isbn) {
    return dao.findByIsbn(isbn);
}

但现在我想在启动时预先填充完整的图书缓存。这意味着我想调用dao.findAll()并将所有值放入缓存中。该例程应仅定期安排。

但是在使用时如何显式填充缓存@Cacheable

4

7 回答 7

24

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

@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将返回一个参数的对象(默认情况下)。否则,暴露putToCache方法BookService以避免直接使用cacheManager。

@CachePut(value = "books", key = "#book.isbn")
public Book putToCache(Book book) {
    return book;
}
于 2015-02-04T00:10:10.280 回答
13

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

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

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

@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent> {

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

}
于 2018-11-12T12:17:05.463 回答
3

一个选项是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));
    }
}
于 2017-11-30T23:50:48.370 回答
2

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

于 2015-01-14T10:38:13.193 回答
1

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

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

于 2015-01-25T19:07:34.460 回答
1

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

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

    return null;
}
于 2020-06-25T18:49:48.360 回答
-1

添加另一个 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());
        }
    }
}
于 2015-01-14T10:42:31.280 回答