[스프링] @Transactional 에서는 트랜잭션과 스프링에서의 @Transactional 어노테이션에 대해 알아보았는데, 프로그래밍 방식의 트랜잭션도 함께 소개를 했었다. 이번에는 프로그래밍 방식의 트랜잭션에 집중해서 정리를 해보고자 한다.
Programmatic Transaction
트랜잭션을 관리하기 위해 흔히 사용하는 @Transactional 어노테이션을 활용하지 않고, 코드 내에서 개발자가 직접 트랜잭션 범위를 설정하며 관리하는 방식을 의미한다. 의견이 다양하겠지만 개인적으로는 개발자에게 트랜잭션 범위를 직접 설정할 수 있도록 하여 자율성을 추구하고 @Transactional 어노테이션으로는 구현하기 어려운 (예: 동일 클래스 내 여러 메소드 간 호출에서 트랜잭션을 각각 적용하고 싶은 경우) 경우도 편리하게 구현할 수 있다.
여러 장단점이 있지만 트랜잭션 범위를 작은 단위로 관리하거나 @Transactional 으로 구현하기 어려울 때, 사용하면 좋다고 생각한다.
스프링 공식 문서에 따르면 스프링에서 프로그래밍 방식의 트랜잭션 관리는 두 가지 방법이 존재하지만 스프링 팀은 첫 번째 방법을 사용하길 권장한다.
- TransactionTemplate 을 활용
- PlatformTransactionManager 의 구현체를 직접 활용
스프링에서 트랜잭션 관리를 활성화하기
트랜잭션 관리를 활성화하기 위해서는 spring-tx 또는 spring-data 관련 의존성을 build.gradle 에 추가하면 자동으로 트랜잭션 관리가 활성화된다.
implementation("org.springframework:spring-tx")
// 또는
implementation("org.springframework.data:spring-data-{...}")
의존성 추가 없이 진행하려면 @EnableTransactionManagement 어노테이션을 설정 (@Configuration) 클래스에 적용해야 한다.
@EnableTransactionManagement // 추가
@SpringBootApplication
public class ReviewApiApplication {
public static void main(String[] args) {
SpringApplication.run(ReviewApiApplication.class, args);
}
}
설정하기
별도의 설정 클래스를 만들고 (e.g., JpaConfig.java) TransactionTemplate 을 활용하여 읽기 전용과 쓰기 전용 TransactionTemplate Bean 을 설정한다.
@Configuration
public class JpaConfig {
@Bean
public TransactionTemplate readTransactionOperations(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setReadOnly(true);
return transactionTemplate;
}
@Bean
public TransactionTemplate writeTransactionOperations(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setReadOnly(false);
return transactionTemplate;
}
}
사용 방법
조회를 해야 할 땐 readTransactionOperations 빈을 주입받아 활용하고 생성, 삭제, 수정 등 에서는 writeTransactionOperations 를 주입받으면 된다.
transactionOperations.execute(...)
사용자를 조회하는 트랜잭션 메소드를 만들어보자.
@Service
public class UserSearchService {
private final UserPort userPort;
private final UserDtoConverter userDtoConverter;
private final TransactionOperations readTransactionOperations;
public UserSearchService(UserPort userPort,
UserDtoConverter userDtoConverter,
TransactionOperations readTransactionOperations) {
this.userPort = userPort;
this.userDtoConverter = userDtoConverter;
this.readTransactionOperations = readTransactionOperations;
}
public UserDto findUserDtoByUserId(Long userId) {
var user = readTransactionOperations.execute(status -> {
return userPort.findByUserId(userId);
});
return userDtoConverter.convert(user);
}
}
readTransactionOperations 빈을 주입받아서 .execute(...) 를 통해 트랜잭션 범위를 설정하고 그 결과값을 user 로 반환한다.
transactionOperations.executeWithoutResult(...)
만약 데이터 수정만 하고 그 결과값은 필요없을 땐 어떻게 하면 될까? 똑같이 .execute(...) 를 활용해도 되지만 결과값이 필요없는 경우에 대해서는 .executeWithoutResult(...) 를 활용하면 된다.
public void modify(Long userId, ModifyUserCommand command) {
writeTransactionOperations.executeWithoutResult(status -> {
var user = userPort.findByUserId(userId);
user.modifyUserName(command.getUserName());
});
}
'스프링' 카테고리의 다른 글
[스프링] JUnit5, AssertJ, Mockito 기반 테스트 환경 구축하기 (0) | 2023.02.06 |
---|---|
[스프링] 스프링에서 관리하는 자바 객체, 빈 (Bean) (0) | 2023.02.03 |
[스프링] 스프링의 역사 (0) | 2023.02.02 |
[스프링] 트랜잭션과 스프링의 @Transactional (0) | 2023.02.02 |
[스프링] @NotEmpty, @NotBlank, @NotNull 비교하기 (0) | 2023.02.02 |
댓글