SessionCallback

Redis provides transaction support through multi, exec, or discard commands, which are also available in the RedisTemplate. However, the RedisTemplate uses the RedisCallBack interface by default, and there is no guarantee that the same connection will be used to perform all operations in the same Transaction (Transaction is invalid at this point).

However, Spring Data Redis provides the SessionCallback interface for situations where multiple operations need to be guaranteed on the same connection, such as when Redis transactions are required. We can see:

public <T> T execute(SessionCallback<T> session) {
		Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
		Assert.notNull(session, "Callback object must not be null");

		RedisConnectionFactory factory = getConnectionFactory();
		// bind connection
		RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); // Line 8 try {returnsession.execute(this); } finally { RedisConnectionUtils.unbindConnection(factory); }}Copy the code
  • RedisTemplate.execute(SessionCallback<T> session)methodsLine 8Has been doneConnect the binding;

The usage is as follows:

//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 ops in the transaction
    returnoperations.exec(); }}); System.out.println("Number of items added to set: " + txResults.get(0));
Copy the code

Before returning, the RedisTemplate will use its value, hash key, and Hash Value serializer to deserialize all of exec’s results. An additional exec method allows you to pass custom serializers for transaction results.


@ Transactional support

As you can see above, Redis transactions can be supported through the SessionCallback binding and implement multi, exec, or discard. But this becomes complicated and the location of the Redis operation (opsXXx. X) becomes limited (although it does not affect functionality). However, with Spring we can make it even simpler in just two steps:

  • formethodAdd the annotation ** @transactionalorXml configuration ** (< tx:method />), registrationTransaction point of tangency. It’s like callingTransactionSynchronizationManager.setActualTransactionActive(true);
  • throughsetEnableTransactionSupport(true)Explicitly enableRedisTemplateThe instanceTransaction support(Disabled by default)
/** Sample Configuration **/
@Configuration
public class RedisTxContextConfiguration {
  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);
    returntemplate; }}Copy the code

The redisTemplate instance calls execute(RedisCallback Action) by default.

public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline){
		/** * variable declaration and other operations...... * /
		try {
			if (enableTransactionSupport) {
				// only bind resources in case of potential transaction synchronization
				conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
			} else {
				conn = RedisConnectionUtils.getConnection(factory);
			}
		/** * Other operations...... * /
}

public static RedisConnection bindConnection(RedisConnectionFactory factory, 
			boolean enableTransactionSupport) {
		/** ** * /
		RedisConnection conn = factory.getConnection();
		RedisConnection connectionToBind = conn;
		//redisTemplate turns on transaction support while transactionManager non-read-only actual transactions are activated
		if (enableTransactionSupport && isActualNonReadonlyTransactionActive()) {
			connectionToBind = createConnectionProxy(conn, factory);
		}
		/** ** * /
		return conn;
}
Copy the code

As you can see, enableTransactionSupport = true will lower the current Thread attempts to bind RedisConnection, only when also isActualNonReadonlyTransactionActive = true, The connection will bind successfully.

The connection is bound successfully and the MULTI will be triggered. Once MULTI is called:

  • The currentRedisConnectionWill be waiting in lineThe write operation;
  • allReadonly operation, e.g.KEYSWill be distributed to a brand new (nonThreadBinding)RedisConnection;
  • The commandEXECorDISCARDWill be passed on toSpringAOPtheDynamic proxy objectTo call:
  • ifTransaction to buildNot in the processException is thrown(the defaultRuntimeExceptionAnd its subclasses), thenEXECCall, execute command queue;
  • Otherwise,DISCARDTo clear the command queue.

After transaction support is enabled:

/** Usage Constrainsts **/
// executed on thread bound connection
template.opsForValue().set("foo"."bar");

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

// returns null as values set within transaction are not visible
template.opsForValue().get("foo");
Copy the code

The above sample code is from the Spring website, and the third is apparently the result of the WATCH command enabling optimistic locking. However, at least in the spring-data-redis-1.8.10.release.jar I’m using,

<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-redis</artifactId>
	<version>1.8.10. RELEASE</version>
</dependency>
Copy the code

The WATCH command is not used, and the third effect does not exist (you can try it based on your dependencies), so show the code here.

  • org.springframework.data.redis.core.RedisConnectionUtils.potentiallyRegisterTransactionSynchronisation
private static void potentiallyRegisterTransactionSynchronisation(RedisConnectionHolder connHolder,
			final RedisConnectionFactory factory) {

		if (isActualNonReadonlyTransactionActive()) {

			if(! connHolder.isTransactionSyncronisationActive()) { connHolder.setTransactionSyncronisationActive(true);

				RedisConnection conn = connHolder.getConnection();
				conn.multi();Conn.watch () is not called before this

				TransactionSynchronizationManager.registerSynchronization(newRedisTransactionSynchronizer(connHolder, conn, factory)); }}}Copy the code

The statementtwoRedisTemplate instance

twoRedisTemplateThe instance?

  • supportTransactions:commandseitherThe enforcement, orHave been clearedTo maintain data integrity;
  • Does not supportThe transaction,commandExecute immediately and return immediatelyThe execution resultAnd moreefficient;
/** Sample Configuration **/
@Configuration
public class RedisTxContextConfiguration {
  @Bean
  public StringRedisTemplate redisTransactionTemplate(a) {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);
    return template;
  }
 @Bean
  public StringRedisTemplate redisTemplate(a) {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    returntemplate; }}Copy the code