Redis Transactions

Redis provides support for transactions through the multi, exec, and discard commands. These operations are available on RedisTemplate. However, RedisTemplate is not guaranteed to execute all operations in the transaction with the same connection.

Spring Data Redis provides the SessionCallback interface for use when multiple operations need to be performed with the same connection, such as when using Redis transactions. The following example uses the multi method:

//execute a transaction
List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
  public List<Object> execute(RedisOperations operations) throws DataAccessException {
    operations.multi();
    operations.opsForSet().add("key", "value1");

    // This will contain the results of all operations in the transaction
    return operations.exec();
  }
});
System.out.println("Number of items added to set: " + txResults.get(0));

RedisTemplate uses its value, hash key, and hash value serializers to deserialize all results of exec before returning. There is an additional exec method that lets you pass a custom serializer for transaction results. NOTE: As of version 1.1, an important change has been made to the exec methods of RedisConnection and RedisTemplate. Previously, these methods returned the results of transactions directly from the connectors. This means that the data types often differed from those returned from the methods of RedisConnection. For example, zAdd returns a boolean indicating whether the element has been added to the sorted set. Most connectors return this value as a long, and Spring Data Redis performs the conversion. Another common difference is that most connectors return a status reply (usually the string, OK) for operations such as set. These replies are typically discarded by Spring Data Redis. Prior to 1.1, these conversions were not performed on the results of exec. Also, results were not deserialized in RedisTemplate, so they often included raw byte arrays. If this change breaks your application, set convertPipelineAndTxResults to false on your RedisConnectionFactory to disable this behavior.

@Transactional Support

By default, transaction Support is disabled and has to be explicitly enabled for each RedisTemplate in use by setting setEnableTransactionSupport(true). Doing so forces binding the current RedisConnection to the current Thread that is triggering MULTI. If the transaction finishes without errors, EXEC is called. Otherwise DISCARD is called. Once in MULTI, RedisConnection queues write operations. All readonly operations, such as KEYS, are piped to a fresh (non-thread-bound) RedisConnection.

The following example shows how to configure transaction management:

Example 1. Configuration enabling Transaction Management
@Configuration
@EnableTransactionManagement                                 (1)
public class RedisTxContextConfiguration {

  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);              (2)
    return template;
  }

  @Bean
  public RedisConnectionFactory redisConnectionFactory() {
    // jedis || Lettuce
  }

  @Bean
  public PlatformTransactionManager transactionManager() throws SQLException {
    return new DataSourceTransactionManager(dataSource());   (3)
  }

  @Bean
  public DataSource dataSource() throws SQLException {
    // ...
  }
}
1 Configures a Spring Context to enable declarative transaction management.
2 Configures RedisTemplate to participate in transactions by binding connections to the current thread.
3 Transaction management requires a PlatformTransactionManager. Spring Data Redis does not ship with a PlatformTransactionManager implementation. Assuming your application uses JDBC, Spring Data Redis can participate in transactions by using existing transaction managers.

The following examples each demonstrate a usage constraint:

Example 2. Usage Constraints
// must be performed on thread-bound connection
template.opsForValue().set("thing1", "thing2");

// read operation must be executed on a free (not transaction-aware) connection
template.keys("*");

// returns null as values set within a transaction are not visible
template.opsForValue().get("thing1");