写点什么

缓存服务详解!SpringBoot 二级缓存服务实现

发布于: 2021 年 05 月 22 日
缓存服务详解!SpringBoot二级缓存服务实现

创建缓存服务

创建缓存服务接口项目

  • 创建 myshop-service-redis-api 项目,该项目只负责定义接口

  • 创建项目的 pom.xml:


<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.oxford</groupId>        <artifactId>myshop-dependencies</artifactId>        <version>1.0.0-SNAPSHOT</version>        <relativePath>../myshop-dependencies/pom.xml</relativePath>    </parent>
<artifactId>myshop-service-redis-api</artifactId> <packaging>jar</packaging></project>
复制代码


  • 定义数据 Redis 接口 RedisService:


package com.oxford.myshop.service.redis.api
public interface RedisService{ void set(String key,Object value); void set(String key,Object value,int seconds);
void del(String key);
Object get(String key);}
复制代码

创建缓存服务提供者项目

  • 创建 myshop-service-redis-provider 项目,该项目用作缓存服务提供者

  • 创建项目的 pom.xml:


<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <parent>        <groupId>com.oxford</groupId>        <artifactId>myshop-dependencies</artifactId>        <version>1.0.0-SNAPSHOT</version>        <relativePath>../myshop-dependencies/pom.xml</relativePath>    </parent>
<artifactId>myshop-service-redis-api</artifactId> <packaging>jar</packaging>
<dependencies> <!-- Spring Boot Starter Settings--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>

<!--Common Setting--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>de.javakaffee</groupId> <artifactId>kryo-serializers</artifactId> </dependency>
<!--Project Settings--> <dependency> <groupId>com.oxford</groupId> <artifactId>my-shop-commons-dubbo</artifactId> <version>${Project.parent.version}</version> </dependency> <dependency> <groupId>com.oxford</groupId> <artifactId>my-shop-service-redis-api</artifactId> <version>${Project.parent.version}</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.oxford.myshop.service.redis.provider.MyshopServiceRedisProviderApplication</mainClass> </configuration> </plugin> </plugins> </build></project>
复制代码


Redis 底层实现的 Java 的 lettuce 客户端


  • 创建缓存服务接口实现类 RedisServiceImpl


package com.oxford.myshop.service.redis.provider.api.impl;
@Service(version="${service.versions.redis.v1}")public class RedisServiceImpl implements RedisService{ @Override public void set(String key,Object value){ redisTemplate.opsForValue().set(key,value); }
@Override public void set(String key,Object value,int seconds){ redisTemplate.opsForValue().set(key,value,seconds,TimeUnit.SECONDS); }
@Override public void del(String key){ redisTemplate.delete(key); }
@Override public Object get(String key){ return redisTemplate.opsForValue().get(key); }}
复制代码


  • 创建启动类 SpringBootApplication


package com.oxford.myshop.service.redis.provider;
import com.alibaba.dubbo.container.Main;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.hystrix.EnableHystrix;import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;


@EnableHystrix@EnableHystrixDashboardpublic class MyShopServiceRedisrProviderApplication { public static void main(String[]args) { SpringApplication.run(MyShopServiceRedisProviderApplication.class,args); Main.main(args); }}
复制代码


  • 创建配置文件 application.yml


spring:  application:    name: myshop-service-redis-provider  redis:    lettuce:      pool:        max-active: 8        max-idle: 8        max-wait: -1ms        min-idle: 0    sentinel:      master: mymaster      nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381server:  port: 8503
services: version: redis: v1: 1.0.0 user: v1: 1.0.0
dubbo: scan: basePackages: com.oxford.myshop.service.redis.provider.api.impl application: id: com.oxford.myshop.service.redis.provider.api name: com.oxford.myshop.service.redis.provider.api qos-port: 22224 qos-enable: true protocal: id: dubbo name: dubbo port: 20883 status: server serialization: kryo
regitry: id: zookeeper address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
management: endpoint: dubbo: enabled: true dubbo-shutdown: enabled: true dubbo-configs: enabled: true dubbo-sevicies: enabled: true dubbo-reference: enabled: true dubbo-properties: enabled: true health: dubbo: status: defaults: memory extras: load,threadpool
复制代码

创建缓存服务消费者项目

  • 在 pom 文件中引入 redis 接口依赖

  • 在缓存服务消费者项目的 ServiceImpl 中调用 RedisService


@Reference(version="services.versions.redis.v1")private RedisService redisService;
复制代码

MyBatis Redis 二级缓存

MyBatis 缓存

  • 一级缓存:

  • MyBatis 会在表示会话的 SqlSession 对象中建立一个简单的缓存: 将每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询

  • 一级缓存是 SqlSession 级别的缓存:

  • 在操作数据库时需要构造 SqlSession 对象

  • 对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据

  • 不同的 SqlSession 之间的缓存数据区域(HashMap)互不影响,

  • 一级缓存的作用域是同一个 SqlSession

  • 在同一个 SqlSession 中两次执行相同的 SQL 语句: 第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,将不再从数据库查询,从而提高查询效率

  • 当一个 SqlSession 结束后该 SqlSession 中的一级缓存就不存在了

  • MyBatis 默认开启一级缓存

  • 二级缓存:

  • 二级缓存是 Mapper 级别的缓存: 多个 SqlSession 去操作同一个 Mapper 的 SQL 语句,多个 SqlSession 去操作数据库得到数据会存在二级缓存区域,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的

  • 二级缓存的作用域是 mapper 的同一个 namespace

  • 不同的 SqlSession 两次执行相同 namespace 下的 SQL 语句且向 SQL 中传递参数也相同即最终执行相同的 SQL 语句: 第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率

  • MyBatis 默认没有开启二级缓存,需要在 setting 全局参数中配置开启二级缓存

配置 MyBatis 二级缓存

SpringBoot 中开启 MyBatis 二级缓存
  • 在 myshop-service-user-provider 的配置文件中开启 MyBatis 二级缓存


spring:  application:    name: myshop-service-user-provider  datasource:    druid:      url: jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false      username: root      password: 123456      initial-size: 1      min-idle: 1      main-active: 20      test-on-borrow: true      driver-class-name: com.mysql.cj.jdbc.Driver  redis:    lettuce:      pool:        max-active: 8        max-idle: 8        max-wait: -1ms        min-idle: 0    sentinel:      master: mymaster      nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
server: port: 8501
# MyBatis Config propertiesmybatis: configuration: cache-enabled: true type-aliases-package: com.oxford.myshop.commons.domain mapper-location: classpath:mapper/*.xml
services: version: redis: v1: 1.0.0 user: v1: 1.0.0
dubbo: scan: basePackages: com.oxford.myshop.service.user.provider.api.impl application: id: com.oxford.myshop.service.user.provider.api name: com.oxford.myshop.service.user.provider.api qos-port: 22222 qos-enable: true protocal: id: dubbo name: dubbo port: 20001 status: server serialization: kryo
regitry: id: zookeeper address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
management: endpoint: dubbo: enabled: true dubbo-shutdown: enabled: true dubbo-configs: enabled: true dubbo-sevicies: enabled: true dubbo-reference: enabled: true dubbo-properties: enabled: true health: dubbo: status: defaults: memory extras: load,threadpool
复制代码


  • 在 myshop-commons-mapper 的 pom.xml 中增加 redis 依赖:


<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-redis</artifacted></dependency>
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifacted></dependency>
复制代码
实体类实现序列化接口并声明序列号
private static final long serialVersionUID = 82897704415244673535L
复制代码


IDEA生成序列号方法:- 使用GenerateSerialVersionUID插件生成,安装完插件后在实现了序列化接口的类中- 使用快捷键Alt+Insert即可呼出生成菜单,即可自动生成序列号
复制代码
实现 Mybatis Cache 接口,自定义缓存为 Redis
  • 在 myshop-commons 项目中创建 ApplicationContextHolder 类


package com.oxford.myshop.commons.context;
@Componentpublic class ApplicationContextHolder implements ApplicationContextAware,DisposableBean{
private static final Logger logger=LoggerFactory.getLogger(ApplicationContext.class);
private static ApplicationContext applicationContext;
/** * 获取存储在静态变量中的ApplicationContext */ public static ApplicationContext getApplicationContext(){ assertContextInjected(); return applicationContext; }
/** * 从静态变量applicationContext中获取Bean,自动转型成所赋值对象的类型 */ public static <T> T getBean(String name){ assertContextInjected(); return (T) applicationContext.getBean(name); }
/** * 从静态变量applicationContext中获取Bean,自动转型成所赋值对象的类型 */ public static <T> T getBean(Class<T> clazz){ assertContextInjected(); return (T) applicationContext.getBean(clazz); }
/** * 实现DisposableBean接口,在Context关闭时清理静态变量 */ public void destroy() throws Exception{ logger.debug("清除 SpringContext 中的 ApplicationContext: {}",applicationContext); applicationContext=null; }
/** * 实现ApplicationContextAware接口,注入Context到静态变量中 */ public void setApplicationContext(ApplicationContext applicationContext) throws BeanException{ ApplicationContext.applicationContext=applicationContext; }
/** * 断言Context已经注入 */ private static void assertContextInjected(){ Validate.validState(applicationContext !=null,"applicationContext 属性未注入,请在配置文件中配置定义ApplicationContextContext"); }}
复制代码


  • 在 myshop-commons-mapper 项目中创建一个 RedisCache 的工具类


package com.oxford.myshop.commons.utils;
public class RedisCache implements Cache{ private static final Logger logger=LoggerFactory.getLogger(RedisCache.class);
private final ReadWriteLock readWriteLock=new ReentranReadWriteLock(); private final String id; private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES=30 // redis过期时间
public RedisCache(String id){ if(id==null){ throw new IllegalArgumentException("Cache instances require an ID"); } this.id=id; }
@Override public String getId(){ return id; }
/** * Put query result to redis */ @Override public void putObject(Object key,Object value){ try{ RedisTemplate redisTemplate=getRedisTemplate(); ValueOperations opsForValue=redisTemplate.opsForValue(); opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); logger.debug("Put query result to redis"); }catch(Throwable t){ logger.error("Redis put failed",t); } }
/** * Get cached query result from redis */ @Override public Object getObject(Object key){ try{ RedisTemplate redisTemplate=getRedisTemplate(); ValueOperations opsForValue=redisTemplate.opsForValue(); opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); logger.debug("Get cache query result from redis"); return opsForValue.get(key); }catch(Throwable t){ logger.error("Redis get failed, fail over to db"); return null; } }
/** * Get cached query result from redis */ @Override public Object getObject(Object key){ try{ RedisTemplate redisTemplate=getRedisTemplate(); ValueOperations opsForValue=redisTemplate.opsForValue(); opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES); logger.debug("Get cache query result from redis"); return opsForValue.get(key); }catch(Throwable t){ logger.error("Redis get failed, fail over to db"); return null; } }
/** * Remove cached query result from redis */ @Override @SuppressWarnings("unchecked") public Object removeObject(Object key){ try{ RedisTemplate redisTemplate=getRedisTemplate(); redisTemplate.delete(key); logger.debug("Remove cached query result from redis"); }catch(Throwable t){ logger.error("Redis remove failed"); } return null; }
/** * Clear this cache instance */ @Override public void clear(){ RedisTemplate redisTemplate=getRedisTemplate(); redisTemplate.execute((RedisCallback)->{ connection.flushDb(); return null; }); logger.debug("Clear all the cached query result from redis"); }
@Override public int getSize(){ return 0; } @Override public ReadWriteLock getReadWriteLock(){ return readWriteLock; }
private RedisTemplate getRedisTemplate(){ if(redisTemplate==null){ redisTemplate=ApplicationContextHolder.getBean("redisTemplate"); } return redisTemplate; }}
复制代码
Mapper 接口类中标注注解
  • 在 Mapper 接口类上标注注解,声明使用二级缓存


@CacheNamespace(implementation=RedisCache.class)
复制代码


发布于: 2021 年 05 月 22 日阅读数: 46
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
缓存服务详解!SpringBoot二级缓存服务实现