spring-boot整合redis(多数据源)

官网:https://spring.io/projects/spring-data-redis

Spring Data Redis是Spring Data系列的一部分,可轻松配置并从Spring应用程序访问Redis,并支持low-level和hight-level的抽象接口。可以看到spring data下面共提供了如下技术方案:

市面上已经有 Redis、Redisson、Lettuce 等优秀的 Java Redis 工具库,为什么还要有 Spring Data Redis 呢?

  • 对于下层,Spring Data Redis 提供了统一的操作模板(RedisTemplate 类),封装了 Jedis、Lettuce 客户端库的 API 操作,来访问 Redis 数据(Spring Data Redis 底层使用的是 Jedis、Lettuce 库)。
  • 对于上层,开发者学习如何使用 Spring Data Redis 即可,而无需关心 Jedis、Lettuce 的 API 操作。甚至,未来如果我们想将 Redis 访问从 Jedis 迁移成 Lettuce 来,无需做任何的变动。
  • springboot 2.X 版本使用Lettce作为默认连接池,可以切换到jedis。

注意:目前Spring Data Redis 暂时只支持 Jedis、Lettuce 的内部封装,而 Redisson 是由 redisson-spring-data 来提供。

说明:jedis的使用见 https://blog.csdn.net/liuxiao723846/article/details/86724124

接下来,我们主要介绍spring-data-redis的使用(redis单机情况),对于redis cluster、redis sentinel的情况后续介绍。

1、单数据源(springboot)

1)maven:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.4.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <!--去掉对 Lettuce 的依赖,因为 Spring Boot 优先使用 Lettuce作为Redis 客户端 -->
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- 引入 Jedis 的依赖,这样 Spring Boot 实现对 Jedis 的自动化配置 -->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
</dependencies>

说明:spring-boot-starter-data-redis中默认使用lettuce,若使用jedis库,需要将其exclude,然后手动引入jedis。

2)redis.properties:

redis.host=localhost
redis.port=7395
redis.password=123
redis.timeout=600000
#  Redis 数据库号,默认为 0 
redis.database=0

#最大空闲数  
redis.maxIdle=300  
#连接池的最大数据库连接数。设为0表示无限制,如果是jedis 2.4以后用redis.maxTotal  
#redis.maxActive=600  
#控制一个pool可分配多少个jedis实例,用来替换上面的redis.maxActive,如果是jedis 2.4以后用该属性  
redis.maxTotal=1000  
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。  
redis.maxWaitMillis=1000  
#连接的最小空闲时间 默认1800000毫秒(30分钟)  
redis.minEvictableIdleTimeMillis=300000  
#每次释放连接的最大数目,默认3  
redis.numTestsPerEvictionRun=1024  
#逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1  
redis.timeBetweenEvictionRunsMillis=30000  
#是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个  
redis.testOnBorrow=true  
#在空闲时检查有效性, 默认false  
redis.testWhileIdle=true  

3)RedisConfig.java

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@PropertySource("classpath:redis.properties")
public class RedisConfig {
    
    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private Integer port;
    @Value("${redis.password}")
    private String pwd;
    @Value("${redis.timeout}")
    private Integer timeout;
    
    @Value("${redis.maxIdle}")
    private Integer maxIdle;
    @Value("${redis.maxTotal}")
    private Integer maxTotal;
    @Value("${redis.maxWaitMillis}")
    private Integer maxWaitMillis;
    @Value("${redis.minEvictableIdleTimeMillis}")
    private Integer minEvictableIdleTimeMillis;
    @Value("${redis.numTestsPerEvictionRun}")
    private Integer numTestsPerEvictionRun;
    @Value("${redis.timeBetweenEvictionRunsMillis}")
    private long timeBetweenEvictionRunsMillis;
    @Value("${redis.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${redis.testWhileIdle}")
    private boolean testWhileIdle;
    
    /**
     * JedisPoolConfig 连接池
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);// 最大空闲数
        jedisPoolConfig.setMaxTotal(maxTotal);// 连接池的最大数据库连接数
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);// 最大建立连接等待时间
        jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);//逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
        jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);//每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1        
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);//是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
        jedisPoolConfig.setTestWhileIdle(testWhileIdle);//在空闲时检查有效性, 默认false
        return jedisPoolConfig;
    }
    
    /**
     * 链接工厂
     */
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
        JedisConnectionFactory JedisConnectionFactory = new JedisConnectionFactory();
        JedisConnectionFactory.setPoolConfig(jedisPoolConfig);  
        JedisConnectionFactory.setHostName(host);  
        JedisConnectionFactory.setPort(port);  
        if (pwd != null && !pwd.equals("")) {
            JedisConnectionFactory.setPassword(pwd);  
        }
        JedisConnectionFactory.setTimeout(timeout);  
        return JedisConnectionFactory; 
    }

    /**
     * RedisTemplate
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);// key采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);// hash的key采用String的序列化方式

//        GenericJackson2JsonRedisSerializer jacksonSerializer = new GenericJackson2JsonRedisSerializer();
//        template.setValueSerializer(jacksonSerializer);// value采用jackson的序列化方式
//        template.setHashValueSerializer(jacksonSerializer);// hash的value采用jackson序列化方式
        
        template.setEnableTransactionSupport(true);// 开启事务
        template.setConnectionFactory(factory);// 设置链接工厂
        
        return template;
    }
}

说明:

  • 如果代码中需要自己设置value的序列化方式,在RedisConfig中就无需指定。(比如:业务代码中有的按照json,有的按照pb序列化)
  • spring-data-redis中字带了string、jackson等方式的序列化

注意如果没有指定key的序列化方式,使用时发现在redis中的key会多出来类似\xac\xed\x00\x05t\x00这种字符串,所以,最好指定key的序列化方式为string。

说明:新的spring-data-redis中,上面的方式创建JedisConnectionFactory已经被废弃了,如下:

新的api中引入了RedisStandaloneConfiguration和JedisClientConfiguration,可以使用如下方式创建:

4)使用:

@Service
public class TestService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public String test1(String value) {
        redisTemplate.opsForValue().set("test_1",value);
        
        return (String)redisTemplate.opsForValue().get("test_1");
    }
    
    public String test2(String value) {
        User user = new User(99,value);
        byte[] serialize = SerializeUtil.serialize(user);
        
        redisTemplate.opsForValue().set("test_9",serialize);
        
        byte[] bytes = (byte[])redisTemplate.opsForValue().get("test_9");
        User obj = (User) SerializeUtil.unserialize(bytes);

        return obj.toString();
    }
}


//序列化工具
public class SerializeUtil {
    public static byte[] serialize(Object object) {//序列化
        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            byte[] bytes = baos.toByteArray();
            return bytes;
        } catch (Exception e) {
            System.out.println(e);
        }
        return null;
    }
 
    public static Object unserialize(byte[] bytes) {//反序列化
        ByteArrayInputStream bais = null;
        try {
            bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (Exception e) {
            System.out.println(e);
        }
        return null;
    }
}

2、单数据源2(spring)

上面使用的是注解方式(springboot默认采用config注解,来代替application.xml中各种bean的声明),接下来我们采用原始的spring application.xml配置的方式来实现一遍。

1)maven:

<properties>
  <org.springframework.version>5.0.8.RELEASE</org.springframework.version>
</properties>

<dependencies>
  <!-- spring mvc -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  <!-- spring -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.2</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${org.springframework.version}</version>
  </dependency>
  
  <!-- spring-data-redis -->
  <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>2.0.9.RELEASE</version>
      <exclusions>
              <exclusion><!-- 去掉对 Lettuce 的依赖-->
                  <groupId>io.lettuce</groupId>
                  <artifactId>lettuce-core</artifactId>
              </exclusion>
      </exclusions>
  </dependency>
  <!-- redis -->
  <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>2.9.0</version>
  </dependency>
  
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.6</version>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
  </dependency>
  
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.5</version>
  </dependency>
</dependencies>

spring的版本要和spring-data-redis的版本对应,否则在启动应用时会报java.lang.NoSuchMethodError(spring应用报该类错误,一般来说都是版本冲突导致);其次,由于springmvc中依赖了jackson,所以jackson的版本也要对应。由于springboot简化了spring 项目的配置,所以我们可以在springboot项目中找到一份相匹配的spring、springmvc、spring-data-*、jackson等版本信息。

2)spring配置文件:

a、springMVC.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:aop="http://www.springframework.org/schema/aop"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:mvc="http://www.springframework.org/schema/mvc"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.2.xsd
     http://www.springframework.org/schema/mvc
     http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
  <!-- <aop:aspectj-autoproxy/> -->
  <!-- 基于Annotation的映射方式 -->
   <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

  <!-- 自动搜索Sping的注解类 -->
  <context:component-scan base-package="cn.edu.nuc.springmvc_test" >
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>  
      <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>  
  </context:component-scan>
  
  <!-- 配置静态资源,直接映射到对应的文件夹,不被DispatcherServlet处理 -->
  <mvc:resources mapping="/images/**" location="/images/"/>
  <mvc:resources mapping="/html/**" location="/html/"/>
  <mvc:resources mapping="/js/**" location="/js/"/>
  <mvc:resources mapping="/css/**" location="/css/"/>

  <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/"></property>
    <property name="suffix" value=".jsp"></property>
  </bean>
</beans> 

b、application.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:aop="http://www.springframework.org/schema/aop"
     xmlns:context="http://www.springframework.org/schema/context"
     xmlns:mvc="http://www.springframework.org/schema/mvc"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xmlns:jaxws="http://cxf.apache.org/jaxws"
     xmlns:task="http://www.springframework.org/schema/task"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context-3.2.xsd
     http://www.springframework.org/schema/mvc
     http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
     http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task-3.2.xsd">
     
  <task:annotation-driven/><!--开启spring scheduled注解-->
     
  <!-- 自动搜索Sping的注解类-->
  <context:component-scan base-package="cn.edu.nuc.springmvc_test" >
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
  </context:component-scan>

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

  <import resource="redis.xml"/>
</beans> 

c、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: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">

    <!--redis connection pool-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" primary="true">
        <property name="maxTotal" value="${redis.maxTotal}"></property>
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>

    <!-- redis connection factory -->
    <bean id="redisConnectionFactory" primary="true" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}"/>
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

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

    <!-- redis template -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="redisConnectionFactory"/>
        <property name="keySerializer" ref="stringReadisSerializer"/>
    </bean>
</beans>

:多个spring配置文件中只能配置一个PropertyPlaceholderConfigurer,见:https://blog.csdn.net/liuxiao723846/article/details/116493944

说明:在spring项目中,使用了各种xml配置代替了springboot中的各种Config类。

3)redis.properties:

在classpath下创建redis.properties文件,同上。

4)测试类:同上

 

3、单数据源(springboot中使用xml配置文件)

上面分别介绍了在springboot和spring项目中使用spring-data-redis组件。都知道springboot是用来简化spring应用的一个框架,我们可以把spring项目中的redis.xml 放到springboot项目中,并且在main方法上通过@ImportResource注解来指定redis.xml配置文件,同时在项目中去掉对应的RedisConfig类。如下:

1)main方法:

@SpringBootApplication
@ImportResource(value = {"classpath:redis.xml"})
@ComponentScan(basePackages = {"cn.edu.nuc.springboot_test.*"})
public class App {

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(App.class, args);

    }
}

2)redis.xml文件:

把上面的redis.xml放到springboot项目的classpath下,在redis.xml中通过<context:property-placeholder>标签来指定redis.properties文件,同时将RedisConfig类删掉,即可。

4、多数据源

在实际项目中,一个应用往往会从多个redis数据源中获取数据,接下来,我们介绍如何在springboot项目中配置多个redis数据源。

4.1)springboot中通过spring-data-redis配置多个redis数据源:

1)RedisConfig.java:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
@PropertySource("classpath:redis.properties")
public class RedisConfig {
    //数据源1
    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private Integer port;
    @Value("${redis.password}")
    private String pwd;
    @Value("${redis.timeout}")
    private Integer timeout;
    //数据源2
    @Value("${demo.redis.host}")
    private String demoHost;
    @Value("${demo.redis.port}")
    private Integer demoPort;
    @Value("${demo.redis.password}")
    private String demoPwd;
    @Value("${demo.redis.database}")
    private Integer demoDatabase;
    @Value("${demo.redis.timeout}")
    private Integer demoTimeout;
    //连接池配置信息
    @Value("${redis.maxIdle}")
    private Integer maxIdle;
    @Value("${redis.maxTotal}")
    private Integer maxTotal;
    @Value("${redis.maxWaitMillis}")
    private Integer maxWaitMillis;
    @Value("${redis.minEvictableIdleTimeMillis}")
    private Integer minEvictableIdleTimeMillis;
    @Value("${redis.numTestsPerEvictionRun}")
    private Integer numTestsPerEvictionRun;
    @Value("${redis.timeBetweenEvictionRunsMillis}")
    private long timeBetweenEvictionRunsMillis;
    @Value("${redis.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${redis.testWhileIdle}")
    private boolean testWhileIdle;
    
    //JedisPoolConfig 连接池
    @Bean(name="jedisPoolConfig")
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);// 最大空闲数
        jedisPoolConfig.setMaxTotal(maxTotal);// 连接池的最大数据库连接数
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);// 最大建立连接等待时间
        jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);//逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
        jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);//每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
        jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1        
        jedisPoolConfig.setTestOnBorrow(testOnBorrow);//是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
        jedisPoolConfig.setTestWhileIdle(testWhileIdle);//在空闲时检查有效性, 默认false
        return jedisPoolConfig;
    }
    
    //数据源1的链接工厂
    @Bean(name="jedisConnectionFactory")
    @Primary
    public JedisConnectionFactory jedisConnectionFactory(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig){
        JedisConnectionFactory JedisConnectionFactory = new JedisConnectionFactory();
        JedisConnectionFactory.setPoolConfig(jedisPoolConfig);  
        JedisConnectionFactory.setHostName(host);  
        JedisConnectionFactory.setPort(port);  
        if (pwd != null && !pwd.equals("")) {
            JedisConnectionFactory.setPassword(pwd);  
        }
        JedisConnectionFactory.setTimeout(timeout);  
        return JedisConnectionFactory; 
    }

    //数据源2的连接工厂
    @Bean(name="demoJedisConnectionFactory")
    public JedisConnectionFactory demoJedisConnectionFactory(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig){
        //设置host、port等
        RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
        redisConfig.setHostName(demoHost);
        redisConfig.setPort(demoPort);
        if (demoPwd != null && !demoPwd.equals("")) {
            redisConfig.setPassword(RedisPassword.of(demoPwd));
        }
        redisConfig.setDatabase(demoDatabase);
        
        //设置连接池、timeout等
        JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder()
                .connectTimeout(Duration.ofMillis(demoTimeout))
                .usePooling().poolConfig(jedisPoolConfig).build();
        
        return new JedisConnectionFactory(redisConfig,jedisClientConfiguration);
    }

    //数据源1的redisTemplate
    @Bean(name="redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(@Qualifier("jedisConnectionFactory") RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);// key采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);// hash的key采用String的序列化方式
        template.setEnableTransactionSupport(true);// 开启事务
        template.setConnectionFactory(factory);// 设置链接工厂
        
        return template;
    }

    //数据源2的redisTemplate
    @Bean(name="demoRedisTemplate")
    public RedisTemplate<String, Object> demoRedisTemplate(@Qualifier("demoJedisConnectionFactory") RedisConnectionFactory demoJedisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);// key采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);// hash的key采用String的序列化方式
        
        template.setEnableTransactionSupport(true);// 开启事务
        template.setConnectionFactory(demoJedisConnectionFactory);// 设置链接工厂
        
        return template;
    }
}

说明:每个数据源都配置一个连接工厂。在配置JedisConnectionFactory bean时,需要指定primary,否则会报如下错误:

Parameter 0 of method redisTemplate in org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration required a single bean, but 2 were found:

2)使用:

@Service
public class TestService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;//数据源1
    @Autowired
    private RedisTemplate<String, Object> demoRedisTemplate;//数据源 2

4.2)源码解读:

通过spring-data-redis使用redis,通常需要配置以下三个bean:

  • JedisPoolConfig:连接池相关的配置;
  • JedisConnectionFactory:redis连接工厂,依赖JedisPoolConfig对象;
  • RedisTemplate:redis模版,依赖JedisConnectionFactory对象;

我们知道,在spring中默认bean都是单例(除非指定bean的Scope="prototype"),在上面的多数据源配置中,JedisPoolConfig bean只配置了一个,每个数据源都配置了一个JedisConnectionFactory bean,这样会不会造成不同数据源的连接工厂使用的是一个连接池对象?先给答案:不会!!!

启动上面多数据源的应用,使用JvisualVM连接上去,通过MBean可以看到,系统中一共有两个对象池,这说明每个redis数据源的连接工厂使用了一个连接池:

通过源码发现:

前面我们知道,spring-data-redis底层使用的是jedis或者lettuce库,这两个库都提供了redis连接池的功能。这里我们一jedis为例子:

在JedisConnectionFactory中有一个redis.clients.util.Pool pool 属性,这个属性正是jedis库提供的连接池对象(底层是org.apach.commons.pool2),该属性的初始化是通过如下方法完成的:

从这里可以清晰的看到,我们配置的JedisPoolConfig bean只是保存了连接池相关的信息,在JedisConnectionFactory中会根据这个信息来真正创建redis的连接池。所以,JedisPoolConfig bean 是单例还是多实例,都无所谓!

4.3)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: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*:redis.properties" ignore-unresolvable="true" />
    
    <!-- 连接池信息 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" primary="true">
        <property name="maxTotal" value="${redis.maxTotal}"></property>
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>

    <!-- 数据源1 factory -->
    <bean id="redisConnectionFactory" primary="true" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}"/>
        <!--<property name="database" value="${redis.database}"/>-->
        <property name="poolConfig" ref="poolConfig"/>
    </bean>
    <!-- 数据源2 factory -->
    <bean id="demoRedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${demo.redis.host}"/>
        <property name="port" value="${demo.redis.port}"/>
        <property name="password" value="${demo.redis.password}"/>
        <!--<property name="database" value="${demo.redis.database}"/>-->
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

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

    <!-- 数据源1 template -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="redisConnectionFactory"/>
        <property name="keySerializer" ref="stringReadisSerializer"/>
    </bean>
    <!-- 数据源2 template -->
    <bean id="demoRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="demoRedisConnectionFactory"/>
        <property name="keySerializer" ref="stringReadisSerializer"/>
    </bean>
</beans>

注意:在springboot中,多个数据源的JedisConnectionFactory bean需要配置primary,在springmvc项目中,就不需要指定primary。

相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页