<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<!--引入spring mvc 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--
spirng boot 单元测试
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- redis 配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
server.port=8080
# redis 单机配置
spring.redis.host=192.168.12.130
spring.redis.port=6379
#redis 集群配置
#spring.redis.cluster.nodes=192.168.12.130:7001,192.168.12.130:7002,192.168.12.130:7003,192.168.12.130:7004,192.168.12.130:7005,192.168.12.130:7006
#spring.activemq.close-timeout=5000
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
重点:必须和启动类同一包
package com.qfedu;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
/**
* 当前单元测试类 必须和启动类 在同一包下
*/
@RunWith(SpringRunner.class)
@SpringBootTest // 表明当前类是 springboot 单元测试类 必须和启动类 在同一包下
public class RedisTest {
/**
* StringRedisTemplate 所有存储数据都是string 字符串
* Template 模板 模板设计模式
* 作用为使用方式 提供统一的 方法调用
*
*/
@Autowired // 从容器中获取stringRedisTemplate key String value 也是string (hash 除外)
private StringRedisTemplate stringRedisTemplate;
@Test
public void stringTest(){
// ValueOperations 就是 springboot 对应 redis 提供的 key String (key value) 类型的的操作类
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set("name", "xiaoing ");
String name = valueOperations.get("name");
System.out.println("name = " + name);
}
@Test
public void hastTest(){
// 操作 值为 hash 接口
HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
// 设置数据
hashOperations.put("user1", "name", "xiaoxue");
String name = (String) hashOperations.get("user1", "name");
System.out.println("name = " + name);
}
/**
* 测试 list
*/
@Test
public void listTest(){
ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();
listOperations.leftPush("list1", "a");
listOperations.leftPush("list1", "b");
listOperations.leftPush("list1", "c");
List<String> list1 = listOperations.range("list1", 0, -1);
System.out.println("list1 = " + list1);
}
@Test
public void boundValueTest(){
// 在获取时 直接绑定了key 省去了每次操作制定 key 的流程
BoundValueOperations<String, String> valueOps = stringRedisTemplate.boundValueOps("name");
valueOps.set("xiaowang");
String name = valueOps.get();
System.out.println("name = " + name);
}
/**
* 绑定 key value Hash
*/
@Test
public void boundHashTest(){
BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps("user1");
hashOps.put("name", "xiaocui");
Object name = hashOps.get("name");
System.out.println("name = " + name);
}
}
public class StringRedisTemplate extends RedisTemplate<String, String> {
public StringRedisTemplate() {
this.setKeySerializer(RedisSerializer.string());
this.setValueSerializer(RedisSerializer.string());
this.setHashKeySerializer(RedisSerializer.string());
this.setHashValueSerializer(RedisSerializer.string());
}
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashKeySerializer == null) {
this.hashKeySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.hashValueSerializer == null) {
this.hashValueSerializer = this.defaultSerializer;
defaultUsed = true;
}
}
if (this.enableDefaultSerializer && defaultUsed) {
Assert.notNull(this.defaultSerializer, "default serializer null and not all serializers initialized");
}
if (this.scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor(this);
}
this.initialized = true;
}
- RedisTemplate:
RedisTemplate是最基本的操作类,它默认的序列化方式是JdkSerializationRedisSerializer,在存值时,键值会被序列化为字节数组,可读性差,取值时也是一样,如果redis中存的值正常的字符串形式,取值时将返回null
- StringRedisTemplate:
StringRedisTemplate继承于 RedisTemplate,默认的序列化方式是StringRedisSerializer,存值取值都是按照字符串的形式
@Autowired// 序列化 jdk 序列化
private RedisTemplate redisTemplate;
@Test
public void redisTemplateTest1(){
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("str1", "hallo world");
System.out.println("str1:"+valueOperations.get("str1"));
}
@Configuration
public class RedisConfig {
@Bean("redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory factory, Jackson2JsonRedisSerializer redisJsonSerializer) {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
//redis连接工厂
template.setConnectionFactory(factory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//redis.key序列化器
template.setKeySerializer(stringRedisSerializer);
//redis.value序列化器
template.setValueSerializer(redisJsonSerializer);
//redis.hash.key序列化器
template.setHashKeySerializer(stringRedisSerializer);
//redis.hash.value序列化器
template.setHashValueSerializer(redisJsonSerializer);
//调用其他初始化逻辑
template.afterPropertiesSet();
//这里设置redis事务一致
template.setEnableTransactionSupport(true);
return template;
}
/**
* 配置redis Json序列化器
*
* @return
*/
@Bean
public Jackson2JsonRedisSerializer redisJsonSerializer() {
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
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);
return serializer;
}
}
单机 mybatis 开启二级缓存
1、在配置文件开启全局开关
2、在xml 注解 @CacheNamespace// 开启器二级缓存
mybatis 一级二级 缓存共同点:
在查询时 commit 产生缓存,只要发生增删改 清空缓存
mybatis 查询先去二级缓存,二级再去一级 ,一级没有去数据库差
mybatis-spring 应用中,每调用一次dao接口对应的方法,都会产生一个新的sqlSession,不存在一级缓存
优点:
1.提交查询效率
2.节约每个应用的jvm 内存空间 便于垃圾回收
# 开启mybatis 二级缓存
mybatis.configuration.cache-enabled=true
package com.qfedu.cache;
import org.apache.ibatis.cache.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 自定义 mybatis 二级缓存 底层使用 redis 存储
*
* 当前 cache 没有加入到容器
*
*/
public class RedisCache implements Cache {
private String id;
// 读写锁 可以多个人同时读,只要有一个人写 其他人都不能读
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public RedisCache() {
}
public RedisCache(String id) {
System.out.println("命名空间-----id:"+id);
this.id = id;
}
// 获取的id 就是 当前mapper 的命名空间
public String getId() {
return id;
}
/**
* 向缓存中存放数据 key(类似sql) value (查询结果)
* @param o
* @param o1
*/
public void putObject(Object key, Object value) {
System.out.println("存储 key = " + key);
getRedisTemplate().opsForValue().set(key.toString(), value, 10, TimeUnit.MINUTES);
}
/**
* 获取缓存是 通过 key
* @param key
* @return
*/
public Object getObject(Object key) {
System.out.println("获取key 对应的值 key = " + key);
return getRedisTemplate().opsForValue().get(key.toString());
}
/**
* 清除key 对应的缓存
* @param key
* @return
*/
public Object removeObject(Object key) {
System.out.println("删除 key = " + key);
return getRedisTemplate().delete(key.toString());
}
/**
* 清空当前命名空间二级缓存
* 就是删除所有 对应namespace(id) 的 key
*/
public void clear() {
// 获取到所有包含 namespace(id) 的 key
Set<String> keys = getRedisTemplate().keys("*" + id + "*");
for (String key : keys) {
System.out.println("遍历清空所有的namespace 下 的key = " + key);
getRedisTemplate().delete(key);
}
}
/**
* 获取当前命名空间对应的条数
* @return
*/
public int getSize() {
System.out.println("获取当前命名空间下 的条数");
// 获取到所有包含 namespace(id) 的 key
Set<String> keys = getRedisTemplate().keys("*" + id + "*");
return keys.size();
}
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
/**
* 获取容器中的 redisTemplate
* @return
*/
public RedisTemplate getRedisTemplate(){
// 获取到容器
ApplicationContext applicationContext = ApplicationHolder.getApplicationContext();
return (RedisTemplate) applicationContext.getBean("redisTemplate");
}
}
package com.qfedu.cache;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
*
* ApplicationContextAware 作用 就是 在生成 对应的实例是调用,将容器ApplicationContext 传进来
*/
@Component// 将当前类加入到容器中 ,如果当前类实现ApplicationContextAware 还会在创建bean调用改接口对应的setApplicationContext
public class ApplicationHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 持有上下文对象
this.applicationContext = applicationContext;
}
/**
* 获取核心容器
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qfedu.dao.StudentDao">
<!-- 开启二级缓存
type="" mybatis 会使用 用户自定的二级缓存 存储 如果没有配置就是用 jvm
mybatis 创建 RedisCache ,没有加入到容器中
-->
<cache type="com.qfedu.cache.RedisCache" eviction="LRU" size="1024" flushInterval="60"></cache>
<!--
在springboot中使用别名有 警告 误报没有影响
可以使用全限定名解决 com.qfedu.entity.Student
-->
<select id="findAllStudent" resultType="com.qfedu.entity.Student">
select * from student_tb
</select>
<update id="updateStudent">
update student_tb set name = #{
name},age = #{
age},sex = #{
sex},height=#{
height} where id =#{
id}
</update>
</mapper>
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyBatisTest {
@Autowired
private StudentService studentService;
@Test
public void findAllStudentTest(){
// 第一次查询
List<Student> allStudent = studentService.findAllStudent();
System.out.println("allStudent = " + allStudent);
System.out.println(" xiaocui!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*********************");
// 第二次查询
List<Student> allStudent2 = studentService.findAllStudent();
System.out.println("allStudent2 = " + allStudent2);
}
@Test
public void updateStudentTest(){
Student student = new Student();
student.setId(2);
student.setName("崔老师");
student.setAge(20);
student.setSex("M");
student.setHeight(190);
// 只要发生增删改 清空二级缓存
int num = studentService.updateStudent(student);
System.out.println("num = " + num);
}
}
Spring 本身也支持缓存,也可以使用使用redis 作为缓存
缓存方法调用的结果
Spring Cache 默认使用SimpleCacheConfiguration 来进行缓存的,如果配置了Springboot-redis启动器则使用Redis缓存
Spring Boot缓存注解有 @EnableCaching @Cacheable、@CacheEvict、@CachePut @Caching
前提条件 关闭二级缓存
# 开启mybatis 二级缓存
mybatis.configuration.cache-enabled=false
@MapperScan("com.qfedu.dao")
@SpringBootApplication
@EnableCaching// 开启 spring 缓存
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class,args);
}
}
- @Cacheable
作用在方法上 ,将该方法对应的结果进行缓存
- @CachePut(value = “student”,key = “#student.id”)// 在更新时 会更新 spring 缓存 ,并且写入mysql
// 先更新缓存 (缓存结果为 返回的数据) 在 修改数据库
- @CacheEvict(value = “student”,key = “#id”,allEntries = true,beforeInvocation=true)// 在删除的时候清空所有的缓存(否则会出现数据不一致问题)
// 会删除所有以 student 开头的 key ,allEntries = true
// 调用在这个 方法之前 先清空缓存 在去数据库删除(防止脏数据出现)
package com.qfedu.controller;
import com.qfedu.entity.Student;
import com.qfedu.service.StudentService;
import org.junit.After;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Student 相关
*/
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping("/findAllStudent")
@Cacheable(value = "student") // 将结果进行缓存 student 对应缓存key 的前缀
public List<Student> findAllStudent(){
System.out.println("查询所有学生");
return studentService.findAllStudent();
}
@RequestMapping("/findStudentById")
@Cacheable(value = "student",key = "#id",condition = "#id>2")// key 制定缓存key condition 制定缓存条件,不满足条件不会缓存
public Student findStudentById(int id) {
System.out.println("查询所有学生id"+ id);
return studentService.findStudentById(id);
}
@RequestMapping("/updateStudent")
@CachePut(value = "student",key = "#student.id")// 在更新时 会更新 spring 缓存 ,并且写入mysql
// 先更新缓存 (缓存结果为 返回的数据) 在 修改数据库
// 更新也尽量用 @CacheEvict 有可能发生脏数据
public Student updateStudent(Student student){
int num = studentService.updateStudent(student);
return student;
}
@RequestMapping("/deleteStudentById")
@CacheEvict(value = "student",key = "#id",allEntries = true,beforeInvocation=true)// 在删除的时候清空所有的缓存(否则会出现数据不一致问题)
// 会删除所有以 student 开头的 key ,allEntries = true
// 调用在这个 方法之前 先清空缓存 在去数据库删除(防止脏数据出现) beforeInvocation=true
public String deleteStudentById(int id){
int i = studentService.deleteStudentById(id);
return i +"";
}
}
穿透:穿过去(redis)去mysql 查询 ,查询数据库中不存在的数据,每次都要穿过reddis 去数据库查询一下
当前我们查询的结果id=1000 student 在数据库中没有时,不会在redis中缓存,下一次再去查询id=1000 student (经过redis ,redis 没有 去数据库拿,没有,)
缓存击穿,就是缓存中的数据失效,用户访问时,不得已去数据库查询
雪崩:就是因为 mysql 响应时间过长,造成 tomcat 请求响应堆积,可能造成tomcat 连接数 内存不够用 tomcat挂掉 整个系统崩塌