计算缓存
所谓计算缓存,指把当前对象id映射为用户定义的复杂计算属性的计算值。
计算属性回顾
在复杂计算属性一文中,我们详细地讲解了复杂计算属性。
在本文中,我们将为复杂计算属性中定义的计算属性BookStore.avgPrice添加缓存支持。
为简化文档,本文只讨论BookStore.avgPrice,不讨论另外一个关联型计算属性BookStore.newestBooks,读者可阅读和运行如下官方例子
- jimmer-examples/java/jimmer-sql
 - jimmer-examples/java/jimmer-sql-graphql
 - jimmer-examples/kotlin/jimmer-sql-kt
 - jimmer-examples/kotlin/jimmer-sql-graphql-kt
 
启用计算缓存
- Java
 - Kotlin
 
@Bean
public CacheFactory cacheFactory(
    RedisConnectionFactory connectionFactory,
    ObjectMapper objectMapper
) {
    return new CacheFactory() {
        @Override
        public Cache<?, ?> createObjectCache(@NotNull ImmutableType type) {
            ...省略代码...
        }
        @Override
        public Cache<?, ?> createAssociatedIdCache(@NotNull ImmutableProp prop) {
            ...省略代码...
        }
        @Override
        public Cache<?, ?> createAssociatedIdCache(@NotNull ImmutableProp prop) {
            ...省略代码...
        }
        @Override
        public Cache<?, ?> createResolverCache(ImmutableProp prop) {
            return createPropCache(
                prop, 
                Duration.ofHours(1), 
                Duration.ofHours(24)
            );
        }
        private <K, V> Cache<K, V> createPropCache(
            ImmutableProp prop, 
            Duration caffeineDuration,
            Duration redisDuration
        ) {
            return new ChainCacheBuilder<>()
                .add(
                    CaffeineValueBinder
                        .forProp(prop)
                        .maximumSize(512)
                        .duration(caffeineDuration)
                        .build()
                )
                .add(
                    RedisValueBinder
                        .forProp(prop)
                        .redis(connectionFactory)
                        .objectMapper(objectMapper)
                        .duration(redisDuration)
                        .build()
                )
                .build();
        }
    };
}
@Bean
fun cacheFactory(
    connectionFactory: RedisConnectionFactory,
    objectMapper: ObjectMapper
): KCacheFactory {
    return object: KCacheFactory {
        override fun createObjectCache(type: ImmutableType): Cache<*, *>? =
            ...省略代码...
        override fun createAssociatedIdCache(prop: ImmutableProp): Cache<*, *>? =
            ...省略代码...
        override fun createAssociatedIdListCache(prop: ImmutableProp): Cache<*, List<*>>? =
            ...省略代码...
        override fun createResolverCache(prop: ImmutableProp): Cache<*, *> =
            return createPropCache(
                prop, 
                Duration.ofHours(1),
                Duration.ofHours(24)
            )
        private fun <K, V> createPropCache(prop: ImmutableProp, duration: Duration): Cache<K, V> =
            ChainCacheBuilder<Any, Any>()
                .add(
                    CaffeineValueBinder
                        .forProp(prop)
                        .maximumSize(512)
                        .duration(caffeineDuration)
                        .build()
                )
                .add(
                    RedisValueBinder
                        .forProp(prop)
                        .redis(connectionFactory)
                        .objectMapper(objectMapper)
                        .duration(redisDuration)
                        .build()
                )
                .build()
    }
}
使用
- Java
 - Kotlin
 
BookStoreTable table = Tables.BOOK_STORE_TABLE;
List<BookStore> stores = sqlClient
    .createQuery(table)
    .select(
        table.fetch(
            Fetchers.BOOK_STORE_FETCHER
                .allScalarFields()
                .avgPrice()
        )
    )
    .execute();
System.out.println(stores);
val stores = sqlClient
    .createQuery(BookStore::class) {
        select(
            table.fetchBy {
                allScalarFields()
                avgPrice()
            }
        )
    }
    .execute()
println(stores)
- 
第一步:查询聚合根
首先查询聚合根对象,执行如下SQL
select
tb_1_.ID,
tb_1_.NAME,
tb_1_.WEBSITE
from BOOK_STORE tb_1_这里实现了代码中的查询,得到了一些BookStore对象。这种被用户直接查询而得的对象叫做聚合根对象
警告Jimmer不会对用户查询返回的聚合对 象进行缓存,因为这种查询结果的一致性无法保证。 即便需要以牺牲一致性为代价对其缓存,也是用户的业务问题,不是应该由框架抽象并统一的行为。
 - 
第二步:通过计算缓存把当前对象id转化为计算值
上面的代码会得到一系列聚合根对象,如果数据库采用官方例子的数据,会得到两个聚合根对象。
代码中的对象抓取器包含了计算属性
BookStore.avgPrice这2条BOOK_STORE的主键
ID为1和2。Jimmer先从Redis查找数据,被查找的键为
BookStore.avgPrice-1和BookStore.avgPrice-2。假设无法在Redis中找到这些键所对应的数据
127.0.0.1:6379> keys BookStore.avgPrice-*
(empty array)所以,执行如下SQL,完成计算属性的计算
select
tb_1_.ID,
avg(tb_2_.PRICE)
from BOOK_STORE tb_1_
left join BOOK tb_2_
on tb_1_.ID = tb_2_.STORE_ID
where
tb_1_.ID in (
? /* 1 */, ? /* 2 */
)
group by
tb_1_.IDJimmer会把查询结果放入Redis,因此,我们可以从redis中查看这些数据
127.0.0.1:6379> keys BookStore.avgPrice-*
1) "BookStore.avgPrice-2"
2) "BookStore.avgPrice-1"
127.0.0.1:6379> get BookStore.avgPrice-1
"58.5"
127.0.0.1:6379> get BookStore.avgPrice-2
"80.333333"
127.0.0.1:6379>这样,两个
BookStore对象可以通过其计算属性BookStore.avgPrice得到各自书籍的平均价格。毫无疑问,在Redis中的数据因超时而被清除之前,再次执行上述Java/Kotlin代码,将直接从Redis中返回计算数据,第二条SQL不会被生成。
 
最终,Jimmer把3个步骤的结果拼接在一起,作为最终返回给用户的数据
[
    {
        "id":2,
        "name":"MANNING",
        "website":null,
        "avgPrice":58.5
    },
    {
        "id":1,
        "name":"O'REILLY",
        "website":null,
        "avgPrice":80.333333
    }
]