web-dev-qa-db-fra.com

Comment charger @Cache au démarrage au printemps?

J'utilise spring-cache pour améliorer les requêtes de base de données, ce qui fonctionne bien comme suit:

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

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

Mais maintenant, je veux pré-remplir le book-cache complet au démarrage. Ce qui signifie que je veux appeler dao.findAll() et mettre toutes les valeurs dans le cache. Cette routine ne doit être programmée que périodiquement.

Mais comment puis-je explicitement remplir un cache en utilisant @Cacheable?

12
membersound

Utilisez simplement le cache comme avant, ajoutez un planificateur pour mettre à jour le cache, l’extrait de code est ci-dessous.

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

Assurez-vous que votre KeyGenerator retournera l'objet pour un paramètre (par défaut). Sinon, exposez la méthode putToCache dans BookService pour éviter d'utiliser directement cacheManager.

@CachePut(value = "books", key = "#book.isbn")
public Book putToCache(Book book) {
    return book;
}
12
Loki

J'ai rencontré le problème suivant lors de l'utilisation de @PostConstruct: - même si la méthode que je voulais mettre en cache était appelée, après l'avoir appelée depuis swagger, elle n'utilisait toujours pas la valeur mise en cache. Seulement après l'avoir appelé une fois de plus.

C'est parce que @PostConstruct est trop tôt pour mettre quelque chose en cache. (Au moins je pense que c'était le problème)

Maintenant, je l'utilise plus tard dans le processus de démarrage et cela fonctionne sans problème:

@Component
public class CacheInit implements ApplicationListener<ApplicationReadyEvent> {

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

}
4
Andrea Calin

Si vous avez toutes les instances de Book au démarrage, vous devez les stocker vous-même dans une mémoire tampon. Les placer dans le cache avec la méthode findAll () signifie que vous devez annoter findAll () avec @Cacheable. . Ensuite, vous devrez appeler findAll () au démarrage. Mais cela ne signifie pas que l'appel de getByIsbn (String isbn) accédera au cache, même si l'instance correspondante a été placée dans le cache lors de l'appel de findAll (). En fait, ce ne sera pas le cas, car ehcache mettra en cache la valeur renvoyée par la méthode sous forme de paire clé/valeur où la clé est calculée lorsque la méthode est appelée. Par conséquent, je ne vois pas comment vous pourriez faire correspondre la valeur de retour de findAll () et la valeur de retour de getByIsbn (String) car les types renvoyés ne sont pas les mêmes et, de plus, la clé ne correspondra jamais à toutes vos instances.

3
Olivier Meurice

Une option serait d’utiliser la variable CommandLineRunner pour remplir le cache au démarrage.

Dans la documentation officielle de CommandLineRunner, il s’agit d’un:

Interface utilisée pour indiquer qu'un bean doit exécuter lorsqu'il est contenu dans un SpringApplication .

Par conséquent, nous avons juste besoin de récupérer la liste de tous les livres disponibles, puis, en utilisant CacheManager, nous remplissons le cache de livres.

@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));
    }
}
2
mladzo

Comme Olivier l'a spécifié, depuis que les caches de printemps produisent une fonction sous la forme d'un objet unique, l'utilisation de la notation @ cacheable avec findAll ne vous permettra pas de charger tous les objets dans le cache de manière à pouvoir y accéder ultérieurement individuellement.

Une façon possible de charger tous les objets dans le cache est si la solution de cache utilisée vous fournit un moyen de charger tous les objets au démarrage. Les solutions E.g telles que NCache / TayzGrid fournit la fonction de chargeur de démarrage du cache, qui vous permet de charger le cache au démarrage avec des objets à l'aide d'un chargeur de démarrage de cache configurable.

1
Sameer Shah

Ajouter un autre bean BookCacheInitialzer

Autowire le bean BookService actuel dans BookCacheInitialzer

dans la méthode PostConstruct du pseudo-code BookCacheInitialzer

Puis peut faire quelque chose comme

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());
    }

}   

}

0
Dheerendra Kulkarni