spring整合redis做数据缓存(windows环境)

     2022-03-20     398

关键词:

当我们一个项目的数据量很大的时候,就需要做一些缓存机制来减轻数据库的压力,提升应用程序的性能,对于java项目来说,最常用的缓存组件有Redis、Ehcache和Memcached。

Ehcache是用java开发的缓存组件,和java结合良好,直接在jvm虚拟机中运行,不需要额外安装什么东西,效率也很高;但是由于和java结合的太紧密了,导致缓存共享麻烦,分布式集群应用不方便,所以比较适合单个部署的应用。

Redis需要额外单独安装,是通过socket访问到缓存服务,效率比Ehcache低,但比数据库要快很多很多,而且处理集群和分布式缓存方便,有成熟的方案,比较适合分布式集群部署的项目;也有很多的应用将Ehcache和Redis结合使用,做成二级缓存。

至于Memcached嘛和Redis很类似,功能方面嘛理论上来说没有Redis强大(但对于我们来说也完全足够了),因此我们这里先不讲,后面如果有时间在写一篇关于Memcached的文章;由于我们后面会涉及到Tomcat的集群部署,所以这里就先讲讲Redis的应用,好为后面的文章打个基础~~下面正式开始!

 

代码URL:http://git.oschina.net/tian5017/UserDemoRedis

 

一、Redis环境准备

Redis有中文官方网站,地址为http://www.redis.cn/

Redis没有官方的Windows版本,但是微软开源技术团队(Microsoft Open Tech group)开发和维护着一个 Win64 的版本,下载地址为

https://github.com/MicrosoftArchive/redis/releases

截止文章完成之时,Redis的最新win64版本为3.2.100,我们这里就是使用的此版本;下载安装好之后,打开安装目录,如下

技术分享

上图红框中标出的redis-cli.exe就是我们用来操作Redis的客户端,在此安装目录下打开命令行窗口,输入redis-cli.exe回车,如下

技术分享

 可以看到已经连接到了Redis,地址是127.0.0.1(本机),默认的端口为6379,至于操作Redis的命令,大家可以自己百度,常用的也就那么几个,很简单,我们来简单演示下

技术分享

Redis是采用key-value键值对来存储数据的,使用命令 set "haha" "123456",就表示将值(value)"123456"赋给键(key)"haha",再用 get "haha",就可以查看到值,好了关于Redis的别的东西,大家自己去玩,我们继续我们的正题。

二、Spring集成

1、我们都知道,要在java中使用一个组件或者框架什么的,第一步都是加载它的jar包,java中操作Redis的jar包叫做jedis,这里我们还是利用上一篇文章《Spring+Mybatis+SpringMVC整合》所建立的UserDemo(项目连接:https://git.oschina.net/tian5017/UserDemo)

2、利用Maven来加载jedis的jar包,在UserDemo中的pom.xml的dependencies中添加如下代码

    <!-- redis相关 -->
    <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.10.RELEASE</version>
    </dependency>

 

 除了jedis之外,还需要另外一个jar包spring-data-redis,这是让Spring管理Redis用的;我们先来改造我们的代码,试试往Redis里面存点数据,再来查询。

3、在/resources/下新建redis.properties文件

技术分享

配置项如下

##Redis连接信息配置
#redis地址
cache.redis.host=127.0.0.1
#redis端口号
cache.redis.port=6379
#redis密码
cache.redis.password=
#redis使用的数据库(Redis内置18个数据库,编号为0-17,默认使用0)
cache.redis.db=0
#redis链接超时时间
cache.redis.timeout=2000
#redis链接池中最大空闲数
cache.redis.maxIdle=5
#redis链接池中最大连接数
cache.redis.maxActive=20
#建立连接最长等待时间
cache.redis.maxWait=1000

 

4、在/resources/springConfig/下新建applicationContext-redis.xml文件作为Spring集成Redis的配置文件

技术分享

配置代码如下

<?xml version="1.0" encoding="UTF-8"?>
<!-- 缓存redis相关配置 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${cache.redis.maxActive}"/>
        <property name="maxIdle" value="${cache.redis.maxIdle}"/>
        <property name="maxWaitMillis" value="${cache.redis.maxWait}"/>
        <property name="testOnBorrow" value="${cache.redis.testOnBorrow}"/>
    </bean>

    <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="usePool" value="true"/>
        <property name="hostName" value="${cache.redis.host}"/>
        <property name="port" value="${cache.redis.port}"/>
        <property name="password" value="${cache.redis.password}"/>
        <property name="timeout" value="${cache.redis.timeout}"/>
        <property name="database" value="${cache.redis.db}"/>
        <constructor-arg index="0" ref="jedisPoolConfig"/>
    </bean>

    <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />

    <bean id="redisCache" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="redisConnectionFactory"/>
        <property name="keySerializer" ref="stringRedisSerializer"/>
        <property name="valueSerializer" ref="stringRedisSerializer"/>
        <property name="hashKeySerializer" ref="stringRedisSerializer"/>
        <property name="hashValueSerializer" ref="stringRedisSerializer"/>
    </bean>
</beans>

 

 5、到这里,我们的准备工作已经全部完成,接下来就是在代码中使用redis了,我们修改UserServiceImpl.java

技术分享

修改思路为,查询数据的时候,先查询Redis缓存,如果查到了就直接返回数据,如果没查到数据,就去数据库中查询,查到了数据先缓存进Redis再返回,代码如下:

package com.user.demo.service.impl;

import com.alibaba.fastjson.JSON;
import com.user.demo.dao.UserDao;
import com.user.demo.entity.User;
import com.user.demo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Service接口实现类
 */
@Service
public class UserServiceImpl implements IUserService {

    @Resource
    private UserDao userDao;

    @Autowired
    private RedisTemplate<String, String> redisCache;

    @Override
    public List<User> findAll() {
        List<User> users = new ArrayList<User>();
        //先从redis缓存中获取数据,如果缓存中没有,去数据库中查询数据,查到后在写入缓存
        Set<String> sets = redisCache.keys("USER*");
        if(sets==null || sets.isEmpty()){
            users = userDao.findAll();
            if(!CollectionUtils.isEmpty(users)){
                for(User user : users){
                    redisCache.opsForValue().set("USER"+user.getUserId(), JSON.toJSONString(user));
                }
            }
        }else{
            Iterator<String> it = sets.iterator();
            while (it.hasNext()){
                String item = it.next();
                String value = redisCache.opsForValue().get(item);
                users.add(JSON.parseObject(value, User.class));
            }
        }
        return users;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) //事物
    public void saveUser(User user) {
        userDao.saveUser(user);
    }
}

 

6、接下来我们来验证缓存是否生效,编译,运行项目,如下图

技术分享

打开redis-cli,输入命令KEYS * (查看所有的key),如下

技术分享

可以看到我们的缓存已经加入进去了,key就是我们在UserServiceImpl.java中设置的(redisCache.opsForValue().set("USER"+user.getUserId(), JSON.toJSONString(user))),以USER开头加上userId构成,为了进一步验证有了缓存之后,查询的数据优先来源于缓存,我们在数据库中删除一条数据,就删除userId为1的那条数据

技术分享  技术分享

删除之后,由于我们代码中检测缓存的逻辑只是检测了能否查询到缓存,而没有检测缓存数量是否和数据库数据量一致,所以缓存中还是会存在这条数据

技术分享              技术分享

可以看到,userId为1的数据依然存在,说明数据是从Redis缓存中查询出来的。

三、数据同步

上面其实说明了一个问题,就是数据同步,我们很可能会出现,数据库中数据变了,但是缓存中的数据还没有变,因此和数据库中的数据不一致,所以我们需要一些策略来保证数据库的数据和Redis的数据能够保持一致,至于选用什么策略,那要具体项目具体分析,如果对数据的实时性要求很高的项目,那么就要在查询的时候,检测数据库的数据和Redis的缓存数据是否一致,如果不一致就要刷新Redis的数据,但是这样必然对性能会有很高的要求;如果项目对数据的实时性要求没有那么高,我们完全可以做一个定时任务,比如每隔10分钟或者半小时去数据库拉一次数据,再刷新到Redis缓存中,所以下面我们就来做一个定时任务,每隔10分钟去拉一次数据,然后往Redis中刷新一次。

四、定时刷新缓存

 我们用Spring中的InitializingBean和DisposableBean接口(这两个接口的具体用法请自行百度,大概就是在SpringBean的生命周期中,影响bean的行为,我们这里就是影响了bean,让它去刷新缓存)来实现刷新缓存,在com.user.demo下新建包cache,在cache下面新建类UserCache.java,具体代码如下

package com.user.demo.cache;

import com.alibaba.fastjson.JSON;
import com.user.demo.dao.UserDao;
import com.user.demo.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Redis缓存User数据并定时刷新
 * 每间隔一定时间后(如10分钟)刷新一次缓存,刷新时另起一个线程,不影响执行主任务的线程
 */
@Component
public class UserCache implements InitializingBean, DisposableBean {

    private final Logger log = LoggerFactory.getLogger(UserCache.class);

    //获取电脑的CPU核心数量,设置线程池大小为核心数的2倍(比如我的电脑为4核心,那么这里的值就为8)
    private static final int CORE_NUM = Runtime.getRuntime().availableProcessors() * 2;

    //初始化值为redis.properties中配置的cache.redis.cacheExpire的值,表示每隔多长时间后执行任务
    @Value("${cache.redis.cacheExpire}")
    private long cacheExpire;

    //执行定时任务的类
    private ScheduledThreadPoolExecutor executor = null;

    @Resource
    private RedisTemplate<String, String> redisCache;

    @Resource
    private UserDao userDao;


    @Override
    public void destroy() throws Exception {
        executor.shutdownNow();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        executor = new ScheduledThreadPoolExecutor(CORE_NUM);
        RefreshCache refreshCache = new RefreshCache();
        refreshCache.run();
        executor.scheduleWithFixedDelay(refreshCache, cacheExpire, cacheExpire, TimeUnit.SECONDS);
    }

    //内部类,开启新线程执行缓存刷新
    private class RefreshCache implements Runnable {
        @Override
        public void run() {
            log.info("---开始刷新用户信息缓存---");
            List<User> userList = userDao.findAll();
            if(!CollectionUtils.isEmpty(userList)){
                for(User user : userList){
                    redisCache.opsForValue().set("USER" + user.getUserId(), JSON.toJSONString(user));
                }
            }
        }
    }
}

在redis.properties中加入cache.redis.cacheExpire配置项,代码如下

##Redis连接信息配置
#redis地址
cache.redis.host=127.0.0.1
#redis端口号
cache.redis.port=6379
#redis密码
cache.redis.password=
#redis使用的数据库(Redis内置18个数据库,编号为0-17,默认使用0)
cache.redis.db=0
#redis链接超时时间
cache.redis.timeout=2000
#redis链接池中最大空闲数
cache.redis.maxIdle=5
#redis链接池中最大连接数
cache.redis.maxActive=20
#建立连接最长等待时间
cache.redis.maxWait=1000

#定时任务执行时间间隔,单位为毫秒,这里相当于10分钟
cache.redis.cacheExpire=600

同时修改UserServiceImpl.java中的代码如下

package com.user.demo.service.impl;

import com.alibaba.fastjson.JSON;
import com.user.demo.dao.UserDao;
import com.user.demo.entity.User;
import com.user.demo.service.IUserService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Service接口实现类
 */
@Service
public class UserServiceImpl implements IUserService {

    @Resource
    private UserDao userDao;

    @Resource
    private RedisTemplate<String, String> redisCache;

    @Override
    public List<User> findAll() {
        List<User> result = new ArrayList<User>();
        Set<String> sets = redisCache.keys("USER*");
        //如果缓存有数据则从缓存中取数据,如果没有则从数据库中取数据
        if(!CollectionUtils.isEmpty(sets)){
            Iterator<String> it = sets.iterator();
            while(it.hasNext()){
                String item = it.next();
                String value = redisCache.opsForValue().get(item);
                result.add(JSON.parseObject(value, User.class));
            }
        }else{
            result = userDao.findAll();
        }
        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) //事物
    public void saveUser(User user) {
        userDao.saveUser(user);
    }

}

此时,项目已经可以定时(每隔十分钟)刷新缓存,我们编译启动

技术分享

我们新提交一个用户“三德子”,刚提交后,刷新是查不出来数据的,但是数据库是有数据的

技术分享    技术分享

然后等待10分钟之后,再来查看,可以看到缓存已经被自动刷新到了Redis中

技术分享

 

OK,到这里,这篇文章就结束了,关于Spring集成Redis做数据缓存我们也讲的差不多了,其实关于Redis的应用,远远不止这么简单,我们可以很容易的搭建Redis集群,做分布式数据管理,也可以实现分布式session共享(这个我后面会有一篇文章讲到),甚至假如写数据量很大,我们也可以先缓存进Redis中,再利用多线程来将数据写入数据库中,减轻数据库的负担(因为数据库写操作是很耗费资源的)等等~~那如果大家有什么意见和建议,也欢迎留言交流;下一篇文章我会讲讲mysql的读写分离,欢迎继续关注!!

 

代码URL:http://git.oschina.net/tian5017/UserDemoRedis

 

sping整合redis,以及做mybatis的第三方缓存

一、spring整合redisRedis作为一个时下非常流行的NOSQL语言,不学一下有点过意不去。背景:学习Redis用到的框架是maven+spring+mybatis(框架如何搭建这边就不叙述了)首先在pom里面添加当前所需要的jar包,有下面几个:………………... 查看详情

(转)spring整合redis作为缓存

    采用Redis作为Web系统的缓存。用Spring的Cache整合Redis。一、关于redis的相关xml文件的写法<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/bea 查看详情

springboot整合redis做简单缓存(代码片段)

...依赖<!--引入redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--使用fastjson做序列化和反序列化--><dependency><groupId>com.alibaba</gr... 查看详情

spring优雅整合redis缓存

 “小明,多系统的session共享,怎么处理?”“Redis缓存啊!”“小明,我想实现一个简单的消息队列?”“Redis缓存啊!”“小明,分布式锁这玩意有什么方案?”“Redis缓存啊!”“... 查看详情

redis整合spring结合使用缓存实例(转)

...net/evankaka     摘要:本文介绍了如何在Spring中配置redis,并通过Spring中AOP的思想,将缓存的方法切入到有需要进入缓存的类或方法前面。一、Redis介绍什么是Redis?&nb 查看详情

guava和spring整合使用(这里只用到了缓存)

...e.CacheBuilder;2importcom.google.common.cache.CacheBuilderSpec;34importorg.springframework.cache 查看详情

spring整合redis后怎么更改db

...化到磁盘中!本人有通过redis的hash数据类型来做过购物车spring配置文件spring-redis.xml<beanid="jedisPoolConfig"class="redis.clients.jedis.JedisPoolConfig"><propertyname="maxIdle"value="6"></property><propertyname=&qu... 查看详情

精通springboot--整合redis实现缓存

今天我们来讲讲怎么在springboot中整合redis实现对数据库查询结果的缓存。首先第一步要做的就是在pom.xml文件添加spring-boot-starter-data-redis。要整合缓存,必不可少的就是我们要继承一个父类CachingConfigurerSupport。我们先看看这个类... 查看详情

springboot日记——redis整合

...录了一下缓存的使用方法,这篇文章将把我们熟悉的redis整合进来。  那么如何去整合呢?首先需要下载和安装,为了使用方便,也可以做环境变量的配置。  下载和安装的方法,之前有介绍,在docker中的使用,这里不做相... 查看详情

springboot中使用redistemplate实现redis数据缓存

SpringBoot整合Redis数据库实现数据缓存的本质是整合Redis数据库,通过对需要“缓存”的数据存入Redis数据库中,下次使用时先从Redis中获取,Redis中没有再从数据库中获取,这样就实现了Redis做数据缓存。??按照惯例,下面一步一步... 查看详情

32springboot——缓存之整合redis

springboot缓存默认使用ConcurrentMapCacheManager 将数据保存在下面的Map中1、docker中开启Redis 2、添加Redis相关依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-st 查看详情

springboot整合spring@cache和redis(代码片段)

转载请注明出处:https://www.cnblogs.com/wenjunwei/p/10779450.htmlspring基于注解的缓存对于缓存声明,spring的缓存提供了一组java注解:@Cacheable:触发缓存写入。@CacheEvict:触发缓存清除。@CachePut:更新缓存(不会影响到方法的运行)。@Caching:重新... 查看详情

springboot整合springseesion实现redis缓存

参考技术A使用SpringBoot开发项目时我们经常需要存储Session,因为Session中会存一些用户信息或者登录信息。传统的web服务是将session存储在内存中的,一旦服务挂了,session也就消失了,这时候我们就需要将session存储起来,而Redis就... 查看详情

03.redis+ssm整合(mybatis二级缓存)

SSM+redis整合ssm框架之前已经搭建过了,这里不再做代码复制工作。这里主要是利用redis去做mybatis的二级缓存,mybaits映射文件中所有的select都会刷新已有缓存,如果不存在就会新建缓存,所有的insert,update操作都会更新缓存。redis... 查看详情

spring整合redis客户端及缓存接口设计

一、写在前面缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法... 查看详情

springboot整合mybatis,redis,代码

一说明这是spring整合redis注解开发的系类:二正文在注解开发时候,会有这几个注解需要注意:具体含义:  [email protected]可以标记在方法上,也可以标记在类上。当标记在方法上时表示该方法是支持缓存的,当标记在类上时则... 查看详情

重学springboot系列之redis与springcache缓存(代码片段)

重学SpringBoot系列之redis缓存使用docker安装redis准备工作获取redis镜像创建容器创建持久化存储目录获取redis的默认配置文件模版使用镜像创建一个容器查看活跃的容器访问redis容器服务开启防火墙端口,提供外部访问redis数据结... 查看详情

spring,mybatis整合memcache

Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap... 查看详情