乐趣区

关于redis:Spring-Boot-demo系列十Redis缓存

1 概述

本文演示了如何在 Spring Boot 中将 Redis 作为缓存应用,具体的内容包含:

  • 环境搭建
  • 我的项目搭建
  • 测试

2 环境

  • Redis
  • MySQL
  • MyBatis Plus

3 Redis装置

Redis装置非常简单,以笔者的 Manjaro 为例,间接 paru 装置:

paru -S redis

UbuntuCentOS之类的都提供了软件包装置:

sudo apt install redis
sudo yum install redis

如果想从源码编译装置:

wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
make

Windows以及其余零碎的装置能够参考此处。

4 新建我的项目

新建我的项目,退出如下依赖:

Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>

Gradle

implementation("com.baomidou:mybatis-plus-boot-starter:3.4.2")
implementation("mysql:mysql-connector-java:8.0.23")

我的项目构造:

5 配置类

MyBatis Plus+Redis配置类:

@Configuration
@MapperScan("com.example.demo.dao")
public class MyBatisPlusConfig {}
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(factory);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
        ).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())
        );
        return RedisCacheManager.builder(factory).cacheDefaults(configuration).build();}
}

重点说一下 Redis 配置类,这个类次要生成两个Bean

  • RedisTemplate:简化 Redis 操作的数据拜访类
  • CacheManagerSpring的地方缓存管理器

其中 RedisTemplate 是一个模板类,第一个参数的类型是该 template 应用的键的类型,通常是 String,第二个参数的类型是该template 应用的值的类型,通常为 ObjectSeriazable

setKeySerializersetValueSerializer 别离设置键值的序列化器。键个别为 String 类型,能够应用自带的StringRedisSerializer。对于值,能够应用自带的GenericJackson2RedisSerializer

CacheManager的配置相似,就不从新说了。

6 实体类

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
}

7 长久层

public interface UserMapper extends BaseMapper<User> {}

8 业务层

@org.springframework.stereotype.Service
@Transactional
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class Service {
    private final UserMapper mapper;

    @CachePut(value = "user",key = "#user.id")
    public User save(User user){User oldUser = mapper.selectById(user.getId());
        if(oldUser == null){mapper.insert(user);
            return user;
        }
        if(mapper.updateById(user) == 1)
            return user;
        return oldUser;
    }

    @CacheEvict(value = "user",key = "#id")
    public boolean delete(Integer id){return mapper.deleteById(id) == 1;
    }

    @Cacheable(value = "user",key = "#id")
    public User select(Integer id){return mapper.selectById(id);
    }

    @Cacheable(value="allUser",key = "#root.target+#root.methodName")
    //root.target 是指标类,这里是 com.example.demo.Service,root.methodName 是办法名,这里是 selectAll
    public List<User> selectAll(){return mapper.selectList(null);
    }
}

注解阐明如下:

  • @CachePut:执行办法体再将返回值缓存,个别用于更新数据
  • @CacheEvict:删除缓存,个别用于删除数据
  • @Cacheable:查问缓存,如果有缓存就间接返回,没有缓存的话执行办法体并将返回值存入缓存,个别用于查问数据

三个注解都波及到了 key 以及 value 属性,实际上,真正的存入 Rediskey是两者的组合,比方:

@Cacheable(value="user",key="#id")

则存入的 Redis 中的 key 为:

而存入对应的值为办法返回值序列化后的后果,比方如果返回值为User,则会被序列化为:

9 配置文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
logging:
  level:
    com.example.demo: debug

spring.redis.database指定数据库的索引,默认为 0,hostport 别离指定主机(默认本地)以及端口(默认6379)。

也就是说,简略配置的话能够齐全省略 Redis 相干配置,仅指定数据库连贯url、用户名以及明码:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
logging:
  level:
    com.example.demo: debug

10 启动Redis

10.1 启动 Redis 服务器

Redis服务器启动须要一个配置文件,默认地位为/etc/redis.conf(源码编译装置的话在源文件夹内),倡议先复制一份:

cp /etc/redis.conf ~/Desktop/

默认的配置文件为单机 Redis 配置,端口 6379redis-server 能够间接运行:

sudo redis-server redis.conf

10.2 连贯服务器

连贯能够通过自带的 redis-cli 命令:

redis-cli -h localhost -p 6379

默认状况下能够间接应用

redis-cli

连贯。

基本操作:

  • keys *:查问所有键
  • get key:查问 key 所对应的值
  • flushall:清空所有键

11 测试

@SpringBootTest
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class DemoApplicationTests {

    private final Service service;

    @Test
    void select() {service.select(1);
        service.select(1);
    }

    @Test
    void selectAll(){service.selectAll();
        service.selectAll();}

    @Test
    void delete(){service.delete(1);
    }

    @Test
    void save(){User user = new User(1,"name1");
        service.save(user);
        service.select(user.getId());
        user.setName("name2");
        service.save(user);
        service.select(user.getId());
    }
}

执行其中的 select,会发现MyBatis Plus 只有一次 select 的输入,证实缓存失效了:

而把缓存注解去掉后,会有两次 select 输入:

其它测试方法就不截图了,原理相似。

12 附录:Kotlin中的一些细节

12.1 String数组

其实 @Cacheable/@CacheEvict/@CachePut 中的 value 都是 String [],在Java 中能够间接写上 value,在Kotlin 中须要[value]

12.2 @class

序列化到 Redis 时,实体类会被加上一个 @class 字段:

这个标识供 Jackson 反序列化时应用,笔者一开始的实体类实现是:

data class User(var id:Int?=null, var name:String="")

然而序列化后不携带 @class 字段:

在反序列化时间接报错:

Could not read JSON: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
 at [Source: (byte[])"{"id":1,"name":"name2"}"; line: 1, column: 23]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
 at [Source: (byte[])"{"id":1,"name":"name2"}"; line: 1, column: 23]

解决办法有两个:

  • 手动增加 @class 字段
  • 将实体类设为open

12.2.1 手动增加@class

精确来说并不是手动增加,而是让注解增加,须要增加一个类注解@JsonTypeInfo

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
data class User(var id:Int?=null, var name:String="")

该注解的 use 用于指定类型标识码,该值只能为JsonTypeInfo.Id.CLASS

12.2.2 将实体类设置为open

Java 中,实体类没有任何额定配置,Redis序列化 / 反序列化一样没有问题,是因为值序列化器 GenericJackson2JsonRedisSerializer,该类会主动增加一个@class 字段,因而不会呈现下面的问题。

然而在 Kotlin 中,类默认不是 open 的,也就是无奈增加 @class 字段,因而便会反序列化失败,解决方案是将实体类设置为open

open class User(var id:Int?=null, var name:String="")

然而毛病是不能应用 data class 了。

13 参考源码

Java版:

  • Github
  • 码云
  • CODECHINA

Kotlin版:

  • Github
  • 码云
  • CODECHINA
退出移动版