首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《从零搭建Redis生产级应用:连接工厂+序列化器+Template保姆级教程》

《从零搭建Redis生产级应用:连接工厂+序列化器+Template保姆级教程》

作者头像
北极的代码
发布2026-04-22 17:32:46
发布2026-04-22 17:32:46
1020
举报
Redis的java客户端:

Redis 的Java 客户端很多,常用的几种: Jedis Lettuce Spring Data Redis Spring Data Redis是Spring的一部分,对Redis底层开发包进行了高度封装。 在Spring 项目中,可以使用spring Data Redis来简化操作。

1. 引入依赖(在idea中导入Spring Data Redis的maven坐标)

xml

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 如果需要连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
2. 配置文件(配置Redis数据源)

yaml

代码语言:javascript
复制
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 5000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms

注意层级关系,运用占用符可以方便代码的修改,解耦。

3. Redis配置类(编写RedisTemplate对象)---模板对象
代码语言:javascript
复制
java

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean(连接工厂对象已经在spring的容器中创建完成,所以我们只需要通过Bean注解)
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // JSON序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        
        // String序列化配置
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        
        // key采用String序列化
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        
        // value采用Jackson序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        // 配置缓存
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1))  // 缓存有效期1小时
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
            .disableCachingNullValues();
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
}

这里需要注意的是,我们在idea中编写配置类时,idea可能会给我们报错,找不到连接工厂对象,这是我们不用担心,也不用想着怎么不报错,先运行再说,结果发现能运行,这就是idea自己的问题了,

  • IDEA 的静态代码分析认为 redisTemplate 可能为 null
  • 但实际上 Spring 在运行时确实注入了对象

连接工厂 (ConnectionFactory) - 就像"快递分拨中心"

形象理解:

想象你要寄快递(操作 Redis),你需要: 没有连接工厂 = 你自己开车去每个收件人家门口

  • ❌ 每次都要重新找路
  • ❌ 每次都要重新停车
  • ❌ 效率极低

有连接工厂 = 顺丰/京东的分拨中心

  • ✅ 统一管理所有运输资源
  • ✅ 提前建好运输网络
  • ✅ 帮你处理所有复杂物流
代码对应:
代码语言:javascript
复制
java

// 连接工厂帮你管理所有Redis连接
redisTemplate.setConnectionFactory(redisConnectionFactory);

// 之后你只需要
redisTemplate.opsForValue().set("name", "张三");  // 工厂会自动分配连接

连接工厂的工作

  1. 创建连接:和Redis服务器建立网络连接
  2. 管理连接池:维护多个连接,用完回收
  3. 处理异常:连接断开后自动重连
  4. 配置管理:处理密码、超时等配置

📦 序列化器 (Serializer) - 就像"打包员"

形象理解:

你要往Redis存东西,Redis只认识字节,就像快递员只认识包裹没有序列化器 = 你直接把活猪交给快递员

  • ❌ 快递员懵了:这怎么运输?
  • ❌ 猪会跑、会叫、会拉屎
  • ❌ 无法标准化处理

有序列化器 = 专业的打包员

  • ✅ 把活猪变成标准猪肉盒
  • ✅ 统一规格,方便运输
  • ✅ 收货时能还原成猪
不同类型的序列化器:

1. StringRedisSerializer - 就像"标准纸箱"

代码语言:javascript
复制
java

// 存的时候: "张三" -> 字节数组 [45, 67, 88]
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.opsForValue().set("name", "张三");

// 取的时候: 字节数组 [45, 67, 88] -> "张三"
String name = (String) redisTemplate.opsForValue().get("name");

2. JdkSerializationRedisSerializer - 就像"定制木箱"

代码语言:javascript
复制
java

User user = new User("张三", 18);
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.opsForValue().set("user", user);
// user对象 -> 复杂的Java序列化格式

3. Jackson2JsonRedisSerializer - 就像"透明塑封"

代码语言:javascript
复制
java

User user = new User("张三", 18);
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(User.class));
redisTemplate.opsForValue().set("user", user);
// user对象 -> {"name":"张三","age":18}  JSON格式

🎯 实际场景对比

场景1:存用户登录信息
代码语言:javascript
复制
java

// 不好的方式:没有设置序列化器
redisTemplate.opsForValue().set("token:123", userObject);
// 结果:Redis里存的是乱码 \xAC\xED\x00\x05...

// 好的方式:用JSON序列化器
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(User.class));
redisTemplate.opsForValue().set("token:123", userObject);
// 结果:Redis里存的是 {"id":1,"name":"张三","age":18} 可读性好
场景2:做计数器
代码语言:javascript
复制
java

// 存数字:用StringRedisSerializer最合适
stringRedisTemplate.opsForValue().set("visits", "100");
stringRedisTemplate.opsForValue().increment("visits"); // 变成101
场景3:存复杂对象
代码语言:javascript
复制
java

// 对象需要序列化才能存
@RedisHash("people")  // 对象注解
public class Person {
    private String id;
    private String name;
    private List<String> hobbies;  // 复杂类型
}
// 序列化器会帮你把List也处理好

💡 关键原理解释

为什么必须设置连接工厂?
代码语言:javascript
复制
java

// 这行代码看似简单
redisTemplate.opsForValue().set("name", "张三");

// 背后连接工厂做了:
1. 从连接池获取一个Redis连接
2. 通过socket发送: SET name 张三
3. 等待Redis返回: +OK
4. 把连接放回连接池
5. 处理可能的异常(超时、断线等)
为什么需要序列化器?

不设置序列化器的后果

代码语言:javascript
复制
java

// 不设置key序列化器
redisTemplate.opsForValue().set("name", "张三");

// Redis里实际存的key是:
// \xAC\xED\x00\x05t\x00\x04name  
// 而不是你期望的 "name"

// 导致的问题:
redisTemplate.opsForValue().get("name"); // 查不到!
redisTemplate.delete("name"); // 删不掉!

🎮 最佳实践总结

代码语言:javascript
复制
java

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        
        // 1. 连接工厂(必设)- 让模板知道怎么连Redis
        template.setConnectionFactory(factory);
        
        // 2. key序列化器(推荐设)- 让key可读
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        
        // 3. value序列化器(按需设)- JSON格式方便跨语言
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        
        return template;
    }
    
    // 简单场景直接用 StringRedisTemplate
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }
}

一句话总结

  • 连接工厂 = 给你准备好通往Redis的高速公路和车队
  • 序列化器 = 把你Java世界的对象打包成Redis世界能识别的包裹

我们搭建好了开发环境,接下来就是看在java中如何通过SpringDataRedis操作Redis中的数据

我们在测试类中通过SpringDataRedis来操作redis的不同数据

1---操作String字符串类型的数据

测试类:

代码语言:javascript
复制
@Autowired
private StringRedisTemplate redisTemplate;

// 基础操作
public void stringOperations() {
    // SET/GET
    redisTemplate.opsForValue().set("key", "value");
    String value = redisTemplate.opsForValue().get("key");
    
    // SETEX (带过期时间)
    redisTemplate.opsForValue().set("key", "value", 10, TimeUnit.SECONDS);
    
    // SETNX (不存在才设置)
    Boolean success = redisTemplate.opsForValue().setIfAbsent("key", "value");
    
    // 递增递减
    Long newValue = redisTemplate.opsForValue().increment("counter", 1);
    
    // 批量操作
    Map<String, String> map = new HashMap<>();
    map.put("k1", "v1");
    map.put("k2", "v2");
    redisTemplate.opsForValue().multiSet(map);
    
    List<String> values = redisTemplate.opsForValue().multiGet(Arrays.asList("k1", "k2"));
}

唯一需要注意的是在运行测试之前我们要先启动Redis这样我们才能正确连接上。

2----操作哈希类型的数据
代码语言:javascript
复制
public void hashOperations() {
    HashOperations<String, String, Object> hashOps = redisTemplate.opsForHash();
    String key = "user:1001";
    
    // 设置单个字段
    hashOps.put(key, "name", "张三");
    hashOps.put(key, "age", "25");
    hashOps.put(key, "city", "北京");
    
    // 批量设置
    Map<String, Object> userMap = new HashMap<>();
    userMap.put("name", "李四");
    userMap.put("age", "30");
    userMap.put("city", "上海");
    hashOps.putAll(key, userMap);
    
    // 获取单个字段
    String name = (String) hashOps.get(key, "name");
    
    // 获取多个字段
    List<Object> values = hashOps.multiGet(key, Arrays.asList("name", "age"));
    
    // 获取所有字段和值
    Map<String, Object> allFields = hashOps.entries(key);
    
    // 获取所有字段名
    Set<String> fields = hashOps.keys(key);
    
    // 判断字段是否存在
    Boolean hasName = hashOps.hasKey(key, "name");
    
    // 递增某个字段
    Long newAge = hashOps.increment(key, "age", 1);
    
    // 删除字段
    Long deleteCount = hashOps.delete(key, "age", "city");
    
    // 获取 Hash 大小
    Long size = hashOps.size(key);
}
3----List 操作 (OpsForList) - 类似 LinkedList
代码语言:javascript
复制
java

public void listOperations() {
    ListOperations<String, String> listOps = redisTemplate.opsForList();
    String key = "messages";
    
    // 右推 (RPUSH) - 从右侧添加
    listOps.rightPush(key, "msg1");
    listOps.rightPush(key, "msg2");
    listOps.rightPushAll(key, "msg3", "msg4", "msg5");
    
    // 左推 (LPUSH) - 从左侧添加
    listOps.leftPush(key, "msg0");
    
    // 左弹 (LPOP) - 从左侧取出并移除
    String firstMsg = listOps.leftPop(key);
    
    // 右弹 (RPOP) - 从右侧取出并移除
    String lastMsg = listOps.rightPop(key);
    
    // 阻塞式弹出 (BLPOP) - 等待5秒
    String blockedMsg = listOps.leftPop(key, 5, TimeUnit.SECONDS);
    
    // 获取指定范围的元素 (LRANGE)
    List<String> range = listOps.range(key, 0, -1); // 全部
    
    // 获取指定索引的元素 (LINDEX)
    String element = listOps.index(key, 0);
    
    // 获取列表长度 (LLEN)
    Long length = listOps.size(key);
    
    // 修剪列表 (LTRIM) - 只保留指定范围
    listOps.trim(key, 0, 100);
    
    // 在指定值前后插入 (LINSERT)
    listOps.rightPush(key, "msg2", "inserted_after_msg2");
}
4---Set 操作 (OpsForSet) - 无序不重复集合
代码语言:javascript
复制
java

public void setOperations() {
    SetOperations<String, String> setOps = redisTemplate.opsForSet();
    String key1 = "user:1001:tags";
    String key2 = "user:1002:tags";
    
    // 添加元素 (SADD)
    setOps.add(key1, "java", "python", "javascript", "mysql");
    setOps.add(key2, "java", "python", "mongodb", "redis");
    
    // 获取所有成员 (SMEMBERS)
    Set<String> tags1 = setOps.members(key1);
    
    // 判断成员是否存在 (SISMEMBER)
    Boolean hasJava = setOps.isMember(key1, "java");
    
    // 获取集合大小 (SCARD)
    Long size = setOps.size(key1);
    
    // 移除元素 (SREM)
    setOps.remove(key1, "mysql");
    
    // 交集 (SINTER)
    Set<String> intersect = setOps.intersect(key1, key2);
    
    // 交集并存储到新集合
    setOps.intersectAndStore(key1, key2, "user:common:tags");
    
    // 并集 (SUNION)
    Set<String> union = setOps.union(key1, key2);
    
    // 差集 (SDIFF) - 在 key1 不在 key2 中的元素
    Set<String> diff = setOps.difference(key1, key2);
    
    // 随机弹出元素 (SPOP)
    String randomTag = setOps.pop(key1);
    
    // 随机获取元素不弹出 (SRANDMEMBER)
    String randomTag2 = setOps.randomMember(key1);
    List<String> randomTags = setOps.randomMembers(key1, 3); // 获取3个
}
5--- ZSet (Sorted Set) 操作 - 有序集合
代码语言:javascript
复制
java

public void zSetOperations() {
    ZSetOperations<String, String> zSetOps = redisTemplate.opsForZSet();
    String key = "game:rank";
    
    // 添加元素,带分数 (ZADD)
    zSetOps.add(key, "player1", 1000);
    zSetOps.add(key, "player2", 2000);
    zSetOps.add(key, "player3", 1500);
    
    // 批量添加
    Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
    tuples.add(new DefaultTypedTuple<>("player4", 1800.0));
    tuples.add(new DefaultTypedTuple<>("player5", 1200.0));
    zSetOps.add(key, tuples);
    
    // 获取分数 (ZSCORE)
    Double score = zSetOps.score(key, "player1");
    
    // 获取排名 (ZRANK) - 从低到高
    Long rank = zSetOps.rank(key, "player1"); // 0开始
    
    // 获取反向排名 (ZREVRANK) - 从高到低
    Long revRank = zSetOps.reverseRank(key, "player2");
    
    // 获取分数范围内的元素 (ZRANGEBYSCORE)
    Set<String> rangeByScore = zSetOps.rangeByScore(key, 1000, 2000);
    
    // 获取指定排名范围的元素 (ZRANGE)
    Set<String> top3 = zSetOps.range(key, 0, 2); // 前三名
    
    // 带分数的范围查询
    Set<ZSetOperations.TypedTuple<String>> rangeWithScores = 
        zSetOps.rangeWithScores(key, 0, -1);
    
    // 增加分数 (ZINCRBY)
    Double newScore = zSetOps.incrementScore(key, "player1", 500);
    
    // 统计分数范围内的元素数量 (ZCOUNT)
    Long count = zSetOps.count(key, 1500, 2000);
    
    // 获取集合大小 (ZCARD)
    Long size = zSetOps.zCard(key);
    
    // 移除元素 (ZREM)
    Long removeCount = zSetOps.remove(key, "player5");
    
    // 按排名范围移除 (ZREMRANGEBYRANK)
    zSetOps.removeRange(key, 0, 1); // 移除排名最低的两个
    
    // 按分数范围移除 (ZREMRANGEBYSCORE)
    zSetOps.removeRangeByScore(key, 500, 1000);
}

整个流程的形象比喻

想象你住在一个小区,小区里有快递柜(Redis服务器),你要存取快递:

1. Redis服务器 = 小区快递柜
代码语言:javascript
复制
java

// Redis服务器就是你本地启动的那个程序
redis-server.exe 启动后 -> 相当于快递柜通上电,开始工作
  • 快递柜就在那里放着(Redis服务在6379端口监听)
  • 任何人都可以来存取(多个应用可以连接)
2. 连接工厂 = 物业办的"取件授权"
代码语言:javascript
复制
java

RedisConnectionFactory factory = new LettuceConnectionFactory("localhost", 6379);

这就像你去物业办登记:

  • 物业给你一张门禁卡(连接)
  • 告诉你怎么走到快递柜(localhost:6379)
  • 如果取件人多,物业会准备多张门禁卡(连接池)
3. RedisTemplate = 你的"万能取件工具包"
代码语言:javascript
复制
java

RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factory);

这就像物业给你的一个多功能工具包

  • 里面有扫码枪(opsForValue)- 扫普通快递
  • 里面有指纹识别(opsForHash)- 取家庭共用快递
  • 里面有钥匙串(opsForList)- 取多个快递
  • 等等...
4. 序列化器 = 快递打包/拆包规则
代码语言:javascript
复制
java

template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(User.class));

这就像快递的打包标准

  • StringRedisSerializer = 普通纸箱(只能装文字)
  • Jackson2JsonRedisSerializer = 透明塑封(能看见里面是啥)
  • JdkSerializer = 黑箱子(看不见里面,但很安全)

🔄 完整流程演示

场景:你要存一个用户对象到Redis
代码语言:javascript
复制
java

// 1. 你创建了一个用户对象(你要寄的东西)
User user = new User("张三", 18, "北京");

// 2. 拿出工具包(RedisTemplate)
redisTemplate.opsForValue().set("user:1", user);

背后发生的事情:

代码语言:javascript
复制
graph TD
    A[你的Java代码] -->|1. 调用set方法| B[RedisTemplate]
    B -->|2. 找序列化器| C[Jackson2JsonRedisSerializer]
    C -->|3. 对象转JSON| D[{"name":"张三","age":18,"city":"北京"}]
    D -->|4. 找连接工厂| E[LettuceConnectionFactory]
    E -->|5. 从连接池拿门禁卡| F[Redis连接]
    F -->|6. 通过网络发送| G[Redis服务器]
    G -->|7. 存到内存| H[快递柜: user:1]
代码对应的比喻:
代码语言:javascript
复制
java

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // 去物业办登记,拿到门禁卡系统
        return new LettuceConnectionFactory("localhost", 6379);
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        
        // 把这个门禁卡系统装进工具包
        template.setConnectionFactory(factory);
        
        // 设置key的打包方式:用普通纸箱(字符串格式)
        template.setKeySerializer(new StringRedisSerializer());
        
        // 设置value的打包方式:用透明塑封(JSON格式)
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        
        return template;  // 给你一个完整的工具包
    }
}

🎯 实际使用场景

场景1:存储用户信息
代码语言:javascript
复制
java

// 用你的工具包存数据
User user = new User("李四", 25);
redisTemplate.opsForValue().set("user:2", user);

// 相当于:
// 1. 序列化器把李四变成JSON: {"name":"李四","age":25}
// 2. 连接工厂用门禁卡打开快递柜
// 3. 把JSON放进编号为"user:2"的格子
场景2:读取用户信息
代码语言:javascript
复制
java

User user = (User) redisTemplate.opsForValue().get("user:2");

// 相当于:
// 1. 连接工厂找到对应的快递柜
// 2. 打开"user:2"的格子取出JSON
// 3. 序列化器把JSON变回User对象

💡 关键理解

为什么需要这三层?

没有这些层,你要自己写:

代码语言:javascript
复制
java

// 噩梦般的原始写法
Socket socket = new Socket("localhost", 6379);
OutputStream out = socket.getOutputStream();
out.write("*3\r\n$3\r\nSET\r\n$5\r\nuser:1\r\n$15\r\n{\"name\":\"张三\"}\r\n".getBytes());
// 还要处理返回结果、异常、连接关闭...

有了这些层,你只需要:

代码语言:javascript
复制
java

redisTemplate.opsForValue().set("user:1", user);
生活类比总结:

技术概念

生活比喻

职责

Redis服务器

小区快递柜

存放数据的地方

连接工厂

物业门禁系统

管理如何进入小区、找到快递柜

连接池

多张门禁卡

多人同时取件也不怕

RedisTemplate

多功能工具包

封装各种取件工具

opsForXXX

不同工具(扫码枪等)

针对不同快递的操作方法

序列化器

打包/拆包规则

Java对象 ↔ 网络传输数据

结语:如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的动力!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引入依赖(在idea中导入Spring Data Redis的maven坐标)
  • 2. 配置文件(配置Redis数据源)
  • 3. Redis配置类(编写RedisTemplate对象)---模板对象
  • 连接工厂 (ConnectionFactory) - 就像"快递分拨中心"
    • 形象理解:
    • 代码对应:
  • 📦 序列化器 (Serializer) - 就像"打包员"
    • 形象理解:
    • 不同类型的序列化器:
  • 🎯 实际场景对比
    • 场景1:存用户登录信息
    • 场景2:做计数器
    • 场景3:存复杂对象
  • 💡 关键原理解释
    • 为什么必须设置连接工厂?
    • 为什么需要序列化器?
  • 🎮 最佳实践总结
  • 我们搭建好了开发环境,接下来就是看在java中如何通过SpringDataRedis操作Redis中的数据
  • 我们在测试类中通过SpringDataRedis来操作redis的不同数据
    • 1---操作String字符串类型的数据
    • 2----操作哈希类型的数据
    • 3----List 操作 (OpsForList) - 类似 LinkedList
    • 4---Set 操作 (OpsForSet) - 无序不重复集合
    • 5--- ZSet (Sorted Set) 操作 - 有序集合
  • 整个流程的形象比喻
    • 1. Redis服务器 = 小区快递柜
    • 2. 连接工厂 = 物业办的"取件授权"
    • 3. RedisTemplate = 你的"万能取件工具包"
    • 4. 序列化器 = 快递打包/拆包规则
  • 🔄 完整流程演示
    • 场景:你要存一个用户对象到Redis
    • 代码对应的比喻:
  • 🎯 实际使用场景
    • 场景1:存储用户信息
    • 场景2:读取用户信息
  • 💡 关键理解
    • 为什么需要这三层?
    • 生活类比总结:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档