如果值为null,如何避免缓存?

Shi*_*oft 49 caching guava

我正在使用guava来缓存热数据.当缓存中不存在数据时,我必须从这样的数据库中获取它.

public final static LoadingCache<ObjectId, User> UID2UCache = CacheBuilder.newBuilder()
        //.maximumSize(2000)
        .weakKeys()
        .weakValues()
        .expireAfterAccess(10, TimeUnit.MINUTES)
        .build(
        new CacheLoader<ObjectId, User>() {
            @Override
            public User load(ObjectId k) throws Exception {
                User u = DataLoader.datastore.find(User.class).field("_id").equal(k).get();
                return u;
            }
        });
Run Code Online (Sandbox Code Playgroud)

我的问题是当数据在数据库中也不存在时,我宁愿返回null而不做任何缓存.但是guava只是在缓存中使用密钥保存null并在我得到它时抛出异常

com.google.common.cache.CacheLoader $ InvalidCacheLoadException:CacheLoader为key shisoft返回null.

那么,如何避免缓存空值?

Xae*_*ess 73

如果找不到用户,只需抛出一些异常,并在使用get(key)方法时在客户端代码中捕获它.

new CacheLoader<ObjectId, User>() {
    @Override
    public User load(ObjectId k) throws Exception {
        User u = DataLoader.datastore.find(User.class).field("_id").equal(k).get();
        if (u != null) {
             return u;
        } else {
             throw new UserNotFoundException();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

来自CacheLoader.load(K)Javadoc:

Returns:  
  the value associated with key; must not be null  
Throws:  
  Exception - if unable to load the result
Run Code Online (Sandbox Code Playgroud)

回答你对缓存空值的疑虑:

返回与此缓存中的键关联的值,必要时首先加载该值.在加载完成之前,不会修改与此高速缓存关联的可观察状态.

(来自LoadingCache.get(K)Javadoc)

如果抛出异常,则不认为load是完整的,因此不会缓存新值.

编辑:

请注意,在Caffeine中,这是一种Guava缓存2.0并"使用Google Guava启发的API提供内存缓存",您可以nullload方法返回:

 Returns:
   the value associated with key or null if not found
Run Code Online (Sandbox Code Playgroud)

如果您可以考虑迁移,那么当找不到用户时,您的数据加载器可以自由返回.

  • @Arash你可以捕获`ExecutionException`,在这种情况下它的原因是`UserNotFoundException`(当然你应该在`.getCause()之后执行`instanceof`检查以确保它不是另一个检查过的异常).或者你可以使用`getUnckecked`(如果你不喜欢检查异常)或`getIfPresent`如果`get`不是你需要的. (2认同)
  • 这违反了有效Java 69:仅在特殊情况下使用例外。 (2认同)

卢声远*_* Lu 51

简单的解决方案:使用com.google.common.base.Optional<User>而不是User作为值.

public final static LoadingCache<ObjectId, Optional<User>> UID2UCache = CacheBuilder.newBuilder()
        ...
        .build(
        new CacheLoader<ObjectId, Optional<User>>() {
            @Override
            public Optional<User> load(ObjectId k) throws Exception {
                return Optional.fromNullable(DataLoader.datastore.find(User.class).field("_id").equal(k).get());
            }
        });
Run Code Online (Sandbox Code Playgroud)

编辑:我认为@Xaerxess的答案更好.

  • 当使用`cache.get(key)`时,如果返回`Optional.absent`,你可以`cache.invalidate(key)`来确保`Optional.absent`只存在于`得到电话.然后噗..它已经消失了. (8认同)
  • 存储Optional.absent()对于您希望一直避免重新加载缺失值的情况非常有用,而是与其他缓存值具有相同的到期时间范围,这与Optional相当适用! (6认同)
  • 技术上它会缓存"null"值(即`Optional.absent()`对象),因此它不会像OP那样避免缓存空值,但成本并不高. (4认同)

Ram*_*ich 5

面临同样的问题,导致源中缺少值是正常工作流程的一部分。没有找到比自己使用getIfPresent,getput方法编写一些代码更好的方法了。请参阅下面的方法,其中localCache<Object, Object>

private <K, V> V getFromLocalCache(K key, Supplier<V> fallback) {
    @SuppressWarnings("unchecked")
    V s = (V) local.getIfPresent(key);
    if (s != null) {
        return s;
    } else {
        V value = fallback.get();
        if (value != null) {
            local.put(key, value);
        }
        return value;
    }
}
Run Code Online (Sandbox Code Playgroud)