跳到主要内容

基本概念

概念

截止目前为止,我们所介绍的缓存都是单视图缓存,所有客户端都会看到相同的缓存数据。

然而,很多时候,不同的客户端会看到不同的持久化数据,这往往是因为权限系统导致的。

既然不同的客户端会看到不同的持久化数据,我们自然也期望不同客户端能看到不同的缓存数据。

多视图缓存很好地解决这个问题,让不同的客户端看到不同的缓存,即缓存为不同的客户端提供了不同的视图。

警告

和之前讨论的缓存可以分为对象缓存关联缓存计算缓存不同, 多视图缓存并不是为已有的分类集合添加一种新的分类,而是另外一个维度的特性。

除了对象缓存外,关联缓存计算缓存都可以和多视图缓存相结合

单视图缓存多视图缓存
对象缓存对象缓存NA
关联缓存单视图关联缓存多视图关联缓存
计算缓存单视图计算缓存多视图计算缓存
信息

我们可以把关联缓存和计算缓存统称为属性缓存

因此,这个的表格也可被解释为属性缓存可以被多视图化。

场景

多视图缓存由用户自定义全局过滤器导致。

信息

只要某个实体被施加了用户自定义全局过滤器

  • 所有以它作为目标类型的关联缓存

  • 依赖这些关联缓存的计算缓存

都需要多视图化。

比如,如果为Book添加全局过滤器,那么如下这些属性:

  • Book作为目标类型的关联属性。比如,BookStore.booksAuthor.books

  • 依赖于上述关联属性的计算属性。比如,BookStore.avgPriceBookStore.newestBooks(文档没提及newestBook,可参见例子)

都会对过滤器敏感,即不同的过滤条件会导致不同的客户端看到不同的数据。

警告

这些对过滤器敏感的属性,要么不支持缓存,要么就支持多视角缓存。如果为其配置单视图缓存,将被视为无效操作,缓存配置会被忽略。

然而,无需担心,Jimmer会告诉开发人员为什么缓存未生效

SubKey

SubKey是多视图缓存的重要概念,也是后续文档所需的前提概念。

让我们先来看看单视图缓存的结构

KeyValue
Book-10{"id":10,"name":"GraphQL in Action",...}
Book.authors-1[1,2]
BookStore.avgPrice-280.333333

这里,我们列举了对象缓存关联缓存计算缓存。 虽然缓存类型不尽相同,但缓存结构都是统一的简单KV结构。

再让我们来看看多视图缓存的例子

KeySubKeyValue
BookStore.books-1{}[6,5,4,3,2,1,9,8,7]
{"tenant":"a"}[5,3,1,9,7]
{"tenant":"b"}[6,4,2,8]
{"module":"x"}[6,5,3,2,9,8]
{"module":"x","tenant":"a"}[5,3,9]
{"module":"x","tenant":"b"}[6,2,8]
{"module":"y"}[4,1,7]
{"module":"y","tenant":"a"}[1,7]
{"module":"y","tenant":"b"}[4]
BookStore.books-2{}[12,11,10]
{"tenant":"a"}[11]
{"tenant":"b"}[12,10]
{"module":"x"}[12,11]
{"module":"x","tenant":"a"}[11]
{"module":"x","tenant":"b"}[12]
{}[10]
{"tenant":"a"}[]
{"tenant":"b"}[10]

多视图缓存不再是简单的KV结构,而是嵌套的两级KV结构。

提示

对于Redis而言,这种结构就是Redis Hashes

此时表格中的SubKey就是Redis中的Hash Key

很明显,相比于单视图缓存而言,多视图缓存按照SubKey对数据进行了更细的分化,让不同的客户端看到不同的数据。

  • Key

    多视图缓存中的Key,和单视图缓存中的Key无异,表示特定的实体属性,由Jimmer自定决定

  • SubKey

    多视图缓存的关键特性,权限系统让不同的客户端具备不同的SubKey,最终从缓存中提取到不同的数据

    信息

    在Jimmer中,SubKey一定是java.util.SortedMap<String, Object>经过JSON序列化后的字符串。

    SortedMap必须采用默认的排序规则,不得自定义Comparator

    这点非常重要。比如,SubKey只可能是{"module":"x","tenant":"a"},而不可能与之不同但等价的{"tenant":"a", "module":"x"}。 这样,多视图缓存保证了其内部没有冗余的信息。

多视图缓存只针对属性缓存,即关联缓存和计算缓存。所以,关联属性和复杂计算属性必须为其多视图缓存指定SubKey

关联属性的SubKey

对于关联属性而言,缓存的多视图化必然是因其关联实体被用户自定义全局过滤器影响导致。 支持多视图缓存的全局过滤器仅仅实现Filter/KFilter接口是不够的,必须实现CacheableFilter/KCacheableFilter接口

CacheableFilter.java
package org.babyfish.jimmer.sql.filter;

public interface CacheableFilter<P extends Props> extends Filter<P> {

SortedMap<String, Object> getParameters();

...省略其他代码...
}

用户需要实现getParameters()方法,为SubKey贡献一部分信息。

信息

当关联实体受多个CacheableFilter/KCacheableFilter影响时,每个过滤器对象的getParameters()方法返回的数据合并在一起,作为SubKey。

复杂计算属性的SubKey

实现复杂计算属性需实现TransientResolver/KTransientResolver接口

TransientResolver.java
package org.babyfish.jimmer.sql;

import org.babyfish.jimmer.lang.Ref;
import java.util.SortedMap;

public interface TransientResolver<ID, V> {

default Ref<SortedMap<String, Object>> getParameterMapRef() {
return Ref.empty();
}

...省略其他代码...
}

该方法返回SortedMapRef包装器

  • 如果Ref本身为null,表示获取SubKey遇到困难,无法为计算属性应用多视图缓存

    信息

    这种情况下,Jimmer会告诉开发人员为什么缓存未生效

  • 否则,Ref内部的value就表示当前计算属性的SubKey

信息

Jimmer在维护缓存一致性时,会自动删除失效的缓存数据。

多视图的缓存项总是按照Key进行整体删除,不会按照Key + SubKey进行局部删除,在最大程度上保证多视图缓存的相对简单性。