60

我正在使用 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;
            }
        });

我的问题是当数据库中不存在数据时,我希望它返回null并且不进行任何缓存。但是 Guava 将null密钥保存在缓存中,并在我得到它时抛出异常:

com.google.common.cache.CacheLoader$InvalidCacheLoadException:CacheLoader 为密钥 shisoft 返回了 null。

我们如何避免缓存null值?

4

5 回答 5

83

如果找不到用户,只需抛出一些异常,并在使用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();
        }
    }
}

来自CacheLoader.load(K)Javadoc:

Returns:  
  the value associated with key; must not be null  
Throws:  
  Exception - if unable to load the result

回答您对缓存空值的疑问:

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

(来自LoadingCache.get(K)Javadoc)

如果抛出异常,则加载不会被视为完成,因此不会缓存新值。

编辑

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

 Returns:
   the value associated with key or null if not found

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

于 2012-11-14T13:42:44.257 回答
59

简单的解决方案:使用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());
            }
        });

编辑:我认为@Xaerxess 的回答更好。

于 2012-11-14T13:23:14.697 回答
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;
    }
}
于 2017-01-04T06:58:25.297 回答
0

当您想缓存一些 NULL 值时,您可以使用其他表现为 NULL 的人员。

在给出解决方案之前,我建议您不要将 LoadingCache 暴露在外部。相反,您应该使用方法来限制缓存的范围。

例如,您可以LoadingCache<ObjectId, List<User>>用作返回类型。然后,当您无法从数据库中检索值时,您可以返回空列表。您可以使用 -1 作为 Integer 或 Long NULL值,您可以使用 "" 作为 String NULL值,依此类推。在此之后,您应该提供一种方法来处理 NULL 值。

when(value equals NULL(-1|"")){
   return null;
}
于 2018-08-08T10:22:03.167 回答
0

我使用 getIfPresent

@Test
    public void cache() throws Exception {
        System.out.println("3-------" + totalCache.get("k2"));
        System.out.println("4-------" + totalCache.getIfPresent("k3"));
    }



    private LoadingCache<String, Date> totalCache = CacheBuilder
            .newBuilder()
            .maximumSize(500)
            .refreshAfterWrite(6, TimeUnit.HOURS)
            .build(new CacheLoader<String, Date>() {
                @Override
                @ParametersAreNonnullByDefault
                public Date load(String key) {
                    Map<String, Date> map = ImmutableMap.of("k1", new Date(), "k2", new Date());
                    return map.get(key);
                }
            });
于 2020-04-15T10:57:24.873 回答