SpringBoot+Redis作為二級快取整合的基本Demo

Yulinasham發表於2019-04-09

一、Redis簡介

1、概述

  Redis 是一個高效能的key-value資料庫。 redis的出現,很大程度補償了memcached這類key/value儲存的不足,在部 分場合可以對關聯式資料庫起到很好的補充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客戶端,使用很方便。

2、優點

(1)資料操作全在記憶體,讀寫效能強。
(2)資料結構豐富,支援string,hash,list,set及zset(sorted set)。
(3)支援主從複製,以及資料持久化等

二、Redis的搭建

1、安裝

按順序執行如下命令:

$ wget http://download.redis.io/releases/redis-5.0.4.tar.gz
$ tar xzf redis-5.0.4.tar.gz
$ cd redis-5.0.4
$ make
$ make install
複製程式碼

2、測試

開啟服務

$ redis-server
複製程式碼

啟動客戶機互動測試

$ redis-cli -p 6379
127.0.0.1:6379> set k1 k2
OK
127.0.0.1:6379> get k1
"k2"
127.0.0.1:6379> 

複製程式碼

三、基本環境配置

這裡使用MyBatis作為持久層框架,使用Druid作為連線池,使用MySql資料庫。

1、POM依賴

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <!-- Druid開啟SQL監控所需要的依賴-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
複製程式碼

2、YML配置檔案

server:
  port: 80
spring:
  http:
    encoding:
      charset: UTF-8
  cache:
    type: redis
  redis:
    #redis伺服器地址
    host: 192.168.78.128
    #設定埠號,預設6379
    port: 6379
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/test
#控制檯檢視SQL執行狀況
logging:
  level:
   com.yurui.rediscache: debug
複製程式碼

3、測試連線

package com.yurui.rediscache;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RediscacheApplicationTests {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    @Test
    public void contextLoads() {
        stringRedisTemplate.opsForValue().set("k1","v1");
        System.out.println(stringRedisTemplate.opsForValue().get("k1"));
    }
}
複製程式碼

測試成功效果:

測試成功
測試失敗的可能原因:

1、Linux防火牆沒設定好
2、啟動伺服器前配置檔案內沒有註釋bind 127.0.0.1
3、配置檔案中沒把protected-mode yes改為no

四、三個基本的快取標籤

1、@Cacheable

  @Cacheable可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支援快取的,當標記在一個類上時則表示該類所有的方法都是支援快取的。對於一個支援快取的方法,Spring會在其被呼叫後將其返回值快取起來,以保證下次利用同樣的引數來執行該方法時可以直接從快取中獲取結果,而不需要再次執行該方法。

2、@CachePut

  @CachePut可以宣告一個方法支援快取功能。與@Cacheable不同的是使用@CachePut標註的方法在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的快取中。

3、@CacheEvict

  @CacheEvict是用來標註在需要清除快取元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發快取的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。

三者共有屬性:

  • value
    表示對應生成快取的名稱,以及所操作的快取的名稱

  • key
    表示需要三種操作所操作的key值,如未指定則會使用預設策略生成的key;

  • condition
    表示操作發生的條件

@CacheEvict的allEntries和beforeInvocation屬性

  • allEntries
      allEntries是boolean型別,表示是否需要清除快取中的所有元素。預設為false,表示不需要。當指定了allEntries為true時,Spring Cache將忽略指定的key。可一下清除所有名稱相同的快取。
  • beforeInvocation
      清除操作預設是在對應方法成功執行之後觸發的,即方法如果因為丟擲異常而未能成功返回時也不會觸發清除操作。使用beforeInvocation可以改變觸發清除操作的時間,當我們指定該屬性值為true時,Spring會在呼叫該方法之前清除快取中的指定元素。

五、基本的Demo

專案結構

專案結構

Entity類:

package com.yurui.rediscache.entity;

import org.springframework.stereotype.Component;
import java.io.Serializable;      //    返回實體快取需要實現可序列化

@Component
public class User implements Serializable {
    private int userId;
    private String username;
    //  setters,getters,toString()方法已被省略
}
複製程式碼

由於業務邏輯簡單,取消了Service層

Dao層:

package com.yurui.rediscache.dao;

import com.yurui.rediscache.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserDao {
    @Insert("insert into user(userId,username) values(#{userId},#{username})")
    int userInsert(User user);

    @Select("select * from user where userId=#{userId}")
    User userQuery(int userId);
}
複製程式碼

Controller:

package com.yurui.rediscache.cotroller;

import com.yurui.rediscache.dao.UserDao;
import com.yurui.rediscache.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {

    @Autowired
    private UserDao userDao;

    @PostMapping("user")
    public String userAdd(User user) {
        return userDao.userInsert(user) != 0 ? "success" : "fail";
    }

    //  測試Cacheable註解
    @Cacheable(cacheNames = "User", key = "#userId")
    @GetMapping("/testCacheable/{userId}")
    public User testCacheable(@PathVariable("userId") Integer userId) {
        return userDao.userQuery(userId);
    }

    //  測試CachePut註解
    @CachePut(cacheNames = "User", key = "#userId") //快取名字為"User","userId"作為key
    @GetMapping("/testCachePut/{userId}")
    public User testCachePut(@PathVariable("userId") Integer userId) {
        return userDao.userQuery(userId);
    }

    //  測試CacheEvict註解清空指定使用者快取
    @CacheEvict(cacheNames = "User", key = "#userId")
    @GetMapping("/testCacheEvict/{userId}")
    public String testCacheEvict(@PathVariable("userId") Integer userId) {
        return "cache for " + userId + " has been flushed";
    }

    //  測試CacheEvict註解的allEntries屬性清空所有使用者快取
    @CacheEvict(cacheNames = "User", allEntries = true)
    @GetMapping("/testAllEntries")
    public String testAllEntries() {
        return "All cache has been flushed";
    }
}
複製程式碼

主類

package com.yurui.rediscache;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching                          //    開啟快取註解
public class RediscacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(RediscacheApplication.class, args);
    }

}
複製程式碼

主頁(index.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主頁</title>
</head>
<body>
<h1>新增員工</h1>
<form method="post" action="/user">
    <table>
        <tr>
            <td>userId:</td>
            <td><input type="text" name="userId"></td>
        </tr>
        <tr>
            <td>username:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit"></td>
        </tr>
    </table>
</form>
</body>
</html>
複製程式碼

六、Demo效果測試

測試之前,先向資料庫插入兩條記錄:1、張三,2、李四,並清空redis的快取

127.0.0.1:6379> flushall
OK
複製程式碼

1、Cacheable

分別訪問兩次

localhost/testCacheable/1
localhost/testCacheable/2
複製程式碼

控制檯列印情況:

控制檯
由上圖可得,兩條語句均只列印一次,再查詢redis:

127.0.0.1:6379> keys *
1) "User::2"
2) "User::1"
127.0.0.1:6379> 
複製程式碼

綜上,完成了使用@Cacheable註解實現redis作為二級快取的測試。

2、CachePut

清空快取後分別訪問兩次

localhost/testCachePut/1
localhost/testCachePut/2
複製程式碼

再訪問

localhost/testCacheable/1
複製程式碼

控制檯列印:

控制檯列印
上圖共有四條SQL語句執行,可發現每次訪問@CachePut註解標註的方法時都會直接從資料庫查詢,並且查詢結果已經存入快取。

3、CacheEvict

1、不開啟allEntries

測試前先訪問兩次@Cacheable標註的路徑完成快取,然後分別訪問

http://localhost/testCacheEvict/1
http://localhost/testCacheable/1
http://localhost/testCacheable/2
複製程式碼

控制檯列印:

控制檯輸出
可發現,使用者張三的快取已被清除,李四不受影響,再次訪問testCacheable/1會從資料庫查詢並重新快取張三。

2、開啟allEntries

測試前先訪問兩邊@Cacheable標註的路徑完成快取,然後分別訪問

http://localhost/testAllEntries
http://localhost/testCacheable/1
http://localhost/testCacheable/2
複製程式碼

控制檯列印:

控制檯輸出
可發現,使用者張三、李四的快取均被清空,再次查詢會呼叫資料庫重新快取。

六、CacheManager的定製

1、未定製前

當我們使用引入redis的starter時,容器中預設使用的是RedisCacheManager。它在操作redis時使用的是RedisTemplate<Object, Object>,預設採用JDK的序列化機制,例如redis中檢視張三的快取:

JDK配置
我們可以通過定製CacheManager改變採取序列化機制。

2、進行定製

配置類

package com.yurui.rediscache.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

@Configuration
public class Config {

    @Bean
    public RedisCacheManager JsonCacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                //     使用Jackson2JsonRedisSerializer序列化得到Value
                .serializeValuesWith(RedisSerializationContext.SerializationPair.
                        fromSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)));
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }
}

複製程式碼

快取之後檢視:

Json
其他配置可根據RedisCacheConfiguration中的方法配置。

相關文章