У меня есть требование, когда мы загружаем статические данные из базы данных для использования в приложении Java.Любой механизм кэширования должен иметь следующие функциональные возможности:
Ленивая загрузка всех данных не является вариантом, поскольку приложение будет развернуто в нескольких географических точках и должно будет взаимодействовать с одной базой данных. Ленивая загрузка данных сделает первый запрос определенного элемента слишком медленным, если приложение находится в другом регионе базы данных.
Я успешно использовал API MapMaker в Guava, но сейчас мы обновляем его до последней версии, и я не могу найти ту же функциональность в CacheBuilder API; Кажется, я не могу найти чистый способ загрузки всех данных при запуске.
Один из способов - загрузить все ключи из базы данных и загрузить их через кэш по отдельности. Это сработает, но приведет к N + 1 вызовам базы данных, что не совсем эффективное решение, которое я ищу.
public void loadData(){
List<String> keys = getAllKeys();
for(String s : keys)
cache.get(s);
}
Или другое решение - использовать реализацию ConcurrentHashMap и самостоятельно обрабатывать все потоки и отсутствующие записи? Мне не очень нравится это делать, поскольку API-интерфейсы MapMaker и CacheBuilder предоставляют блокировку потоков на основе ключей бесплатно, без необходимости проводить дополнительное тестирование. Я также почти уверен, что реализации MapMaker / CacheBuilder будут иметь некоторую эффективность, о которой я не знаю / у меня нет времени исследовать.
public Element get(String key){
Lock lock = getObjectLock(key);
lock.lock();
try{
Element ret = map.get(key)
if(ret == null){
ret = getElement(key); // database call
map.put(key, e);
}
return ret;
}finally {
lock.unlock();
}
}
Может ли кто-нибудь придумать лучшее решение для моих двух требований?
Запрос функции
Я не думаю, что предварительная загрузка кеша является необычным требованием, поэтому было бы неплохо, если бы CacheBuilder предоставил параметр конфигурации для предварительной загрузки кеша. Я думаю, что предоставление интерфейса (очень похожего на CacheLoader), который будет заполнять кеш при запуске, было бы идеальным решением, например:
CacheBuilder.newBuilder().populate(new CachePopulator<String, Element>(){
@Override
public Map<String, Element> populate() throws Exception {
return getAllElements();
}
}).build(new CacheLoader<String, Element>(){
@Override
public Element load(String key) throws Exception {
return getElement(key);
}
});
Эта реализация позволит предварительно заполнить кэш всеми соответствующими объектами Element, сохраняя при этом базовый CustomConcurrentHashMap, невидимый для внешнего мира.