Redis API
I. Basic Usage
The Oinone platform extends based on Spring Data Redis, having completed the registration of redisTemplate and stringRedisTemplate in advance, and built-in the Oinone Cache Key construction logic. Developers can directly use dependency injection, and are prohibited from redefining Redis-related beans to ensure the consistency of cache services.
package pro.shushi.pamirs.demo.core.service;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Component
public class Test {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
}II. Best Practices
(Ⅰ) AbstractRedisCacheService
Function Overview: AbstractRedisCacheService<T> encapsulates Redis cache operations, simplifying data read-write logic while standardizing usage. Even without using this encapsulated class, you can directly call RedisTemplate or stringRedisTemplate with unaffected functionality.
Usage Recommendations:
- Sharding Management: When caching multiple groups of data, it is recommended to manage cache shards through a constant pool to avoid key conflicts and improve storage efficiency.
- Data Streamlining: Control the size of cached data, storing only necessary information to reduce memory occupation.
- Clear Positioning: Caching should serve as a performance optimization tool, avoiding substitution of persistent storage.
Usage Example:
@Component
public class DemoCacheService extends AbstractRedisCacheService<DemoModel> {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    protected StringRedisTemplate fetchRedisTemplate() {
        return stringRedisTemplate;
    }
    @Override
    protected Class<DemoModel> fetchTargetClass() {
        return DemoModel.class;
    }
    @Override
    protected String prepareKey(String key) {
        return StringHelper.concat(CharacterConstants.SEPARATOR_COLON,
                DemoCacheConstant.DTO_PREFIX, key);
    }
    @Override
    protected DemoModel minimizeStorage(DemoModel data) {
        return new DemoModel()
               .setName(data.getName())
               .setId(data.getId());
    }
}(Ⅱ) Jedis and Lettuce Switching Guide
1. Core Differences:
- Jedis: Synchronous blocking client, instances are not thread-safe, requiring reliance on connection pools for reuse, suitable for single-threaded or connection pool management scenarios.
- Lettuce: Asynchronous reactive client based on Netty, thread-safe, supporting direct operation of single connections by multiple threads while being compatible with connection pools, suitable for high-concurrency asynchronous scenarios.
2. Switching Steps:
Dependency Adjustment: Remove the Jedis dependency in pom.xml and introduce Lettuce and connection pool dependencies:
<lettuce.version>5.3.6.RELEASE</lettuce.version>
<commons-pool2.version>2.8.1</commons-pool2.version>
<dependencies>
    <dependency>
        <groupId>pro.shushi.pamirs.framework</groupId>
        <artifactId>pamirs-connectors-data-api</artifactId>
        <exclusions>
            <exclusion>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>${lettuce.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>${commons-pool2.version}</version>
    </dependency>
</dependencies>Configuration Modification: Add Lettuce connection pool configuration in application.yml:
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    prefix: pamirs
    timeout: 2000
    # Optional password configuration
    password: xxxxx
    lettuce:
      pool:
        enable: true
        max-idle: 16
        min-idle: 1
        max-active: 16
        max-wait: 2000III. Source Code Analysis of Core Configuration
(Ⅰ) RedisSimpleConfig
This configuration class takes effect in the single Redis mode, responsible for the initialization of redisTemplate and stringRedisTemplate:
- Serialization Customization: Add tenant prefixes to keys through PamirsStringRedisSerializer, and use Jackson2JsonRedisSerializer for JSON serialization of values, ensuring data compatibility in multi-tenant scenarios.
- Template Injection: Inject RedisConnectionFactoryand custom serializers to complete Redis template configuration, simplifying the usage process for developers.
@Validated
@Component
@Conditional(RedisSimpleModeCondition.class)
public class RedisSimpleConfig {
    @Autowired
    private PamirsFrameworkSystemConfiguration systemConfiguration;
    @Value("${spring.redis.prefix:}")
    private String prefix;
    @Bean(name = "pamirsStringRedisSerializer")
    public PamirsStringRedisSerializer pamirsStringRedisSerializer() {
        // Process tenant prefix logic
    }
    @Bean(name = "redisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory factory,
                                       PamirsStringRedisSerializer serializer) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(factory);
        setKeySerializer(template, serializer);
        setValueSerializer(template, Object.class);
        return template;
    }
    @Bean(name = "stringRedisTemplate")
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory,
                                                   PamirsStringRedisSerializer serializer) {
        // Similarly configure StringRedisTemplate
    }
    // Serializer setting methods
    private <K, V> void setKeySerializer(RedisTemplate<K, V> template, PamirsStringRedisSerializer serializer) {
        template.setKeySerializer(serializer);
    }
    private <K, V> void setValueSerializer(RedisTemplate<K, V> template, Class<V> valueClass) {
        Jackson2JsonRedisSerializer<V> jsonSerializer = new Jackson2JsonRedisSerializer<>(valueClass);
        ObjectMapper mapper = new ObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jsonSerializer.setObjectMapper(mapper);
        template.setValueSerializer(jsonSerializer);
    }
}(Ⅱ) PamirsStringRedisSerializer
A custom string serializer that implements automatic addition and parsing of tenant prefixes:
- Serialization: Add tenant prefixes and global identifiers to keys to ensure key uniqueness in multi-tenant environments.
- Deserialization: Automatically remove prefixes and identifiers to restore original key values, ensuring data read-write consistency.
public class PamirsStringRedisSerializer extends StringRedisSerializer {
    private final String prefix;
    private final int prefixLength;
    public PamirsStringRedisSerializer(String prefix) {
        super(StandardCharsets.UTF_8);
        this.prefix = fixPrefix(prefix).trim();
        this.prefixLength = this.prefix.length();
    }
    @Override
    public byte[] serialize(String string) {
        return super.serialize(serializeString(string));
    }
    public String serializeString(String string) {
        if (string == null) return null;
        if (prefixLength > 0) string = prefix + string;
        return KeyPrefixManager.generate(CharacterConstants.SEPARATOR_COLON, CharacterConstants.SEPARATOR_COLON) + string;
    }
    @Override
    public String deserialize(byte[] bytes) {
        String key = super.deserialize(bytes);
        if (key == null) return null;
        String prefixKey = KeyPrefixManager.generate(CharacterConstants.SEPARATOR_COLON, CharacterConstants.SEPARATOR_COLON);
        String fullPrefix = prefixKey + prefix;
        int len = prefixKey.length() + prefixLength;
        if (len == 0) return key;
        if (key.startsWith(fullPrefix)) {
            return key.substring(len);
        }
        return key;
    }
    private String fixPrefix(String prefix) {
        if (StringUtils.isBlank(prefix)) return prefix;
        return prefix.endsWith(CharacterConstants.SEPARATOR_COLON)? prefix : prefix + CharacterConstants.SEPARATOR_COLON;
    }
}