Implementation of spring data redis Connection Operation redis

  • 2021-10-27 07:21:58
  • OfStack

There are many clients for Java to connect to redis, among which Jedis is commonly used. (Reference: redis client)

spring-data-redis is a highly encapsulated Jedis, which is very convenient to use. The following is a code example to illustrate the use of spring-data-redis.

The whole project uses maven to manage the jar package, and the pom file is as follows:


<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.snow</groupId>
 <artifactId>redis-test</artifactId>
 <version>0.0.1</version>
 <packaging>jar</packaging>
 
 <name>redis-test</name>
 <url>http://maven.apache.org</url>
 
 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <spring.version>4.3.2.RELEASE</spring.version>
  <slf4j.version>1.7.12</slf4j.version>
  <log4j.version>1.2.17</log4j.version>
  <junit.version>4.12</junit.version>
  <spring-data-redis.version>1.7.2.RELEASE</spring-data-redis.version>
 </properties>
 
 <dependencies>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>${spring.version}</version>
   <scope>test</scope>
  </dependency>
 
  <dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.5.0</version>
   <type>jar</type>
  </dependency>
  <dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-redis</artifactId>
   <version>${spring-data-redis.version}</version>
   <type>jar</type>
  </dependency>
 
  <!-- log -->
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>${log4j.version}</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>${slf4j.version}</version>
  </dependency>
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>${slf4j.version}</version>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>${junit.version}</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
</project>

The main jia packets used are spring-context, spring-data-redis, jedis and three jar packets related to log printing

Configure spring-data-redis as follows application-context-redis. xml:


    <!--  See the configuration method  //www.ofstack.com/database/201311/254449.html -->
 <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
   <property name="maxTotal" value="500"/> <!--  Control 1 A pool How many can be allocated jedis Instances  --> 
   <property name="maxIdle" value="100"/><!--  Maximum can be maintained idel Number of objects in state  -->
   <property name="maxWaitMillis" value="1000"/><!--  Indicates when borrow1 A jedis Instance, the maximum waiting time, if it exceeds the waiting time, it will be thrown directly JedisConnectionException --> 
   <property name="timeBetweenEvictionRunsMillis" value="30000"/><!--  How long does it take to check 1 Idle connections in secondary connection pool  -->
   <property name="minEvictableIdleTimeMillis" value="30000"/><!--  How long will idle connections be retracted ,  In milliseconds  -->
   <property name="testOnBorrow" value="true"/> <!--  When the call borrow Object Method, whether to check the validity  -->
   <property name="testOnReturn" value="true"/> <!--  When the call return Object Method, whether to check the validity  -->
   <property name="testWhileIdle" value="true"/>
 </bean>
      
   <span style="white-space:pre"> </span><!--  Direct connection master -->
 <bean id="jedisConnectionFactory"
  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  <constructor-arg ref="jedisPoolConfig" />
  <property name="hostName" value="${redis.hostName}" />
  <property name="port" value="${redis.port}" />
  <!-- <property name="password" value ="${redis.password}" /> -->
 </bean> 
    
    <span style="white-space:pre"> </span><bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" > 
     <span style="white-space:pre"> </span><property name="connectionFactory" ref="jedisConnectionFactory" />  
    <span style="white-space:pre"> </span></bean>

Configure a connection pool in the configuration file, then configure an connection factory, and finally configure bean redisTemplate. The class we use is StringRedisTemplate, which determines that our subsequent operations key and value must be String type. Usually, we want to store an object in redis. At this time, we can convert the object into json string and then store it. When we take it out, we can convert json string into an object. This kind of operation is relatively convenient, all of which are threaded jar packages. In addition to StringRedisTemplate, we can also use the RedisTemplate class, which is not discussed here.

host and port information of redis are also needed in the configuration file application-context-redis. xml. We can configure one file properties as follows: redis. properties


redis.hostName=127.0.0.1
redis.port=6379

This configuration file needs to be loaded in application-contex. xml, and application-context. xml also needs to load the application-context-redis. xml configuration file


 <bean id="dbPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="ignoreUnresolvablePlaceholders" value="true" />
  <property name="locations">
   <list>
    <value>classpath:redis.properties</value>
   </list>
  </property>
  <property name="fileEncoding" value="UTF-8" /><!--  Encoding of resource files  -->
 </bean> 
 <import resource="classpath:application-context-redis.xml" />  

Next, you can write an interface for redis operation


public interface RedisService { 
    public void setStr(String key, String value);    
    public String getStr(String key);    
    public void rPushList(String key, String value);
    public String lPopList(String key); 
    public void delKey(String key); 
}

The interface is implemented as follows:


@Service(value = "redisService")
public class RedisServiceImpl extends AbstractRedisDao<String, String> implements RedisService {
 
    @Override
    public void setStr(String key, String value) {
        getRedisTemplate().opsForValue().set(key, value);
    }
 
    @Override
    public String getStr(String key) {
        return getRedisTemplate().opsForValue().get(key);
    }
 
    @Override
    public void rPushList(String key, String value) {
        getRedisTemplate().opsForList().rightPush(key, value);
 
    }
 
    @Override
    public String lPopList(String key) {
        return getRedisTemplate().opsForList().leftPop(key);
    }
    
    @Override
    public void delKey(String key) {
        getRedisTemplate().delete(key);
    }
}

This implementation inherits an AbstractRedisDao, which mainly provides the getRedisTemplate () function that enables us to call the redisTemplate bean instance configured in application-context-redis. xml


public abstract class AbstractRedisDao<K, V> {
 
    @Autowired
    protected RedisTemplate<K, V> redisTemplate;
 
    //  Settings redisTemplate
    public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
 
    public RedisTemplate<K, V> getRedisTemplate() {
        return redisTemplate;
    }
}

The main program has been written, next you can use Junit to write a unit test under the interface test.


public class RedisServiceTest extends AbstractUnitTest {
    private static final Logger logger = LoggerFactory.getLogger(RedisServiceTest.class);
 
    @Resource
    private RedisService redisService;
 
    @Test
    public void testSetStr() {
        String key = "test";
        String value = "valuetest";
        redisService.setStr(key, value);
    }
 
    @Test
    public void testGetStr() {
        String key = "test";
        String value = redisService.getStr(key);
        logger.info("The value is {}", value);
    }
 
    @Test
    public void testRPushList() {
        String key = "list";
        for (int i = 0; i < 10; i++) {
            redisService.rPushList(key, String.valueOf(i));
        }
    }
       
    @Test
    public void testLPopList() {
        String key = "list";
        
        for(int i = 0; i < 9; i++) {
            String value = redisService.lPopList(key);
            logger.info("lpop value is {}", value);
        }
    }
    
    @Test
    public void testDelKey() {
        String key = "list";
        redisService.delKey(key);
    } 
}

In this test class, in order to run these test functions, all bean needs to be instantiated. This process is implemented in AbstractUnitTest, and the code is as follows:


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application-context.xml"})
public abstract class AbstractUnitTest {
    private static final Logger logger = LoggerFactory.getLogger(AbstractUnitTest.class);
 
//    @Test
//    public void stub() {
//        logger.info("msg from abstract unit test, just ignore this.");
//    }
 
    @After
    public void teardown() throws InterruptedException {
        logger.info("unit test complete.");
        TimeUnit.MILLISECONDS.sleep(500);//  Because some tests require asynchronous insertion of operation records, sleep1 Wait for the thread to end 
    } 
}

The AbstractUnitTest class can be used as a generic class for testing spring.

That's all the main code. You can see that there is no problem when you run it. Below I extract a printout to explain a problem:

016-08-02 20:43:16,608 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,609 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,610 INFO RedisServiceTest:54 - lpop value is 0
2016-08-02 20:43:16,610 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,611 INFO RedisServiceTest:54 - lpop value is 1
2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,612 INFO RedisServiceTest:54 - lpop value is 2
2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,612 INFO RedisServiceTest:54 - lpop value is 3
2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,613 INFO RedisServiceTest:54 - lpop value is 4
2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,613 INFO RedisServiceTest:54 - lpop value is 5
2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,614 INFO RedisServiceTest:54 - lpop value is 6
2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,618 INFO RedisServiceTest:54 - lpop value is 7
2016-08-02 20:43:16,618 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
2016-08-02 20:43:16,618 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
2016-08-02 20:43:16,618 INFO RedisServiceTest:54 - lpop value is 8
2016-08-02 20:43:16,618 INFO AbstractUnitTest:34 - unit test complete.

This output is obtained by running testLPopList, in which opening RedisConnection is performed 9 times, and then Closing Redis Connection 9 times. Does this mean that every time redis operation is performed, one connection needs to be created, and then the connection is closed? In fact, this is not the case. Reading the source code, we can find that all our operations on redis are performed by calling back the execute function. The code is as follows:


public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {
    return execute(action, exposeConnection, false);
}
// execute The implementation is as follows: 
// org.springframework.data.redis.core.RedisTemplate<K, V> ---  Final realization 
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
    Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
    Assert.notNull(action, "Callback object must not be null");
    RedisConnectionFactory factory = getConnectionFactory();
    RedisConnection conn = null;
    try {
        if (enableTransactionSupport) {
            // only bind resources in case of potential transaction synchronization
            conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
        } else {
            conn = RedisConnectionUtils.getConnection(factory);
        }
        boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
        RedisConnection connToUse = preProcessConnection(conn, existingConnection);
        boolean pipelineStatus = connToUse.isPipelined();
        if (pipeline && !pipelineStatus) {
            connToUse.openPipeline();
        }
        RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
        T result = action.doInRedis(connToExpose);
        // close pipeline
        if (pipeline && !pipelineStatus) {
            connToUse.closePipeline();
        }
        // TODO: any other connection processing?
        return postProcessResult(result, connToUse, existingConnection);
    } finally {
        if (!enableTransactionSupport) {
            RedisConnectionUtils.releaseConnection(conn, factory);
        }
    }
}

Here, RedisConnectionUtils. getConnection (factory) is called before action. doInRedis (connToExpose) is executed every time; Get a connection and enter the RedisConnnectionUtils class. getConnection (factory) finally calls the function doGetConnection (factory, true, false, enableTranactionSupport). With this function, we can look at the api document and find that it is not really creating a new redis connection, but only getting a connection in connectFactory, that is, taking out a connection from the connection pool. Of course, if no connection is available for connectFactory, then if allowCreate=true, a new connection will be created and added to connectFactory.
Basically, it can be determined that the real situation is that spring-data-redis has encapsulated the connection pool management for us, and we only need to call 1 series of operation functions, which brings great convenience to operate redis.

Finally, the source code of this article is attached: redis-test


Related articles: