创立缓存服务
创立缓存服务接口我的项目
- 创立 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 @EnableHystrixDashboard public 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:26381 server: 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 properties mybatis: 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
- 应用 GenerateSerialVersionUID 插件生成, 装置完插件后在实现了序列化接口的类中
-
应用快捷键 Alt+Insert 即可呼出生成菜单, 即可主动生成序列号
-
在 myshop-commons 我的项目中创立 ApplicationContextHolder 类
package com.oxford.myshop.commons.context; @Component public 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)