Cached map with data initialization and whole data is periodically updated in Java

I need a cached map in my java code. The map is loaded from the database and periodically reloaded from the database (for all data on the map). Since we can not import a new package now. Is there any existing function in google guava package or sample code?

It is better if the card is implemented as thread-safe. But still OK, if it is not so simple.

Kata loading is what I like, but I don’t have a data initialization method so that I can put the data on the map at the beginning. And he needs to get to the database so that each time he “receives” it comes after the card has expired.

Thanks!


Some sample code may help here:

public interface AToBMapper { public static final String DEFAULT_B_NAME = "DEFAULT"; public String getBForA(final String a); } public class AToBMapperImpl implements AToBMapper { private final SomeDAO dao; private Map<String, String> cachedMap; public AToBMapperImpl(final SomeDAO dao) { this.dao = dao; cachedMap = new HashMap<String, String>(); } public String getBForA(final String a) { // if the map is not initialized, initialize it with the data // if the map is expired, refresh all the data in the map // return the mapped B for A (if there is no mapping for A, return the "DEFAULT") } private Map<String, String> getTheData(final List<String> listOfB) { Map<String, String> newData = dao.getAToBMapping(listOfB); } } 
+4
source share
3 answers

"LoadingCache" is what I like, but it has no data initialization method for entering data onto the card to the beginning.

Of course, he has such a method - putAll(Map<K, V>) from the Cache interface, which LoadingCache expands. Method Copies all mappings from the specified map to the cache.

There is also a similar put(K, V) method that you can use for this purpose.

EDIT:

Based on your comments, I can say that you don’t want LoadingCache at all, but rather save the end of all entries yourself. Here is a simple example of what you could use (JDK and Guava classes only):

 public class AToBMapperImpl implements AToBMapper { public static final long EXPIRE_TIME_IN_SECONDS = TimeUnit.SECONDS.convert(1, TimeUnit.HOURS); // or whatever private final SomeDAO dao; private final ConcurrentMap<String, String> cache; private final Stopwatch stopwatch; public AToBMapperImpl(SomeDAO dao) { this.dao = dao; stopwatch = new Stopwatch(); cache = new MapMaker().concurrencyLevel(2).makeMap(); } @Override public synchronized String getBForA(final String a) { // if the map is not initialized, initialize it with the data if (!stopwatch.isRunning()) { cache.putAll(getNewCacheContents()); stopwatch.start(); } // if the map is expired, refresh all the data in the map if (stopwatch.elapsedTime(TimeUnit.SECONDS) >= EXPIRE_TIME_IN_SECONDS) { cache.clear(); cache.putAll(getNewCacheContents()); stopwatch.reset(); } // return the mapped String for A // (if there is no mapping for A, return the "DEFAULT") return cache.containsKey(a) ? cache.get(a) : new String(DEFAULT_B_NAME); } private Map<String, String> getNewCacheContents() { return getTheData(Arrays.asList("keys", "you", "want", "to", "load")); } private Map<String, String> getTheData(List<String> listOfB) { return dao.getAToBMapping(listOfB); } } 
+1
source

While @Xaerxess provided a workable solution, I feel that you could still achieve this just by using a LoadingCache instance with one key value. I understand your reasoning about the scenario, since I worked on projects where a single object load can be as expensive as the whole set.

 public class AToBMapperImpl implements AToBMapper { private final LoadingCache<Boolean, Map<String, String>> cachedMap; public AToBMapperImpl(final SomeDAO dao, final List<String> listOfB) { this.cachedMap = CacheBuilder.newBuilder() .expireAfterAccess(4, TimeUnit.HOURS) .build(new CacheLoader<Boolean, Map<String, String>>() { @Override public Map<String, String> load(Boolean k) throws Exception { return dao.getAToBMapping(listOfB); } }); } @Override public String getBForA(String a) { try { Map<String, String> map = cachedMap.get(Boolean.TRUE); return map.containsKey(a) ? map.get(a) : DEFAULT_B_NAME; } catch (ExecutionException ex) { // Do what you need to do on exception. throw new RuntimeException(ex.getMessage(), ex); } } } 

Thus, the cache is tied to a single record with a value that is the card on which you are located. You benefit from the expiration time. If you find that listOfB may change over time, you may need to write a different service method to get this, which may cause the cache loader to be called.

0
source

You do not need a data initialization method, it can be initialized at first access. Here is a sample code on how to do this

 cachedWhatever = cacheBuilder .maximumSize(5) .expireAfterAccess(30, TimeUnit.MINUTES) .removalListener(new RemovalListener<String, CachedWhatever>() { @Override public void onRemoval(RemovalNotification<String, CachedMetadata> notification) { logger.info("removed"); } }) .build(new CacheLoader<String, CachedWhatever>(){ @Override public CachedMetadata load(String databaseName) throws Exception { logger.info("created"); return new CachedWhatever(); } }); 

and after accessing the cache using

 cachedWhatever.get([key]); 

it will create a cache if it does not exist

and using cachedWhatever.invalidate([key]); clear key cache

-1
source

All Articles