Redis トランザクション

Redis は、multiexecdiscard コマンドを通じてトランザクション (英語) のサポートを提供します。これらの操作は RedisTemplate (Javadoc) で使用できます。ただし、RedisTemplate では、トランザクション内のすべての操作が同じ接続で実行されることは保証されません。

Spring Data Redis は、Redis トランザクションを使用する場合など、同じ connection を使用して複数の操作を実行する必要がある場合に使用する SessionCallback (Javadoc) インターフェースを提供します。次の例では、multi メソッドを使用しています。

//execute a transaction
List<Object> txResults = redisOperations.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 は、その値、ハッシュキー、ハッシュ値シリアライザーを使用して、exec のすべての結果を逆直列化してから返します。トランザクション結果のカスタムシリアライザーを渡すことができる追加の exec メソッドがあります。

multi() と exec() の間で例外が発生した場合 (Redis がタイムアウト内に応答しない場合のタイムアウト例外など)、接続がトランザクション状態でスタックする可能性があることに注意してください。このような状況を防ぐには、トランザクション状態を破棄して接続をクリアする必要があります。

List<Object> txResults = redisOperations.execute(new SessionCallback<List<Object>>() {
  public List<Object> execute(RedisOperations operations) throws DataAccessException {
    boolean transactionStateIsActive = true;
	try {
      operations.multi();
      operations.opsForSet().add("key", "value1");

      // This will contain the results of all operations in the transaction
      return operations.exec();
    } catch (RuntimeException e) {
	    operations.discard();
		throw e;
    }
  }
});

@Transactional サポート

デフォルトでは、RedisTemplate はマネージド Spring トランザクションに参加しません。@Transactional または TransactionTemplate を使用するときに RedisTemplate で Redis トランザクションを使用する場合は、setEnableTransactionSupport(true) を設定して、各 RedisTemplate のトランザクションサポートを明示的に有効にする必要があります。トランザクションサポートを有効にすると、RedisConnection が ThreadLocal に基づく現在のトランザクションにバインドされます。トランザクションがエラーなしで終了した場合、Redis トランザクションは EXEC でコミットされます。それ以外の場合は、DISCARD でロールバックされます。Redis トランザクションはバッチ指向です。進行中のトランザクション中に発行されたコマンドはキューに入れられ、トランザクションをコミットするときにのみ適用されます。

Spring Data Redis は、進行中のトランザクションで読み取り専用コマンドと書き込みコマンドを区別します。KEYS などの読み取り専用コマンドは、読み取りを許可するために新しい(スレッドにバインドされていない) RedisConnection にパイプされます。書き込みコマンドは RedisTemplate によってキューに入れられ、コミット時に適用されます。

次の例は、トランザクション管理を構成する方法を示しています。

例 1: トランザクション管理を可能にする構成
@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 宣言型トランザクション管理を有効にするために Spring コンテキストを構成します。
2 接続を現在のスレッドにバインドすることにより、トランザクションに参加するように RedisTemplate を構成します。
3 トランザクション管理には PlatformTransactionManager が必要です。Spring Data Redis には、PlatformTransactionManager 実装は付属していません。アプリケーションが JDBC を使用していると仮定すると、Spring Data Redis は、既存のトランザクションマネージャーを使用してトランザクションに参加できます。

次の例はそれぞれ、使用上の制約を示しています。

例 2: 使用上の制約
// must be performed on thread-bound connection
template.opsForValue().set("thing1", "thing2");

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

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