springboot集成redis😁

  • 2019 年 10 月 3 日
  • 笔记

  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