springboot整合redis?

xfk發表於2019-08-14

  2019-08-13

一直想在springboot上整合帶快取的redis,終於成功了。網上有1000種寫法,想找到一篇合適的還真不容易?。走下流程,加深下印象。

 環境:

springboot版本:2.1.7

orm框架:mybatis

實現?:

在serviceImpl層方法上加註解@Cacheable和@CachEvict。

@Cacheable把資料放進redis,下一次需要資料直接在快取中取;@CacheEvict使redis中的快取失效。

關於註解的更多詳細可以參考https://www.cnblogs.com/fashflying/p/6908028.html寫得很詳細。

準備工作?:

pom.xml?:

 1 <parent>
 2         <groupId>org.springframework.boot</groupId>
 3         <artifactId>spring-boot-starter-parent</artifactId>
 4         <version>2.1.7.RELEASE</version>
 5         <relativePath/>
 6 </parent>
 7 
 8 <dependencies>
 9         <dependency>
10             <groupId>org.springframework.boot</groupId>
11             <artifactId>spring-boot-starter-web</artifactId>
12         </dependency>
13         <dependency>
14             <groupId>org.springframework.boot</groupId>
15             <artifactId>spring-boot-starter-test</artifactId>
16             <scope>test</scope>
17         </dependency>
18         <!-- servlet依賴. -->
19         <dependency>
20             <groupId>javax.servlet</groupId>
21             <artifactId>javax.servlet-api</artifactId>
22         </dependency>
23         <dependency>
24             <groupId>javax.servlet</groupId>
25             <artifactId>jstl</artifactId>
26         </dependency>
27 
28         <!--tomcat-->
29         <dependency>
30             <groupId>org.apache.tomcat.embed</groupId>
31             <artifactId>tomcat-embed-jasper</artifactId>
32         </dependency>
33 
34         <!--熱部署-->
35         <dependency>
36             <groupId>org.springframework.boot</groupId>
37             <artifactId>spring-boot-devtools</artifactId>
38             <optional>true</optional>
39         </dependency>
40 
41         <!-- mysql -->
42         <dependency>
43             <groupId>mysql</groupId>
44             <artifactId>mysql-connector-java</artifactId>
45             <version>5.1.21</version>
46         </dependency>
47         <!--mybatis-->
48         <dependency>
49             <groupId>org.mybatis.spring.boot</groupId>
50             <artifactId>mybatis-spring-boot-starter</artifactId>
51             <version>1.1.1</version>
52         </dependency>
53         <!--mybatis逆向工程-->
54         <dependency>
55             <groupId>org.mybatis.generator</groupId>
56             <artifactId>mybatis-generator-core</artifactId>
57             <version>1.3.7</version>
58         </dependency>
59 
60         <!--json格式-->
61         <dependency>
62             <groupId>com.fasterxml.jackson.datatype</groupId>
63             <artifactId>jackson-datatype-jsr310</artifactId>
64         </dependency>
65 
66         <!-- redis -->
67         <dependency>
68             <groupId>org.springframework.boot</groupId>
69             <artifactId>spring-boot-starter-data-redis</artifactId>
70         </dependency>
71 
72         <!--thymeleaf-->
73         <!--<dependency>-->
74             <!--<groupId>org.springframework.boot</groupId>-->
75             <!--<artifactId>spring-boot-starter-thymeleaf</artifactId>-->
76         <!--</dependency>-->
77     </dependencies>
View Code

application.properties檔案?:

#mvc
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/common?characterEncoding=utf-8
spring.datasource.username=xfk
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#mybatis
mybatis.mapper-locations=classpath:/mapper/*.xml
mybatis.type-aliases-package=com.xfk.sb.pojo

#redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.timeout=5000
View Code

 

書寫?:

一,允許使用快取:

?在springboot的主啟動類上新增註解@EnableCaching

SbApplication.java

 1 package com.xfk.sb;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cache.annotation.EnableCaching;
 6 
 7 @SpringBootApplication
 8 @EnableCaching
 9 public class SbApplication {
10     public static void main(String[] args) {
11         SpringApplication.run(SbApplication.class, args);
12     }
13 }

 

 二,redis配置類:

?這裡一個重要的點就是Serializer。RedisCache預設使用的是JdkSerializationRedisSerializer,我們要實現json格式的資料在redis上的儲存。利用Jackson2JsonRedisSerializerGenericJackson2JsonRedisSerializer,其優點是儲存的長度小。在這裡我們用GenericJackson2JsonRedisSerializer。成功之後可以換Jackson2JsonRedisSerializer試試,看一看儲存的資料有什麼不同。

?cacheManager方法是用作註解@Cacheable和@CacheEvict執行service實現層方法快取資料的,另外就是定義一個redisTemplate,哪個controller需要就在哪個controller中注入,靈活使用。

RedisConfig.java

 1 package com.xfk.sb.config;
 2 
 3 import org.springframework.cache.annotation.CachingConfigurerSupport;
 4 import org.springframework.cache.annotation.EnableCaching;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.data.redis.cache.RedisCacheConfiguration;
 8 import org.springframework.data.redis.cache.RedisCacheManager;
 9 import org.springframework.data.redis.connection.RedisConnectionFactory;
10 import org.springframework.data.redis.core.RedisTemplate;
11 import org.springframework.data.redis.serializer.*;
12 import java.time.Duration;
13 
14 @Configuration
15 @EnableCaching
16 public class RedisConfig extends CachingConfigurerSupport {
17     // 過期時間
18     private Duration timeToLive = Duration.ofHours(12);
19 
20     @Bean
21     public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
22         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
23                 .entryTtl(this.timeToLive)
24                 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
25                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
26                 .disableCachingNullValues();
27 
28         return RedisCacheManager.builder(connectionFactory)
29                 .cacheDefaults(config)
30                 .transactionAware()
31                 .build();
32     }
33     
34     @Bean(name = "redisTemplate")
35     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
36         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
37         redisTemplate.setConnectionFactory(redisConnectionFactory);
38         redisTemplate.setKeySerializer(keySerializer());
39         redisTemplate.setHashKeySerializer(keySerializer());
40         redisTemplate.setValueSerializer(valueSerializer());
41         redisTemplate.setHashValueSerializer(valueSerializer());
42         return redisTemplate;
43     }
44 
45     private RedisSerializer<String> keySerializer() {
46         return new StringRedisSerializer();
47     }
48 
49     private RedisSerializer<Object> valueSerializer() {
50         return new GenericJackson2JsonRedisSerializer();
51     }
52 }

 

三,增刪改查Demo:

簡單的pojo類,Student.java

 1 package com.xfk.sb.pojo;
 2 
 3 public class Student {
 4     private int id;
 5     private String name;
 6     private int age;
 7 
 8     public Student() {
 9     }
10 
11     public int getId() {
12         return id;
13     }
14 
15     public void setId(int id) {
16         this.id = id;
17     }
18 
19     public String getName() {
20         return name;
21     }
22 
23     public void setName(String name) {
24         this.name = name;
25     }
26 
27     public int getAge() {
28         return age;
29     }
30 
31     public void setAge(int age) {
32         this.age = age;
33     }
34 
35     @Override
36     public String toString() {
37         return "Student{" +
38                 "id=" + id +
39                 ", name='" + name + '\'' +
40                 ", age=" + age +
41                 '}';
42     }
43 }
View Code

?我使用的是mybatis的註解方式,簡單的幾個增刪改查方法。

 

xml檔案,studentMapper.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 
 6 <mapper namespace="com.xfk.sb.mapper.StudentMapper">
 7     <select id="selectStudent" resultType="com.xfk.sb.pojo.Student">
 8         select * from student
 9     </select>
10     <delete id="deleteStudent" parameterType="Integer">
11         delete from student where id = #{id}
12     </delete>
13     <insert id="createStudent" parameterType="com.xfk.sb.pojo.Student" keyColumn="id" keyProperty="student.id" useGeneratedKeys="true">
14         insert into student(id, name, age) values(null, #{student.name}, #{student.age})
15     </insert>
16     <update id="updateStudent" parameterType="com.xfk.sb.pojo.Student">
17         update student set name=#{student.name}, age=#{student.age} where id = #{student.id}
18     </update>
19     <select id="selectStudentByPrimaryKey" parameterType="Integer" resultType="com.xfk.sb.pojo.Student">
20         select * from student where id = #{id} limit 1
21     </select>
22 </mapper>
View Code

 

mapper介面,StudentMapper.java

 1 package com.xfk.sb.mapper;
 2 
 3 import com.xfk.sb.pojo.Student;
 4 import org.apache.ibatis.annotations.Mapper;
 5 import org.apache.ibatis.annotations.Param;
 6 import org.springframework.stereotype.Component;
 7 import java.util.List;
 8 
 9 @Mapper
10 @Component
11 public interface StudentMapper {
12     List<Student> selectStudent();
13     int deleteStudent(@Param("id")int id);
14     int createStudent(@Param("student")Student student);
15     int updateStudent(@Param("student")Student student);
16     Student selectStudentByPrimaryKey(@Param("id")int id);
17 }
View Code

 

service層介面,StudentService.java

 1 package com.xfk.sb.service;
 2 
 3 import com.xfk.sb.pojo.Student;
 4 import java.util.List;
 5 
 6 public interface StudentService {
 7     List<Student> selectStudent();
 8     int deleteStudent(int id);
 9     int createStudent(Student student);
10     int updateStudent(Student student);
11     Student selectStudentByPrimaryKey(int id);
12 }
View Code

 

service實現層,StudentServiceImpl.java

 1 package com.xfk.sb.service.implement;
 2 
 3 import com.xfk.sb.mapper.StudentMapper;
 4 import com.xfk.sb.pojo.Student;
 5 import com.xfk.sb.service.StudentService;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.cache.annotation.CacheConfig;
 8 import org.springframework.cache.annotation.CacheEvict;
 9 import org.springframework.cache.annotation.Cacheable;
10 import org.springframework.cache.annotation.Caching;
11 import org.springframework.stereotype.Service;
12 import java.util.List;
13 
14 @Service
15 @CacheConfig(cacheNames="students")
16 public class StudentServiceImpl implements StudentService {
17     private final StudentMapper studentMapper;
18 
19     @Autowired
20     public StudentServiceImpl(StudentMapper studentMapper) {
21         this.studentMapper = studentMapper;
22     }
23 
24     @Cacheable(key="'students'")
25     @Override
26     public List<Student> selectStudent() {
27         System.out.println("從資料庫中取selectStudent");
28         return studentMapper.selectStudent();
29     }
30 
31     @Override
32     @Caching(evict={
33             @CacheEvict(key="'singleStudent'+#id"),
34             @CacheEvict(key="'students'"),
35     })
36     public int deleteStudent(int id) {
37         System.out.println("從資料庫中刪除deleteStudent");
38         return studentMapper.deleteStudent(id);
39     }
40 
41     @Override
42     @Caching(evict={
43             @CacheEvict(key="'singleStudent'+#student.id"),
44             @CacheEvict(key="'students'"),
45     })
46     public int createStudent(Student student) {
47         System.out.println("從資料庫中建立createStudent");
48         return studentMapper.createStudent(student);
49     }
50 
51     @Caching(evict={
52             @CacheEvict(key="'singleStudent'+#student.id"),
53             @CacheEvict(key="'students'"),
54     })
55     @Override
56     public int updateStudent(Student student) {
57         System.out.println("從資料庫中更新updateStudent");
58         return studentMapper.updateStudent(student);
59     }
60 
61     @Cacheable(key="'singleStudent'+#p0")
62     @Override
63     public Student selectStudentByPrimaryKey(int id) {
64         System.out.println("從資料庫中取一個selectStudentByPrimaryKey");
65         return studentMapper.selectStudentByPrimaryKey(id);
66     }
67 }

?使用@CacheConfig註解,相當於在redis資料庫下建一個資料夾,以cacheNames作為資料夾的名字,統一管理這個實現層快取的資料。正如在Redis Desktop Manager下看到的目錄結構,db0下有一個students資料夾。

 

?使用@Cacheable註解使快取生效,以實現層的selectStudentByPrimaryKey()方法為例,從資料庫中根據id查詢一個Student物件。

使用@Cacheable(key="'singleStudent'+#p0"),#p0就是形參parameter0,多個引數就是#p1,#p2,,,也可以寫成#id,注意singleStudent字串一定要用單引號擴上,然後使用字串的拼接模式,這個變數的規則是spring的EL表示式,一定要用加上#符號,如果是一個物件則可以直接用"."引用屬性,參考createStudent()方法中的#student.id 。

屬性key相當於在students資料夾下的資料夾建立一條以singleStudent+#p0為名字的一條快取資料,在Redis Desktop Manager可以看到,由於students資料夾下的資料夾沒有名字,所以成功快取資料的命名是students::singleStudent1,兩個引號之間為空。這就相當以這個名稱空間下的唯一key,可以根據唯一key準確的失效快取資料。

 

?@CacheEvict註解使快取失效,根據需求要保證資料庫與快取的一致性,所以運算元據庫之後要同步快取。

在更新,刪除和增加後要使快取失效,不能返回過時的資訊。在這裡使用@Caching的目的是使多條快取失效,它集合了@Cacheable,@CacheEvict,@CachePut,可以很直觀的管理生效與失效。還可以直接使用@CacheEvict(allEntries=true)使這個名稱空間下的所有快取失效。

到這裡核心工作完成得差不多了,就還差controller返回檢視層了。

 

controller層,StudentController.java

 1 package com.xfk.sb.web;
 2 
 3 import com.xfk.sb.pojo.Student;
 4 import com.xfk.sb.service.StudentService;
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.data.redis.core.StringRedisTemplate;
 7 import org.springframework.stereotype.Controller;
 8 import org.springframework.ui.Model;
 9 import org.springframework.web.bind.annotation.*;
10 import java.util.List;
11 
12 @Controller
13 public class StudentController {
14     private final StudentService studentService;
15     private final StringRedisTemplate redis;
16 
17     @Autowired
18     public StudentController(StudentService studentService, StringRedisTemplate redis) {
19         this.studentService = studentService;
20         this.redis = redis;
21     }
22 
23     @GetMapping("/students")
24     public String listStudent(Model model){
25         List<Student> students = studentService.selectStudent();
26         model.addAttribute("students", students);
27         return "listStudent";
28     }
29 
30     @DeleteMapping("/students/{id}")
31     public String deleteStudent(@PathVariable("id")int id) throws Exception{
32         studentService.deleteStudent(id);
33         return "redirect:/students";
34     }
35 
36     @PutMapping("/students")
37     public String updateStudent(Student student){
38         studentService.updateStudent(student);
39         return "redirect:/students";
40     }
41     @PostMapping("/students")
42     public String createStudent(Student student){
43         studentService.createStudent(student);
44         return "redirect:/students";
45     }
46 
47     @GetMapping("/students/{id}")
48     public String editStudent(@PathVariable("id")int id, Model model){
49         Student s = studentService.selectStudentByPrimaryKey(id);
50         model.addAttribute("student", s);
51         return "editStudent";
52     }
53 
54     @RequestMapping("/test")
55     public String test(Model model){
56         List<Student> students = studentService.selectStudent();
57         model.addAttribute("students", students);
58         return "test";
59     }
60 
61     @RequestMapping("/getOne")
62     public String getOne(Model model){
       // 獲取id為1的Student物件到test.jsp
63 Student student = studentService.selectStudentByPrimaryKey(1); 64 model.addAttribute("student", student); 65 return "test"; 66 } 67 }

?使用的restful風格,返回的jsp頁面,/test和/getOne用來驗證快取是否生效。

 

小貼一下jsp:

listStudent.jsp

 1 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 2 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 3 <html>
 4 <head>
 5     <title>students</title>
 6 </head>
 7 <body>
 8 <form action="${pageContext.request.contextPath}/students" method="post">
 9     增加:<br/>
10     name:<input type="text" name="name"/>&emsp;<input type="number" name="age" /><br/>
11     <input type="submit" value="提交"/>
12 </form>
13 <table>
14     <tr>
15         <th>id</th>
16         <th>name</th>
17         <th>age</th>
18         <th>編輯</th>
19         <th>刪除</th>
20     </tr>
21     <c:forEach items="${students}" var="each">
22         <tr>
23             <td>${each.id}</td>
24             <td>${each.name}</td>
25             <td>${each.age}</td>
26             <td><a href="${pageContext.request.contextPath}/students/${each.id}">修改</a></td>
27             <td>
28                 <button class="deleteStudent" value="${pageContext.request.contextPath}/students/${each.id}">刪除</button>
29             </td>
30         </tr>
31     </c:forEach>
32     <form id="deleteType" action="" method="POST">
33         <input type="hidden" name="_method" value="DELETE"/>
34     </form>
35 </table>
36 <button class="hhh">驗證你的jquer是否生效</button>
37 <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
38 <script>
39     $(function(){
40         $(".deleteStudent").click(function(){
41             var href = $(this).attr("value");
42             alert(href);
43             $("#deleteType").attr("action", href).submit();
44         })
45         $(".hhh").click(function(){
46             alert("你的jquer已生效");
47         })
48     })
49 </script>
50 </body>
51 </html>
View Code

 

editStudent.jsp

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 2 <html>
 3 <head>
 4     <title>editStudent</title>
 5 </head>
 6 <body>
 7 <form action="${pageContext.request.contextPath}/students" method="post">
 8     <input type="hidden" name="_method" value="PUT"/>
 9     <input type="hidden" name="id" value="${student.id}"/>
10     name:<input type="text" name="name" value="${student.name}"/><br/>
11     age&nbsp;:<input type="text" name="age" value="${student.age}"/><br/>
12     <input type="submit" value="更新"/><a href="${pageContext.request.contextPath}/">返回主頁</a>
13 </form>
14 </body>
15 </html>
View Code

 

test.jsp

 1 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 2 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 3 <html>
 4 <head>
 5     <title>test</title>
 6 </head>
 7 <body>
 8 <c:forEach items="${students}" var="each">
 9     <div>${each.id}, ${each.name}, ${each.age}</div>
10 </c:forEach>
11 <br/>
12 得到一個Student<br/>
13 ${student.id}, ${student.name}, ${student.age}
14 </body>
15 </html>

 

四,驗證測試:

?步驟:

1,/getOne

由於StudentServiceImpl.java中的System.out.println("從資料庫中取一個selectStudentByPrimaryKey"); 檢視後臺控制檯,可以知道這條資料是從資料庫中獲取的。

/getOne獲取的Student的id為1,所以會在Redis Desktop Manager中看到一條singleStudent1的快取記錄。

http://localhost:8080/getOne

2,更改Redis Desktop Manager中的記錄

?比如redis庫中原來的資料是

?更改資料,因為這裡改的資料是redis的,mysql裡的資料還是沒變,這樣就知道了是從快取中讀取的資料

?點選save之後,重新整理http://localhost:8080/getOne

 成功!????? 然後@CacheEvict是一樣的邏輯,指定失效的key就好了

github:https://github.com/CaseyFu/sb

參考博文:https://liuyanzhao.com/9207.html

相關文章