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版:

Kotlin版:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章