分布式学习笔记SpringBoot整合redis实现mybatis二级缓存
- 格式:pdf
- 大小:489.13 KB
- 文档页数:5
SpringBoot与Redis缓存集成实践一、引言随着现代应用程序的复杂性不断增加,缓存成为提高系统性能和响应速度的重要手段之一。
而SpringBoot与Redis的集成可以帮助我们更加高效地实现缓存功能。
本文将介绍SpringBoot与Redis缓存集成的实践,从配置Redis、使用RedisTemplate进行缓存操作以及解决缓存穿透与缓存雪崩等问题进行探讨。
二、配置Redis1. 引入依赖在pom.xml文件中添加SpringDataRedis的依赖:```xml<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>```2. 配置Redis连接参数在application.properties或application.yml文件中配置Redis连接参数:```spring.redis.host=127.0.0.1spring.redis.port=6379spring.redis.database=0spring.redis.password=```其中,host为Redis服务器的IP地址,port为端口号,database为数据库索引,password为连接密码(如果有设置)。
3. 配置RedisTemplate在SpringBoot的配置类中,通过@Bean注解创建一个RedisTemplate的实例,并设置相关参数,例如连接工厂、序列化器等:```java@Configurationpublic class RedisConfig {@Beanpublic RedisTemplate<String, Object>redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(newGenericJackson2JsonRedisSerializer());return redisTemplate;}}```在上述代码中,我们使用了StringRedisSerializer对key进行序列化,使用了GenericJackson2JsonRedisSerializer对value进行序列化。
Mybatis缓存配置——⼆级缓存⼀、配置⼆级缓存1. 在mybatis_config.xml中进⾏如下配置:<setting name="cacheEnabled" value="true"/>其实这⾥的⼆级缓存默认是出于开启状态,因此这个位置可以不进⾏配置,知道有这么回事⼉即可。
2.MyBatis⼆级缓存是和命名空间是绑定的,即⼆级缓存需要配置在 Mapper.xml 映射⽂件中,或者配置在 Mapper.java 接⼝中。
在映射⽂件中命名空间就是 XML 根节点 mapper namespace 属性 Mapper 接⼝中,命名空间就是接⼝的全限定名称。
(1)在RoleMapper.xml中进⾏⼆级缓存配置,添加配置如下:<?xml version="1.0" encoding="utf-8" ?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.example.simple.mapper.RoleMapper"><cacheeviction="FIFO"flushInterval="6000"size="512"readOnly="false"/>/mapper>(2)在RoleMapper.java类中添加注解:@CacheNamespaceRef(RoleMapper.class)public interface RoleMapper {//代码}最基本的⼆级缓存就配置好了。
SpringBoot+MySQL+MyBatis整合Redis实现缓存操作本地安装 Redis项⽬结构:SpringBootRedis ⼯程项⽬结构如下: controller - Controller 层 dao - 数据操作层 model - 实体层 service - 业务逻辑层 Application - 启动类 resources 资源⽂件夹 application.properties - 应⽤配置⽂件,应⽤启动会⾃动读取配置 generatorConfig.xml - mybatis 逆向⽣成配置(这⾥不是本⽂只要重点,所以不进⾏介绍) mapper ⽂件夹 StudentMapper.xml - mybatis 关系映射 xml ⽂件项⽬⼯程代码详情pom.xml 配置<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.7.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!-- web 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- tomcat插件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope></dependency><!-- Test 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- mysql 依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><!-- redis 依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.1.6.RELEASE</version></dependency><!-- mybatis 依赖 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><!-- Gson json 格式化 --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!-- mybatis.generator 插件 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.7</version><configuration><configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile><overwrite>true</overwrite></configuration><!-- 数据库依赖 --><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.15</version></dependency></dependencies></plugin></plugins></build></project>application.properties 配置spring:datasource:url: jdbc:mysql://localhost:3306/mydb?useSSL=trueusername: oukelepassword: oukeledriver-class-name: com.mysql.jdbc.Driver# 配置 redisredis:# redis 数据库索引(默认为0)database: 0# redis 服务地址host: 127.0.0.1# redis 连接端⼝port: 6379# redis 服务器链接密码(默认为空)password:# 连接超时时间(毫秒)timeout: 5000# 配置 redis 连接池jedis:pool:# 连接池最⼤连接数 (使⽤负值表⽰没有限制)max-active: 8# 连接池最⼤阻塞等待时间(使⽤负值表⽰没有限制)max-wait: -1# 连接池的最⼤空闲连接max-idle: 8# 连接池中最⼩空闲连接min-idle: 0# 配置 mybatismybatis:# 设置实体类所在的包名typeAliasesPackage: com.example.demo.model# mybatis xml 映射关系mapper-locations: classpath:mapper/*.xml项⽬结构图model 层 Student.javapackage com.example.demo.model;public class Student {private Integer id;private String numbercode;private String stuname;private String stusex;private Integer stuage;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getNumbercode() {return numbercode;}public void setNumbercode(String numbercode) {this.numbercode = numbercode == null ? null : numbercode.trim(); }public String getStuname() {return stuname;}public void setStuname(String stuname) {this.stuname = stuname == null ? null : stuname.trim();}public String getStusex() {return stusex;}public void setStusex(String stusex) {this.stusex = stusex == null ? null : stusex.trim();}public Integer getStuage() {return stuage;}public void setStuage(Integer stuage) {this.stuage = stuage;}@Overridepublic String toString() {return "Student{" +"id=" + id +", numbercode='" + numbercode + '\'' +", stuname='" + stuname + '\'' +", stusex='" + stusex + '\'' +", stuage=" + stuage +'}';}}View Codedao 层 StudentMapper.java (使⽤了注解 + xml 形式 )package com.example.demo.dao;import com.example.demo.model.Student;import org.apache.ibatis.annotations.Delete;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.annotations.Select;import org.springframework.stereotype.Repository;import java.util.List;@Repositorypublic interface StudentMapper {@Select("select * from student")List<Student> selectAll();@Select("select * from student where numbercode = #{numberCode}") Student getStudent(@Param("numberCode") String numberCode);@Delete("delete from student where numbercode = #{numberCode}") int delete(@Param("numberCode") String numberCode);int update(Student student);int insert(Student student);}View Codeservice 层 StudentService.javapackage com.example.demo.service;import com.example.demo.model.Student;import java.util.List;public interface StudentService {List<Student> selectAll();Student getStudent(String numberCode);int delete(String numberCode);int update(Student student);int insert(Student student);}View Codeservice imple (业务实现)层 StudentServiceImpl.javapackage com.example.demo.service.impl;import com.example.demo.dao.StudentMapper;import com.example.demo.model.Student;import com.example.demo.service.StudentService;import com.google.gson.Gson;import com.google.gson.reflect.TypeToken;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;import org.springframework.stereotype.Service;import ng.reflect.Type;import java.util.List;import java.util.concurrent.TimeUnit;@Servicepublic class StudentServiceImpl implements StudentService {@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic List<Student> selectAll() {String key = "student_list";Boolean hasKey = redisTemplate.hasKey(key);ValueOperations operations = redisTemplate.opsForValue();if (hasKey) {String redisList = (String) operations.get(key);Type type = new TypeToken<List<Student>>() {}.getType();List<Student> list = new Gson().fromJson(redisList,type);System.out.println("StudentServiceImpl.selectAll() : 从缓存取得数据,条数:" + list.size());return list;}List<Student> list = studentMapper.selectAll();String toJson = new Gson().toJson(list);// 存在到缓存中operations.set(key, toJson, 10, TimeUnit.SECONDS);return list;}/*** 获取⼀位学⽣逻辑:* 如果缓存存在,从缓存中获取学⽣信息* 如果缓存不存在,从 DB 中获取学⽣信息,然后插⼊缓存*/@Overridepublic Student getStudent(String numberCode) {// 从缓存中取出学⽣信息String key = "student_" + numberCode;Boolean hasKey = redisTemplate.hasKey(key);ValueOperations operations = redisTemplate.opsForValue();// 缓存存在if (hasKey) {String str = (String) operations.get(key);Student student = new Gson().fromJson(str, Student.class);System.out.println("StudentServiceImpl.getStudent() : 从缓存取得数据 >> " + student.toString());return student;}Student student = studentMapper.getStudent(numberCode);String str = new Gson().toJson(student);// 插⼊缓存中operations.set(key, str, 10, TimeUnit.SECONDS);System.out.println("StudentServiceImpl.getStudent() : 学⽣信息插⼊缓存 >> " + student.toString());return student;}@Overridepublic int delete(String numberCode) {String key = "student_" + numberCode;Boolean hasKey = redisTemplate.hasKey(key);int delete = studentMapper.delete(numberCode);if( delete > 0){// 缓存存在,进⾏删除if (hasKey) {redisTemplate.delete(key);System.out.println("StudentServiceImpl.update() : 从缓存中删除编号学⽣ >> " + numberCode); }}return delete;}/*** 更新学⽣信息逻辑:* 如果缓存存在,删除* 如果缓存不存在,不操作*/@Overridepublic int update(Student student) {String key = "student_" + student.getNumbercode();Boolean hasKey = redisTemplate.hasKey(key);int update = studentMapper.update(student);if( update > 0 ){// 缓存存在,进⾏删除if (hasKey) {redisTemplate.delete(key);System.out.println("StudentServiceImpl.update() : 从缓存中删除学⽣ >> " + student.toString()); }}return update;}@Overridepublic int insert(Student student) {String key = "student_list";int insert = studentMapper.insert(student);if (insert > 0) {redisTemplate.delete(key);}return insert;}}View Codecontroller 层 StudentController.javapackage com.example.demo.controller;import com.example.demo.model.Student;import com.example.demo.service.StudentService;import com.google.gson.Gson;import org.apache.ibatis.annotations.Param;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;@RestController@RequestMapping(path = "/student")public class StudentController {@Autowiredprivate StudentService studentService;@GetMapping( path = "/getList")public List<Student> getList(){List<Student> students = studentService.selectAll();return students;}@GetMapping( path = "/getStudent")public String getList(@Param("numberCode") String numberCode){ Student students = studentService.getStudent(numberCode); String str = new Gson().toJson(students);return str;}@PostMapping(path = "/insert")public String insert(@RequestBody Student student){int insert = studentService.insert(student);String msg = "";if( insert > 0 ){msg = "{\"msg\":\"新增成功\",\"flag\":true}";}else {msg = "{\"msg\":\"新增失败\",\"flag\":false}";}return msg;}@GetMapping(path = "/delete")public String delete(@Param("numberCode") String numberCode){ int delete = studentService.delete(numberCode);String msg = "";if( delete > 0 ){msg = "{\"msg\":\"删除成功!!\",\"flag\":true}";}else {msg = "{\"msg\":\"删除失败!!\",\"flag\":false}";}return msg;}@PostMapping(path = "/update")public String update(@RequestBody Student student){int update = studentService.update(student);String msg = "";if( update > 0 ){msg = "{\"msg\":\"更新成功\",\"flag\":true}";}else {msg = "{\"msg\":\"更新失败\",\"flag\":false}";}return msg;}}View Code。
程序员看了不后悔一步一步轻松实现SpringBoot整合Redis缓存前言随着技术的发展,程序框架也越来越多,非常考验程序的学习能力,但是框架的产生在程序员开发过程中也提供很多便利,框架可以为程序员减少许多工作量,比如spring boot 、mybatis等都可以提高开发人员的工作效率,今天就来聊聊spring boot与redis 的整合。
一、基本概况为什么使用缓存缓存是在内存中存储的数据备份,当数据没有发生本质变化时就可以直接从内存中查询数据,而不用去数据库查询(在磁盘中)CPU读取内存的速度要比读取磁盘快,可以提高效率Redis缓存Remote Dictionnary Server(远程数据服务),是一款内存高速缓存数据库。
五种常用数据类型: String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)可持久化:一边运行,一边向硬盘备份一份,防止断电等偶然情况,导致内存中数据丢失二、搭建Redis环境1.下载Redis平台限制不能贴链接,自行去官网下载哈!2.设置Redis开机自启在解压好的文件夹下输入cmd命令window下安装Redis服务redis-server --service-install redis.windows.conf检查安装是否成功搜索服务点击设置为开机自启三、新建SpringBoot项目新建好项目的童鞋可以自动跳过添加web依赖选择数据库依赖选择项目位置,点击finish四、使用StringRedisTemplate操作Redis1.pom.xml文件引入坐标<!--redis依赖包--> <dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>2.在appliaction.properties配置redis数据库连接信息#redis配置#Redis服务器地址spring.redis.host=127.0.0.1#Redis服务器连接端口spring.redis.port=6379#Redis数据库索引(默认为0)spring.redis.database=03.在SpringbootdemoApplicationTests中测试操作Redis@SpringBootTestclass SpringbootdemoApplicationTests {@Autowired StringRedisTemplate stringRedisTemplate; //操作key-value都是字符串,最常用@Test public void test01(){ //字符串操作stringRedisTemplate.opsForValue().append("msg","coder"); //列表操作stringRedisTemplate.opsForList().leftPush("mylist","1"); stringRedisTemplate.opsForList().leftPush("mylist","2"); }}对于Redis的五大常用数据类型都提供了方法String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)stringRedisTemplate.opsForValue();[String(字符串)]stringRedisTemplate.opsForList();[List(列表)]stringRedisTemplate.opsForSet();[Set(集合)]stringRedisTemplate.opsForHash();[Hash(散列)]stringRedisTemplate.opsForZSet();[ZSet(有序集合)]使用RedisDesktopManager可视化工具查看结果StringTemplate类中方法存取的key-value值是String类型,RedisTemplate中key-value值是Object类型,RedisTemplate是StringTemplate父类下面就用RedisTemplate实现从MySQL数据库取出数据放到Redis缓存五、使用RedisTemplate操作Redis1.项目目录结构2.建立与数据库相关的类建表的sql脚本application.properties配置文件MySQL及Redis连接的相关配置User类采用ORM思想,属性和数据库字段对应package com.thinkcoder.bean;import java.io.Serializable;importjava.util.Date;/*** @ClassName User * @Author Think-Coder * @Data 2020/5/27 10:35* @Version 1.0 */public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex;private String address; //getter和setter方法 public Integer getId() {returnid;} public void setId(Integer id) {this.id = id;} public StringgetUsername() {return username;} public void setUsername(String username){ername = username;} public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;} publicString getSex() {return sex;} public void setSex(String sex) {this.sex = sex;}public String getAddress() {return address;} public void setAddress(Stringaddress) {this.address = address;} //重写toString方法 @Override publicString toString() { return "User{" + "id=" + id +", username='" + username + '\'' + ", birthday=" + birthday + ", sex='" + sex + '\'' + ", address='" + address + '\'' + '}'; }}UserMapper类使用注解方法操作数据库@Mapperpublic interface UserMapper {@Select("SELECT * FROM user WHERE id =#{id}") User findById(int id);}3.MyRedisConfig自定义序列化类,将存储在Redis的对象序列化为json格式,不会产生乱码@Configuration@EnableAutoConfigurationpublic class MyRedisConfig {@Beanpublic RedisTemplate<Object, User> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<Object, User> template = newRedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer<User> ser = newJackson2JsonRedisSerializer<>(User.class);template.setDefaultSerializer(ser); return template; }}4.工具类RedisUtil类//工具类中使用Autowired注解需要加上Compoent@Componentpublic class RedisUtil{@Autowired RedisTemplate redisTemplate; //key-value是对象的 //判断是否存在key public boolean hasKey(String key){ returnredisTemplate.hasKey(key); } //从redis中获取值 public Object get(String key){ return redisTemplate.opsForValue().get(key); } //向redis插入值 public boolean set(final String key,Object value){ boolean result = false; try{ redisTemplate.opsForValue().set(key,value); result = true; }catch (Exceptione){ e.printStackTrace(); } return result; }}5.sevice包代码IUserService@Servicepublic interface IUserService {//根据id查用户信息 UserfindById(int id);}UserService实现类@Servicepublic class UserServiceImpl implements IUserService {User user;@Autowired UserMapper userMapper; @Autowired private RedisUtil redisUtil; @Override public User findById(int id) { String key = "user"+id;if(redisUtil.hasKey(key)) { user = (User)redisUtil.get(key); System.out.println("查询的是缓存"); }else{ user =userMapper.findById(id); System.out.println("查询的是数据库"); System.out.println(redisUtil.set(key,user) ? "插入成功" : "插入失败"); } return user; }}erController类@RestControllerpublic class UserController {@Autowired IUserService userService; @GetMapping("/user/{id}") public UserfindById(@PathVariable("id") Integer id){ User user =userService.findById(id); return user; }}7.测试打开浏览器输入下方url查看控制台输出Redis Desktop Manager显示结果六、总结整体来说,操作Redis是两个类,RedisTemplate类和StringTemplate类,为父子关系,提供的方法正好对应操作Redis数据类型的指令,所以要把数据类型及常用的指令练熟。
SpringBoot+SpringCache实现两级缓存(Redis+Caffeine)1. 缓存、两级缓存1.1 内容说明Spring cache:主要包含spring cache定义的接⼝⽅法说明和注解中的属性说明springboot+spring cache:rediscache实现中的缺陷caffeine简介spring boot+spring cache实现两级缓存使⽤缓存时的流程图1.2 Sping Cachespring cache是spring-context包中提供的基于注解⽅式使⽤的缓存组件,定义了⼀些标准接⼝,通过实现这些接⼝,就可以通过在⽅法上增加注解来实现缓存。
这样就能够避免缓存代码与业务处理耦合在⼀起的问题。
spring cache的实现是使⽤spring aop中对⽅法切⾯(MethodInterceptor)封装的扩展,当然spring aop也是基于Aspect来实现的。
spring cache核⼼的接⼝就两个:Cache和CacheManager1.2.1 Cache接⼝提供缓存的具体操作,⽐如缓存的放⼊,读取,清理,spring框架中默认提供的实现有1.2.2 CacheManager接⼝主要提供Cache实现bean的创建,每个应⽤⾥可以通过cacheName来对Cache进⾏隔离,每个CaheName对应⼀个Cache实现,spring框架中默认提供的实现与Cache的实现都是成对出现的1.2.3 常⽤的注解说明@Cacheable:主要应⽤到查询数据的⽅法上@CacheEvict:清除缓存,主要应⽤到删除数据的⽅法上@CachePut:放⼊缓存,主要⽤到对数据有更新的⽅法上@Caching:⽤于在⼀个⽅法上配置多种注解@EnableCaching:启⽤spring cache缓存,作为总的开关,在spring boot的启动类或配置类上需要加⼊次注解才会⽣效2.实战多级缓存的⽤法package com.xfgg.demo.config;import lombok.AllArgsConstructor;import com.github.benmanes.caffeine.cache.Caffeine;import org.springframework.cache.CacheManager;import org.springframework.cache.caffeine.CaffeineCacheManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.concurrent.TimeUnit;@Configuration@AllArgsConstructor//把定义的缓存加⼊到Caffeine中public class CacheConfig {@Beanpublic CacheManager cacheManager(){CaffeineCacheManager cacheManager = new CaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder()//使⽤refreshAfterWrite必须要设置cacheLoader//在5分钟内没有创建/覆盖时,会移除该key,下次取的时候从loading中取【重点:失效、移除Key、失效后需要获取新值】.expireAfterWrite(5, TimeUnit.MINUTES)//初始容量.initialCapacity(10)//⽤来控制cache的最⼤缓存数量.maximumSize(150));return cacheManager;}}package com.xfgg.demo.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.connection.RedisPassword;import org.springframework.data.redis.connection.RedisStandaloneConfiguration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;//⽣成的redis连接public class RedisConfig<GenericObjectPoolConfig> {@Value("${spring.redis1.host}")private String host;@Value("${spring.redis1.port}")private Integer port;@Value("${spring.redis1.password}")private String password;@Value("${spring.redis1.database}")private Integer database;@Value("${spring.redis1.lettuce.pool.max-active}")private Integer maxActive;@Value("${spring.redis1.lettuce.pool.max-idle}")private Integer maxIdle;@Value("${spring.redis1.lettuce.pool.max-wait}")private Long maxWait;@Value("${spring.redis1.lettuce.pool.min-idle}")private Integer minIdle;@Beanpublic RedisStandaloneConfiguration redis1RedisConfig() {RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();config.setHostName(host);config.setPassword(RedisPassword.of(password));config.setPort(port);config.setDatabase(database);return config;}//配置序列化器@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object>template=new RedisTemplate<>();//关联template.setConnectionFactory(factory);//设置key的序列化器template.setKeySerializer(new StringRedisSerializer());//设置value的序列化器template.setValueSerializer(new StringRedisSerializer());return template;}}⼀个使⽤cacheable注解,⼀个使⽤redistemplate进⾏缓存因为公司项⽬中⽤到的是jedis和jediscluster所以这⾥只是做个了解,没有写的很细到此这篇关于SpringBoot+SpringCache实现两级缓存(Redis+Caffeine)的⽂章就介绍到这了,更多相关SpringBoot SpringCache两级缓存内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!。
MyBatis二级缓存的用法什么是MyBatis二级缓存?在理解MyBatis二级缓存之前,我们先来了解一下MyBatis的一级缓存。
MyBatis的一级缓存是指SqlSession级别的缓存,它默认开启,并且是线程私有的。
也就是说,在同一个SqlSession中执行相同的SQL语句,第一次查询会将查询结果放入缓存中,后续再执行相同的SQL语句时,直接从缓存中获取结果,而不需要再去数据库查询。
而MyBatis的二级缓存则是SqlSessionFactory级别的缓存,它可以被多个SqlSession共享。
当多个SqlSession共享同一个SqlSessionFactory时,如果其中一个SqlSession执行了查询操作并将结果放入了二级缓存中,在后续其他SqlSession执行相同的查询操作时,可以直接从二级缓存中获取结果。
如何配置MyBatis二级缓存?要使用MyBatis二级缓存,我们需要进行以下几个步骤:1.在MyBatis配置文件(例如mybatis-config.xml)中开启二级缓存:<configuration><settings><setting name="cacheEnabled" value="true"/></settings></configuration>2.在需要使用二级缓存的Mapper接口上添加@CacheNamespace注解:@CacheNamespacepublic interface UserMapper {// ...}3.在需要进行缓存的查询语句上添加@Options注解,设置useCache属性为true:@Select("SELECT * FROM user WHERE id = #{id}")@Options(useCache = true)User getUserById(Long id);经过以上配置,我们就可以开始使用MyBatis的二级缓存了。
一、默认开启一级缓存。
一级缓存是 SqlSession 级别的。
具体什么意思测试一下。
一次事务中,同一语句调用两次,代码:VarDateEntity varDateEntity = dateMapper.getVarDate();System.out.println("var1 var:"+varDateEntity.getVarTime());System.out.println("var1 not:"+varDateEntity.getNotTime());System.out.println("var1 null:"+varDateEntity.getNullTime());varDateEntity = dateMapper.getVarDate();System.out.println("var2 var:"+varDateEntity.getVarTime());System.out.println("var2 not:"+varDateEntity.getNotTime());System.out.println("var2 null:"+varDateEntity.getNullTime());Postman 中执行两次:测试结果:结论1:同一事务中的两次查询,只查询了一次。
但是两次事务中,每次均进行了一次查询;一级缓存的 scope 默认值session;设置为:statment 。
重启 springboot 应用再次测试:结论2:设置mybatis.configuration.local-cache-scope=statement后,即使 xmxxxxl 语句中配置useCache="true",一级缓存均失效。
总结:•Mybatis一级缓存的生命周期和SqlSession一致。
springboot整合redis缓存1.【准备】pom.xml⽂件加⼊redis依赖<!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>2.【载⼊】⾃动装配redis类模板@Autowiredprivate RedisTemplate redisTemplate;注:使⽤该模板直接在redis客户端查看内容key编码需转换,需要另外配置⼀般使⽤:StringRedisTemplate 模板,已经是使⽤String类型模板不需要额外配置3.【使⽤】设置key-value进缓存@Testvoid set(){ValueOperations ops = redisTemplate.opsForValue();ops.set("name","zhangsan",5,TimeUnit.SECONDS);}4.【使⽤】查看缓存内容@Testvoid get(){ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();System.out.println(ops.get("name"));}5.【补充】存⼊和查看Hash值进缓存// 存⼊hash值内容@Testvoid hset(){HashOperations ops = redisTemplate.opsForHash();ops.put("info","name","张三");}// 查看hash内容@Testvoid hget(){HashOperations ops = redisTemplate.opsForHash();System.out.println(ops.get("info","name"));}。
Mybatis整合Redis实现⼆级缓存mybatis集成ehcache1、集成ehcache2、集成redis1. 为什么需要缓存拉⾼程序的性能2. 什么样的数据需要缓存很少被修改或根本不改的数据业务场景⽐如:耗时较⾼的统计分析sql、电话账单查询sql等3. ehcache是什么Ehcache 是现在最流⾏的纯Java开源缓存框架,配置简单、结构清晰、功能强⼤注1:本章介绍的是2.X版本,3.x的版本和2.x的版本API差异⽐较⼤4. ehcache的特点4.1够快Ehcache的发⾏有⼀段时长了,经过⼏年的努⼒和不计其数的性能测试,Ehcache终被设计于large, high concurrency systems.4.2够简单开发者提供的接⼝⾮常简单明了,从Ehcache的搭建到运⽤运⾏仅仅需要的是你宝贵的⼏分钟。
其实很多开发者都不知道⾃⼰⽤在⽤Ehcache,Ehcache被⼴泛的运⽤于其他的开4.3够袖珍关于这点的特性,官⽅给了⼀个很可爱的名字small foot print ,⼀般Ehcache的发布版本不会到2M,V 2.2.3才 668KB。
4.4够轻量核⼼程序仅仅依赖slf4j这⼀个包,没有之⼀!4.5好扩展Ehcache提供了对⼤数据的内存和硬盘的存储,最近版本允许多实例、保存对象⾼灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性⽀持热配置、⽀持的插件多4.6监听器缓存管理器监听器(CacheManagerListener)和缓存监听器(CacheEvenListener),做⼀些统计或数据⼀致性⼴播挺好⽤的4.7分布式缓存从Ehcache 1.2开始,⽀持⾼性能的分布式缓存,兼具灵活性和扩展性3、ehcache的使⽤3.1 导⼊相关依赖3.2 核⼼接⼝CacheManager:缓存管理器Cache:缓存对象,缓存管理器内可以放置若⼲cache,存放数据的实质,所有cache都实现了Ehcache接⼝Element:单条缓存数据的组成单位4. ssm中整合ehcache4.1 导⼊相关依赖<dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version></dependency><!--mybatis与ehcache整合--><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.1.0</version></dependency><!--ehcache依赖--><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.0</version></dependency>4.2 修改⽇志配置,因为ehcache使⽤了Slf4j作为⽇志输出⽇志我们使⽤slf4j,并⽤log4j来实现。
mybatis 二级缓存实现原理MyBatis是一个流行的持久层框架,它提供了二级缓存来提高数据库访问性能。
二级缓存可以在多个会话之间共享数据,从而减少数据库访问次数,提高系统性能。
下面我将从多个角度来解释MyBatis二级缓存的实现原理。
1. 缓存范围,MyBatis的二级缓存是SessionFactory级别的缓存,也就是说,多个SqlSession共享同一个二级缓存。
当多个会话对同一数据进行操作时,第一个会话查询的数据会被放入二级缓存中,后续的会话可以直接从缓存中获取数据,而不需要再次访问数据库。
2. 缓存实现机制,MyBatis的二级缓存是通过Cache接口来实现的,它提供了缓存数据的存储、读取和移除等操作。
MyBatis默认使用PerpetualCache作为二级缓存的实现,它采用HashMap来存储缓存数据。
3. 缓存更新策略,MyBatis的二级缓存采用了基于时间戳和事务的缓存更新策略。
当一个会话对数据进行了更新、插入或删除操作时,会清空该数据对应的缓存项,从而保证缓存数据的一致性。
此外,MyBatis还提供了flushCache属性来控制是否在执行SQL语句后清空缓存。
4. 缓存配置,MyBatis的二级缓存可以通过配置文件进行开启和关闭。
在MyBatis的配置文件中,可以使用<setting>元素的<setting name="cacheEnabled" value="true"/>来开启二级缓存。
5. 缓存失效,MyBatis的二级缓存可以通过配置缓存的失效时间来控制缓存数据的有效期。
当缓存中的数据超过设定的时间没有被访问时,数据将失效并被移除。
总的来说,MyBatis的二级缓存实现原理是通过缓存范围、缓存实现机制、缓存更新策略、缓存配置和缓存失效等多个方面来保证数据的一致性和有效性,从而提高系统的性能和并发访问能力。
springboot(七).springboot整合jedis实现redis缓存我们在使⽤springboot搭建微服务的时候,在很多时候还是需要redis的⾼速缓存来缓存⼀些数据,存储⼀些⾼频率访问的数据,如果直接使⽤redis的话⼜⽐较⿇烦,在这⾥,我们使⽤jedis来实现redis缓存来达到⾼效缓存的⽬的,接下来,让我们⼀起来使⽤jedis来实现redis 缓存 1.在pom.xml⽂件中添加依赖<!-- jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.2</version></dependency> 2. 在springboot的配置⽂件中加⼊redis的配置信息#redis jedis配置# Redis数据库索引(默认为0)spring.redis.database=0# Redis服务器地址spring.redis.host=10.100.140.84# Redis服务器连接端⼝spring.redis.port=6379# Redis服务器连接密码(默认为空)#spring.redis.password=# 连接池最⼤连接数(使⽤负值表⽰没有限制)spring.redis.pool.max-active=200# 连接池最⼤阻塞等待时间(使⽤负值表⽰没有限制)spring.redis.pool.max-wait=-1# 连接池中的最⼤空闲连接spring.redis.pool.max-idle=8# 连接池中的最⼩空闲连接spring.redis.pool.min-idle=0# 连接超时时间(毫秒)spring.redis.timeout=0#spring-session 使⽤spring.session.store-type=none 3.创建jedis配置⽂件,配置⽂件的作⽤是在项⽬启动的时候将jedis注⼊,接着我们就可以在其他类中获取到JedisPool类的信息@Configurationpublic class JedisConfig extends CachingConfigurerSupport{private Logger logger = LoggerFactory.getLogger(JedisConfig.class);/*** SpringSession 需要注意的就是redis需要2.8以上版本,然后开启事件通知,在redis配置⽂件⾥⾯加上* notify-keyspace-events Ex* Keyspace notifications功能默认是关闭的(默认地,Keyspace 时间通知功能是禁⽤的,因为它或多或少会使⽤⼀些CPU的资源)。
mybatis二级缓存整合redis生成的key的规律MyBatis 二级缓存整合 Redis 生成的 Key 的规律:在 MyBatis 二级缓存与 Redis 整合的场景中,生成的 Key 具有特定的规律和模式。
想象一下,MyBatis 二级缓存就像是一个巨大的宝库,而 Redis 则是宝库的管理员。
这个管理员在给宝库中的宝贝做标记时,可不是随便乱来的,而是遵循着一套严格的规则。
生成的 Key 就像是宝库中宝贝的“身份证号码”。
它可不是简单的几个数字或字母的组合,而是包含了很多重要的信息。
比如,会包含相关的Mapper 接口的全限定名,这就好像是宝贝所属的“家族”名称。
还会有方法名,这就像是宝贝在家族中的“具体身份”。
另外,一些关键的参数也会被包含进来,就像是宝贝的独特“特征”。
打个比方,这就好比是一个超级详细的身份信息。
比如说有一个查找用户信息的方法,那么生成的 Key 可能就包含了“erMapper.findUserById”这样的接口名,再加上“findUserById”这个方法名,以及传入的用户 ID 这个“特征”。
这样,Redis 就能准确无误地识别和管理每个缓存数据,就像管理员能清楚地知道每个宝贝的来历和归属。
我们来看看实际中的例子。
假如一个电商网站,在查询商品详情的时候,每次都要去数据库获取数据,那速度得多慢呀!有了 MyBatis二级缓存整合 Redis 后,生成的准确的 Key 就能快速找到之前缓存的商品详情数据,大大提高了查询效率。
据相关测试数据表明,在高并发的场景下,合理利用 MyBatis 二级缓存整合 Redis 生成的 Key 规律,能够让系统的响应速度提升 30%以上,这对于用户体验的提升是非常显著的。
总结一下,了解 MyBatis 二级缓存整合 Redis 生成的 Key 的规律,对于优化系统性能、提高数据访问效率有着至关重要的作用。
就像在一场激烈的赛车比赛中,掌握了最佳的路线和策略,就能一马当先,快速冲线。
springboot+springcache实现两级缓存(redis+caffeine)spring boot中集成了spring cache,并有多种缓存⽅式的实现,如:Redis、Caffeine、JCache、EhCache等等。
但如果只⽤⼀种缓存,要么会有较⼤的⽹络消耗(如Redis),要么就是内存占⽤太⼤(如Caffeine这种应⽤内存缓存)。
在很多场景下,可以结合起来实现⼀、⼆级缓存的⽅式,能够很⼤程度提⾼应⽤的处理效率。
内容说明:1. 缓存、两级缓存2. spring cache:主要包含spring cache定义的接⼝⽅法说明和注解中的属性说明3. spring boot + spring cache:RedisCache实现中的缺陷4. caffeine简介5. spring boot + spring cache 实现两级缓存(redis + caffeine)缓存、两级缓存简单的理解,缓存就是将数据从读取较慢的介质上读取出来放到读取较快的介质上,如磁盘-->内存。
平时我们会将数据存储到磁盘上,如:数据库。
如果每次都从数据库⾥去读取,会因为磁盘本⾝的IO影响读取速度,所以就有了像redis这种的内存缓存。
可以将数据读取出来放到内存⾥,这样当需要获取数据时,就能够直接从内存中拿到数据返回,能够很⼤程度的提⾼速度。
但是⼀般redis是单独部署成集群,所以会有⽹络IO上的消耗,虽然与redis集群的链接已经有连接池这种⼯具,但是数据传输上也还是会有⼀定消耗。
所以就有了应⽤内缓存,如:caffeine。
当应⽤内缓存有符合条件的数据时,就可以直接使⽤,⽽不⽤通过⽹络到redis中去获取,这样就形成了两级缓存。
应⽤内缓存叫做⼀级缓存,远程缓存(如redis)叫做⼆级缓存spring cache当使⽤缓存的时候,⼀般是如下的流程:从流程图中可以看出,为了使⽤缓存,在原有业务处理的基础上,增加了很多对于缓存的操作,如果将这些耦合到业务代码当中,开发起来就有很多重复性的⼯作,并且不太利于根据代码去理解业务。
mybatis 二级缓存 cache 参数MyBatis二级缓存参数详解一、概述MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。
MyBatis提供了两种类型的缓存:一级缓存和二级缓存。
其中,一级缓存在MyBatis内部实现,而二级缓存则是由用户自定义实现的。
二级缓存通常用于提高查询效率,减少数据库的访问压力。
本篇文章将详细介绍MyBatis二级缓存的参数设置。
二、缓存参数设置MyBatis的二级缓存可以通过配置文件进行设置。
以下是一些常用的缓存参数:1. <property name="cacheEnabled" value="true|false" />该属性用于启用或禁用二级缓存。
默认值为true,表示启用二级缓存。
如果设置为false,则不会使用二级缓存,所有的查询操作都会直接发送到数据库。
2. <property name="cacheKeyGenerator" value="自定义的KeyGenerator" />该属性用于指定用于生成缓存键的KeyGenerator。
MyBatis提供了默认的KeyGenerator,但有时可能需要自定义KeyGenerator以满足特定的需求。
3. <property name="cacheEvictionPolicy" value="自定义的EvictionPolicy" />该属性用于指定缓存的驱逐策略。
MyBatis提供了几种默认的EvictionPolicy,如FIFO(先进先出)和LFU(最少使用)。
如果需要使用自定义的EvictionPolicy,则需要实现EvictionPolicy接口。
4. <property name="cacheCloner" value="true|false" />该属性用于启用或禁用二级缓存的克隆功能。
Mybatis-plus使⽤redis做⼆级缓存1. mybatis-plus开启⼆级缓存spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverjdbc-url: jdbc:mysql://192.168.222.155:3306/sys?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf-8 username: rootpassword: 123456redis:host: 39.104.203.155port: 6380password: 123456database: 1timeout: 2000ms # 连接超时时间(毫秒)默认是2000mslettuce:pool:max-active: 200 # 连接池最⼤连接数(使⽤负值表⽰没有限制)max-wait: -1ms # 连接池最⼤阻塞等待时间(使⽤负值表⽰没有限制)max-idle: 100 # 连接池中的最⼤空闲连接min-idle: 50 # 连接池中的最⼩空闲连接shutdown-timeout: 100ms# sentinel: # 哨兵模式# master: mymaster# nodes: 192.168.222.155:26379,192.168.222.155:26380,192.168.222.155:26381mybatis-plus:mapper-locations: classpath*:/mapper/*.xmltype-aliases-package: com.redis.shaobing.entityglobal-config:db-config:id-type: autotable-underline: trueconfiguration:cache-enabled: truemap-underscore-to-camel-case: true2. ⾃定义⾃⼰的缓存管理package com.redis.shaobing.utils;import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.cache.Cache;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** @author shuangyueliao* @create 2019/9/10 14:02* @Version 0.1*/@Slf4jpublic class MybatisRedisCache implements Cache {// 读写锁private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);//这⾥使⽤了redis缓存,使⽤springboot⾃动注⼊private RedisTemplate<String, Object> redisTemplate;private String id;public MybatisRedisCache(final String id) {if (id == null) {throw new IllegalArgumentException("Cache instances require an ID");}this.id = id;}public RedisTemplate<String, Object> getRedisTemplate() {redisTemplate = (RedisTemplate<String, Object>) ApplicationContextUtils.getBean("redisTemplate");Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper mapper = new ObjectMapper();mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);serializer.setObjectMapper(mapper);//使⽤StringRedisSerializer来序列化和反序列化redis的key值redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(serializer);redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(serializer);redisTemplate.afterPropertiesSet();return redisTemplate;}@Overridepublic String getId() {return this.id;}@Overridepublic void putObject(Object key, Object value) {redisTemplate = getRedisTemplate();if (value != null) {redisTemplate.opsForHash().put(id.toString(), key.toString(), value);}}@Overridepublic Object getObject(Object key) {redisTemplate = getRedisTemplate();try {if (key != null) {return redisTemplate.opsForHash().get(id.toString(), key.toString());}} catch (Exception e) {e.printStackTrace();log.error("缓存出错 ");}return null;}@Overridepublic Object removeObject(Object key) {redisTemplate = getRedisTemplate();if (key != null) {redisTemplate.delete(key.toString());}return null;}@Overridepublic void clear() {System.out.println("清空缓存");log.debug("清空缓存");redisTemplate = getRedisTemplate();redisTemplate.delete(id.toString());}@Overridepublic int getSize() {redisTemplate = getRedisTemplate();Long size = redisTemplate.opsForHash().size(id.toString());return size.intValue();}@Overridepublic ReadWriteLock getReadWriteLock() {return this.readWriteLock;}}3. 在mapper上加上注解@CacheNamespace或者在Mpper.xml中加⼊ <cache type="com.redis.shaobing.utils.MybatisRedisCache"/>两种⽅式根据实际情况⼆选⼀即可!@CacheNamespace(implementation= MybatisRedisCache.class,eviction=MybatisRedisCache.class) @Mapperpublic interface SysConfigDao extends BaseMapper<SysConfig> {}如果调⽤该mapper下的⽅法,那么会使⽤redis缓存。
SpringBoot+MyBatis+Redis(⼆级缓存)应⽤场景:保存⼤数据量,避免重复请求。
⼀、添加Maven依赖<!-- SpringBoot Boot Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>⼆、编写Redis相关类RedisService.javaimport java.util.Collection;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.HashOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;import ponent;@SuppressWarnings(value = { "unchecked", "rawtypes" })@Componentpublic class RedisService{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit) {return redisTemplate.expire(key, timeout, unit);}/*** 获得缓存的基本对象。
SpringBoot下mybatis的缓存背景: 说起 mybatis,作为 Java 程序员应该是⽆⼈不知,它是常⽤的数据库访问框架。
与 Spring 和 Struts 组成了 Java Web 开发的三剑客---SSM。
当然随着 Spring Boot 的发展,现在越来越多的企业采⽤的是 SpringBoot + mybatis 的模式开发,我们公司也不例外。
⽽ mybatis 对于我也仅仅停留在会⽤⽽已,没想过怎么去了解它,更不知道它的缓存机制了,直到那个⽣死难忘的 BUG。
故事的背景⽐较长,但并不是啰嗦,只是让读者知道这个 BUG 触发的场景,加深记忆。
在遇到类似问题时,可以迅速定位。
先说下故事的前提,为了防⽌⽤户在动态中输⼊特殊字符,⽤户的动态都是编码后发到后台,⽽后台在存⼊到 DB 表之前会解码以⽅便在 DB 中查看以及上报到搜索引擎。
⽽在查询⽤户动态的时候先从 DB 表中读取并在后台做⼀次编码再传到前端,前端再解码既可以正常展⽰了。
流程如下图: 有⼀天后端预发环境发布完毕后,⽤户的动态页⾯有的动态显⽰正常,⽽有的动态却是被编码过的。
看到现象后的第⼀个反应就是部分被编码了两次,但是编码操作只会在 service 层的 findById 中有。
理论不会在上层犯这种低级错误,于是开始排查新增加的代码。
发现只要进⼊了新增加代码中的某个 if 分⽀则被编码了两次。
分⽀中除了再次调⽤ findById(必要性不讨论),也⽆其他特殊代码了。
百思不得其解后请教了旁边的⽼司机,⽼司机说可能是 mybatis 缓存。
于是看了下我代码,将编码的操作从 findById 中移出来后再次发布到预发,正常了,⼼想⽼司机不愧是⽼司机。
本次 BUG 触发的有两个条件需要注意:整个操作过程都在⼀个函数中,⽽函数上⾯加了 @Transactional 的注解(对 mybatis 来说是在同⼀个 SESSION 中)⼀般只会调⽤ findByIdy ⼀次,如果进⼊分⽀则会调⽤两次(第⼀次调⽤后做了编码后被缓存,第⼆次从缓存读后继续被编码) 于是,便开始⾕歌 mybatis 的缓存机制,搜到了⼀篇⾮常不错的⽂章《聊聊 mybatis 的缓存机制》,推荐⼤家看⼀下,特别是⾥⾯的流程图。
springboot2集成redis缓存序列化springboot 缓存为了实现是在数据中查询数据还是在缓存中查询数据,在application.yml 中将mybatis 对应的mapper 包⽇志设置为debug 。
spring:datasource:username: rootpassword: rootpasswordurl: jdbc:mysql://localhost:3306/springbootdriver-class-name: com.mysql.jdbc.Driverdebug: truelogging:level:com:springbootmybatis:mapper: debug然后在springboot的主类上添加 @EnableCaching 启动缓存。
然后在service 类中的⽅法上添加上缓存注解。
@Cacheable(value = "user")public User selectUserById(Integer id) {User user = userMapper.selectUserById(id);return user;}@Cacheable默认的是将传⼊参数(id)作为缓存的 key ,⽅法的返回值作为 value 存⼊缓存中。
在⽅法 selectUserById(Integer id ) 执⾏之前先去根据 key 去缓存中查询是否有该 key 的数据,如果有,则直接在缓存中查询数据,然后返回,不再执⾏ selectUserById ⽅法,如果没有在缓存中查到该 key 的数据,才回去执⾏ selectUserById ⽅法。
@CachePut@CachePut(value = "user")public User updateUser(User user) {userMapper.updateUser(user);return user;}默认的是将传⼊参数(id)作为缓存的 key ,@CachePut 在⽅法执⾏之后执⾏,将⽅法返回的结果写⼊缓存,从缓存中查询到的仍然是旧的缓存数据,需要在 @CachePut(value = "user",key = "#result.id") 或者@CachePut(value = "user",key ="#user.id") 只要在 key 设置为 user 的 id ,然后根据id 去查询,就能从缓存中获取修改后的数据。
springboot中使⽤⾃定义两级缓存的⽅法⼯作中⽤到了springboot的缓存,使⽤起来挺⽅便的,直接引⼊redis或者ehcache这些缓存依赖包和相关缓存的starter依赖包,然后在启动类中加⼊@EnableCaching注解,然后在需要的地⽅就可以使⽤@Cacheable和@CacheEvict使⽤和删除缓存了。
这个使⽤很简单,相信⽤过springboot缓存的都会玩,这⾥就不再多说了。
美中不⾜的是,springboot使⽤了插件式的集成⽅式,虽然⽤起来很⽅便,但是当你集成ehcache的时候就是⽤ehcache,集成redis的时候就是⽤redis。
如果想两者⼀起⽤,ehcache作为本地⼀级缓存,redis作为集成式的⼆级缓存,使⽤默认的⽅式据我所知是没法实现的(如果有⾼⼈可以实现,⿇烦指点下我)。
毕竟很多服务需要多点部署,如果单独选择ehcache可以很好地实现本地缓存,但是如果在多机之间共享缓存⼜需要⽐较费时的折腾,如果选⽤集中式的redis缓存,因为每次取数据都要⾛⽹络,总感觉性能不会太好。
本话题主要就是讨论如何在springboot的基础上,⽆缝集成ehcache和redis作为⼀⼆级缓存,并且实现缓存同步。
为了不要侵⼊springboot原本使⽤缓存的⽅式,这⾥⾃⼰定义了两个缓存相关的注解,如下@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface Cacheable {String value() default "";String key() default "";//泛型的Class类型Class<?> type() default Exception.class;}@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface CacheEvict {String value() default "";String key() default "";}如上两个注解和spring中缓存的注解基本⼀致,只是去掉了⼀些不常⽤的属性。