一、了解Redis
1.1、关于NoSQL
NoSQL的全称是Not only SQL,在过去的几年中,NoSQL数据库一度成为高并发、海量数据存储解决方案的代名词,与之相应的产品也呈现出雨后春笋般的生机。然而在众多产品中能够脱颖而出的却屈指可数,如Redis、MongoDB、BerkeleyDB和memcached等内存数据库。
1.2、关于Redis
Redis,中文网站 。典型的NoSQL数据库服务器,它可以作为服务程序独立运行于自己的服务器主机。在很多时候,人们只是将Redis视为Key/Value数据库服务器,然而事实并非如此,在目前的版本中,Redis除了Key/Value之外还支持List、Set、Hash和Ordered Set等数据结构,因此它的用途也更为宽泛。
Redis的特性:速度快、支持很多语言、持久化、多种数据结构、主从复制以及高可用与分布式
二、Redis的安装
2.1、Redis 在CentOS7下的安装
下载、解压以及编译Redis
代码语言:javascript复制#安装流程
wget http://download.redis.io/releases/redis-5.0.4.tar.gz
tar xzf redis-5.0.4.tar.gz
cd redis-5.0.4
make
#遇到[adlist.o]Error 127
yum install gcc
#运行Redis
src/redis-server redis.conf
2.2、Redis 在Windows下的安装
很不幸的是,Redis项目不正式支持Windows。 但是,Microsoft开放技术小组开发和维护这个Windows端口针对Win64,目前从网上找到win环境的最新版本是3.2.100,虽然不是最新版本,但本地开发学习足够用啦。下面就来聊下,Win下如何安装Redis。下载地址
下载完成,解压后的文件结构:
双击redis-server.exe
,启动redis-server服务:
redis默认启动6379端口,需要保证本地6379端口没被占用!出现此界面表示redis服务已经正常启动了,这时候就可启动redis客户端进行相关操作了。
双击redis-cli.exe
,启动redis-cli客户端:
当然,你也可以将redis安装成win系统服务,这样每次就不需要去双击打开客户端服务端啦(推荐)
代码语言:javascript复制#提示successful的字样表示成功,服务不能重复安装
#需要进入你安装包解压的路径中,win10注意cmd要以管理员打开
redis-server --service-install redis.windows-service.conf --loglevel verbose
#安装成功后,可以将解压路径配置到环境变量path中去:
C:Program FilesMZJRedis-x64-3.2.100(这是我本地解压的路径,将这个路径配置到环境变量中,下次执行redis-cli就不需要到指定路径了,全局使用方便)
#常用的命令
卸载服务:redis-server --service-uninstall
开启服务:redis-server --service-start
停止服务:redis-server --service-stop
#注意:如果需要修改配置文件信息,可在redis.windows-service.conf文件(非redis.windows.conf)自定义配置信息,因为Redis安装的服务默认加载的是该文件。当然你启动redis-server的时候也可以指定配置文件
2.3、Redis常用的配置选项
常用配置选项:
- daemonize yes 配置为守护进程,后台启动,默认为no
- port 6379 修改默认监听端口
- bind 127.0.0.1 默认只支持本地访问,改行注释掉则允许所有主机访问redis,正式环境不推荐
- protected-mode no 关闭保护模式
- requirepass xxxx 配置redis密码,使用时候需要输入:auth xxxx 进行认证,认证成功后才能操作redis
- logfile 设置日志文件
- databases 255 设置redis数据库总量,推荐不超过255
- dir 设置数据文件储存目录
三、Redis命令
3.1、Redis通用命令
代码语言:javascript复制查看客户端连接信息:
info replication
info memory
ping
PONG 表示数据库正常运行
选择数据库:
select number (0-15) ,默认提供16个数据库,可以扩展到256(推荐,尽量不要再大)select 0 表示选择0号数据库,一次类推
keys * 查看数据库中所有的key的值;不推荐在生产环境中使用,keys 命令是阻塞的
keys he* keys ph? ....
dbsize 返回是key 的数量
exists key 返回 1 表示key 存在
del key 表示删除 key 对应的数据
expire key 时间(单位秒) 设置key 的有效时间
ttl key 查看key是否有效 ,返回-2 表示过期
flushdb 表示清空当前数据库中所有的数据
flushall 表示删除所有数据库中的所有数据
redis-cli shutdown 关闭redis服务器(推荐)
netstat -tulpn | grep redis 与 kill -9 pid
四、Redis数据结构
Redis中支持五种数据结构
- String 字符串类型(较多)
- Hash hash类型(Map)
- List 列表类型
- Set 集合类型
- ZSet 有序集合类型
4.1、Redis字符串类型
特点:
- String 类型最大存储不超过512mb,单个key - value 不超过100kb,可以是数字、字符串以及json字符串(javaBean)
- String类型结构的应用场景:缓存、秒杀、分布式锁(分布式事务的一致性,分布式事务)、配置中心(统一分布式各系统的配置,统一设置读取)、对象序列化(jackson,gson序列化工具)、计数器(用于统计一些数据,保存为字符串数据类型)
常用的指令:
代码语言:javascript复制get key 获取key的值
set key value 设置key 的值为value (重复set ,新的值会把旧的值覆盖掉) mset hello world java best 一次性设置多个key的值,这里hello 和java作为key
mget hello java 一次性获取多个key的值
del key 删除key的值
incr/decr key 表示key的值自增/自减1 (有点类似mysql 数据库中的主键)
incrby/decrby key num 增或减少指定的num
4.2、Redis Hash类型
特点:
- Hash用于存储结构化数据,可以看做是Map
- 典型的应用场景:对象的存储(可实现部分字段更新操作)
常用的指令:
代码语言:javascript复制redis 中 hash key 的命名规则:对象类型:id:属性
hget user:1:info age 表示获取hash中key=age的值
hset user:1:info age 23 设置hash 中age=23 (多次hset同一个值表示覆盖,成功会返回 0)
hmset user:1:info height 178 birthday 1992-06-26 设置多个key()height birthday
hmget user:1:info name age height birthday 获取多个值
hgetall user:1:info 表示一次性获取所有的值
hdel user:1:info birthday 删除 birthday 的 value值
hlen user:1:info 查看有多少个值
hexists user:1:info name 查看key是否存在 存在返回 1
4.3、Redis List类型
特点:
- List列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾(右边)
- 一个列表最多可以包含 2的32次方 - 1 个元素(4294967295, 每个列表超过40亿个元素)。
典型的应用场景:时间轴的展示
常用的指令:
代码语言:javascript复制List 相关指令
rpush listkey c b a 右侧插入(每一次从右侧插入,就是从列表的尾部添加)
lpush listkey f e d 左侧插入,就是从列表的头部添加
rpop listkey 右侧元素弹出
lpop listkey 左侧元素弹出
llen listkey 获取从航都
lrange listkey 0 2 (表示从左边第一个元素取到第2个元素)
lrange listkey 1 -1 获取子集 从第二个元素开始 都末端所有元素
4.3、Redis Set类型
特点:
- Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
- Redis 中集合是通过哈希表实现的,所以添加,删除,查找的速度极快。
- 集合中最大的成员数为 2的32次方 - 1 (4294967295,每个集合可存储40多亿个成员)。
典型的应用场景:微关系中,共同关注的人sinter ;如随机弹出srandmember(可以设计奖池),抽奖活动(spop)
常用的指令:
代码语言:javascript复制sadd key element 添加集合元素
srem key 移除集合中的指定元素
scard user:1:follow 计算集合数量
smembers user:1:follow 获取所有集合元素 不推荐使用
srandmember user:1:follow 3 随机挑选3个参数
spop user:1:follow 随机弹出元素
sdiff set1 set2 差集
sdiff user:1:follow user:2:follow
sinter set1 set2 交集
sinter user:1:follow user:2:follow
sunion set1 set2 并集
sunion user:1:follow user:2:follow
4.3、Redis Zset类型
特点:是String类型的有序集合,集合成员是唯一的,不能出现重复的数据。
典型的应用场景:排行榜
常用的指令:
代码语言:javascript复制zadd key score elements 添加集合中的元素
zadd player:rank 1000 ronaldo 900 messi 800 cronaldo 600 kaka
zrem key element 移除
zscore key element 获取数据
zscore player:rank kaka (元素排名下标是从0开始)
zcard key 元素总数
zrank key 元素总数
zrange key scope withscores 获得排序索引数据
zrange player:rank 0 -1 withscores
zcount key scope 获得排序数据总量
zcount player:rank 700 901
zrangebyscore key 获得按分数排序元素
zrangebyscore player:rank 700 901 withscores
五、Redis客户端和使用
5.1、Redis 客户端
windows 环境下 有 RDM 0.9.3版本是最新免费版本,类似数据库(navicate)可视化管理工具文末会提供下载!
java 下客户端 Jedis:
- Jedis是Java语言开发的Redis客户端工具包,用于Java语言与Redis数据进行交互.
- Jedis只是对Redis命令的封装,掌握Redis命令便可轻易上手Jedis
- Jedis遵循RESP协议规范开发,具有良好的通用性与可读性
- jedis 直连: 简单粗暴,适用于少量连接的场景
- jedis线程不安全,存在连接泄露的可能
Jedis连接池(JedisPool): 对象预先生成,降低开销,便于连接资源进行监管和控制;使用麻烦,参数较多,规划不合理容易出现问题
5.2、Redis的使用
Jedis直连的使用:
- 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xmlvhy</groupId>
<artifactId>jedis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 单元测试类编写:
package com.xmlvhy;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.util.List;
import java.util.Map;
/**
* @ClassName JedisTest
* @Description TODO
* @Author 小莫
* @Date 2019/04/17 21:57
* @Version 1.0
**/
public class JedisTest {
@Test
public void testJedis() throws Exception{
//创建一个redis 通道
Jedis jedis = new Jedis("127.0.0.1",6379, 1000);
try {
//设置登录密码密码
//jedis.auth("123456");
//选择第4个数据库,数据库下标从 0 开始
jedis.select(3);
jedis.flushDB();//清空第四个数据库
//jedis.xxx方法名就是命令
jedis.set("hello","world");
System.out.println(jedis.get("hello"));
jedis.mset(new String[]{"a","1","b","2","c","3"});
List<String> strs = jedis.mget(new String[]{"a", "b", "c"});
System.out.println(strs);
System.out.println(jedis.incr("c"));
Long b = jedis.del("b");
System.out.println(b);
} catch (Exception e) {
throw e;
}finally {
//释放连接
jedis.close();
}
}
@Test
public void testHash(){
//创建一个redis 通道
Jedis jedis = new Jedis("127.0.0.1",6379, 1000);
try {
//设置登录密码密码
//jedis.auth("123456");
//选择第4个数据库,数据库下标从 0 开始
jedis.select(3);
jedis.flushDB();//清空第四个数据库
//jedis.xxx方法名就是命令
jedis.hset("user:1:info","name","xiaomo");
jedis.hset("user:1:info","age","27");
jedis.hset("user:1:info","sex","男");
Map<String, String> all = jedis.hgetAll("user:1:info");
System.out.println(all);
System.out.println(jedis.hget("user:1:info","name"));
} catch (Exception e) {
throw e;
}finally {
//释放连接
jedis.close();
}
}
}
Jedis使用连接池jedisPool:
代码语言:javascript复制package com.xmlvhy;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @ClassName JedisTestPool
* @Description TODO
* @Author 小莫
* @Date 2019/04/18 9:30
* @Version 1.0
**/
public class JedisTestPool {
@Test
public void testJedisPool(){
//创建一个jedis 池
GenericObjectPoolConfig config = new JedisPoolConfig();
//设置连接池中最多允许放100个jedis对象
config.setMaxTotal(100);
//设置连接池中允许的最大连接数
//maxIdle 设置一般跟 maxTotal一致即可
config.setMaxIdle(50);
//设置连接池中允许的最小连接数
config.setMinIdle(10);
//设置借出连接的时候是否测试有效性 推荐false 提升效率
config.setTestOnBorrow(false);
//设置归还连接的时候是否测试有效性 推荐false 提升效率
config.setTestOnReturn(false);
//创建时候测试有效性,设置true(注意这个属性设置true的话,如果不是本地连接则会报错,是由于自我保护引起的问题)
config.setTestOnCreate(true);
//当连接池内jedis无可用资源的时候,是否等待资源
config.setBlockWhenExhausted(true);
//没有获取资源时最长等待时间设置 1 秒,1秒后还没有的话就报错
config.setMaxWaitMillis(1000);
JedisPool pool = new JedisPool(config,"127.0.0.1",6379);
Jedis jedis = null;
try {
//从连接池中获取(borrow)一个jedis对象
jedis = pool.getResource();
//jedis.auth("123456");
jedis.set("abc","bb");
String abc = jedis.get("abc");
System.out.println(abc);
} catch (Exception e) {
e.printStackTrace();
}finally {
if (jedis != null) {
//在使用连接池的时候,使用close方法不是关闭。而是归还到连接池中
jedis.close();
}
}
}
}
Spring 环境下使用JedisPool:
- 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xmlvhy</groupId>
<artifactId>spring-jedis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--引用 spring-data-redis-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--日志框架-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
<!--jackson用于序列化和反序列化-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<!--封装了javaBean相关的操作-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>
</project>
- applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--连接池配置类-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100"/>
<property name="maxIdle" value="100"/>
<property name="minIdle" value="10"/>
<property name="maxWaitMillis" value="1000"/>
<property name="blockWhenExhausted" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="testOnCreate" value="false"/>
</bean>
<!--sentinel配置核心-->
<bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster"/>
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1"/>
<constructor-arg name="port" value="26379"/>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1"/>
<constructor-arg name="port" value="26380"/>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="127.0.0.1"/>
<constructor-arg name="port" value="26381"/>
</bean>
</set>
</property>
</bean>
<!--配置jedis
usePool = false 表示jedis直连,true表示使用连接池
-->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:usePool="true" p:hostName="127.0.0.1" p:port="6666" p:database="2" p:poolConfig-ref="poolConfig"/>
<!--<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:usePool="true" p:database="2" p:poolConfig-ref="poolConfig">-->
<!--TODO:windows环境下执行提示失败-->
<!--<!–整合sentinel配置,通知jedisconnectionFactory 使用sentinel创建jedis连接–>-->
<!--<constructor-arg index="0" ref="sentinelConfiguration"/>-->
<!--</bean>-->
<!--jedis 核心操作类 redisTemplate 本质上就是jedis封装,在jedis基础上进行了大幅度的简化,并且
对连接池友好,允许自动回收连接
在JedisPool 中如果没有调用 close方法可能会出现连接泄露的问题,但在spring-date-redis中则不会
-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="jedisConnectionFactory">
<property name="keySerializer">
<!--设置 StringRedisSerializer 字符串原本序列化进行保存-->
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<!--设置 值序列化采用原文字符串的序列化方式-->
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
<property name="hashKeySerializer">
<!--设置 StringRedisSerializer 字符串原本序列化进行保存-->
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
</property>
</bean>
</beans>
- 单元测试
package com.xmlvhy.springredis;
import com.xmlvhy.entity.User;
import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
/**
* @ClassName SpringRedisTests
* @Description TODO
* @Author 小莫
* @Date 2019/04/18 10:30
* @Version 1.0
**/
@RunWith(SpringJUnit4ClassRunner.class) //启动时候初始化spring ioc 容器
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringRedisTests {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
public void test() {
//对string 字符串操作的类
//默认情况下Spring-data-redis 会采用jdk序列化的方式将所有key value 进行二次序列化
//这样会导致可读性差,通常我们需要字符串或json的方式来保存对象
redisTemplate.opsForValue().set("a", "b1111");
String a = (String) redisTemplate.opsForValue().get("a");
System.out.println(a);
}
//string 序列化
@Test
public void testObjectSerializer() {
User user = new User("u1", "test1");
User user1 = new User("u2", "test2");
redisTemplate.opsForValue().set("user:u1", user);
redisTemplate.opsForValue().set("user:u2", user1);
//spring data redis 底层提供jackson 进行序列化
}
//string 反序列化
@Test
public void testObjectDeserializer() {
//这里注意需要实体类中有一个空的默认构造方法
User u1 = (User) redisTemplate.opsForValue().get("user:u1");
System.out.println(u1);
}
//hash 序列化
@Test
public void testHashSerializer() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
User user = new User("u3", "test3");
Map<String, String> map = BeanUtils.describe(user);
//将javaBean 转化为 map
redisTemplate.opsForHash().putAll("user:m1", map);
}
//hash 反序列化
@Test
public void testHashDeserializer() throws InvocationTargetException, IllegalAccessException {
//拿到所有值
Map map = redisTemplate.opsForHash().entries("user:m1");
User user = new User();
BeanUtils.populate(user, map);
System.out.println(user.getUsername());
}
@Test
public void testList() {
for (int i = 0; i < 10; i ) {
User u = new User("u" i, "p" i);
redisTemplate.opsForList().rightPush("VipUserRank", u);
}
//下标是从0开始的
List<Object> list = redisTemplate.opsForList().range("VipUserRank", 1l, 5l);
System.out.println(list);
}
@Test
//数据库层面的命令要使用 execute 接口回调实现
public void testFlashdb() {
redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
redisConnection.flushDb();
return null;
}
});
}
}
5.3、SpringBoot和Spring Cache
Redis在我们程序中最重要的应用便是缓存,利用内存的高吞吐解决数据查询慢的问题; Spring Cache是Spring生态的一员,用于对主流缓存组件进行一致性集成.通过暴露统一的接口,让我们轻松的使用并进行组件之间的切换。
声明式缓存
- 声明式缓存通俗来说是采用注解的形式对当前应用的”非侵入式”扩展
- 声明式缓存是Spring Cache的默认支持. 底层采用Spring AOP技术实现
直接上代码:
- 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 http://maven.apache.org/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.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xmllvhy</groupId>
<artifactId>spring-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-cache</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 主程序入口,SpringCacheApplication 添加 @EnableCaching 注解,表示启用spring cache缓存
- application.properties 中配置jedisPool
spring.redis.database=2
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10
spring.redis.jedis.pool.max-wait=1000ms
- RedisCacheConfig 序列化相关配置
package com.xmllvhy.spring.cache.config;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.stereotype.Component;
/**
* @ClassName RedisCacheConfig
* @Description TODO
* @Author 小莫
* @Date 2019/04/18 14:15
* @Version 1.0
**/
@Component
public class RedisCacheConfig {
//@Bean
//public RedisCacheConfiguration redisCacheConfiguration() {
// //加载redis缓存的默认配置
// RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
// //使用jackson 进行json 序列化,jdk默认序列化是二进制的方式,那样出现乱码不易查看
// configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// return configuration;
//}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//以下这种方式可以序列化,但是如果对集合或者javabean反序列化时候会出错
//Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
}
- service类,集成cache相关业务逻辑
package com.xmllvhy.spring.cache.service;
import com.xmllvhy.spring.cache.entity.Emp;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @ClassName EmpService
* @Description TODO
* @Author 小莫
* @Date 2019/04/18 13:58
* @Version 1.0
**/
@Service
public class EmpService {
//对于默认情况下,redis对象的序列化使用的是 jdk序列化,必须要求实体类实现seriliziable 接口
//cacheable 会将方法的返回值序列化后存储到redis,key就是参数执行的字符串
//cacheable 的用途就是在执行方法前检查对应的key是否存在,存在则直接从redis中取出来不执行方法中的代码
//没有对应的key则执行方法代码,并将返回的值序列化保存在缓存中
//condition 代表条件成立的时候才执行缓存的数据
@Cacheable(value = "emp",key = "#empno"/*,condition = "#empno == 1000"*/)
public Emp findByEmpNo(Integer empno){
System.out.println("执行了findByEmpNo方法:empno" empno);
return new Emp(empno,"xiaomo",new Date(),1000f,"研发部");
}
//冒号分割
@Cacheable(value = "emp:rank:salary")
public List<Emp> getEmpRank(){
System.out.println("第一次获取数据");
List list = new ArrayList();
for(int i =0; i<10;i ){
list.add(new Emp(i,"emp" i,new Date(),500 i*100f,"SALES"));
}
return list;
}
//CachePut 中的代码都会被执行
//CachePut 作用是不管redis是否存在key,都把返回的数据进行保存(强制更新)
@CachePut(value = "emp",key = "#emp.empno")
public Emp create(Emp emp){
System.out.println("正在创建" emp.getEmpno() " 的员工信息");
return emp;
}
//CachePut 作用是不管redis是否存在key,都把返回的数据进行保存(强制更新)
//update 也是 cache put 有则更新无则创建
@CachePut(value = "emp",key = "#emp.empno")
public Emp update(Emp emp){
System.out.println("正在更新" emp.getEmpno() " 的员工信息");
return emp;
}
//CacheEvict 从缓存中删除指定key的数据
@CacheEvict(value = "emp",key = "#empno")
public void delete(Integer empno){
System.out.println("正在删除" empno " 的员工信息");
}
}
- 单元测试类
package com.xmllvhy.spring.cache;
import com.xmllvhy.spring.cache.entity.Emp;
import com.xmllvhy.spring.cache.service.EmpService;
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.test.context.junit4.SpringRunner;
import java.util.Date;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest //spring boot 中,springboot test 的作用就是在junit启动的时候自动初始化springboot 的ioc容器
public class SpringCacheApplicationTests {
@Autowired
private EmpService empService;
@Test
public void contextLoads() {
}
@Test
public void findByEmpno(){
empService.findByEmpNo(1000);
empService.findByEmpNo(1000);
empService.findByEmpNo(1000);
empService.findByEmpNo(1000);
Emp emp = empService.findByEmpNo(1000);
System.out.println(emp.getName());
System.out.println(emp.getBirthday());
}
@Test
public void testEmpRank(){
List<Emp> list = empService.getEmpRank();
String name = list.get(list.size() - 1).getName();
System.out.println(name);
}
@Test
public void testCreate(){
empService.create(new Emp(1001,"xiaomo",new Date(),1111f,"sales"));
}
@Test
public void testUpdate(){
empService.create(new Emp(1001,"xiaomo-update",new Date(),1111f,"sales"));
}
@Test
public void testDelete(){
empService.delete(1000);
}
}
六、提供Redis一个工具类(jedisPool)
- 初始化配置 redisTemplate 和 cacheManager
package com.zhly.sso.auth.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @ClassName RedisCacheConfig
* @Description TODO
* @Author 小莫
* @Date 2019/04/18 14:15
* @Version 1.0
**/
@Configuration
@EnableCaching
public class CacheRedisConfig {
/**
*功能描述: 配置 redis 缓存管理
* @Author 小莫
* @Date 17:44 2019/04/20
* @Param [connectionFactory]
* @return org.springframework.cache.CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//以下这种方式可以序列化,但是如果对集合或者javabean反序列化时候会出错
//Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
/**
*功能描述: 配置 redis 操作类 redisTemplate
* @Author 小莫
* @Date 17:45 2019/04/20
* @Param [factory]
* @return org.springframework.data.redis.core.RedisTemplate<java.lang.String,java.lang.Object>
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
- 封装 redisTemplate 对五种数据类型的操作(RedisUtil.java)
package com.zhly.sso.auth.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @ClassName RedisUtil
* @Description TODO redis 工具类
* 这里直接通过注入 redisTemplate 来封装redis的相关操作
* @Author 小莫
* @Date 2019/04/20 17:46
* @Version 1.0
**/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
以上就是我学习 Redis 的一些总结!
参考学习
代码语言:javascript复制来自老齐的IT加油站课程学习
官网:http://www.itlaoqi.com/
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html
本篇文章涉及的 源码下载
本文作者: AI码真香
本文标题: 小白轻松入门Redis
本文网址: https://www.xmlvhy.com/article/65.html
版权说明: 自由转载-非商用-非衍生-保持署名 署名-非商业性使用4.0 国际 (CC BY-NC 4.0)