使用独立键的番石榴缓存

Guava caching using independent keys

当处理来自数据库的用户对象时,通常有一个 ID 和一个用户名,通常按 ID 或用户名搜索用户。

如果我现在想要查找用户并且喜欢使用 Guava 缓存,我必须创建两个缓存。一种是按id缓存,一种是按用户名缓存。
但是都指向同一个对象。

是否可以只用一个LoadingCache?

我考虑过使用 User 对象本身作为键 LoadingCache<User, User> 并在 User 对象中实现 equals 和 hashcode。
在 equals 方法中,如果 id 用户名相等,则很容易说两个 User 对象相等。
但是如何生成适用于这种情况的良好 hashCode 方法?

有什么想法吗?

When working with user objects coming from a database one has usually an id and a username and it's common to search a user by id or by username.

备注:"search"对我来说意义不同,然后访问。也许 id 和 username 有不同的使用模式?也许用户名只在登录时需要?

避免在您的应用程序中使用两个不同的概念来引用/访问用户。决定一贯使用它。用户名是否唯一?能改吗?

两个缓存:您可以使用两个缓存并使用 name2user.put(user.getName(), user)id2user.put(user.getId(), user) 从加载程序填充 "sister cache"。这样,相同的用户对象在两个缓存中。尽管如此,我还是不喜欢它,因为清洁度和一致性问题。

第三个问题是数据重复,如果你决定换一个解决方案。缓存可以不通过引用存储值,而是将其复制到紧凑的字节数组中并将其存储在堆外(EHCache3、Hazelcast 等)。 (干净的)代码不应该依赖于这样一个事实,即缓存通过引用在堆中存储其数据,如果没有真正需要的话。

正如上面假设的那样,这两个访问路径在使用上不会相等。我的推荐:

  • 一个用于缓存用户数据的缓存:id -> User
  • 仅解析id的二级缓存:name -> id

不介意 name 情况下的额外缓存访问。当然,第二个缓存的加载程序我已经为此目的请求了一个用户,因此您可能希望用它预填充第一个缓存。

非常感谢您的回答,尤其是来自 Guava 开发人员的回答。建议的解决方案对我来说很管用,我很懒 ;)。

所以如果我永远不会少缓存,我决定这样解决。

final LoadingCache<Serializable, Optional<ITemplate>> templatesById = CacheBuilder.newBuilder()
        .maximumSize(MAX_CACHE_SIZE).expireAfterAccess(MAX_CACHE_LIFE_TIME, TimeUnit.MINUTES)
        .build(new CacheLoader<Serializable, Optional<ITemplate>>() {

            @Override
            public Optional<ITemplate> load(final Serializable id) {
                final ITemplate template = readInternal(id);
                final Optional<ITemplate> optional = Optional.ofNullable(template);
                if (template != null) {
                    templatesByKey.put(template.getKey(), optional);
                }
                return optional;
            }
        });

final LoadingCache<String, Optional<ITemplate>> templatesByKey = CacheBuilder.newBuilder()
        .maximumSize(MAX_CACHE_SIZE).expireAfterAccess(MAX_CACHE_LIFE_TIME, TimeUnit.MINUTES)
        .build(new CacheLoader<String, Optional<ITemplate>>() {

            @Override
            public Optional<ITemplate> load(final String key) {
                final ITemplate template = byKeyInternal(key);
                final Optional<ITemplate> optional = Optional.ofNullable(template);
                if (template != null) {
                    templatesById.put(template.getId(), optional);
                }
                return optional;
            }
        });

这意味着,我不会因为在两个缓存中有两个模板实例而浪费内存。所以我只是向两个缓存添加一个模板,如果它是从数据库接收到的。

它真的很好用而且速度很快。
唯一的问题是,何时告诉缓存刷新。 在我的场景中,仅在删除或更新时才需要它。

@Override
@Transactional
public void update(final ITemplate template) {
    super.update(new DBTemplate(template));
    templatesById.invalidate(template.getId());
    templatesByKey.invalidate(template.getKey());
}

就是这样。
对此有何评论?