1.整合流程图
2、SpringBoot+Mybatis整合redis
2.1 创建springboot+mybatis项目,引入jar包
<!--springboot整合redis jar 开始-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--springboot整合redis jar 结束-->
2.2配置连接redis数据源
把数据单独配置,防止application.properties/.yml 配置过于臃肿,防止硬编码
加上一个配置 project.properties
#整合redis配置
# redis数据源配置
#spirngboot+mybatis连接redis cluster配置
#最大能够保持空闲状态的链接数
redis.maxIdle=2000
#最大连接数
redis.maxTotal=20000
#最大的等待时长 毫秒
redis.maxWaitMillis=20000
#当调用borrow Object方法时,是否进行有效性检查
redis.testOnBorrow=false
#集群节点配置
redis.nodes=192.168.206.41:8001,192.168.206.41:8002,192.168.206.42:8003,192.168.206.42:8004,192.168.206.43:8005,192.168.206.43:8006
#企业中配置会更多 更深入的了解 配置,优化企业中实战使用
2.3 springboot加载自定义配置文件
package com.aaa.springboot.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* @version 1.0
* @author:张先生
* @description:
* @date 2022/8/12 9:51
*/
@Component//交给IOC容器管理
@PropertySource("classpath:project.properties") //指定自定义配置文件的路径
@ConfigurationProperties(prefix = "redis")//如果自定义配置文件中有多个不同技术配置,指定前缀
@Data
public class RedisProperties {
//最大能够保持空闲状态的链接数
private int maxIdle;
//最大连接数
private int maxTotal;
//最大的等待时长 毫秒
private int maxWaitMillis;
//当调用borrow Object方法时,是否进行有效性检查
private boolean testOnBorrow;
//集群节点配置
private String nodes;
}
2.4 配置rediscluster-config 使用配置连接配置数据源
使用spring-boot-starter-data-redis中提供的JedisConnectionFactory连接redis集群
package com.aaa.springboot.config;
import com.aaa.springboot.property.RedisProperty;
import com.aaa.springboot.util.RedisCustomCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPoolConfig;
import javax.annotation.Resource;
/**
* @version 1.0
* @author:张先生(2498841809@qq.com)
* @description:
* @date 2022/8/12 14:26
*/
@Configuration //相当于 redis-cluster-config.xml <beans></beans>
public class RedisClusterConfig {
//依赖注入
@Resource
private RedisProperty redisProperty;
/**
* 初始化 JedisConnectionFactory redis 连接工厂类
* @return
*/
@Bean//相当于 <bean id=jedisConnectionFactory class =org.springframework.data.redis.connection.jedis.JedisConnectionFactory>
public JedisConnectionFactory jedisConnectionFactory(){
//实例化对象 使用 redis集群 + redis连接池
// RedisClusterConfiguration clusterConfig, JedisPoolConfig poolConfig
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(clusterConfig(),poolConfig());
//返回对象
return jedisConnectionFactory;
}
/**
* 初始化RedisClusterConfiguration redis 集群配置
* @return
*/
@Bean
public RedisClusterConfiguration clusterConfig(){
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
String nodes = redisProperty.getNodes();
String[] nodesArray = nodes.split(",");
for (String hostAndPort : nodesArray) {
String[] hostAndPortArray = hostAndPort.split(":");
RedisNode node1 = new RedisNode(hostAndPortArray[0],Integer.valueOf(hostAndPortArray[1]));
redisClusterConfiguration.addClusterNode(node1);
}
return redisClusterConfiguration;
}
/**
* 初始化 JedisPoolConfig 连接池
* @return
*/
@Bean
public JedisPoolConfig poolConfig(){
//实例化连接池配置 企业实战中可以更多自定义配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(redisProperty.getMaxIdle());
jedisPoolConfig.setMaxTotal(redisProperty.getMaxTotal());
jedisPoolConfig.setTestOnBorrow(redisProperty.isTestOnBorrow());
jedisPoolConfig.setMaxWaitMillis(redisProperty.getMaxWaitMillis());
return jedisPoolConfig;
}
/**
* 在SpringRedisConfig加载时 执行该方法给RedisCustomCache中jedisConnectionFactory对象赋值
*/
@Bean
public void setRedisCustomCacheJCF(){
RedisCustomCache.setJedisConnectionFactory(jedisConnectionFactory());
}
}
2.5 编写mybatis自定义缓存类
通过mybatis调用redis集群缓存。 实际使用的mybatis二级缓存,调用第三方缓存。
MyBatis 内置了一个强大的事务性(一旦进行CUD,缓存就会被清空)查询(只有查询有缓存)缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存(只开启了一级缓存,无论想不想用,都是开启),它仅仅对一个会话中的数据进行缓存(一级缓存是基于SqlSession)。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行(二级缓存需要自己开启,二级缓存是语句XxxMapper.xml) <cache>
mybatis二级缓存详解:
1,所有 select 语句的结果将会被缓存 除非自己配置去掉缓存useCache="false"
2,所有 insert、update 和 delete 语句会刷新缓存。(增删改是改变数据库数据,改变之后一定要重新缓存)除非自己配置不刷新 flushCache="false"
3,缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。(当1025个热点数据准备缓存时,先把缓存最近最少使用的踢出去)
默认策略可以通过 eviction="FIFO" 改为先进先出
4,缓存不会定时进行刷新(也就是说,没有刷新间隔)。如果想定时刷新(定时把所有缓存内容清空)配置flushInterval=""
5,缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用(一共有多少个被缓存)。
6,缓存会被视为读/写缓存 (readOnly="false"),这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
如果配置的是readOnly="false" 每次请求获取缓存数据时,缓存会把当前缓存的数据复制一份返回给调用或者线程,修改复制品,对原来缓存没有任何影响,
速度慢,但是安全性好。。。。 浪费内存
如果配置的是readOnly="true" 每一个请求获取缓存数据时,都是同一份数据,如果有请求改了,其他人拿到的也会被修改,速度快,因为没有复制品
但是安全性不好。。。 节省内存
package com.aaa.springboot.util;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @version 1.0
* @author:张先生
* @description:
* @date 2022/8/12 15:09
*/
public class RedisCustomCache implements Cache {
//每个被缓存对象都要生成一个唯一id
private String id;
//定义静态方法
private static JedisConnectionFactory jedisConnectionFactory;
//缓存读写策略 读写互斥 写读互斥 写写互斥 读读共享
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 无法使用正常注入,使用该对象,所以使用静态方法注入,让里面的jedisConnectionFactory通过该方法赋值
* @param jedisConnectionFactory
*/
public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
RedisCustomCache.jedisConnectionFactory = jedisConnectionFactory;
}
/**
* 提供一个接受Spring参数最为id的构造器
* @return id
*/
public RedisCustomCache(String id) {
if (null==id){
throw new IllegalArgumentException("id不能为空!!!!");
}
this.id = id;
}
/**
* 获取缓存对象ID 一定把ID返回
* @return
*/
@Override
public String getId() {
return this.id;
}
/**
* 向redis集群中写入缓存对象
* @param key
* @param value
*/
@Override
public void putObject(Object key, Object value) {
//获取redis连接
RedisConnection connection = jedisConnectionFactory.getConnection();
//使用spring整合redis包中的对象对key和value序列化
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
byte[] keySerializeByteArray = jdkSerializationRedisSerializer.serialize(key);
byte[] valueSerializeByteArray = jdkSerializationRedisSerializer.serialize(value);
//向redis写入缓存对象
connection.set(keySerializeByteArray,valueSerializeByteArray);
}
/**
* 通过key获取
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
//获取redis连接
RedisConnection connection = jedisConnectionFactory.getConnection();
//使用spring整合redis包中的对象对key和value序列化
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
byte[] keySerializeByteArray = jdkSerializationRedisSerializer.serialize(key);
//通过序列化key获取序列化的值
byte[] valueSerializeByteArray = connection.get(keySerializeByteArray);
//反序列化并返回对象
return jdkSerializationRedisSerializer.deserialize(valueSerializeByteArray);
}
/**
* 通过key 移除缓存
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
//获取redis连接
RedisConnection connection = jedisConnectionFactory.getConnection();
//使用spring整合redis包中的对象对key和value序列化
JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
byte[] keySerializeByteArray = jdkSerializationRedisSerializer.serialize(key);
//建议不要直接删除,因为高并发时,如果每个key都直接删除,会影响redis效率
//设置key立马过期,后面redis使用不同的淘汰策略去批量删除过期key
return connection.expireAt(keySerializeByteArray,0);
}
/**
* 清空所有缓存
*/
@Override
public void clear() {
//获取redis连接
RedisConnection connection = jedisConnectionFactory.getConnection();
//清空所以库
//connection.flushDb();
connection.flushAll();
}
/**
* 获取缓存数量
* @return
*/
@Override
public int getSize() {
//获取redis连接
RedisConnection connection = jedisConnectionFactory.getConnection();
Long aLong = connection.dbSize();
return Integer.valueOf(aLong.toString());
}
/**
* 缓存读写锁 (缓存读写策略)
* @return
*/
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
}
2.6 整合测试
1)注意所有的实体类必须序列化 (只要使用了mybatis二级缓存,就需要这么做)
2) 启动redis集群,使用客户端命令测试是否正常
使用脚本启动集群:
./shelldir/redis-start-stop.sh
使用客户端命令测试:
/usr/redis/bin/redis-cli -c -h 192.168.170.31 -p 8001
192.168.170.31:8001> cluster info 发现正常
3) 启动sbm项目,使用swagger做查询 测试
第一请求时,在idea控制台可以看到执行语句,说明是从数据获取
清空控制台,再次请求,发现控制台就不再执行语句,而是看到cache hit Ratio 命中率
请求两次,从缓存中取了一次 1/2
请求3次 2次从缓存中获取 2/3
请求4次, 3次从缓存中获取 3/4
清空缓存,再次请求,再观察控制台
/usr/redis/bin/redis-cli -c -h 192.168.206.43 -p 8006
192.168.206.43:8006> flushdb
/usr/redis/bin/redis-cli -c -h 192.168.206.43 -p 8004
192.168.206.43:8004> flushdb
说明清空缓存后,会再次从数据获取
继续刷新请求
增删改后,再次观察控制台(增删改后,一定会自动清空缓存,再次从数据库查询,再次缓存)
先执行添加操作
再执行查询:
查询方法和CUD方法必须同一个mapper才会CUD后,清空缓存,重新缓存