spring-data-redis 操作

Spring Data Redis详解(超详细版) Spring Data Redis是一个功能强大且易于使用的Redis客户端库。它封装了Redis的底层操作,为开发者提供了更加简洁、易用的API,使得在Spring应用中集成和使用Redis变得更加方便。通过合理的配置和使用,开发者可以轻松地在Spring应用中实现高效的数据存储和访问。随着Redis的不断发展和完善,Spring Data Redis也将不断更新和升级,以支持更多的Redis特性和功能。 阅读详情

spring-data-Redis为spring-data模块中对redis的支持部分,简称为“SDR”,提供了基于jedis客户端API的高度封装以及与spring容器的整合,事实上jedis客户端已经足够简单和轻量级,而spring-data-redis反而具有“过度设计”的嫌疑。

一. jedis客户端在编程实施方面存在如下不足:

    1) connection管理缺乏自动化,connection-pool的设计缺少必要的容器支持。

    2) 数据操作需要关注“序列化”/“反序列化”,因为jedis的客户端API接受的数据类型为string和byte,对结构化数据(json,xml,pojo等)操作需要额外的支持。

    3) 事务操作纯粹为硬编码

    4) pub/sub功能,缺乏必要的设计模式支持,对于开发者而言需要关注的太多。

    不过jedis与spring整合,也是非常的简单,参见“jedis连接池实例”.

 

二. spring-data-redis针对jedis提供了如下功能:

    1. 连接池自动管理,提供了一个高度封装的“RedisTemplate”类

    2. 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口

  • ValueOperations:简单K-V操作
  • SetOperations:set类型数据操作
  • ZSetOperations:zset类型数据操作
  • HashOperations:针对map类型的数据操作
  • ListOperations:针对list类型的数据操作

    3. 提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations:

  • BoundValueOperations
  • BoundSetOperations
  • BoundListOperations
  • BoundSetOperations
  • BoundHashOperations

  

    4. 将事务操作封装,有容器控制。

    5. 针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer)

  • JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。
  • StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
  • JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】
  • OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】

    针对“序列化和发序列化”中JdkSerializationRedisSerializer和StringRedisSerializer是最基础的策略,原则上,我们可以将数据存储为任何格式以便应用程序存取和解析(其中应用包括app,Hadoop等其他工具),不过在设计时仍然不推荐直接使用“JacksonJsonRedisSerializer”和“OxmSerializer”,因为无论是json还是xml,他们本身仍然是String。

    如果你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是JdkSerializationRedisSerializer。

    如果你的数据格式必须为json或者xml,那么在编程级别,在redisTemplate配置中仍然使用StringRedisSerializer,在存储之前或者读取之后,使用“SerializationUtils”工具转换转换成json或者xml,请参见下文实例。

 

    6. 基于设计模式,和JMS开发思路,将pub/sub的API设计进行了封装,使开发更加便捷。

    7.spring-data-redis中,并没有对sharding提供良好的封装,如果你的架构是基于sharding,那么你需要自己去实现,这也是sdr和jedis相比,唯一缺少的特性。


三. 有关BoundKeyOperations中API的用法

  1. /** 
  2.  * 需要指定hashtable的名字 
  3.  *  
  4.  * @param tableName 
  5.  */  
  6. public void boundHashOps(String tableName) {  
  7.     System.out.println(”==================Hash==============”);  
  8.     BoundHashOperations<String, String, String> ops = stringRedisTemplate  
  9.             .boundHashOps(tableName);  
  10.     stringRedisTemplate.delete(”student”);  
  11.     stringRedisTemplate.delete(”student:1”);  
  12.     ops.put(”cs01”, “123”);// 存入数据 ops.putAll(maps); 存入多条数据  
  13.     String key1 = ops.getKey();// tableName的名字  
  14.     System.out.println(”key1:” + key1);  
  15.     String key11 = ops.get(”cs01”);  
  16.     System.out.println(”key11:” + key11);// 获取key的值  
  17.   
  18.     ops.putIfAbsent(”cs02”, “456”);  
  19.     String key2 = ops.getKey();  
  20.     System.out.println(”ops.getKey()-key2:” + key2);  
  21.     String key21 = ops.get(”cs02”);  
  22.     System.out.println(”ops.get(cs02)-key21:” + key21);  
  23.   
  24.     Map<String, String> maps = ops.entries();// 获取所有的key-value值  
  25.     for (String key : maps.keySet()) {  
  26.         System.out.println(”map-key:” + key + “map-value:” + maps.get(key));  
  27.     }  
  28.     // ops.persist();//删除过期(如果有的话)的数据。  
  29.     System.out.println(”ops.getExpire():” + ops.getExpire());// -1  
  30.     System.out.println(”ops.expireAt(new Date()):”  
  31.             + ops.expireAt(new Date()));// true 设置生存过期时间  
  32.     System.out.println(”ops.getType():” + ops.getType());// Hash  
  33.     System.out.println(”ops.hasKey(cs01):” + ops.hasKey(“cs01”));// true  
  34.     System.out.println(”ops.hasKey(cs02):” + ops.hasKey(“cs02”));// true  
  35.     System.out.println(”ops.size():” + ops.size());// 2  
  36.   
  37.     Set<String> keys = ops.keys();// 获取所有的key  
  38.     for (String string : keys) {  
  39.         System.out.println(”ops.keys():” + string);  
  40.     }  
  41.   
  42.     System.out.println(”ops.values():” + ops.values());// 获取所有的value  
  43.     System.out.println(”ops.size():” + ops.size());// 2 获取数量  
  44.   
  45.     ops.delete(”cs01”);// 删除key为cs01的数据  
  46. }  
  47.   
  48. /** 
  49.  * 未指定hashtable的名字 
  50.  *  
  51.  * @param tableName 
  52.  */  
  53. public void opsForHash(String tableName) {  
  54.     System.out.println(”==================Hash==============”);  
  55.     HashOperations<String, Object, Object> ops = stringRedisTemplate  
  56.             .opsForHash();  
  57.     stringRedisTemplate.delete(”student”);  
  58.     stringRedisTemplate.delete(”student:1”);  
  59.     ops.put(tableName, ”cs01”, “123”);// 存入数据 ops.putAll(maps); 存入多条数据  
  60.     Object key11 = ops.get(tableName, ”cs01”);  
  61.     System.out.println(”key11:” + key11);// 获取key的值  
  62.   
  63.     ops.putIfAbsent(tableName, ”cs02”, “456”);  
  64.     Object key21 = ops.get(tableName, ”cs02”);  
  65.     System.out.println(”ops.get(cs02)-key21:” + key21);  
  66.   
  67.     Map<Object, Object> maps = ops.entries(tableName);// 获取所有的key-value值  
  68.     for (Object key : maps.keySet()) {  
  69.         System.out.println(”map-key:” + key + “map-value:” + maps.get(key));  
  70.     }  
  71.     // ops.persist();//删除过期(如果有的话)的数据。  
  72.     System.out.println(”ops.hasKey(cs01):”   
  73.                       + ops.hasKey(tableName, ”cs01”));// true  
  74.     System.out.println(”ops.hasKey(cs02):”   
  75.                       + ops.hasKey(tableName, ”cs02”));// true  
  76.     System.out.println(”ops.size():” + ops.size(tableName));// 2  
  77.   
  78.     Set<Object> keys = ops.keys(tableName);// 获取所有的key  
  79.     for (Object string : keys) {  
  80.         System.out.println(”ops.keys():” + string);  
  81.     }  
  82.   
  83.     System.out.println(”ops.values():”   
  84.                       + ops.values(tableName));// 获取所有的value  
  85.     System.out.println(”ops.size():”   
  86.                       + ops.size(tableName));// 2 获取数量  
  87.   
  88.     ops.delete(”cs01”);// 删除key为cs01的数据  
  89. }  
  90.   
  91. /** 
  92.  * List 里面有重复数据 
  93.  *  
  94.  * @param tableName 
  95.  */  
  96. public void boundListOps(String tableName) {  
  97.     System.out.println(”==================List==============”);  
  98.     BoundListOperations<String, String> ops = stringRedisTemplate  
  99.             .boundListOps(tableName);  
  100.     ops.leftPush(”cs01”);// left push 左侧入栈,先进后出,和右侧相比,左侧先出  
  101.     ops.leftPushIfPresent(”cs011”);// 不知道和上面的有什么区别  
  102.     ops.leftPush(”cs01”, “cs0111”);// 在cs01的左侧入栈  
  103.     ops.leftPushAll(”cs01111”, “cs011111”);  
  104.   
  105.     List<String> values = ops.range(0, -1);  
  106.     for (String string : values) {  
  107.         System.out.println(”letf push:” + string);  
  108.     }  
  109.   
  110.     ops.rightPush(”cs02”);// right push 右侧入栈 先进先出 ,和左侧相比,左侧先出  
  111.     ops.rightPushIfPresent(”cs022”);  
  112.     ops.rightPush(”cs02”, “cs0222”);// 在cs02的右侧入栈  
  113.     ops.rightPushAll(”cs02222”, “cs022222”);  
  114.   
  115.     ops.set(0, “cs04”);// 把第一个数据替换成cs04  
  116.     // ops.trim(0, 3);//从第一个数据到第4个数据删除  
  117.   
  118.     List<String> values1 = ops.range(0, -1);// 查出所有数据  
  119.     for (String string : values1) {  
  120.         System.out.println(”right push:” + string);  
  121.     }  
  122.   
  123.     List<String> values2 = ops.range(1, 2);// 查出从第二个到第三个  
  124.     for (String string : values2) {  
  125.         System.out.println(”right push1:” + string);  
  126.     }  
  127.     System.out.println(”ops.index(1):” + ops.index(0));// 获得第一个数据  
  128.     System.out.println(”ops.remove(0, cs01):”   
  129.                        + ops.remove(0, “cs01”));// 1,删除“cs01”  
  130.     System.out.println(”ops.leftPop():” + ops.leftPop());// 左侧出栈  
  131.     System.out.println(”ops.rightPop():” + ops.rightPop());// 右侧出栈  
  132.     System.out.println(”ops.remove(0, cs01)1:”   
  133.                        + ops.remove(0, “cs01”));// 0 ,如果“cs01”不存在返回0  
  134.   
  135.     // ops.persist();//删除过期(如果有的话)的数据。  
  136. }  
  137.   
  138. public void boundSetOps() {  
  139.     System.out.println(”==================Set==============”);  
  140.     String tableName2 = ”caoshuai03”;  
  141.     BoundSetOperations<String, String> ops = stringRedisTemplate  
  142.             .boundSetOps(tableName2);  
  143.   
  144.     String[] values = { ”cs03”, “cs04” };  
  145.     System.out.println(”ops.add(values):”   
  146.                       + ops.add(values));// 添加多条数据到set里,返回添加的数量  
  147.   
  148.     Set<String> sets1 = ops.members();// 获取set里的所有数据,每次显示的顺序可能不一样  
  149.     for (String string : sets1) {  
  150.         System.out.println(”ops.members()1:” + string);  
  151.     }  
  152.   
  153.     // 获取随机的数  
  154.     System.out.println(”ops.randomMember():” + ops.randomMember());  
  155.     // 获取一个随机数  
  156.     System.out.println(”ops.randomMembers(1):” + ops.randomMembers(1));  
  157.     // 获取两个随机数,值可能一样  
  158.     System.out.println(”ops.randomMembers(2):” + ops.randomMembers(2));  
  159.   
  160.     System.out.println(”ops.distinctRandomMembers(1):”  
  161.             + ops.distinctRandomMembers(1));// 获取一个随机数  
  162.     System.out.println(”ops.distinctRandomMembers(2):”  
  163.             + ops.distinctRandomMembers(2));// 获取两个不一样的随机数  
  164.   
  165.     System.out.println(ops.isMember(”cs04”));// 是否含有cs04,有的话返回true  
  166.     System.out.println(ops.isMember(”cs01”));// 没有返回false  
  167.   
  168.     System.out.println(ops.size());// set里数据量  
  169.   
  170.     System.out.println(ops.getKey());// 获取set的名字  
  171.   
  172.     System.out.println(ops.getType());// 获取类型  
  173.   
  174.     Set<String> set7 = ops.diff(”caoshuai02”);// 获取和另一set里不一样的数据,差集  
  175.     for (String string : set7) {  
  176.         System.out.println(”ops.diff(caoshuai02):” + string);  
  177.     }  
  178.   
  179.     // 获取和另一set里一样的数据,交集  
  180.     Set<String> set8 = ops.intersect(”caoshuai02”);  
  181.     for (String string : set8) {  
  182.         System.out.println(”ops.intersect(caoshuai02):” + string);  
  183.     }  
  184.   
  185.     // 获取另一set所有的数据,和自己合并起来,去掉重复的数据,并集  
  186.     Set<String> set6 = ops.union(”caoshuai02”);  
  187.     for (String string : set6) {  
  188.         System.out.println(”ops.union(caoshuai02):” + string);  
  189.     }  
  190.   
  191.     // 获取和其它set里一样的数据,交集  
  192.     List<String> keys = new ArrayList<String>();  
  193.     keys.add(”caoshuai02”);  
  194.     Set<String> set = ops.intersect(keys);  
  195.     for (String string : set) {  
  196.         System.out.println(”ops.intersect(keys):” + string);  
  197.     }  
  198.   
  199.     // 获取和其它set里不一样的数据,差集  
  200.     List<String> keys1 = new ArrayList<String>();  
  201.     keys1.add(”caoshuai02”);  
  202.     Set<String> set3 = ops.diff(keys);  
  203.     for (String string : set3) {  
  204.         System.out.println(”ops.diff(keys)3:” + string);  
  205.     }  
  206.   
  207.     // 获取其它set所有的数据,和自己合并起来,去掉重复的数据,并集  
  208.     List<String> keys2 = new ArrayList<String>();  
  209.     keys2.add(”caoshuai02”);  
  210.     Set<String> set4 = ops.union(keys2);  
  211.     for (String string : set4) {  
  212.         System.out.println(”ops.union(keys2):” + string);  
  213.     }  
  214.   
  215.     // 获取和另一set里不一样的数据,差集,存入到另一set里  
  216.     ops.diffAndStore(”caoshuai02”, “caoshuai04”);  
  217.     // ops.diffAndStore(keys, destKey);  
  218.       
  219.     // 获取和另一set里一样的数据,交集,存入到另一set里  
  220.     ops.intersectAndStore(”caoshuai02”, “caoshuai05”);  
  221.     // ops.intersectAndStore(keys, destKey);  
  222.       
  223.     // 获取另一set里的所有数据,并集,存入到另一set里  
  224.     ops.unionAndStore(”caoshuai02”, “caoshuai06”);  
  225.     // ops.unionAndStore(keys, destKey);  
  226.   
  227.     // 把指定的数据移到另一set里,移动成功返回true  
  228.     System.out.println(ops.move(”caoshuai07”, “cs03”));  
  229.     // 把指定的数据移到另一set里,移动失败,返回false,注:当前set里没有cs01  
  230.     System.out.println(ops.move(”caoshuai07”, “cs01”));  
  231.   
  232.     System.out.println(ops.pop());// pop出一个数据,第一个数据被pop出  
  233.   
  234.     System.out.println(ops.remove(”cs03”));// 删除多个数据,返回删除的个数  
  235.     System.out.println(ops.remove(”cs01”));// 删除数据,返回删除的个数  
  236. }  
  237.   
  238. public void boundValueOps() {  
  239.     System.out.println(”==================String==============”);  
  240.     String tableName2 = ”LiMing01”;  
  241.     BoundValueOperations<String, String> ops = stringRedisTemplate  
  242.             .boundValueOps(tableName2);  
  243.   
  244.     System.out.println(ops.append(”stu01”));// 添加数据,返回添加数据字符的个数  
  245.       
  246.     // 先获取原先存在的数据,再添加数据覆盖原先的  
  247.     System.out.println(ops.getAndSet(”stu02”));  
  248.       
  249.     System.out.println(ops.get());// 获取添加的数据  
  250.       
  251.     System.out.println(ops.get(0, 1));//获取从第一个开始到第二个结束的字符  
  252.     System.out.println(ops.get(0, 5));//获取从第一个开始到第六个结束的数据,注实际只有两个数据  
  253.       
  254.     System.out.println(ops.size());//获取数据字符的个数  
  255.     ops.set(”stu03”);//添加数据  
  256.     System.out.println(”ops.set(stu03):” + ops.get());// 获取添加的数据  
  257.     ops.set(”stu04”, 0);//在位置0处添加数据  
  258.     System.out.println(”ops.set(stu04, 0):” + ops.get());// 获取添加的数据  
  259.       
  260.     //如果原先的string里有数据,则使用此方法set新数据会失败,并返回false  
  261.     System.out.println(ops.setIfAbsent(”stu04”));  
  262.     System.out.println(”ops.setIfAbsent(stu04):” +ops.get());// 获取添加的数据  
  263.       
  264.     stringRedisTemplate.delete(tableName2);//删除此string  
  265.     //如果原先的string里没有数据,则使用此方法set新数据会成功并返回true  
  266.     System.out.println(ops.setIfAbsent(”stu06”));  
  267.     System.out.println(”ops.setIfAbsent(stu06):” + ops.get());// 获取添加的数据  
  268.     ops.set(”stu05”, 30, TimeUnit.SECONDS);//设置30秒过期      
  269. }  
  270.   
  271. public void boundZSetOps() {  
  272.     System.out.println(”==================Zset==============”);  
  273.     String tableName2 = ”LiMing03”;  
  274.     BoundZSetOperations<String, String> ops = stringRedisTemplate  
  275.             .boundZSetOps(tableName2);  
  276.       
  277.     System.out.println(ops.add(”stu01”, 1));//Zset里添加数据  
  278.     System.out.println(ops.add(”stu03”, 1));//Zset里添加数据  
  279.       
  280.     System.out.println(ops.count(0, 1));//返回score在给定区间的数量  
  281.       
  282.     //添加数据和score,返回score的大小,如果stu04存在,则只需score相加即可  
  283.     //例如:原先存在数据stu04 score是2.0,执行下面语句后变为:stu04, score:6.0  
  284.     System.out.println(ops.incrementScore(”stu04”, 4));  
  285.       
  286.     ops.intersectAndStore(”LiMing02”, “LiMing04”);  
  287.     //ops.intersectAndStore(otherKeys, destKey);  
  288.     ops.unionAndStore(”LiMing02”, “LiMing05”);  
  289.     //ops.unionAndStore(otherKeys, destKey);  
  290.       
  291.     Set<TypedTuple<String>> sets = new HashSet<TypedTuple<String>>();  
  292.     TypedTuple<String> typedTuple = new TypedTuple<String>() {  
  293.           
  294.         @Override  
  295.         public int compareTo(TypedTuple<String> o) {  
  296.             // TODO Auto-generated method stub  
  297.             return 0;  
  298.         }  
  299.           
  300.         @Override  
  301.         public String getValue() {  
  302.             // TODO Auto-generated method stub  
  303.             return “stu06”;  
  304.         }  
  305.           
  306.         @Override  
  307.         public Double getScore() {  
  308.             // TODO Auto-generated method stub  
  309.             return 6.0;  
  310.         }  
  311.     };  
  312.     sets.add(typedTuple);  
  313.     ops.add(sets);//添加数据  
  314.       
  315.     Set<String> set1 = ops.range(0, -1); //返回指定区间的元素  
  316.     for (String string : set1) {  
  317.         System.out.println(”ops.range(0, 1):” + string);  
  318.     }  
  319.       
  320.     Set<String> set2 = ops.rangeByScore(1, 4);//返回指定score区间的元素,包含1和4  
  321.     for (String string : set2) {  
  322.         System.out.println(”ops.rangeByScore(0, 4):” + string);  
  323.     }  
  324.       
  325.     //返回指定位置的元素  
  326.     Set<TypedTuple<String>> set3 = ops.rangeWithScores(0, 4);  
  327.     for (TypedTuple<String> string : set3) {  
  328.         System.out.println(”ops.rangeByScore(0, 4):” + string.getValue()  
  329.                          + ”score:” + string.getScore());  
  330.     }  
  331.       
  332.     System.out.println(ops.remove(”stu01”));//删除数据,返回删除数据的个数  
  333.       
  334.     Set<String> set5 = ops.range(0, -1); //返回指定区间的元素  
  335.     for (String string : set5) {  
  336.         System.out.println(”ops.range(0, 1)5:” + string);  
  337.     }  
  338.       
  339.     ops.removeRangeByScore(1, 4);//删除score的范围是1和4的数,包含1和4  
  340.       
  341.     Set<String> set6 = ops.range(0, -1); //返回指定区间的元素  
  342.     for (String string : set6) {  
  343.         System.out.println(”ops.range(0, 1)6:” + string);  
  344.     }  
  345.       
  346.     ops.removeRange(0, 1);//删除懂第一个开始到第二个结束的数据  
  347.       
  348.     Set<String> set7 = ops.range(0, -1); //返回指定区间的元素  
  349.     for (String string : set7) {  
  350.         System.out.println(”ops.range(0, 1)7:” + string);  
  351.     }  
  352. }  
   /**
     * 需要指定hashtable的名字
     * 
     * @param tableName
     */
    public void boundHashOps(String tableName) {
        System.out.println("==================Hash==============");
        BoundHashOperations<String, String, String> ops = stringRedisTemplate
                .boundHashOps(tableName);
        stringRedisTemplate.delete("student");
        stringRedisTemplate.delete("student:1");
        ops.put("cs01", "123");// 存入数据 ops.putAll(maps); 存入多条数据
        String key1 = ops.getKey();// tableName的名字
        System.out.println("key1:" + key1);
        String key11 = ops.get("cs01");
        System.out.println("key11:" + key11);// 获取key的值

        ops.putIfAbsent("cs02", "456");
        String key2 = ops.getKey();
        System.out.println("ops.getKey()-key2:" + key2);
        String key21 = ops.get("cs02");
        System.out.println("ops.get(cs02)-key21:" + key21);

        Map<String, String> maps = ops.entries();// 获取所有的key-value值
        for (String key : maps.keySet()) {
            System.out.println("map-key:" + key + "map-value:" + maps.get(key));
        }
        // ops.persist();//删除过期(如果有的话)的数据。
        System.out.println("ops.getExpire():" + ops.getExpire());// -1
        System.out.println("ops.expireAt(new Date()):"
                + ops.expireAt(new Date()));// true 设置生存过期时间
        System.out.println("ops.getType():" + ops.getType());// Hash
        System.out.println("ops.hasKey(cs01):" + ops.hasKey("cs01"));// true
        System.out.println("ops.hasKey(cs02):" + ops.hasKey("cs02"));// true
        System.out.println("ops.size():" + ops.size());// 2

        Set<String> keys = ops.keys();// 获取所有的key
        for (String string : keys) {
            System.out.println("ops.keys():" + string);
        }

        System.out.println("ops.values():" + ops.values());// 获取所有的value
        System.out.println("ops.size():" + ops.size());// 2 获取数量

        ops.delete("cs01");// 删除key为cs01的数据
    }

    /**
     * 未指定hashtable的名字
     * 
     * @param tableName
     */
    public void opsForHash(String tableName) {
        System.out.println("==================Hash==============");
        HashOperations<String, Object, Object> ops = stringRedisTemplate
                .opsForHash();
        stringRedisTemplate.delete("student");
        stringRedisTemplate.delete("student:1");
        ops.put(tableName, "cs01", "123");// 存入数据 ops.putAll(maps); 存入多条数据
        Object key11 = ops.get(tableName, "cs01");
        System.out.println("key11:" + key11);// 获取key的值

        ops.putIfAbsent(tableName, "cs02", "456");
        Object key21 = ops.get(tableName, "cs02");
        System.out.println("ops.get(cs02)-key21:" + key21);

        Map<Object, Object> maps = ops.entries(tableName);// 获取所有的key-value值
        for (Object key : maps.keySet()) {
            System.out.println("map-key:" + key + "map-value:" + maps.get(key));
        }
        // ops.persist();//删除过期(如果有的话)的数据。
        System.out.println("ops.hasKey(cs01):" 
                          + ops.hasKey(tableName, "cs01"));// true
        System.out.println("ops.hasKey(cs02):" 
                          + ops.hasKey(tableName, "cs02"));// true
        System.out.println("ops.size():" + ops.size(tableName));// 2

        Set<Object> keys = ops.keys(tableName);// 获取所有的key
        for (Object string : keys) {
            System.out.println("ops.keys():" + string);
        }

        System.out.println("ops.values():" 
                          + ops.values(tableName));// 获取所有的value
        System.out.println("ops.size():" 
                          + ops.size(tableName));// 2 获取数量

        ops.delete("cs01");// 删除key为cs01的数据
    }

    /**
     * List 里面有重复数据
     * 
     * @param tableName
     */
    public void boundListOps(String tableName) {
        System.out.println("==================List==============");
        BoundListOperations<String, String> ops = stringRedisTemplate
                .boundListOps(tableName);
        ops.leftPush("cs01");// left push 左侧入栈,先进后出,和右侧相比,左侧先出
        ops.leftPushIfPresent("cs011");// 不知道和上面的有什么区别
        ops.leftPush("cs01", "cs0111");// 在cs01的左侧入栈
        ops.leftPushAll("cs01111", "cs011111");

        List<String> values = ops.range(0, -1);
        for (String string : values) {
            System.out.println("letf push:" + string);
        }

        ops.rightPush("cs02");// right push 右侧入栈 先进先出 ,和左侧相比,左侧先出
        ops.rightPushIfPresent("cs022");
        ops.rightPush("cs02", "cs0222");// 在cs02的右侧入栈
        ops.rightPushAll("cs02222", "cs022222");

        ops.set(0, "cs04");// 把第一个数据替换成cs04
        // ops.trim(0, 3);//从第一个数据到第4个数据删除

        List<String> values1 = ops.range(0, -1);// 查出所有数据
        for (String string : values1) {
            System.out.println("right push:" + string);
        }

        List<String> values2 = ops.range(1, 2);// 查出从第二个到第三个
        for (String string : values2) {
            System.out.println("right push1:" + string);
        }
        System.out.println("ops.index(1):" + ops.index(0));// 获得第一个数据
        System.out.println("ops.remove(0, cs01):" 
                           + ops.remove(0, "cs01"));// 1,删除“cs01”
        System.out.println("ops.leftPop():" + ops.leftPop());// 左侧出栈
        System.out.println("ops.rightPop():" + ops.rightPop());// 右侧出栈
        System.out.println("ops.remove(0, cs01)1:" 
                           + ops.remove(0, "cs01"));// 0 ,如果“cs01”不存在返回0

        // ops.persist();//删除过期(如果有的话)的数据。
    }

    public void boundSetOps() {
        System.out.println("==================Set==============");
        String tableName2 = "caoshuai03";
        BoundSetOperations<String, String> ops = stringRedisTemplate
                .boundSetOps(tableName2);

        String[] values = { "cs03", "cs04" };
        System.out.println("ops.add(values):" 
                          + ops.add(values));// 添加多条数据到set里,返回添加的数量

        Set<String> sets1 = ops.members();// 获取set里的所有数据,每次显示的顺序可能不一样
        for (String string : sets1) {
            System.out.println("ops.members()1:" + string);
        }

        // 获取随机的数
        System.out.println("ops.randomMember():" + ops.randomMember());
        // 获取一个随机数
        System.out.println("ops.randomMembers(1):" + ops.randomMembers(1));
        // 获取两个随机数,值可能一样
        System.out.println("ops.randomMembers(2):" + ops.randomMembers(2));

        System.out.println("ops.distinctRandomMembers(1):"
                + ops.distinctRandomMembers(1));// 获取一个随机数
        System.out.println("ops.distinctRandomMembers(2):"
                + ops.distinctRandomMembers(2));// 获取两个不一样的随机数

        System.out.println(ops.isMember("cs04"));// 是否含有cs04,有的话返回true
        System.out.println(ops.isMember("cs01"));// 没有返回false

        System.out.println(ops.size());// set里数据量

        System.out.println(ops.getKey());// 获取set的名字

        System.out.println(ops.getType());// 获取类型

        Set<String> set7 = ops.diff("caoshuai02");// 获取和另一set里不一样的数据,差集
        for (String string : set7) {
            System.out.println("ops.diff(caoshuai02):" + string);
        }

        // 获取和另一set里一样的数据,交集
        Set<String> set8 = ops.intersect("caoshuai02");
        for (String string : set8) {
            System.out.println("ops.intersect(caoshuai02):" + string);
        }

        // 获取另一set所有的数据,和自己合并起来,去掉重复的数据,并集
        Set<String> set6 = ops.union("caoshuai02");
        for (String string : set6) {
            System.out.println("ops.union(caoshuai02):" + string);
        }

        // 获取和其它set里一样的数据,交集
        List<String> keys = new ArrayList<String>();
        keys.add("caoshuai02");
        Set<String> set = ops.intersect(keys);
        for (String string : set) {
            System.out.println("ops.intersect(keys):" + string);
        }

        // 获取和其它set里不一样的数据,差集
        List<String> keys1 = new ArrayList<String>();
        keys1.add("caoshuai02");
        Set<String> set3 = ops.diff(keys);
        for (String string : set3) {
            System.out.println("ops.diff(keys)3:" + string);
        }

        // 获取其它set所有的数据,和自己合并起来,去掉重复的数据,并集
        List<String> keys2 = new ArrayList<String>();
        keys2.add("caoshuai02");
        Set<String> set4 = ops.union(keys2);
        for (String string : set4) {
            System.out.println("ops.union(keys2):" + string);
        }

        // 获取和另一set里不一样的数据,差集,存入到另一set里
        ops.diffAndStore("caoshuai02", "caoshuai04");
        // ops.diffAndStore(keys, destKey);

        // 获取和另一set里一样的数据,交集,存入到另一set里
        ops.intersectAndStore("caoshuai02", "caoshuai05");
        // ops.intersectAndStore(keys, destKey);

        // 获取另一set里的所有数据,并集,存入到另一set里
        ops.unionAndStore("caoshuai02", "caoshuai06");
        // ops.unionAndStore(keys, destKey);

        // 把指定的数据移到另一set里,移动成功返回true
        System.out.println(ops.move("caoshuai07", "cs03"));
        // 把指定的数据移到另一set里,移动失败,返回false,注:当前set里没有cs01
        System.out.println(ops.move("caoshuai07", "cs01"));

        System.out.println(ops.pop());// pop出一个数据,第一个数据被pop出

        System.out.println(ops.remove("cs03"));// 删除多个数据,返回删除的个数
        System.out.println(ops.remove("cs01"));// 删除数据,返回删除的个数
    }

    public void boundValueOps() {
        System.out.println("==================String==============");
        String tableName2 = "LiMing01";
        BoundValueOperations<String, String> ops = stringRedisTemplate
                .boundValueOps(tableName2);

        System.out.println(ops.append("stu01"));// 添加数据,返回添加数据字符的个数

        // 先获取原先存在的数据,再添加数据覆盖原先的
        System.out.println(ops.getAndSet("stu02"));

        System.out.println(ops.get());// 获取添加的数据

        System.out.println(ops.get(0, 1));//获取从第一个开始到第二个结束的字符
        System.out.println(ops.get(0, 5));//获取从第一个开始到第六个结束的数据,注实际只有两个数据

        System.out.println(ops.size());//获取数据字符的个数
        ops.set("stu03");//添加数据
        System.out.println("ops.set(stu03):" + ops.get());// 获取添加的数据
        ops.set("stu04", 0);//在位置0处添加数据
        System.out.println("ops.set(stu04, 0):" + ops.get());// 获取添加的数据

        //如果原先的string里有数据,则使用此方法set新数据会失败,并返回false
        System.out.println(ops.setIfAbsent("stu04"));
        System.out.println("ops.setIfAbsent(stu04):" +ops.get());// 获取添加的数据

        stringRedisTemplate.delete(tableName2);//删除此string
        //如果原先的string里没有数据,则使用此方法set新数据会成功并返回true
        System.out.println(ops.setIfAbsent("stu06"));
        System.out.println("ops.setIfAbsent(stu06):" + ops.get());// 获取添加的数据
        ops.set("stu05", 30, TimeUnit.SECONDS);//设置30秒过期    
    }

    public void boundZSetOps() {
        System.out.println("==================Zset==============");
        String tableName2 = "LiMing03";
        BoundZSetOperations<String, String> ops = stringRedisTemplate
                .boundZSetOps(tableName2);

        System.out.println(ops.add("stu01", 1));//Zset里添加数据
        System.out.println(ops.add("stu03", 1));//Zset里添加数据

        System.out.println(ops.count(0, 1));//返回score在给定区间的数量

        //添加数据和score,返回score的大小,如果stu04存在,则只需score相加即可
        //例如:原先存在数据stu04 score是2.0,执行下面语句后变为:stu04, score:6.0
        System.out.println(ops.incrementScore("stu04", 4));

        ops.intersectAndStore("LiMing02", "LiMing04");
        //ops.intersectAndStore(otherKeys, destKey);
        ops.unionAndStore("LiMing02", "LiMing05");
        //ops.unionAndStore(otherKeys, destKey);

        Set<TypedTuple<String>> sets = new HashSet<TypedTuple<String>>();
        TypedTuple<String> typedTuple = new TypedTuple<String>() {

            @Override
            public int compareTo(TypedTuple<String> o) {
                // TODO Auto-generated method stub
                return 0;
            }

            @Override
            public String getValue() {
                // TODO Auto-generated method stub
                return "stu06";
            }

            @Override
            public Double getScore() {
                // TODO Auto-generated method stub
                return 6.0;
            }
        };
        sets.add(typedTuple);
        ops.add(sets);//添加数据

        Set<String> set1 = ops.range(0, -1); //返回指定区间的元素
        for (String string : set1) {
            System.out.println("ops.range(0, 1):" + string);
        }

        Set<String> set2 = ops.rangeByScore(1, 4);//返回指定score区间的元素,包含1和4
        for (String string : set2) {
            System.out.println("ops.rangeByScore(0, 4):" + string);
        }

        //返回指定位置的元素
        Set<TypedTuple<String>> set3 = ops.rangeWithScores(0, 4);
        for (TypedTuple<String> string : set3) {
            System.out.println("ops.rangeByScore(0, 4):" + string.getValue()
                             + "score:" + string.getScore());
        }

        System.out.println(ops.remove("stu01"));//删除数据,返回删除数据的个数

        Set<String> set5 = ops.range(0, -1); //返回指定区间的元素
        for (String string : set5) {
            System.out.println("ops.range(0, 1)5:" + string);
        }

        ops.removeRangeByScore(1, 4);//删除score的范围是1和4的数,包含1和4

        Set<String> set6 = ops.range(0, -1); //返回指定区间的元素
        for (String string : set6) {
            System.out.println("ops.range(0, 1)6:" + string);
        }

        ops.removeRange(0, 1);//删除懂第一个开始到第二个结束的数据

        Set<String> set7 = ops.range(0, -1); //返回指定区间的元素
        for (String string : set7) {
            System.out.println("ops.range(0, 1)7:" + string);
        }
    }



Spring 源码硬核解析系列专题(十九):Spring Data Redis 的 Redis 数据操作源码解析 在前几期中,我们从 Spring 核心到 Spring Boot 的多个模块,再到 Spring Web Services,逐步揭示了 Spring 生态在分布式系统中的广泛应用。Spring Data Redis 是 Spring Data 家族的一部分,提供了对 Redis(键值存储数据库)的集成支持,简化了数据操作。本篇将深入 Spring Data Redis 的源码,剖析其核心机制与实现原理,并补充图示。 阅读详情

相关推荐

深入学习 Redis - 基于 Spring Data Redis 操作 Redis

RedisTemplate 是把 Redis 的方法,分成了几个类别来进行组织,和原生的 Redis 命令有一定的差异~ 作者的初心是希望通过上述封装,让接口看起来更简单,但在我看来,并没有达成,反而因为与 Redis 原生命令差异,提高了使用者的学习成本.之前使用 Jedis 是通过 Jedis 对象里的各种方法来操作 redis 的,此处 Spring 则是通过 SpringRedisTemplate 来操作 redis.

CYK_byte的博客 2547

Spring Data Redis

Spring Data Redis的使用,什么时候用redis

m0_71560190的博客 6365

C语言—for循环打印矩形,正三角形,倒三角形,九九乘法,等腰三角形,倒着的等腰三角形(简易版代码)(重点考察for循环嵌套)

本章节教大家如何利用for循环打印正三角形,倒三角形,九九乘法表及等腰三角形(简易版代码)说明:1、循环语句的循环体内又包含另一个完整的循环结构,称为循环结构的嵌套。内嵌的循环语句中还可以再嵌套循环结构,这样就形成多重循环结构的嵌套。2、for 语句、while 语句和 do-while 语句都允许嵌套。三种循环语句可以相互嵌套。3、内层的循环语句必须完全被包含在外层的循环体中,不能发生交叉。

m0_63501145的博客 1697

SpringDataRedis

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址提供了对不同Redis客户端的整合(Lettuce和Jedis)提供了RedisTemplate统一API来操作Redis支持Redis的发布订阅模型支持Redis哨兵和Redis集群支持基于Lettuce的响应式编程支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化。

weixin_43811057的博客 4490

避坑指南:vmdk转qcow2镜像时你可能会遇到的5个问题及解决方法

本文详细解析了将VMDK虚拟机镜像转换为QCOW2格式时可能遇到的五大核心问题,特别是启动失败的常见原因及解决方案。内容涵盖格式差异、预处理优化、转换命令详解,并针对根文件系统挂载失败、GRUB引导失败、网卡识别、性能异常等典型故障提供了具体排查与修复步骤,旨在帮助运维人员高效、可靠地完成镜像迁移。

websocket5live的博客 275

基于PyTorch的BP神经网络PID控制器动态调参实战

本文详细介绍了基于PyTorch的BP神经网络PID控制器动态调参实战,结合BP神经网络的自适应能力和PID控制算法的经典优势,实现工业控制中的高效参数调整。通过PyTorch实现BP-PID融合架构,包括网络设计、损失函数优化及动态调参技巧,显著提升控制性能,适用于温度控制、电机控制等场景。

weixin_29094775的博客 523

SpringData Redis

目录1 SpringData Redis简介2 Redis环境搭建2.1 安装redis的依赖环境2.2 上传安装包2.3 解压2.4 编译2.5 安装2.6 复制配置文件2.7 修改redis的配置文件2.8 启动redis服务3 SpringData Redis入门案例3.1 创建工程,引入坐标3.2 创建配置文件3.3 创建测试类,完成一条简单数据的存取4 SpringData Redis的序列化器5 SpringData Redis运行原理分析6 SpringData Redis常见操作6.1 St

赵广陆 3530

NLM去噪算法:原理、优化与医学图像处理实战

本文深入解析NLM去噪算法的核心原理及其在医学图像处理中的优化应用。通过全局自相似性计算,NLM算法有效抑制噪声并保留细节,特别适用于超声图像斑点噪声处理。文章还介绍了积分图像和傅里叶变换等优化技巧,显著提升计算效率,并分享医学影像项目中的实用调参经验。

weixin_29051149的博客 348

使用Spring Data Redis操作Redis

使用Spring Data Redis操作Redis、RedisTemplate

weixin_61769871的博客 1472

【SEED Labs 2.0】V*N Tunneling Lab

本文为 SEED Labs 2.0 - V*N Tunneling Lab 的实验记录。本实验工作量较大,但难度不大,重点在于搞清是谁发给谁,走的什么路径。另外,本次实验由于网络中的设备较多,修改了环境变量方便分清哪个窗口是哪个设备。这样操作起来就方便多了。...

Chenyang的博客 6981

腾讯会议录屏损坏修复:使用 recover_mp4 工具的完整解决方案

腾讯会议录屏损坏修复指南 当腾讯会议录屏因电脑蓝屏等意外导致MP4文件损坏时,可使用recover_mp4工具结合FFMPEG进行修复。操作步骤包括:1)准备相同参数正常视频作为参考;2)分析正常视频生成结构文件;3)分离损坏视频的音视频流;4)使用FFMPEG重新封装。该方法能有效修复因意外中断导致的视频索引损坏问题。修复前需将工具和文件放在同一目录下,并确保参考视频与损坏录屏的参数完全一致。

m0_62498496的博客 2628

Redis系列(四)Spring Data Redis 操作 Redis

Spring Data Redis 操作 Redis

kong7906928的博客 813

使用MATLAB/Simulink搭建一个简化的无人机螺旋桨气动特性模型,并通过仿真验证其基本功能

螺旋桨概述特点:螺旋桨通过旋转产生推力,推动无人机前进或上升。关键参数直径(D):影响推力和功率消耗。螺距(P):定义了螺旋桨每转一圈理论上前进的距离。效率(η):衡量螺旋桨将机械能转化为推力的有效性。应用场景:航拍摄影、农业监测、物流配送等。仿真目标搭建一个简化的无人机螺旋桨气动特性模型,涵盖从电机输入到螺旋桨输出推力的过程。分析不同条件下的系统响应特性(如推力、功率消耗),并探讨优化螺旋桨设计的方法。定义螺旋桨的一些基本物理参数,如直径、螺距、推力系数、功率系数等。使用Constant。

xiaoheshang_123的博客 429

spring-data-redis连接操作redis

Java连接redis的客户端有很多,其中比较常用的是Jedis. (参考:redis client) spring-data-redis则是对Jedis进行了高度封装,使用起来非常方便。下面就以代码为例说明spring-data-redis的使用。 整个项目使用maven管理jar包,pom文件如下: <project xmlns="http://maven.apache.org/PO

雪水 2万+

如何通过单步调试的方法,找到 SAP Gateway 到底把 Fiori 应用发送的 OData 请求,投递到哪一台 ABAP 后台服务器了?

这是一个 Count 请求,得到服务器响应的数据是 204,这个 204 会以数字的形式,显示在 Fiori Launchpad tile 上。那如果当前 Gateway 系统可以连接到多台 ABAP 后台服务器,比如下图所示,可以连接 SAP S/4HANA,ERP 或者 SAP CRM,我怎么知道 SAP UI5 应用上显示的数据,到底来自哪一台 ABAP 后台服务器呢?,就是上图 ABAP 调试器里断点停留的位置,即 SAP Frontend 服务器,通过 RFC 将 OData 请求投递给。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 645

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Spring Data Redis操作

步骤一:打开Window/Linux的Redis服务步骤二:连接Redis服务。

烁岩的博客 309

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

spring data redis 操作redis

这篇文章主要说spring data redis 集成jedis操作 redis中的数据。

hunanlzg的专栏 7137

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

使用spring-data-redis操作redis

http://docs.spring.io/spring-data/data-redis/docs/1.1.0.RELEASE/reference/html/ 一、maven org.springframework.data spring-data-redis 1.1.0.RELEASE

我爱逛菜园 2897

Java使用Spring Data Redis操作Redis

1.导入Spring Data Redis的maven坐标。4.通过RedisTemplate对象操作Redis。3.编写配置类,创建RedisTemplate对象。2.配置Redis数据源。

m0_52302665的博客 331

使用Spring-data进行Redis操作

使用Spring-data进行Redis操作 Redis相信大家都听说过,它是一个开源的key-value缓存数据库,有很多Java的客户端支持,比较有名的有Jedis,JRedis等(见这里)。当然我们可以使用客户端的原生代码实现redis的操作,但实际上在spring中就已经集成了这些客户端的使用,下面我们就以Jedis为例来介绍一下Spring中关于Redis的配置。

末路狂奔》》》》 2917

94、Spring Data Redis 提供的 RedisTemplate 的功能和用法讲解 及 使用 Spring Data Redis 操作redis数据库 及存储机制讲解

Spring Data Redis 提供的 RedisTemplate 的功能和用法讲解 及 使用 Spring Data Redis 操作redis数据库

Java 领域技术分享 2798

5.Spring-data-redis操作List类型

文章目录: 1.RedisTemplate的API 2.Spring-data-redis配置 3.Spring-data-redis处理String类型 4.Spring-data-redis处理Hash类型 5.Spring-data-redis操作List类型 6.Spring-data-redis操作Set类型 7.Spring-data-redis操作ZSet类型 8.StringRedisTemplate @Test public void testList() { ListOper.

earnest_1的博客 1222

spring-data-redis操作redis

1.在maven pom文件中引入依赖2.在yml文件中配置redis的链接信息3.开始编码  3.1   引用RedisTemplate&lt;K, V&gt;模板或者StringRedisTemplate         @Autowired private StringRedisTemplate stringRedisTemplate;// @Autowired private Red...

u011997192的博客 1665

利用Spring-Data-Redis和Jedis操作Redis缓存

只需简单几步,就可以利用Spring-Data-Redis和Jedis操作Redis缓存。步骤如下: 第一步:依赖包 org.springframework.data spring-data-redis 1.4.2.RELEASE redis.clients jedis 2.6.0 第二步:封装自己的Redis操作类 /*

Clement-Xu的专栏 1万+

使用Spring Data Redis操作Redis数据结构全解析

本文全面解析了Spring Data Redis操作Redis五大核心数据结构的方法。首先介绍了Spring Data Redis的核心架构,包括RedisTemplate、连接工厂和序列化机制等组件。然后详细说明了环境配置步骤,包括依赖引入、配置文件设置和模板类自定义。最后通过代码示例展示了String、List、Set、Hash四种数据结构的实际应用场景,如用户会话存储、消息队列、标签系统和用户信息存储等。文章为Java开发者提供了Redis操作的实用指南和最佳实践。

572

使用Spring-data-redis操作Redis的Sentinel

介绍 Spring-Data-Redis项目(简称SDR)是对Redis的Key-Value数据存储操作提供了更高层次的抽象,提供了一个对几种主要的redis的Java客户端(例如:je

dearbaba_1666的博客 698
上一篇: spring-data-redis用配置类连接时,抛异常Cannot get Jedis connection; nested exception is java.lang.NullPointerE
下一篇: HttpClient4.5入门实例教程
光与热
博客等级 码龄11年 55粉丝 126原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值