SpringBoot2_Redis

1.Redis与SpringBoot搭建

1.1 介绍

Redis 是开源免费, key-value 内存数据库,主要解决高并发、大数据场景下,热点数据访问的性能问题,提供高性能的数据快速访问。项目中部分数据访问比较频繁,对下游 DB(例如 MySQL)造成服务压力,这时候可以使用缓存来提高效率。

Redis 的主要特点包括:

  • Redis数据存储在内存中,可以提高热点数据的访问效率
  • Redis 除了支持 key-value 类型的数据,同时还支持其他多种数据结构的存储;
  • Redis 支持数据持久化存储,可以将数据存储在磁盘中,机器重启数据将从磁盘重新加载数据;

Redis 作为缓存数据库和 MySQL 这种结构化数据库进行对比。

  • 从数据库类型上,Redis 是 NoSQL 半结构化缓存数据库, MySQL 是结构化关系型数据库;
  • 从读写性能上,MySQL 是持久化硬盘存储,读写速度较慢, Redis 数据存储读取都在内存,同时也可以持久化到磁盘,读写速度较快;
  • 从使用场景上,Redis 一般作为 MySQL 数据读取性能优化的技术选型,彼此配合使用。Redis用于存储热数据或者缓存数据,并不存在相互替换的关系。

1.2 环境搭建

本地启动一个docker环境

1.本地启动一个
在这里插入图片描述
2.用工具连接查看
在这里插入图片描述
在这里插入图片描述

springboot配置

1.pom主要配置

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.yml配置

server:
  port: 8887

spring:
  redis:
    database: 0
    host: 192.168.232.130
    port: 6379
    timeout: 2000s
    password: 123456

3.代码

@RunWith(SpringJUnit4ClassRunner.class)
//需要指明当前启动类,加载上下文环境
@SpringBootTest(classes = AppMain.class)
@Slf4j
public class PersonRedisTest {


    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Test
    public void testRedis1(){

        String helloRedis = (String)redisTemplate.opsForValue().get("helloRedis");
        if(StringUtils.isEmpty(helloRedis)){
            helloRedis = "helloRedis";
            redisTemplate.opsForValue().set(helloRedis,"你好 ,redis!");
            log.info("第一次访问,缓存没有值,则直接存进去");
        }

    }

}

4.运行

在这里插入图片描述

2.Redis数据类型

2.1 String

2.2 Hash

2.3 List

2.4 Set

2.5 zSet

2.6 Sorted set

3.Redis模板类

RedisTemplate和StringRedisTemplate的区别:

  1. 两者的关系是StringRedisTemplate继承RedisTemplate。
  2. 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
  3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
    StringRedisTemplate默认采用的是String的序列化策略(StringRedisSerializer),保存的key和value都是采用此策略序列化保存的。
    RedisTemplate默认采用的是JDK的序列化策略(JdkSerializationRedisSerializer ),保存的key和value都是采用此策略序列化保存的。

redisTemplate有两个方法经常用到,一个是opsForXXX一个是boundXXXOps,XXX是value的类型,前者获取到一个Opercation,但是没有指定操作的key,可以在一个连接(事务)内操作多个key以及对应的value;后者会获取到一个指定了key的operation,在一个连接内只操作这个key对应的value.

3.1 StringRedisTemplate

3.1.1 简单例子

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Test
    public void testRedisStr(){
        stringRedisTemplate.opsForValue().set("player:str强","str demo烈",60, TimeUnit.SECONDS);
        log.info("suc!");
    }

在这里插入图片描述
其他用法参考下面RedisTemplate

3.2 RedisTemplate

RedisTemplate 的封装使我们能够更方便的进行redis数据操作,比直接使用Jedis或者Lettuce的java SDK要方便很多。

redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作 hash
redisTemplate.opsForList();//操作 list
redisTemplate.opsForSet();//操作 set
redisTemplate.opsForZSet();//操作有序 set

3.2.1 简单举例

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
@Builder
public class Person implements Serializable {
    private static final long serialVersionUID = 5919073407158198332L;
    private Integer pid;
    private String pName;
}
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Test
    public void testRedisObj(){
        Person person = Person.builder().pid(1).pName("xiaoming啊").build();
        redisTemplate.opsForValue().set("player:obj啊",person,60, TimeUnit.SECONDS);
        log.info("suc!");
    }

注意,Person要保存到redis里,一定要实现序列化接口,不然会报org.springframework.data.redis.serializer.SerializationException错误

在这里插入图片描述

其实这个不是严格意义上的乱码,是JDK的二进制序列化之后的存储方式。人看不懂,但是程序是能看懂的。

3.2.2 解决“乱码”

  • 采用StringRedisSerializer对key进行序列化(字符串格式)
  • 采用Jackson2JsonRedisSerializer对value将进行序列化(JSON格式)

乱码问题的症结在于对象的序列化问题:RedisTemplate默认使用的是JdkSerializationRedisSerializer(二进制存储),StringRedisTemplate默认使用的是StringRedisSerializer(redis字符串格式存储)。

序列化方式对比:

JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。而且是以二进制形式保存,自然人无法理解。
Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。似乎没啥缺点。
StringRedisSerializer序列化之后的结果,自然人也是可以理解,但是value只能是String类型,不能是Object。

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //重点在这四行代码
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

}

重新执行保存对象的代码,得到结果是:JSON对象
在这里插入图片描述

3.2.3 使用redisTemplate存取redis各种数据类型

下面的各种数据类型操作的api和redis命令行api的含义几乎是一致的。

    @Resource(name = "redisTemplate")
    private ValueOperations<String,Object> valueOperations;   //以redis string类型存取Java Object(序列化反序列化)

    @Resource(name = "redisTemplate")
    private HashOperations<String, String, Object> hashOperations; //以redis的hash类型存储java Object

    @Resource(name = "redisTemplate")
    private ListOperations<String, Object> listOperations; //以redis的list类型存储java Object

    @Resource(name = "redisTemplate")
    private SetOperations<String, Object> setOperations;   //以redis的set类型存储java Object

    @Resource(name = "redisTemplate")
    private ZSetOperations<String, Object> zSetOperations;  //以redis的zset类型存储java Object


    @Test
    public void testValueObj() {
        Person person = new Person("boke","byrant");
        person.setAddress(new Address("南京","中国"));
        //向redis数据库保存数据(key,value),数据有效期20秒
        valueOperations.set("player:1",person,20, TimeUnit.SECONDS); //20秒之后数据消失
        //根据key把数据取出来
        Person getBack = (Person)valueOperations.get("player:1");
        System.out.println(getBack);
    }

    @Test
    public void testSetOperation() {
        Person person = new Person("kobe","byrant");
        Person person2 = new Person("curry","stephen");

        setOperations.add("playerset",person,person2);  //向Set中添加数据项
        //members获取Redis Set中的所有记录
        Set<Object> result = setOperations.members("playerset");
        System.out.println(result);  //包含kobe和curry的数组
    }

    @Test
    public void HashOperations() {
        Person person = new Person("kobe","byrant");
        //使用hash的方法存储对象数据(一个属性一个属性的存,下节教大家简单的方法)
        hashOperations.put("hash:player","firstname",person.getFirstname());
        hashOperations.put("hash:player","lastname",person.getLastname());
        hashOperations.put("hash:player","address",person.getAddress());
        //取出一个对象的属性值,有没有办法一次将整个对象取出来?有,下节介绍
        String firstName = (String)hashOperations.get("hash:player","firstname");
        System.out.println(firstName);   //kobe
    }

    @Test
    public void  ListOperations() {
        //将数据对象放入队列
        listOperations.leftPush("list:player",new Person("kobe","byrant"));
        listOperations.leftPush("list:player",new Person("Jordan","Mikel"));
        listOperations.leftPush("list:player",new Person("curry","stephen"));
        //从左侧存,再从左侧取,所以取出来的数据是后放入的curry
        Person person = (Person) listOperations.leftPop("list:player");
        System.out.println(person); //curry对象
    }

3.2.4 String操作

1.赋值方式

        //1、通过redisTemplate设置值
        redisTemplate.boundValueOps("StringKey11").set("StringValue");
        redisTemplate.boundValueOps("StringKey12").set("StringValue",30, TimeUnit.SECONDS);

        //2、通过BoundValueOperations设置值
        BoundValueOperations stringKey = redisTemplate.boundValueOps("StringKey22");
        stringKey.set("StringVaule");
        stringKey.set("StringValue",30, TimeUnit.SECONDS);

        //3、通过ValueOperations设置值
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("StringKey31", "StringVaule");
        ops.set("StringKey32","StringVaule",30, TimeUnit.SECONDS);

在这里插入图片描述

2.设置过期时间(单独设置)

在这里插入图片描述

        redisTemplate.boundValueOps("StringKey11").expire(20,TimeUnit.SECONDS);
        redisTemplate.expire("StringKey31",20,TimeUnit.SECONDS);

在这里插入图片描述

3.获取值

在这里插入图片描述

        //1、通过redisTemplate设置值
        String str1 = (String) redisTemplate.boundValueOps("StringKey").get();
        System.out.println("str1 = " + str1);

        //2、通过BoundValueOperations获取值
        BoundValueOperations stringKey = redisTemplate.boundValueOps("StringKey");
        String str2 = (String) stringKey.get();
        System.out.println("str2 = " + str2);

        //3、通过ValueOperations获取值
        ValueOperations ops = redisTemplate.opsForValue();
        String str3 = (String) ops.get("StringKey");
        System.out.println("str3 = " + str3);

在这里插入图片描述

4.删除值

Boolean result = redisTemplate.delete("StringKey");

5.递增or递减

在这里插入图片描述

        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("StringKey1",1);

        //递增  递减(-6L)
        redisTemplate.boundValueOps("StringKey1").increment(6L);

在这里插入图片描述

3.2.5 hash操作

1.新增

在这里插入图片描述

        //1、通过redisTemplate设置值
        redisTemplate.boundHashOps("HashKey11").put("SmallKey", "HashVaue");

        //2、通过BoundValueOperations设置值
        BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey21");
        hashKey.put("SmallKey", "HashVaue");

        //3、通过ValueOperations设置值
        HashOperations hashOps = redisTemplate.opsForHash();
        hashOps.put("HashKey31", "SmallKey", "HashVaue");
        hashOps.put("HashKey31", "SmallKey2", "HashVaue2");

        //4.添加集合方式
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("SmallKey1","HashVaue1");
        hashMap.put("SmallKey2","HashVaue2");
        hashMap.put("SmallKey3","HashVaue3");
        redisTemplate.boundHashOps("HashKey41").putAll(hashMap );

2.查询key

        //1、通过redisTemplate获取值
        Set keys1 = redisTemplate.boundHashOps("HashKey41").keys();

        //2、通过BoundValueOperations获取值
        BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey41");
        Set keys2 = hashKey.keys();

        //3、通过ValueOperations获取值
        HashOperations hashOps = redisTemplate.opsForHash();
        Set keys3 = hashOps.keys("HashKey41");

在这里插入图片描述

3.查询value

        //1、通过redisTemplate获取值
        List values1 = redisTemplate.boundHashOps("HashKey41").values();

        //2、通过BoundValueOperations获取值
        BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey41");
        List values2 = hashKey.values();

        //3、通过ValueOperations获取值
        HashOperations hashOps = redisTemplate.opsForHash();
        List values3 = hashOps.values("HashKey41");

在这里插入图片描述

4.根据key获取value

  		//1、通过redisTemplate获取
        String value1 = (String) redisTemplate.boundHashOps("HashKey41").get("SmallKey3");

        //2、通过BoundValueOperations获取值
        BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey41");
        String value2 = (String) hashKey.get("SmallKey3");

        //3、通过ValueOperations获取值
        HashOperations hashOps = redisTemplate.opsForHash();
        String value3 = (String) hashOps.get("HashKey41", "SmallKey3");

在这里插入图片描述
其他方式,后续自己可以再试试

3.2.6 set操作

1.新增

        //1、通过redisTemplate设置值
        redisTemplate.boundSetOps("setKey").add("setValue1", "setValue2");

        //2、通过BoundValueOperations设置值
        BoundSetOperations setKey = redisTemplate.boundSetOps("setKey");
        setKey.add("setValue3","setValue2");

        //3、通过ValueOperations设置值
        SetOperations setOps = redisTemplate.opsForSet();
        setOps.add("setKey", "SetValue4", "setValue5", "setValue6");

在这里插入图片描述

2.查询

        //1.取出一个元素值
        SetOperations setOps = redisTemplate.opsForSet();
        Object setKey = setOps.pop("setKey");
        System.out.println("setKey = " + setKey);

        //2.是否存在
        Boolean isEmpty = redisTemplate.boundSetOps("setKey").isMember("setValue2");
        System.out.println(isEmpty);

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
其他操作后续介绍

3.2.7 list操作

1.新增

//1、通过redisTemplate设置值
        redisTemplate.boundListOps("listKey").leftPush("listLeftValue1");
        redisTemplate.boundListOps("listKey").rightPush("listRightValue2");

        //2、通过BoundValueOperations设置值
        BoundListOperations listKey = redisTemplate.boundListOps("listKey");
        listKey.leftPush("listLeftValue3");
        listKey.rightPush("listRightValue4");

        //3、通过ValueOperations设置值
        ListOperations opsList = redisTemplate.opsForList();
        opsList.leftPush("listKey", "listLeftValue5");
        opsList.rightPush("listKey", "listRightValue6");

        //4.集合插入
        ArrayList<String> list = new ArrayList<>();
        list.add("listLeftValue7");
        list.add("listLeftValue8");
        redisTemplate.boundListOps("listKey").rightPushAll(list);
        redisTemplate.boundListOps("listKey").leftPushAll(list);

在这里插入图片描述

2.查询

        //获取List缓存全部内容(起始索引,结束索引)
        List listKey1 = redisTemplate.boundListOps("listKey").range(0, 3);

        Object listKey = redisTemplate.boundListOps("listKey").leftPop();//从左侧弹出一个元素
        Object listKey2 = redisTemplate.boundListOps("listKey").rightPop();//从右侧弹出一个元素

在这里插入图片描述
在这里插入图片描述

3.2.8 zset操作

后续再看

4.Redis与缓存

4.1 为什么要做缓存

  • 提升性能
    绝大多数情况下,关系型数据库select查询是出现性能问题最大的地方。一方面,select 会有很多像 join、group、order、like 等这样丰富的语义,而这些语义是非常耗性能的;另一方面,大多数应用都是读多写少,所以加剧了慢查询的问题。
    分布式系统中远程调用也会耗很多性能,因为有网络开销,会导致整体的响应时间下降。为了挽救这样的性能开销,在业务允许的情况(不需要太实时的数据)下,使用缓存是非常必要的事情。

  • 缓解数据库压力
    当用户请求增多时,数据库的压力将大大增加,通过缓存能够大大降低数据库的压力。

4.2 缓存一致性操作

在这里插入图片描述

  • 更新写数据:先把数据存到数据库中,然后再让缓存失效或更新。缓存操作失败,数据库事务回滚。
  • 删除写数据: 先从数据库里面删掉数据,再从缓存里面删掉。缓存操作失败,数据库事务回滚。
  • 查询读数据
    – 缓存命中:先去缓存 cache 中取数据,取到后返回结果。
    – 缓存失效:应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,在将数据放到缓存中。

如果上面的这些更新、删除、查询操作流程全都由程序员通过编码来完成的话

  • 因为加入缓存层,程序员的编码量大大增多
  • 缓存层代码和业务代码耦合,造成难以维护的问题。

4.3 整合Spring Cache

Jedis 和 Lettuce 是 Java 操作 Redis 的客户端。在 Spring Boot 1.x 版本默认使用的是 jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。关于 Jedis 跟 Lettuce 的区别如下:

  • Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
  • Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

这里重新进行配置一遍

1.添加依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!--spring data jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

2.添加入口启动类 @EnableCaching 注解开启 Caching(在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager)),Redis(因为我们之前引入了Redis,所以使用redis作为缓存),实例如下。

@SpringBootApplication
@EnableCaching
public class AppMain {
    public static void main(String[] args) {
        SpringApplication.run(AppMain.class,args);
    }
}

3.yml配置

server:
  port: 8887

spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.232.131:6033/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    # JPA配置
  jpa:
    hibernate:
      ddl-auto: update
#    show-sql: true

    # formatSQL得这样写
#    properties:
#      hibernate:
#        format_sql: true

  redis:
    database: 0
    host: 192.168.232.131
    port: 6379
    timeout: 2000s
    password: 123456

4.redis配置类

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //重点在这四行代码
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    //本节的重点配置,让Redis缓存的序列化方式使用redisTemplate.getValueSerializer()
    //不在使用JDK默认的序列化方式
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

}

5.主要类

@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
@Builder
@Entity
@Table(name="tb_person")
public class Person {

    @Id
    @GeneratedValue
    private Integer pId;
    private String pName;

}
public interface PersonRepository extends JpaRepository<Person,Integer> {
}
@Service
@Slf4j
public class PersonReidsServiceImpl implements PersonReidsService {

    @Autowired
    PersonRepository personRepository;

	@Cacheable(value="person")
    @Override
    public Person getPerson(Integer pid) {
        log.info("*************getPerson**************");
        return personRepository.findById(pid).orElse(null);
    }

    @Override
    public void addPerson(Person person) {
        log.info("*************addPerson**************");
        personRepository.save(person);
    }

    @Override
    public void updatePerson(Person person) {
        log.info("*************updatePerson**************");
        personRepository.save(person);
    }

    @Override
    public void deletePerson(Integer pid) {
        log.info("*************deletePerson**************");
        personRepository.deleteById(pid);
    }
}
@RestController
@RequestMapping("/demo")
public class PersonController {


    @Autowired
    PersonReidsService personReidsService;


    @RequestMapping("/person/{pid}")
    public Person getPerson(@PathVariable Integer pid) {
        return personReidsService.getPerson(pid);
    }


    @PostMapping("/person")
    public String addPerson(@RequestBody Person person) {
        personReidsService.addPerson(person);
        return "addPerson suc!";
    }


    @PutMapping("/person")
    public String updatePerson(@RequestBody Person person) {
        personReidsService.updatePerson(person);
        return "updatePerson suc!";
    }


    @DeleteMapping("/person/{pid}")
    public String delPerson(@PathVariable Integer pid) {
        personReidsService.deletePerson(pid);
        return "delPerson suc!";
    }

}

6.执行
查询第一次,会从数据库里读取数据,第二次后,直接从缓存里读取,因为第一次查询后,就会把结果存放到缓存里,第二次直接从缓存里获取
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

下面把其他注解用上

   /**
     * Cacheable:表示第一次查询的时候,先从数据库查询,然后再把数据放到
     * 缓存中,第二次就直接从缓存中获取
     * @param pid
     * @return
     */
    @Cacheable(value="person")
    @Override
    public Person getPerson(Integer pid) {
        log.info("*************getPerson**************");
        return personRepository.findById(pid).orElse(null);
    }

    /**
     * CachePut:新增和修改,都会调用数据库,然后把数据更新到缓存
     * 当再次查询的时候,只要缓存中有,则直接从缓存中拿取
     * @param person
     */
    @CachePut(value = "person",key="#person.pId")
    @Override
    public void addPerson(Person person) {
        log.info("*************addPerson**************");
        personRepository.save(person);
    }

    @CachePut(value = "person",key="#person.pId")
    @Override
    public void updatePerson(Person person) {
        log.info("*************updatePerson**************");
        personRepository.save(person);
    }

    /**
     * CacheEvict:当删除一个数据时,这个会把数据库的那条数据删除掉
     * 同时,也把缓存中的那条数据也给删除掉
     * @param pid
     */
    @CacheEvict(value = "person",key = "#pid")
    @Override
    public void deletePerson(Integer pid) {
        log.info("*************deletePerson**************");
        personRepository.deleteById(pid);
    }

4.4 缓存注解说明

4.4.1 @Cacheable

通常应用到读取数据的方法上,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据查询结果添加到缓存中。如果缓存中查找到数据,被注解的方法将不会执行。

  • value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
  • key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。我们这里先来看看自定义策略,至于默认策略会在后文单独介绍。
    自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。下面是几个使用参数作为key的示例。
    @Cacheable(value=“users”, key="#id")
    public User find(Integer id)
    @Cacheable(value=“users”, key="#p0")
    public User find(Integer id)
    在这里插入图片描述

4.4.2 @CachePut

通常应用于保存和修改方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发被注解方法的调用。
在这里插入图片描述

4.4.3 @CachEvict

通常应用于删除方法配置,能够根据一定的条件对缓存进行清空。可以清除一条或多条缓存。
在这里插入图片描述

4.4.4 @Caching

如果有两种不同的需求,都是放在同一个方法上,这种需求如果只是使用@CacheEvict或者@CachePut是无法实现,因为他们不能多样化的作用在同一个方法上。可以使用@Caching(evict={@CacheEvict(“a1”),@CacheEvict(“a2”,allEntries=true)})


    @Caching(
            cacheable = {
                    @Cacheable(value = "emp", key = "#lastName")
            },
            put = {                  //更新缓存可以通过id,email或者lastName进行key值查找。
                    @CachePut(value = "emp", key = "#result.id"),
                    @CachePut(value = "emp", key = "#result.email"),
                    @CachePut(value = "emp", key = "#result.lastName"),
            }
    )
    public Employee getEmpByLastName(String lastName) {
        System.out.println("要查询的用户名为:" + lastName);
        return employeeMapper.getEmpBylastName(lastName);
    }

4.4.5 失效时间配置

@Configuration
public class RedisConfig {



    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //重点在这四行代码
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    //本节的重点配置,让Redis缓存的序列化方式使用redisTemplate.getValueSerializer()
    //不在使用JDK默认的序列化方式
//    @Bean
//    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
//        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
//        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
//                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
//        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
//    }


    //自定义redisCacheManager
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());

        RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
                this.buildRedisCacheConfigurationWithTTL(redisTemplate,RedisCacheConfiguration.defaultCacheConfig().getTtl().getSeconds()),  //默认的redis缓存配置
                this.getRedisCacheConfigurationMap(redisTemplate)); //针对每一个cache做个性化缓存配置

        return  redisCacheManager;
    }

    //配置注入,key是缓存名称,value是缓存有效期
    private Map<String,Long> ttlmap;

    //根据ttlmap的属性装配结果,个性化RedisCacheConfiguration
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(RedisTemplate redisTemplate) {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();

        ttlmap = new HashMap<>();
        ttlmap.put("person",20L);
        for(Map.Entry<String, Long> entry : ttlmap.entrySet()){
            String cacheName = entry.getKey();
            Long ttl = entry.getValue();
            redisCacheConfigurationMap.put(cacheName,this.buildRedisCacheConfigurationWithTTL(redisTemplate,ttl));
        }

        return redisCacheConfigurationMap;
    }


    //让缓存的序列化方式使用redisTemplate.getValueSerializer(),并未每一个缓存分别设置ttl
    private RedisCacheConfiguration buildRedisCacheConfigurationWithTTL(RedisTemplate redisTemplate,Long ttl){
        return  RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
                .entryTtl(Duration.ofSeconds(ttl));
    }

}

5.其他

5.1 参考

完整SpringBoot Cache整合redis缓存
springboot整合spring @Cache和Redis

spring 2.0以上 整合redis和cache后使用@Cacheable 时间失效

SpringBoot 2.x 整合 redis 做缓存并对每个缓存空间设置过期时间

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值