1

I've previously implemented caching in my application, to be used with three separate get methods. These get methods are getAllProfiles(), getProfilesByID(), and getProfileByFields(). Because of this, my code looks like this:

private LoadingCache<int[], List<Profile>> loadingCache = CacheBuilder.newBuilder()
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .maximumSize(100).build(
                    new CacheLoader<int[] ids, List<Profile>>() {
                        @Override
                        public List load(int[] ids) throws Exception {
                            return profileDAO.getProfilesById(ids);
                        }
                    }
            );

    private LoadingCache<Integer, List<Profile>> loadingCache2 = CacheBuilder.newBuilder()
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .maximumSize(100).build(
                    new CacheLoader<Integer, List<Profile>>() {

                        @Override
                        public List<Profile> load(Integer size) throws Exception {
                            return profileDAO.getAllProfiles(size);
                        }
                    }
            );

    private LoadingCache<Profile, List<Profile>> loadingCache3 = CacheBuilder.newBuilder()
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .maximumSize(100).build(
                    new CacheLoader<Profile, List<Profile>>() {
                        @Override
                        public List<Profile> load(Profile profile) throws Exception {
                            return profileDAO.getProfileByFields(profile);
                        }
                    }
            );

public ProfileManagerImpl(ProfileDAO profileDAO) {
        this.profileDAO = profileDAO;
    }


public List<Profile> getAllProfiles(Integer size) throws Exception {
    return loadingCache2.get(size);
}

public List<Profile> getProfilesById(int[] idArray) throws Exception {
        return loadingCache.get(idArray);
    }

public List<Profile> getProfileByFields(Profile profile) throws Exception {
        return loadingCache3.get(profile);
    }

To streamline my work, however, I need to make one cache that is created at start-up using getAllProfiles(), for the whole table. All three methods will then use this one cache to work with.

I think I can just reuse the code for loadingCache2 to create the cache in the first place:

private LoadingCache<Integer, List<Profile>> loadingCache2 = CacheBuilder.newBuilder()
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .maximumSize(100).build(
                    new CacheLoader<Integer, List<Profile>>() {

                        @Override
                        public List<Profile> load(Integer size) throws Exception {
                            return profileDAO.getAllProfiles(size);
                        }
                    }
            );

and pass in null for the size, so the SQL statement on the DAO will be 'SELECT * FROM Profiles'. The issue will come from the other methods; I have no idea how to point these methods to this cache given the differing input requirements. Has anyone done anything like this previously?

Edit:

As suggested by Louis Wasserman, I'm making a single Cache object that takes Object as a generic key. From there, the service should use the if statement to detect the input object type and use the oppropriate method to retrieve the contents of the cache, depending on the method used.

As of right now, though, it fails on getAllProfiles, with a null pointer exception, so I need to figure that out.

Based on the code below, does it look like I'm on the right track? I'm using Cache as opposed to LoadingCache for this object:

public Cache<Object, List<Profile>> cache =
            CacheBuilder.newBuilder()
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build(new CacheLoader<Object, List<Profile>>() {
                @Override
                public List<Profile> load(Object k) throws Exception {
                    if (k instanceof Integer) {
                        return profileDAO.getAllProfiles((Integer) k);
                    }
                    else if (k instanceof int[]) {
                        return profileDAO.getMultipleProfiles((int[]) k);
                    }
                    else if (k instanceof Profile)
                        return profileDAO.getProfileByFields((Profile) k);
                }
            });



public List<Profile> getAllProfiles(Integer size) throws Exception {
        return cache.getIfPresent(size);
    }
SVill
  • 331
  • 5
  • 22
  • 55
  • I'm having difficulty telling what you're asking for. Are you just asking how to populate a cache with a given set of data? – Louis Wasserman May 30 '18 at 19:18
  • That's part of it; I'm trying to set up a cache to populate at application startup using getAllProfiles(). I'm also asking if its possible to use one LoadingCache for all three get methods. I'm not sure if this is possible, given the differing inputs in each method (an integer for getAllProfiles, an array for getProfilesById, and a profile object for getProfilesByFields). – SVill May 30 '18 at 19:27
  • Well, you could always create a new object to hold all three of those as fields, cache that, and then write three methods that each get that object out of the cache and then extract the relevant field – Louis Wasserman May 30 '18 at 19:32
  • I would probably use an index cache (e.g. id=>value, key=>id). Then you have better reuse (no duplicate loads). Then its like a SQL table of primary key and secondary indexes. It is a little more tricky to implement, though. – Ben Manes May 30 '18 at 20:21

1 Answers1

-1

I think there's some misuse of the cache concept. The cache will load, only when it doesn't find the elements, so by creating a cache who's key is a list you are nullifying the cache concept (since a list is an object and unless you pass the same list you will never reuse the values thus always loading from the DB and not utilizing the cache). I think you need to use only one cache, use the id as key (as @Ben Manes suggested), and wrap it with helper methods (This code was not tested so try with care).

/** This is whatever class you intended to put the cache in anyways */
public class CacheWrapper {
    private LoadingCache<Integer, Profile> loadingCache;

    public CacheWrapper(/* whatever arguments the class needs*/) {
        loadingCache = CacheBuilder.newBuilder()
            .refreshAfterWrite(5, TimeUnit.MINUTES)
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .maximumSize(100).build(
                new CacheLoader<Integer, Profile>() {
                    @Override
                    public List<Profile> load(Integer id) throws Exception {
                        return profileDAO.getProfileById(id);
                    }
                }
            );

         // init the cache here
         for (Profile profile : profileDAO.getAllProfiles(null)) {
             loadingCache.put(profile.getId(), profile);
         }    

         /* other construction logic here */
    }

    public List<Profile> getAllProfiles() throws Exception {
        return new ArrayList(loadingCache.asMap().values());
    }

    public List<Profile> getProfilesById(int[] idArray) throws Exception {
        List<Profile> profiles = new ArrayList(idArray.length());

        for(int i = 0; i < idArray.length(); i++) {
            profiles.add(loadingCache.get(idArray[i]));
        }

        return profiles;
    }

    public List<Profile> getProfileByFields(Profile profile) throws Exception {
        List<Profile> profiles = new ArrayList(idArray.length());

        for (Profile cachedProfile : loadingCache.asMap().values()) {
            if (profile.hasEqualFields(cachedProfile)) {
                profiles.add(cachedProfile);
            }
        }

        return profiles;
    }
}

You could also optimize this further by building a second cache with Profile as key, and a list of matching profiles as value, and then you can replace getProfileByFields by a similiar method to what you did - having the cache load Profiles with similar fields from the database. Lastly, watch out as loading from the cache like this creates aliasing so you need to be careful with what you do to the data.

Hope this is clear and helps in anyway.

Guy Grin
  • 1,968
  • 2
  • 17
  • 38
  • Bulk lookups can be performed with the method getAll(Iterable extends K>). By default, getAll will issue a a separate call to CacheLoader.load for each key which is absent from the cache. When bulk retrieval is more efficient than many individual lookups, you can override CacheLoader.loadAll to exploit this. The performance of getAll(Iterable) will improve accordingly. – Igor Vuković Jan 15 '20 at 13:25