如何在春季启动时加载@Cache?

时间:2015-01-14 10:25:20

标签: java spring spring-cache

我使用spring-cache来改进数据库查询,工作正常如下:

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

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

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

但是,在使用@Cacheable

时,如何显式填充缓存?

7 个答案:

答案 0 :(得分:12)

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

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

答案 1 :(得分:4)

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

那是因为@PostConstruct缓存某些内容为时尚早。 (至少我认为这是问题所在)

现在,我在启动过程中使用的时间更晚,并且可以正常使用:

@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent> {

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

}

答案 2 :(得分:3)

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

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

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

    return null;
}

答案 6 :(得分:0)

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

}   

}