SpringDataRedis简介
项目常见问题思考
项目首页每天有大量的人访问,对数据库造成很大的访问压力,甚至是瘫痪。那如何解决呢?我们通常的做法有两种:一种是数据缓存、一种是网页静态化。我们今天讨论第一种解决方案。Redis
redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 、MongoDB等。- Jedis
Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等,推荐使用Jedis。 - Spring Data Redis
Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。 - spring-data-redis针对jedis提供了如下功能:
- 1.连接池自动管理,提供了一个高度封装的“RedisTemplate”类
- 2.针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
- ValueOperations:简单K-V操作
- SetOperations:set类型数据操作
- ZSetOperations:zset类型数据操作
- HashOperations:针对map类型的数据操作
- ListOperations:针对list类型的数据操作
Spring Data Redis入门小Demo
1 准备工作
(1)构建Maven工程 SpringDataRedisDemo
(2)引入Spring相关依赖、引入JUnit依赖 (内容参加其它工程)
<!-- 集中定义依赖版本号 -->
<properties>
<junit.version>4.12</junit.version>
<spring.version>4.2.4.RELEASE</spring.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
</dependencies>
(3)引入Jedis和SpringDataRedis依赖
<!-- 缓存 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
(4)在src/main/resources下创建properties文件夹,建立redis-config.properties
# 主机
redis.host=127.0.0.1
# 端口号
redis.port=6379
# 密码 一般不需要
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=true
(5)在src/main/resources下创建spring文件夹 ,创建applicationContext-redis.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:property-placeholder location="classpath*:properties/*.properties" />
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory" />
</bean>
</beans>
- maxIdle :最大空闲数
- maxWaitMillis:连接时的最大等待毫秒数
- testOnBorrow:在提取一个jedis实例时,是否提前进行验证操作;如果为true,则得到的jedis实例均是可用的;
2 值类型操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestValue {
//泛型可以不指定
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
public void setValue(){
//存值
redisTemplate.boundValueOps("name").set("张三");
}
@Test
public void getValue(){
String name = redisTemplate.boundValueOps("name").get();
//如果没有 name 则会返回 null
System.out.println(name);
}
@Test
public void deleteValue(){
//移除值
redisTemplate.delete("name");
}
}
3 Set类型操作
/**
* 特点: 无序,不可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestSet {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
public void setValue(){
redisTemplate.boundSetOps("nameset").add("曹操");
redisTemplate.boundSetOps("nameset").add("刘备");
redisTemplate.boundSetOps("nameset").add("关羽");
}
@Test
public void getValue(){
//如果没有 nameset 返回 []
Set<String> members = redisTemplate.boundSetOps("nameset").members();
// 没有循序,和存入的循序无关
// [关羽, 刘备, 曹操]
System.out.println(members);
}
@Test
public void removeValue(){
//移除一个
redisTemplate.boundSetOps("nameset").remove("刘备");
//全部移除
redisTemplate.delete("nameset");
}
}
4 List类型操作
/**
* 特点: 有序, 可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestList {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 右压栈: 后添加的元素排在后面
* 先进先出
*/
@Test
public void setValueRight(){
redisTemplate.boundListOps("name").rightPush("张三");
redisTemplate.boundListOps("name").rightPush("李四");
redisTemplate.boundListOps("name").rightPush("王五");
redisTemplate.boundListOps("name").rightPush("赵六");
}
/**
* 右压栈的输入
*/
@Test
public void getValue(){
// [张三, 李四, 王五,赵六]
List<String> name = redisTemplate.boundListOps("name").range(0, 10);
System.out.println(name);
}
/**
* 左压栈: 先进后出
*/
@Test
public void setValueLeft(){
redisTemplate.boundListOps("nameLeft").leftPush("关羽");
redisTemplate.boundListOps("nameLeft").leftPush("张飞");
redisTemplate.boundListOps("nameLeft").leftPush("诸葛亮");
redisTemplate.boundListOps("nameLeft").leftPush("刘备");
}
/**
* 左压栈的输入
*/
@Test
public void getValueLeft(){
// [刘备, 诸葛亮, 张飞, 关羽]
List<String> name = redisTemplate.boundListOps("nameLeft").range(0, 10);
System.out.println(name);
}
/**
* 按索引位置查询元素
*/
@Test
public void searchByIndex(){
// 关羽
String index = redisTemplate.boundListOps("nameLeft").index(3);
System.out.println(index);
//集合元素个数
Long size = redisTemplate.boundListOps("nameLeft").size();
System.out.println(size);
}
/**
* 移除
*/
@Test
public void removeByIndex(){
// 移除元素的个数 移除元素的name
redisTemplate.boundListOps("nameLeft").remove(1, "诸葛亮");
redisTemplate.delete("nameLeft");//移除所有值
}
}
5 Hash类型操作
/**
* 存取有序,不可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestHash {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 存值
*/
@Test
public void setValue(){
redisTemplate.boundHashOps("hash").put("a", "唐僧");
redisTemplate.boundHashOps("hash").put("b", "八戒");
redisTemplate.boundHashOps("hash").put("c", "沙和尚");
redisTemplate.boundHashOps("hash").put("d", "孙悟空");
}
/**
* 获取所有的key
*/
@Test
public void getAllKey(){
//获取所有的key : [a, b, c, d]
Set<Object> key = redisTemplate.boundHashOps("hash").keys();
System.out.println(key);
}
/**
* 获取所有的值
*/
@Test
public void getAllValue(){
//获取所有的值
List<Object> values = redisTemplate.boundHashOps("hash").values();
System.out.println(values); //[唐僧, 八戒, 沙和尚, 孙悟空]
//根据key取值
String str = (String)redisTemplate.boundHashOps("hash").get("c");
System.out.println(str); // 沙和尚
}
/**
* 移除某个小key的值
*/
@Test
public void removeKey(){
redisTemplate.boundHashOps("hash").delete("c");
}
}
在项目中的应用
1 需求分析
项目中首页的广告每次都是从数据库读取, 这样当网站访问量达到高峰时段,对数据库压力很大,并且影响执行效率。我们需要将这部分广告数据缓存起来。
2 读取缓存
2.1 公共组件层
因为缓存对于我们整个的系统来说是通用功能。广告需要用,其它数据可能也会用到,所以我们将配置放在公共组件层中较为合理。
(1. 引入依赖
(2. 创建配置文件
2.2 后端服务实现层中添加对公共组件层的依赖
2.3 后端服务实现层中缓存的实现
思路: 在查询数据时,我们要先从缓存中查询数据,如果缓存中没有再从数据库中查询,
将从数据库中查询出来的数据存入缓存中.
@Autowired
private RedisTemplate redisTemplate;
@Override
public List<TbContent> findByCategoryId(Long categoryId) {
//从缓存中查询数据
List<TbContent> list = (List<TbContent>) redisTemplate.boundHashOps("content").get(categoryId);
if(list == null){
System.out.println("从数据库中查询数据并放入缓存");
TbContentExample example = new TbContentExample();
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(categoryId);//指定分类ID
criteria.andStatusEqualTo("1");//指定条件有效的
example.setOrderByClause("sort_order");//排序
list = contentMapper.selectByExample(example);
redisTemplate.boundHashOps("content").put(categoryId, list);//放入缓存
}else{
System.out.println("从缓存中查询");
}
return list;
}
2.4 细节
问题: 当我们数据库中的数据发生改变时,从缓存中查询出的数据并没有改变.
解决: 当广告数据发生变更时,需要将缓存数据清除,这样再次查询才能获取最新的数据
我们看一下那些操作使数据库中的数据发生改变:
(1 增加
再增加之前我们要清除缓存,然后再增加,这样会重新在数据库中查询一次,那么数据库的
改变就会同步到缓存中
contentMapper.insert(content);
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
(2 删除
for(Long id:ids){
//清除缓存 先清除在删除
Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.deleteByPrimaryKey(id);
}
(3 修改
注意`: 用户可能会修改广告的分类,这样需要把原分类的缓存和新分类的缓存都清除掉。
@Override
public void update(TbContent content){
//查询修改前的分类Id
Long categoryId = contentMapper.selectByPrimaryKey(content.getId()).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.updateByPrimaryKey(content);
//如果分类ID发生了修改,清除修改后的分类ID的缓存
if(categoryId.longValue()!=content.getCategoryId().longValue()){
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
}
}