Spring Data Redis事務的正確使用姿勢

之前使用Spring Data Redis的時候,由於使用不當,導致redis連接不釋放的血案。現在來總結下Spring Data Redis事務的兩種使用方式。

1、execute(SessionCallback session)方法

我們都知道Redis的事務的命令主要有multi、exec、discard和watch,在RedisTemplate中也是對應的有這幾種方法

  public interface RedisOperations<K, V> {
      ....
    void watch(K key);
	void watch(Collection<K> keys);
	void unwatch();
	void multi();
	void discard();
	List<Object> exec();
      ....
  }

1.1 錯誤示例

然而,它們是不能單獨直接被調用的,Spring Data官網有句話說:

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.

大概意思是直接單獨調用的話,RedisTemplate是不會保證這些操作在一個連接內完成,

//錯誤示例
public void testRedisTx0() {
	template.multi();
	template.opsForValue().set("hxm", "9999");
	System.out.println(template.opsForValue().get("hxm"));
	List<Object> result = template.exec();//此處拋出異常
	System.out.println(result);
}
//此處會導致一個異常
org.springframework.dao.InvalidDataAccessApiUsageException: No ongoing transaction. Did you forget to call multi?

上面的錯誤實例執行到exec方法時,會拋出以上的異常,可以看出程序認爲你還沒有執行過multi操作,所以不能執行exec。這是因爲這幾個操作都不是在一個連接完成的。

1.2 正確使用姿勢

RedisTemplate提供了一種正確的使用方式,那就是execute(SessionCallback session)方法:

<T> T execute(SessionCallback<T> session);

SessionCallback包含了一個回調函數execute(RedisOperations operations),在這個函數裏實現以上的操作,就可以保證事務的正常使用。

	public void testRedisTx1() {
		List<Object> r = template.execute(new SessionCallback<List<Object>>() {

			@Override
			public List<Object> execute(RedisOperations operations) throws DataAccessException {
				operations.multi();
				operations.opsForValue().set("hxm", "9999");
                  //此處打印null,因爲事務還沒真正執行
				System.out.println(operations.opsForValue().get("hxm"));
				return operations.exec();
			}
		});
		
		System.out.println(r);
	}

我們可以看看這個execute(SessionCallback session)方法的內部實現機理,可以看到該方法中先把當前建立的連接綁定在當前的線程中,確保之後的redis操作可以在一個連接內進行,而redis操作過後,會把當前連接在線程中解綁,並且釋放這個連接。

public <T> T execute(SessionCallback<T> session) {
	RedisConnectionFactory factory = getRequiredConnectionFactory();
	//綁定連接在當前線程,可實現下面execute方法內的redis操作保持在一個連接內完成
	RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
	try {
		return session.execute(this);
	} finally {
		RedisConnectionUtils.unbindConnection(factory);//把連接從當前線程中解綁、釋放連接
	}
}

2、@Transactional的支持

另一種實現事務的是@Transactional註解,這種方法是把事務交由給spring事務管理器進行自動管理。使用這種方法之前,跟jdbc事務一樣,要先注入事務管理器,如果工程中已經有配置了事務管理器,就可以複用這個事務管理器,不用另外進行配置。另外,需要注意的是,跟第一種事務操作方法不一樣的地方就是RedisTemplate的setEnableTransactionSupport(boolean enableTransactionSupport)方法要set爲true,此處貼出官方的配置框架:

@Configuration
@EnableTransactionManagement                                 
public class RedisTxContextConfiguration {

  @Bean
  public StringRedisTemplate redisTemplate() {
    StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
    // explicitly enable transaction support
    template.setEnableTransactionSupport(true);//此處必須設置爲true,不然沒法實現事務管理            
    return template;
  }

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

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

  @Bean
  public DataSource dataSource() throws SQLException {
    // ...
  }
}

這種方法的使用方法比較簡單,就在要使用事務的方法註解@Transactional,這跟jdbc事務使用是一樣的,這樣就不用手工的執行multi、exec方法了,這些事務控制方法會由spring事務管理器自動完成。實例如下:

	@Transactional
	public void testRedisTx2() {

		template.opsForValue().set("hxm", "9999");
		System.out.println(template.opsForValue().get("hxm"));//此處打印爲null

	}

然而,這種便利的使用方法有侷限性,就是不支持只讀操作,如果執行get之類的操作,將會返回null,所以使用的時候要多加註意!

3、兩種方法的對比

3.1 execute(SessionCallback session)方法

  • 事務代碼塊的範圍靈活可控
  • 支持讀操作
  • 代碼略顯臃腫

3.2 @Transactional註解方法

  • 使用方便,代碼比較優雅
  • 不夠靈活,事務控制範圍是整個方法
  • 不支持讀操作

綜合了以上的對比,兩種方法各有優缺點,但個人更偏向於使用execute方法。如果使用@Transactional這種註解式方法,有個建議是初始化兩個RedisTemplate,一個支持事務的,一個不支持事務的,即enableTransactionSupport一個設爲true,一個設爲false(默認是false)。不然,如果用支持事務的RedisTemplate來進行非事務性操作時,有些地方要注意,比如要手工的關閉連接等,不然是會踩坑的!因爲我就是過來人!至於是什麼坑,留作下回分解~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章