启用缓存
CacheFactory接口
要启用缓存,首先需要实现CacheFactory/KCacheFactory接口,该接口定义如下
- Java
- Kotlin
package org.babyfish.jimmer.sql.cache;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public interface CacheFactory {
    @Nullable
    default Cache<?, ?> createObjectCache(@NotNull ImmutableType type) {
        return null;
    }
    @Nullable
    default Cache<?, ?> createAssociatedIdCache(@NotNull ImmutableProp prop) {
        return null;
    }
    @Nullable
    default Cache<?, List<?>> createAssociatedIdListCache(@NotNull ImmutableProp prop) {
        return null;
    }
    @Nullable
    default Cache<?, ?> createResolverCache(@NotNull ImmutableProp prop) {
        return null;
    }
}
package org.babyfish.jimmer.sql.kt.cache
import org.babyfish.jimmer.meta.ImmutableProp
import org.babyfish.jimmer.sql.cache.Cache
import org.babyfish.jimmer.sql.cache.CacheFactory
interface KCacheFactory : CacheFactory {
    override fun createObjectCache(type: ImmutableType): Cache<*, *>? =
        null
    override fun createAssociatedIdCache(prop: ImmutableProp): Cache<*, *>? =
        null
    override fun createAssociatedIdListCache(prop: ImmutableProp): Cache<*, List<*>>? =
        null
    override fun createResolverCache(prop: ImmutableProp): Cache<*, *>? =
        null
}
对于Kotlin而言,为了在覆盖createAssociatedIdListCache时让IDE生成更好的代码,请实现org.babyfish.jimmer.sql.kt.cache.KCacheFactory接口。
否则,IDE生成的override方法代码时,返回类型是Cache<*, MutableList<*>>,而非期望的Cache<*, List<*>>
Jimmer调用此接口初始化缓存系统,用户实现此接口回答问题
- 
createObjectCache:启用对象缓存 参数指定一个实体类型,如果想让它支持对象缓存,就创建缓存并返回;否则,返回null。 所谓 ,指把id映射为实体对象信息该实体对象是孤单的,没有关联属性。 基于外键的一对一/多对一关联除外,它们可以持有只有id属性的关联对象,因为关联对象的id其实就是当前表的外键字段。 
- 
createAssociatedIdCache和createAssociatedIdListCache:启用关联缓存 createAssociatedIdCache和createAssociatedIdListCache都用于启用关联缓存。二者唯一的区别是:- 
createAssociatedIdCache: 一对一或多对一关联
- 
createAssociatedIdListCache:一对多或多对多关联
 参数指定一个关联属性,如果想让它支持关联缓存,就创建缓存并返回;否则返回null。 所谓 ,指把id映射为关联id (或其集合)
- 
- 
createResolverCache:启用计算缓存 参数指定一个复杂计算属性,如果想让它支持计算属性,就创建缓存并返回;否则,返回null。 所谓 缓存,指把id映射为计算结果
多级缓存架构
CacheFactory接口的所有方法的返回类型都是org.babyfish.jimmer.sql.cache.Cache<K, V>。
用户无需直接实现Cache<K, V>,而需要使用org.babyfish.jimmer.sql.cache.chain.ChainCacheBuilder来构建多级别缓存。
从理论上讲,ChainCacheBuilder支持任意级缓存。然而,大部分项目中,两级缓存已经足够了,例如
- Java
- Kotlin
return new CacheFactory() {
    @Override
    @Nullable
    public Cache<?, ?> createObjectCache(@NotNull ImmutableType type) {
        return new ChainCacheBuilder<>()
            .add( ❶
                CaffeineValueBinder
                    .forObject(type)
                    .maximumSize(1024)
                    .duration(caffeineDuration)
                    .build()
            )
            .add( ❷
                RedisValueBinder
                    .forObject(type)
                    .redis(connectionFactory)
                    .objectMapper(objectMapper)
                    .duration(redisDuration)
                    .build()
            )
            .build();
    }
};
return object: KCacheFactory {
    override fun createObjectCache(type: ImmutableType): Cache<*, *>? =
        ChainCacheBuilder<Any, Any>()
            .add( ❶
                CaffeineValueBinder
                    .forObject(type)
                    .maximumSize(1024)
                    .duration(Duration.ofHours(1))
                    .build()
            )
            .add( ❷
                RedisValueBinder
                    .forObject(type)
                    .redis(connectionFactory)
                    .objectMapper(objectMapper)
                    .duration(Duration.ofHours(24))
                    .build()
            )
            .build()
}
如果我们所见,ChainCacheBuilder采用链式编程风格,多次调用add方法就可以构建多级缓存。
ChainCacheBuilder.add方法的定义如下
public class ChainCacheBuilder<K, V> {
    
    public ChainCacheBuilder<K, V> add(LoadingBinder<K, V> binder) { ❶
        ...省略代码...
        return this;
    }
    public ChainCacheBuilder<K, V> add(LoadingBinder.Parameterized<K, V> binder) { ❷
        ...省略代码...
        return this;
    }
    public ChainCacheBuilder<K, V> add(SimpleBinder<K, V> binder) { ❸
        ...省略代码...
        return this;
    }
    ...省略其他代码...
}
- 
❶ org.babyfish.jimmer.sql.cache.chain.LoadingBinder是一个接口,任何首次访问某个键时会自动加载值的缓存技术都可以通过该接口适配。几乎进程内的JVM缓存,都具备自动加载能力。比如上文代码中所用的Caffeine或Guava Cache。 
- 
❷ 处代码仅被多视角缓存使用,读者可以先行忽略 
- 
❸ org.babyfish.jimmer.sql.cache.chain.SimpleBinder是一个接口,任何不具备自动加载值行为的缓存技术都可以通过该接口适配。几乎所有远程缓存,都不具备自动加载能力。比如上文代码中所用的Redis。 
任何缓存技术都可以被适配成抽象接口LoadingBinder或SimpleBinder,所以,在Jimmer的多级缓存架构中,任何一级都不会对缓存的技术选型做出任何假设或限制。
如果采用Jimmer的SpringBoot Starter,则可以使用三个缓存技术适配类,如同上文代码中那样
| Jimmer内置的适配类 | 实现接口 | 支持多视角缓存 | 
|---|---|---|
| org.babyfish.jimmer.spring.cache.CaffeineBinder | org.babyfish.jimmer.sql.cache.chain.LoadingBinder | 否 | 
| org.babyfish.jimmer.spring.cache.RedisValueBinder | org.babyfish.jimmer.sql.cache.chain.SimpleBinder | 否 | 
| org.babyfish.jimmer.spring.cache.RedisHashBinder | org.babyfish.jimmer.sql.cache.chain.SimpleBinder.Parameterized | 是 | 
多视角缓存会在后续文章中阐述,这里请读者先忽略之。
配置CacheFactory
现在,我们已经介绍了CacheFactory接口和多级缓存架构,但离启用缓存还差最后一步。
最后一步,为Jimmer把注册CacheFactory。
SpringBoot配置
如果使用SpringBoot Starter,让CacheFactory受到Spring的托管即可。
- Java
- Kotlin
@Bean
public CacheFactory cacheFactory() {
    return new CacheFactory() {
        ...省略代码...
    };
}
@Bean
fun cacheFactory(): KCacheFactory =
    object: KCacheFactory {
        ...省略代码...
    }
底层API配置
- Java
- Kotlin
JSqlClient sqlClient = JSqlClient
    .newBuilder()
    .setCacheFactory(
        new CacheFactory() {
                ...省略代码...
        }
    )
    ...省略其他配置...
    .build();
}
val sqlClient = newKSqlClient {
    setCacheFactory(
        object: KCacheFactory {
            ...省略代码...
        }
    )
    ...省略其他配置...
}
Redis缓存辅助API
前面我们提到了,如果采用Jimmer的SpringBoot Starter,则可以用现成的org.babyfish.jimmer.spring.cache.RedisValueBinder,无需自己去适配Redis。
RedisHashBinder和多视角缓存相关,本文不讨论。
要构建RedisValueBinder,需要一个RedisOptions<String, byte[]>。
Jimmer的SpringBoot Stater提供org.babyfish.jimmer.spring.cache.RedisCaches类,其静态方法RedisCaches.cacheRedisTemplate可快速构建这个RedisOptions<String, byte[]>对象。
辅助方法RedisCaches.cacheRedisTemplate的例子如下:
- Java
- Kotlin
@Bean
public CacheFactory cacheFactory(
    RedisConnectionFactory connectionFactory,
    ObjectMapper objectMapper
) {
    return new CacheFactory() {
        @Override
        @Nullable
        public Cache<?, ?> createObjectCache(@NotNull ImmutableType type) {
            return new ChainCacheBuilder<Object, Object>()
                .add(
                    RedisValueBinder
                        .forProp(prop)
                        .redis(connectionFactory)
                        .objectMapper(objectMapper)
                        .duration(Duration.ofHours(24))
                        .build()
                )
                .add(
                    CaffeineValueBinder
                        .forProp()
                        .maximumSize(1024)
                        .duration(RedisCaches.ofMinutes(24))
                        .bind()
                )
                .build();
        }
        @Override
        @Nullable
        public Cache<?, ?> createAssociatedIdCache(@NotNull ImmutableProp prop) {
            return createPropCache(
                prop, 
                Duration.ofMinutes(10),
                Duration.ofHours(10)
            );
        }
        @Override
        @Nullable
        public Cache<?, List<?>> createAssociatedIdListCache(@NotNull ImmutableProp prop) {
            return createPropCache(
                prop, 
                Duration.ofMinutes(5),
                Duration.ofHours(5)
            );
        }
        @Override
        @Nullable
        public Cache<?, ?> createResolverCache(@NotNull ImmutableProp prop) {
            return createPropCache(
                prop, 
                Duration.ofMinutes(5),
                Duration.ofHours(5)
            );
        }
        private Cache<?, ?> createPropCache(
            ImmutableProp prop, 
            Duration redisDuration,
            Duration caffeineDuration
        ) {
            return new ChainCacheBuilder<K, V>()
                .add(
                    RedisValueBinder
                        .forProp(prop)
                        .redis(connectionFactory)
                        .objectMapper(objectMapper)
                        .duration(redisDuration)
                        .build()
                )
                .add(
                    CaffeineValueBinder
                        .forProp()
                        .maximumSize(128)
                        .duration(caffeineDuration)
                        .bind()
                )
                .build();
        }
    };
}
@Bean
fun cacheFactory(
    connectionFactory: RedisConnectionFactory,
    objectMapper: ObjectMapper
): KCacheFactory {
    return object: KCacheFactory {
        override fun createObjectCache(type: ImmutableType): Cache<*, *>? =
            ChainCacheBuilder<Any, Any>()
                .add(
                    CaffeineValueBinder
                        .forObject(type)
                        .maximumSize(1024)
                        .duration(caffeineDuration)
                        .build()
                )
                .add(
                    RedisValueBinder
                        .forObject(type)
                        .redis(connectionFactory)
                        .objectMapper(objectMapper)
                        .duration(redisDuration)
                        .build()
                )
                .build()
        override fun createAssociatedIdCache(prop: ImmutableProp): Cache<*, *>? =
            createPropCache(
                prop, 
                Duration.ofMinutes(10),
                Duration.ofHours(10)
            )
        override fun createAssociatedIdListCache(prop: ImmutableProp): Cache<*, List<*>>? =
            createPropCache(
                prop, 
                Duration.ofMinutes(5),
                Duration.ofHours(5)
            )
        override fun createResolverCache(prop: ImmutableProp): Cache<*, *>? =
            createPropCache(
                prop, 
                Duration.ofMinutes(5),
                Duration.ofHours(5)
            )
        private fun createPropCache(
            type: ImmutableType, 
            duration: caffeineDuration,
            duration: redisDuration
        ): Cache<*, *> =
            ChainCacheBuilder<Any, Any>()
                .add(
                    CaffeineValueBinder
                        .forProp(prop)
                        .maximumSize(512)
                        .duration(caffeineDuration)
                        .build()
                )
                .add(
                    RedisValueBinder
                        .forProp(prop)
                        .redis(connectionFactory)
                        .objectMapper(objectMapper)
                        .duration(redisDuration)
                        .build()
                )
                .build()
    }
}