728x90
반응형
SMALL
학습내용
- 트랜잭션 전파 옵션
- DB Read/Write 분리
학습정리
1. 트랜잭션 전파 옵션
스프링 프레임워크에서 트랜션이 메서드 간에 어떻게 이어져서(전파) 실행 되는지를 결정하는 설정
옵션은 하나의 트랜잭션 경계 내에서 여러 비즈니스 로직을 실행할 때, 호출하는 메서드와 호출되는 메서드가 동일한 트랜잭션을 공유할지, 아니면 별도의 트랜잭션을 사용할지를 정합
지정 방법
- 해당 옵션을 적용시키기 위해서는 하위 해당메서드에 전파설정
옵션의 종류
REQUIRED (기본 값)
기존 트랜잭션이 있으면 참여하고, 없으면 새 트랜잭션을 생성함.
A트랜잭션이 B트랜잭션을 실행 할 경우 B트랜잭션이 롤백되면 A트랜잭션도 같이 롤백
예시코드
@Service public class ServiceA { @Autowired private ServiceB serviceB; @Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("A 트랜잭션 시작"); serviceB.methodB(); System.out.println("A 트랜잭션 종료"); } } @Service public class ServiceB { @Transactional(propagation = Propagation.REQUIRED) public void methodB() { System.out.println("B 트랜잭션 실행"); throw new RuntimeException("B에서 예외 발생!"); // B에서 예외 발생 } } // A 트랜잭션 시작 -> B 트랜잭션 시작 -> 예외 발생 -> A 트랜잭션 종료 //A,B 모두 롤백
REQUIRES_NEW
A트랜잭션에 B트랜잭션을 실행 할 경우 항상 새로운 트랜잭션을 생성함.
예제코드
@Service public class ServiceA { @Autowired private ServiceB serviceB; @Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("A 트랜잭션 시작"); serviceB.methodB(); System.out.println("A 트랜잭션 종료"); } } @Service public class ServiceB { @Transactional(propagation = Propagation.REQUIRES_NEW) public void methodB() { System.out.println("B 트랜잭션 실행"); throw new RuntimeException("B에서 예외 발생!"); // B에서 예외 발생 } } // A 트랜잭션 시작 -> B 트랜잭션 시작 -> 예외 발생 -> A 트랜잭션 종료 // A: 커밋 ,B: 롤백
NESTED
기존 트랜잭션 안에서 서브 트랜잭션을 생성함.
서브 트랜잭션에서 독립적으로 커밋이나 롤백을 제공.
상위 트랜잭션이 롤백되면 서브트랜잭션도 롤백됨.
DB의 종류에 따라서 지원되지 않을 수도 있음.
@Service public class ServiceA { @Autowired private ServiceB serviceB; @Transactional(propagation = Propagation.REQUIRED) public void methodA() { System.out.println("A 트랜잭션 시작"); serviceB.methodB(); System.out.println("A 트랜잭션 종료"); } } @Service public class ServiceB { @Transactional(propagation = Propagation.NESTED) public void methodB() { System.out.println("B 트랜잭션 실행"); throw new RuntimeException("B에서 예외 발생!"); // B에서 예외 발생 } } // A 트랜잭션 시작 -> B 트랜잭션 시작 -> 예외 발생 -> A 트랜잭션 종료 //B에서 예외 발생 시 B만 롤백, 부분 롤백이 가능 // A는 커밋 될수도 롤백 될수도 로직에 따라 달라짐
SUPPORTS
상위 트랜잭션이 존재하면 참여하고, 독립적으로 사용 할 경우 트랜잭션 없이 사용됨
주로 조회나 트랜잭션 경계가 반드시 필요하지 않은 작업에서 사용
예시코드
//단독적, 트랜잭션이 없는 메서드에서 호출되면 트랜잭션이 적용되지 않고 사용됨 @Transactional(propagation = Propagation.SUPPORTS) public User getUser(Long userId) { return userRepository.findById(userId) .orElseThrow(() -> new ServiceException(ServiceExceptionCode.NOT_FOUND_USER)); } @Transactional(propagation = Propagation.SUPPORTS) public Order getOrder(Long orderId) { return orderRepository.findById(orderId) .orElseThrow(() -> new ServiceException(ServiceExceptionCode.NOT_FOUND_ORDER)); } @Transactional(propagation = Propagation.REQUIRED) public Order update(Long orderId, Long userId) { //여기서 사용시에는 update메서드에 트랜잭션이 걸려있기에 해당 트랜잭션으로 참여 User orderUser = getUser(userId); Order order = getOrder(orderId); order.setUser(orderUser); order.setTotalPrice(BigDecimal.ZERO); return orderRepository.save(order); }
- 트랜잭션 중 비동기 작업
A트랜잭션에서 B트랜잭션 호출시에 B트랜잭션 메서드가 비동기일 경우
비동기는 새로운 쓰레드를 만들기 때문에 전파옵션과 상관없이 새로운 트랜잭션을 구성함.
2. DB Read/Write 분리 개념
Master-Slave 구조는 데이터베이스 성능을 최적화 하기 위해 Read/Write 작업을 분리하는 아키텍처
- Master 노드: 데이터를 쓰기 전용으로 처리 (INSERT,UPDATE,DELETE) , (SELECT가 되긴함)
- Slave 노드: 데이터를 읽기전용으로 처리, Master로부터 복제된 데이터를 사용 (SELECT)
- 데이터 동기화 : 마스터에서 변경된 데이터가 슬레이브로 실시간 또는 주기로 복
DB 커넥션 설정
spring: datasource: # 마스터와 슬레이브로 나눠서 DB연결시에 자동으로 기본설정이 안되기에 파라미터로 기본설정 추가 master: hikari: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/spring_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root password: root connectionTimeout: 30000 slave: hikari: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/spring_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root password: root connectionTimeout: 30000
DBConfig 클래스 설정
@Configuration public class DataSourceConfiguration { public static final String MASTER_DATASOURCE = "masterDataSource"; public static final String SLAVE_DATASOURCE = "slaveDataSource"; //MASTER_DATASOURCE(masterDataSource)의 빈 이름으로 저장 @Bean(name = MASTER_DATASOURCE) //application.yml 또는 application.properties에 정의된 값을 Java 객체 필드에 자동으로 //prefix로 설정하여 관련된 설정값들을 그룹화할 수 있음 @ConfigurationProperties(prefix = "spring.datasource.master.hikari") public DataSource masterDataSource() { return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } @Bean(name = SLAVE_DATASOURCE) @ConfigurationProperties(prefix = "spring.datasource.slave.hikari") public DataSource slaveDataSource() { return DataSourceBuilder.create() .type(HikariDataSource.class) .build(); } @Bean @Primary //마스터/슬레이브 데이터소스가 모두 초기화된 후 routingDataSource가 동작하도록 함 @DependsOn({MASTER_DATASOURCE,SLAVE_DATASOURCE}) public DataSource routingDataSource( //특정한 빈 이름을 가진 DataSource를 주입받도록 지정하는 어노테이션. @Qualifier(MASTER_DATASOURCE) DataSource masterDataSource, @Qualifier(MASTER_DATASOURCE) DataSource slaveDataSource ) { //RoutingDataSource는 AbstractRoutingDataSource을 상속받아 determineCurrentLookupKey 구현 //트랜잭션이 readOnly면 "slave" / 아니면 "master" RoutingDataSource routingDataSource = new RoutingDataSource(); //Map을 이용해서 RoutingDataSource에서 반환 값과 매핑 Map<Object, Object> datasourceMap = new HashMap<>(); datasourceMap.put("master",masterDataSource); datasourceMap.put("slave", slaveDataSource); //RoutingDataSource 타겟데이터소스를 위에 맵으로 지정 //RoutingDataSource 부모인 AbstractRoutingDataSource 내부에서 룩업 키(lookupKey)를 DataSource 객체에 매핑함. routingDataSource.setTargetDataSources(datasourceMap); //기본 설정처리 routingDataSource.setDefaultTargetDataSource(masterDataSource); routingDataSource.afterPropertiesSet(); return routingDataSource; } }
RoutingDataSource 클래스
public class RoutingDataSource extends AbstractRoutingDataSource { @NotNull @Override protected Object determineCurrentLookupKey() { return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master"; } }
동작 흐름
- application.yml 에 Master,Slave 데이터 소스 연결 정보가 설정되고 각각 데이터 소스 Bean에 바인딩
- RoutingDataSource 에서 ReadOnly 여부를 통해 해당 Master,Slave 데이터소스를 매핑
- @Transactional(readOnly=true) 이면 해당 트랜잭션은 slave (Read) 데이터소스 매핑
- readOnly=true 가 아니면 Master (Write) 데이터 소스로 매핑
ps. 금일에는 트랜잭션 전파옵션과 DB 분리에 대해서 배웠는데
처음 듣는 내용도 있고 유용한 내용이 많아서 재미있게 들었고, 많이 유익한 시간이었습니다.
728x90
반응형
LIST
'TIL' 카테고리의 다른 글
7_4.트랜잭션 예외 처리와 외부 API 연동 실습 (0) | 2025.02.09 |
---|---|
7_3.롤백 전략과 커밋 관리 (0) | 2025.02.07 |
7_1.트랜잭션 개념과 선언적/프로그래밍 방식 비교 (0) | 2025.02.04 |
6_5.데이터베이스 기반 작업 큐 실습 (0) | 2025.02.04 |
6_4.CAP 이론과 일관성 전략 (0) | 2025.02.04 |