计算缓存
所谓计算缓存,指把当前对象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
}
]