안녕하세요. 오늘은 Java의 Spring 환경에서 Transaction을 적용해보도록 하겠습니다.
의존성
먼저 Spring에서 Redis를 사용하기 위해서 아래와 같은 의존성을 부여하도록 하겠습니다. spring-boot-starter
의 버전은 본인이 사용하시는 SpringBoot 버전을 기본적으로 따라가기 때문에 버전은 따로 명시하지 않았습니다.
implements("org.springframework.boot:spring-boot-starter-data-redis")
환경설정
아래는 redis와 연결하기 위한 관련된 application.yml 파일입니다.
spring:
redis:
port: 6379
host: localhost
Redis도 @Transactional
을 이용해서 트랜잭션을 관리할 수 있습니다. 이렇게 해주기 위해서는 PlatformTransactionManager
를 Bean으로 등록해야합니다. 하지만 Spring Data Redis는 PlatformTransactionManager
를 제공해주고 있지 않습니다. 따라서 JDBC 또는 JPA 등 여타 transaction managers를 가지고 있는 의존성에 기생(?)해서 사용할 수 밖에 없습니다.
이런 부분 때문에 환경 설정부분은 2가지로 나누어집니다. 다른 DBMS를 사용하고 있는경우와 그렇지 않은 경우입니다. 먼저 타 DBMS의 의존성을 사용하고 계시다면 AutoConfiguration에 의해서 @EnableTransactionManagement
가 사용되어지고 있을겁니다. 따라서 아래처럼 환경설정을 잡아주시기만 하면 @Transacstional
을 통해서 Redis의 Transaction을 손쉽게 사용하실 수 있습니다.
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setEnableTransactionSupport(true); // redis Transaction On !
return redisTemplate;
}
@Bean // 만약 PlatformTransactionManager 등록이 안되어 있다면 해야함, 되어있다면 할 필요 없음
public PlatformTransactionManager transactionManager() throws SQLException {
// 사용하고 있는 datasource 관련 내용, 아래는 JDBC
return new DataSourceTransactionManager(datasource());
// JPA 사용하고 있다면 아래처럼 사용하고 있음
return new JpaTransactionManager(entityManagerFactory);
}
}
추가로 redis 단독으로 사용하고 있으시다면 @EnableTransactionManagement
를 명시적으로 On 시켜주어야합니다. 그러기 위해서는 아래의 의존성이 추가로 필요해집니다.
implementation("org.springframework:spring-tx:{version}") // 최신 버전 : '5.3.9'
Redis의 환경설정 코드는 아래와 같아집니다.
@Configuration
@EnableTransactionManagement
public class RedisTxContextConfiguration {
@Bean
public StringRedisTemplate redisTemplate() {
StringRedisTemplate template = new StringRedisTemplate(redisConnectionFactory());
// explicitly enable transaction support
template.setEnableTransactionSupport(true);
return template;
}
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager();
}
}
Transaction 예제
위 처럼 설정을 했으면 이제 실제로 Transaction을 동작시켜보도록 하겠습니다.
redisTemplate
이전 [redis] 트랜잭션(Transaction) 포스팅에서 Redis의 transaction이 어떻게 이루어지는지에 대해서 알아보았습니다. transaction을 유지하기 위해서는 동일한 connection을 유지할 필요가 있습니다. 그런데 일반적인 redisTemplate 명령어는 connection을 유지하지 않습니다. connection을 유지하기 위한 명령어로 SessionCallback
를 사용할 필요가 있습니다.
아래 코드를 보면서 이해해보도록 합시다.
List<Object> txResults = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi(); // redis transaction 시작
operations.opsForValue().set("SABARADA", "1");
operations.opsForValue().set("KAROL", "2");
return operations.exec(); // redis transaction 종료
}
});
System.out.println("return values of items : " + Arrays.toString(txResults.toArray()));
위 코드를 실행했을 때 아래와 같은 결과를 얻게되며 redis에는 정상적으로 데이터가 쓰여진 사실을 알 수 있었습니다.
return values of items : [true, true]
아래 코드는 일부러 실패하도록 코드를 만들어보았습니다. if 절의 sideEffect에 의해서 아래 코드는 Exception을 발생시킵니다. 이후 데이터가 redis에 쓰여지지 않은것을 확인할 수 있었습니다.
boolean sideEffect = true;
List<Object> txResults = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set("SABARADA", "1");
operations.opsForValue().set("KAROL", "2");
if (sideEffect) {
throw new RuntimeException("exception occur");
}
return operations.exec();
}
});
@Transactional
@Transactional
을 사용해서 redis Transaction을 유지하기 위해서는 redisTemplate 설정에 setEnableTransactionSupport(true)
를 추가해야합니다. @Transactional
를 redis와 이용하면 기본적으로 ThreadLocal 기반으로 메서드 시작시 transaction 시작으로 MULTI, 메서드 종료시 transaction 커밋으로 EXEC 명령어를 실행하는 것으로 구현하고 있습니다. 만약 Exception이 발생하면 DISCARD가 실행됩니다.
추가적으로 @Transactional
를 사용했을 경우 Read-only 커맨드는 Transaction Queue에 들어가지 않고 바로 실행되며 Write 커맨드만 들어가게 되는점을 알아두시기 바랍니다.
사용은 아래 코드처럼 할 수 있습니다.
@Transactional
public void transactionService2() {
boolean sideEffect = false;
stringRedisTemplate.opsForValue().set("SABARADA", "1");
stringRedisTemplate.opsForValue().set("KAROL", "2");
if (sideEffect) {
throw new RuntimeException("exception occur");
}
String sabarada = stringRedisTemplate.opsForValue().get("SABARADA");
System.out.println("결과 = " + sabarada); // 결과 = 1
}
마무리
오늘은 이렇게 Spring Data Redis를 이용해서 트랜잭션 예제를 처리해보았습니다.
추가적으로 말씀 드리면 @Transactional
은 ThreadLocal 기반이기 때문에 reactive 환경에서는 에서는 동작하지 않습니다. reactive 환경에서 Transactional을 유지하기 위해서는 Netty 기반의 Redisson을 이용해야합니다.
감사합니다.
참조
'datasource > redis' 카테고리의 다른 글
[redis] 트랜잭션(Transaction) - 이론편 (3) | 2021.07.18 |
---|---|
[Cache & Redis] 캐싱 전략 (Cashing Strategies) (0) | 2021.01.30 |
[Redis] Hashes을 이용하여 매핑 만들기 ( Strings VS Hashes ) (0) | 2020.12.31 |
[Redis] Redis 자료구조 알아보기 (0) | 2020.12.24 |
[Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisRepository 편 (1) | 2020.08.18 |
댓글