SpringData

  • 官网: https://spring.io/projects/spring-data

  • 他是Spring的一个子项目。用于简化数据库访问, 支持NoSQL和关系型数据库。其主要目标是使得数据库的访问变得方便快捷

  • 开发人员唯一要做的就是声明持久层的接口,其他的操作就交给 Sprign Data JPA完成。JPA能够根据一定规则的方法名来实现一些的逻辑操作。

  • SpringData所支持的NoSQL存储

    • MongoDB(文档数据库)
    • Neo4j(图形数据库)
    • Redis(键值存储)
    • Hbase(列族数据库)
  • SpringData项目支持的关系型数据库

    • JDBC
    • JPA

spring-data-redis简介

  • https://spring.io/projects/spring-data-redis

  • Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作。

  • Spring-Data-Redis项目(简称SDR)对Redis的Key-Value数据存储操作提供了更高层次的抽象,类似于Spring Framework对JDBC支持一样。

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

    • 连接池自动管理,提供了一个高度封装的“RedisTemplate”类
    • 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
      • ValueOperations:简单K-V操作
      • SetOperations:set类型数据操作
      • ZSetOperations:zset类型数据操作
      • HashOperations:针对map类型的数据操作
      • ListOperations:针对list类型的数据操作

整合Redis

准备依赖

  • pom.xml 文件下添加如下依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- redis -->
    <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>
    <!-- jackson-->
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>

编写配置文件

  • yaml文件如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    spring:
    redis:
    host: 192.168.56.101
    port: 6379
    password: 654321
    database: 0 # 数据库索引,默认0
    timeout: 5000 # 连接超时,单位ms
    # 哨兵模式
    sentinel:
    master: mymaster
    nodes: 192.168.56.101:26379,192.168.56.101:26380 # 哨兵的IP:Port列表
    # 集群模式
    # cluster:
    # nodes: 192.168.40.201:7100,192.168.40.201:7200,192.168.40.201:7300,192.168.40.201:7400,192.168.40.201:7500,192.168.40.201:7600
    # max-redirects: 3 # 重定向的最大次数
    lettuce: # jedis或lettuce, 连接池配置,springboot2.0中使用jedis或者lettuce配置连接池,默认为lettuce连接池
    pool:
    max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
    max-wait: -1 # 连接池分配连接最大阻塞等待时间(阻塞时间到,抛出异常。使用负值表示无限期阻塞)
    max-idle: 8 # 连接池中的最大空闲连接数
    min-idle: 0 # 连接池中的最小空闲连接数

测试

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package tk.fulsun.demo;

    import java.util.UUID;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;

    /**
    * @author fulsun
    * @description: 主测试类
    * @date 5/27/2021 4:27 PM
    */
    class ApplicationTest {
    @Autowired private RedisTemplate redisTemplate;

    @Test
    public void testStringRedisTemplate() {
    ValueOperations<String, String> vo = redisTemplate.opsForValue();
    // 使用redis-cli查看key
    // 发现hello的key变成了 \xac\xed\x00\x05t\x00\x05hello
    vo.set("hello", "世界world_" + UUID.randomUUID().toString());
    String hello = vo.get("hello");
    System.out.println("之前保存的数据:" + hello);
    }
    }

Redistemplate

  • Springboot中自动集成了redistemplate两种实例化方式
    1. redistemplate<object,object>
    2. redistemplate<string,string>
  • 这两种方式并不好用,数据存入中文之后会出现Ascll码,让人看不懂
    • 第一种,存储的方式则为jdk实例化,不太好懂,数据也看不清楚
    • 第二种的话,我需要手动将对象数据进行json转化,影响代码效率,存储起来也非常麻烦

自定义redis配置

  • 重新配置RedisTemplate<String,Object>,修改默认的序列号方式为JdkSerializationRedisSerializer—>Jackson2JsonRedisSerializer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    package tk.fulsun.demo.config;

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonTypeInfo;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
    import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    // @Configuration
    public class RedisConfig {

    /** 自定义RedisTemplate - 配置序列化配置,默认是jdk */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    // 为了开发的方便,一般直接使用 <String, Object>
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory); // redis连接的线程安全工厂,源码也是这样配置的

    // Json序列化配置
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
    new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    // 关闭自动检测。使用两个属性ALL(getter和setter)和字段来序列化和反序列化为json。
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 指定jackson只使用字段
    // om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
    // om.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

    // 指定序列化输入类型,不指定那存储到redis里的数据将是没有类型的纯json,解析后是一个LinkHashMap类型
    // 指定序列化输入类型,java获取到数据后,将会将数据自动转化为转换前的类型
    // NON_FINAL:整个类、除final外的的属性信息都需要被序列化和反序列化
    // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    om.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance,
    ObjectMapper.DefaultTyping.NON_FINAL,
    JsonTypeInfo.As.WRAPPER_ARRAY);
    // 解决jackson2无法序列化LocalDateTime的问题,这里扩展一个LocalDateTime类型,它是日期类型对象
    // jdk1.8出的(并且这个类是不可变的和线程安全的,可以研究一下它的API),当然还需要对这个对象进行json格式的转换,如下图:
    om.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
    om.registerModule(new JavaTimeModule());

    jackson2JsonRedisSerializer.setObjectMapper(om);
    // String 的序列化
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

    // key采用String的序列化方式
    template.setKeySerializer(stringRedisSerializer);
    // value序列化方式采用jackson
    template.setValueSerializer(stringRedisSerializer);

    // hash的key也采用String的序列化方式
    template.setHashKeySerializer(stringRedisSerializer);
    // hash的value序列化方式采用jackson
    template.setHashValueSerializer(jackson2JsonRedisSerializer);
    // 非spring容器必须执行
    template.afterPropertiesSet();

    return template;
    }
    }

  • 重新测试,可以正常看到key的值为hello

操作5种类型

  • Hash类型的操作:经常用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //(1)存入值
    @Test
    public void boundHashOpsSet(){
    redisTemplate.boundHashOps("namehash").put("country1","中国");
    redisTemplate.boundHashOps("namehash").put("country2","日本");
    redisTemplate.boundHashOps("namehash").put("country3","韩国");
    }

    //(2)提取所有的KEY
    @Test
    public void boundHashOpsKeys(){
    Set keys = redisTemplate.boundHashOps("namehash").keys();
    System.out.println(keys);
    }

    //(3)提取所有的值
    @Test
    public void boundHashOpsValues(){
    List values = redisTemplate.boundHashOps("namehash").values();
    System.out.println(values);
    }

    //(4) 根据KEY提取值
    @Test
    public void boundHashOpsByKey(){
    Object name = redisTemplate.boundHashOps("namehash").get("country1");
    System.out.println(name);
    }

    //根据KEY移除值
    @Test
    public void boundHashOpsDelByKey(){
    redisTemplate.boundHashOps("namehash").delete("country2");
    }
  • 值类型的操作:因为操作数量少,所以不常用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Test
    public void setValue(){
    redisTemplate.boundValueOps("name").set("王五");
    }
    @Test
    public void getValue(){
    Object name = redisTemplate.boundValueOps("name").get();
    System.out.println(name);
    }
    @Test
    public void deleteValue(){
    redisTemplate.delete("name");
    }
  • set类型的操作:因为操作数量少,所以不常用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /** 存入值 */
    @Test
    public void boundSetOpsAdd() {
    redisTemplate.boundSetOps("nameset").add("曹操"); // 放入值
    redisTemplate.boundSetOps("nameset").add("刘备");
    redisTemplate.boundSetOps("nameset").add("孙权");
    }
    /** 提取值 */
    @Test
    public void boundSetOpsGet() {
    Set names = redisTemplate.boundSetOps("nameset").members(); // 取出值
    System.out.println(names);
    }
    /** 删除集合中的某一个值 */
    @Test
    public void boundSetOpsDelete() {
    redisTemplate.boundSetOps("nameset").remove("曹操");
    }
    /** 删除整个集合 */
    @Test
    public void boundSetOpsDeleteAll() {
    redisTemplate.delete("nameset");
    }
  • list类型的操作:因为操作数量少,所以不常用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /** 右压栈:后添加的对象排在后边 右压栈用的多,因为快,原理是 */
    @Test
    public void boundListrightPush() {
    redisTemplate.boundListOps("namelist").rightPush("赵子龙");
    redisTemplate.boundListOps("namelist").rightPush("张飞");
    redisTemplate.boundListOps("namelist").rightPush("关羽");
    }
    /** 显示右压栈集合 */
    @Test
    public void boundListRange() {
    List namelist = redisTemplate.boundListOps("namelist").range(0, 10);
    System.out.println(namelist);
    }

    /** 查询:根据索引查询集合某个元素 */
    @Test
    public void boundListIndex() {
    Object name = redisTemplate.boundListOps("namelist").index(1);
    System.out.println(name);
    }
    /** 删除: 根据值移除集合某个元素 */
    @Test
    public void bondListRemove() {
    redisTemplate.boundListOps("namelist").remove(1, "关羽");
    }
    /** 删除: 删除全部 */
    @Test
    public void bondListDelete() {
    redisTemplate.delete("namelist");
    }