1 概述
本文演示瞭如何在Spring Boot
中將Redis
作爲緩存使用,具體的內容包括:
- 環境搭建
- 項目搭建
- 測試
2 環境
Redis
MySQL
MyBatis Plus
3 Redis
安裝
Redis
安裝非常簡單,以筆者的Manjaro
爲例,直接paru
安裝:
paru -S redis
Ubuntu
、CentOS
之類的都提供了軟件包安裝:
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
操作的數據訪問類CacheManager
:Spring
的中央緩存管理器
其中RedisTemplate
是一個模板類,第一個參數的類型是該template
使用的鍵的類型,通常是String
,第二個參數的類型是該template
使用的值的類型,通常爲Object
或Seriazable
。
setKeySerializer
和setValueSerializer
分別設置鍵值的序列化器。鍵一般爲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
屬性,實際上,真正的存入Redis
的key
是兩者的組合,比如:
@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,host
與port
分別指定主機(默認本地)以及端口(默認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
配置,端口6379
,redis-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
版: