[Spring]Redis+Spring整合

本文详细介绍了如何在Spring应用中整合Redis,包括基于已配置的整合和Maven工程全新整合的方法。内容涉及连接池管理、RedisTemplate、RedisKey生成策略、缓存注解的使用以及Redis事务的处理。同时,文章还解决了Spring默认序列化问题并给出了自定义解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。

spring-data-redis针对jedis提供了如下功能:

  • 连接池自动管理,提供了一个高度封装的“RedisTemplate”类

  • 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口

ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作

在上文中我们提到了:[Spring]配置数据库读写分离

接下来我们将在此框架的基础上继续集成Redis

注 : 本文将以两种方式整合(基于上文进行整合、基于Maven工程重新整合)


基于上文进行整合


第一步:引入下面相关Jar包

1:commons-pool-1.6.jar
2:jedis-2.1.0.jar
3:spring-data-redis-1.0.1.RELEASE.jar

第二步:创建redis-config.properties配置文件

# Redis settings  
redis.host=127.0.0.1
redis.port=6379
redis.pass=


redis.maxIdle=300
redis.maxActive=600
redis.maxWait=1000
redis.testOnBorrow=true

第三步:创建spring-redis.xml,与Spring集成

<?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:jee="http://www.springframework.org/schema/jee" 
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    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">

    <!-- <context:property-placeholder location="classpath:redis-config.properties" />   -->

    <!-- 连接池 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
        <property name="maxIdle" value="${redis.maxIdle}" />  
        <property name="maxActive" value="${redis.maxActive}" />  
        <property name="maxWait" value="${redis.maxWait}" />  
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
    </bean>  

    <!-- 工厂 -->
    <bean id="connectionFactory" 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="keySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    <bean id="valueSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
    <!-- 缓存 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer" ref="keySerializer" />
        <property name="valueSerializer" ref="valueSerializer" />
        <property name="hashKeySerializer" ref="keySerializer" />
        <property name="hashValueSerializer" ref="valueSerializer" />
    </bean> 

</beans>

注意:上面的配置中,我们把引入redis的配置文件代码注释了,我们在springmvc-context.xml中配置

<!-- 引入配置文件 --> 
<!-- <context:property-placeholder location="classpath:config.properties"/> -->
<!-- 
        用上面引入配置文件后,在引入redis配置文件异常
            org.springframework.beans.factory.BeanDefinitionStoreException: 
                Invalid bean definition with name 'masterDataSource' defined in file 
        说明:Spring容器采用反射扫描的发现机制,在探测到Spring容器中有一个 
            org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
            的 Bean就会停止对剩余PropertyPlaceholderConfigurer的扫描
            (Spring 3.1已经使用PropertySourcesPlaceholderConfigurer替代
            PropertyPlaceholderConfigurer了)。    

            简单讲就是只能用一次context:property-placeholder引入配置文件。

         解决方案:
            一次加载所有配置文件。
            location="classpath*:*.properties"
            还可以是下面方式,加载多个目录中的。
            location="classpath:*.properties,classpath:*/*.properties"  

         参考:http://blog.youkuaiyun.com/oDeviloo/article/details/51064655      
     -->

    <context:property-placeholder location="classpath:*.properties"/>

以下是 Maven版本整合 Spring-Redis


环境准备

引入pom依赖

<!-- 缓存 -->
<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>   
<!--json-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

redis-config.properties

# Redis settings 
# server IP 
redis.host=127.0.0.1
# server port 
redis.port=6379
# server pass 
redis.pass=
# use dbIndex
redis.database=0
# 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例 
redis.maxIdle=1000
# 表示当borrow(引入)一个jedis实例时,最大的等待时间
# 如果超过等待时间(毫秒),则直接抛出JedisConnectionException;
redis.maxWait=3000
# 在borrow一个jedis实例时,是否提前进行validate操作;
# 如果为true,则得到的jedis实例均是可用的
#redis.testOnBorrow=true
redis.testOnBorrow=false

spring-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"
       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">

    <context:property-placeholder location="classpath*:config/*.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="keySerializer" class="com.sino.redis.StringRedisSerializer" />
    <!--注意 : 下面的class要改成一行,篇幅问题换行了-->
    <bean id="valueSerializer"
            class="org.springframework.data.redis.
                        serializer.GenericJackson2JsonRedisSerializer" />

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="JedisConnectionFactory"/>
        <property name="keySerializer" ref="keySerializer" />
        <property name="valueSerializer" ref="valueSerializer" />
        <property name="hashKeySerializer" ref="keySerializer" />
        <property name="hashValueSerializer" ref="valueSerializer" />
    </bean>

    <!-- 配置RedisCacheManager -->
    <bean id="redisCacheManager"
          class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="redisTemplate"/>
        <property name="defaultExpiration" value="300000"/>
    </bean>

</beans>

注 : 在上面的配置中,修改了序列化方式

原因是spring提供的StringRedisSerializer序列化有类型限制。比如key为Long类型时,反序列化到内存时会报异常 -> Integer不能转化为Long

解决方案 : 重写 StringRedisSerializer 即可,如下

package com.sino.redis;

import com.alibaba.fastjson.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.Assert;
import java.nio.charset.Charset;

public class StringRedisSerializer implements RedisSerializer<Object> {

    private final Charset charset;

    private final String target = "\"";

    private final String replacement = "";

    public StringRedisSerializer() {
        this(Charset.forName("UTF8"));
    }

    public StringRedisSerializer(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }

    @Override
    public String deserialize(byte[] bytes) {
        return (bytes == null ? null : new String(bytes, charset));
    }

    @Override
    public byte[] serialize(Object object) {
        String string = JSON.toJSONString(object);
        if (string == null) {
            return null;
        }
        string = string.replace(target, replacement);
        return string.getBytes(charset);
    }
}

Redis Key生成策略

SpringCacheKeyGenerator 主键生成策略

// 主键样例 : sino:com.sino.test.UserService:908086459
public class SpringCacheKeyGenerator implements KeyGenerator {

    private final static int NO_PARAM_KEY = 0;
    // key前缀,用于区分不同项目的缓存,建议每个项目单独设置
    private String keyPrefix = "sino";

    @Override
    public Object generate(Object target, Method method, Object... params) {

        char sp = ':';
        StringBuilder strBuilder = new StringBuilder(30);
        strBuilder.append(keyPrefix);
        strBuilder.append(sp);
        // 类名
        strBuilder.append(getPackageName(target.getClass()));
        strBuilder.append('.');
        strBuilder.append(target.getClass().getSimpleName());
        strBuilder.append(sp);
        // 方法名
        //这里不能配方法名,否则会导致Redis注解失效
        //例如某个缓存叫 findXXX 与 updateXXX 会不同步
        //strBuilder.append(method.getName());
        //strBuilder.append(sp);
        if (params.length > 0) {
            // 参数值
            for (Object object : params) {
                if (BeanHelper.isSimpleValueType(object.getClass())) {
                    strBuilder.append(object);
                } else {
                    strBuilder.append(JsonHelper.toJson(object).hashCode());
                }
            }
        } else {
            strBuilder.append(NO_PARAM_KEY);
        }
        return strBuilder.toString();
    }

    public static void main(String[] args) {
        System.out.println(getPackageName(Object.class));
    }

    private static String getPackageName(Class classz) {
        return classz.getPackage().getName();
    }

    public String getKeyPrefix() {
        return keyPrefix;
    }

    public void setKeyPrefix(String keyPrefix) {
        this.keyPrefix = keyPrefix;
    }
}

BeanHelper

import org.springframework.util.ClassUtils;
import java.net.URI;
import java.net.URL;
import java.util.Date;
import java.util.Locale;

public class BeanHelper {
    /**
     * 判断是否是简单值类型.包括:基础数据类型、
     *      CharSequence、Number、Date、URL、URI、Locale、Class;
     * @param clazz
     * @return
     */
    public static boolean isSimpleValueType(Class<?> clazz) {
        return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum()
                || CharSequence.class.isAssignableFrom(clazz)
                || Number.class.isAssignableFrom(clazz)
                || Date.class.isAssignableFrom(clazz) || URI.class == clazz
                || URL.class == clazz || Locale.class == clazz
                || Class.class == clazz);
    }
}

JsonHelper

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class JsonHelper {

    /**
     * Java对象序列化为JSON字符串
     * @param obj Java对象
     * @return json字符串
     */
    public static String toJson(Object obj) {
        return JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue);
    }

}

Redis 缓存注解

这里主要介绍@CacheConfig、@Cacheable、@CachePut、@CacheEvict这几个注解

注 : 要开启Redis缓存注解需要依赖上面提到的RedisKey的生成策略,同时要开启cache注解

@CacheConfig 配置在类上,cacheNames即定义了本类中所有用到缓存的地方,都去找这个库。

@Cacheable 配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,下次本方法执行前,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找

@CachePut 类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果

@CacheEvict 用来清除用在本方法或者类上的缓存数据

修改 spring-redis.xml

注意 : xml中新增了cache约束

<?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: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/cache 
            http://www.springframework.org/schema/cache/spring-cache.xsd">

    <!--Key生成策略-->
    <bean id="cacheKeyGenerator"
          class="com.sino.redis.SpringCacheKeyGenerator"/>
    <!--开启Redis注解-->
    <cache:annotation-driven
            cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>

</beans>

入门案例

@Service
@CacheConfig(cacheNames = "redisTest")
public class TestService {
    @Autowired
    private RedisTemplate redisTemplate;

    @Cacheable
    public TbSeller findSeller (TbSeller tbSeller){
        System.out.println("findSeller:"+tbSeller);
        return tbSeller;
    }

    @CachePut
    public TbSeller updateTbSeller (TbSeller tbSeller){
        System.out.println("updateTbSeller:"+tbSeller);
        return tbSeller;
    }

    @CacheEvict
    public void deleteTbSeller (TbSeller tbSeller){
        System.out.println("deleteTbSeller:"+tbSeller);
    }
}

Redis事务

有时我们需要将Service控制在事务中,这时我们需要开启Redis的事务

注 : 开启了事务之后,使用 redisTemplate 时,一定要将它放入Redis事务中,否则会导致Redis连接不释放

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="JedisConnectionFactory"/>
        <property name="keySerializer" ref="keySerializer" />
        <property name="valueSerializer" ref="valueSerializer" />
        <property name="hashKeySerializer" ref="keySerializer" />
        <property name="hashValueSerializer" ref="valueSerializer" />
        <!--开启事务 会导致redisTemplate连接不释放 -->
        <property name="enableTransactionSupport" value="true"></property>
    </bean>

案例

@Service
@CacheConfig(cacheNames = "redisTest")
public class TestService {
    @Autowired
    private RedisTemplate redisTemplate;

    public void test (String keyValue){
        System.out.println("Transactional");
        redisTemplate.multi();
        redisTemplate.boundValueOps(keyValue).set(keyValue);
        redisTemplate.boundHashOps("testtx").put(keyValue,keyValue);
        redisTemplate.exec();//执行
        //redisTemplate.discard();//放弃此事务,执行回滚
    }
}

至此,Spring与Redis的整合就介绍完啦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值