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:
@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:
// 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");