SMALL

Spring에서 트랜잭션을 관리하는 방법은 크게 2가지 방법으로 나눌 수 있다.

 

Programmatic Transaction Management

PlatformTransactionManager, TransactionTemplate 등을 이용하여 

사용자가 직접 트랜잭션 개시, 커밋, 롤백 등을 수행하는 방법

 

Declarative Transaction Management

@Transaction 어노테이션을 통해서

트랜잭션에 관한 코드를 비즈니스 로직으로부터 분리해서 사용하는 방법

 

대부분의 경우에는 Declarative(선언적) Transaction Management를 사용하여 트랜젝션을 관리한다.

하지만, 특수한 경우에는 사용자가 직접 Transaction을 관리해야 하는 상황이 생긴다.

해당 경우에 어떠한 방식으로 트랜잭션을 관리하는지 알아보도록 하자

 

Declarative Transaction Management 예시

우선 일반적인 상황에서의 Transaction Management

 

@RequiredArgsConstructor
@Service
@Slf4j
public class ProductService {
    private final ProductRepository productRepository;
    @Transactional
    public String save(ProductSaveRequestDto requestDto) throws DuplicateException {
    	
    	if (productRepository.existsByCode(requestDto.getCode())) {
    		throw new DuplicateException("Code is duplicated. code = "+ requestDto.getCode());
    	}
        return productRepository.save(requestDto.toEntity()).getId().toString();
    }
}

 

Programmatic Transaction Management 예시

우선 TransactionManager를 @Autowired나 생성자를 통해 주입받는다 (1)

( Spring Boot를 사용한다면 자동적으로 bean이 등록되어, 사용만 하면 되지만, 그렇지 않는다면 직접 등록해주어야 한다) 

후에 Transaction을 개시한다 (transactionManager.getTransaction() ) (2)

DB 관련 작업을 진행한 후에  (3)

원하는 시점에 Transaction을 커밋 또는 롤백한다 (4)

@RequiredArgsConstructor
@Service
@Slf4j
public class TestService {
    
    private final TestRepository testRepositroy;
    private final PlatformTransactionManager transactionManager ;  // (1)
    
    public ResponseDto save(RequestDto requestDto) throws Exception {
        
        List<TestDto> list = requestDto.getParams();
        
         // (2)
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        
        try {
            testRepositroy.save(requestDto);  // (3)
        } catch (Exception e) {
            transactionManager.rollback(status);  // (4)
            throw new Exception();
        }
        
        transactionManager.commit(status);  // (4)
        
        ResponseDto responseDto = new ResponseDto();
        return responseDto;
    }
LIST
SMALL

정의

RestTemplate은 Spring에서 제공하는 Http Client

REST 서비스를 호출하는 복잡한 과정을 단순한 방식으로 만들어 주는 클래스

( 기계적이고 반복적인 코드를 깔끔하게 만들 수 있도록 도와준다)

 

Http Client 종류 

RestTemplate 뿐만 아니고 다른 종류의 Http Client가 존재하는데 각각의 특징을 간단하게 알아보자.

  • RestTemplate
    • Spring 3.0에서부터 지원
    • REST API 호출 후, 응답을 받을 때까지 기다리는 동기 방식
  • AsyncRestTemplate
    • Spring 4.0에서부터 지원
    • 비동기 RestTemplate
  • WebClient
    • Spring 5.0에서부터 지원
    • 논 블록, 리엑티브 웹 클라이언트
    • 동기, 비동식 방식 모두 지원 

 

사용법(예시)

 

Bean 등록

@RequiredArgsConstructor
@Configuration
public class WebConfig {
	
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder
                .requestFactory(() -> 
                    new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())
                )
                .setConnectTimeout(Duration.ofMillis(5000)) // connection-timeout
                .setReadTimeout(Duration.ofMillis(5000)) // read-timeout
                .build();
    }
}

 

예시

@RequiredArgsConstructor
@Service
public class RestTestService {

    private final RestTemplate template;
    
    private String getLoggerFromActuator(String url) {
    
        ResponseEntity<String> response = 
            template.getForEntity(url + "/actuator/loggers", String.class);
        
        HttpStatus statusCode = response.getStatusCode();   //상태코드확인
        HttpHeaders headers = response.getHeaders();    //헤더정보확인
        String body = response.getBody();   //바디정보확인
        
        return body;
    }
}

( 각 메서드 별 자세한 예시 : https://advenoh.tistory.com/46 )

RestTemplate 메서드

출처 :&nbsp;https://advenoh.tistory.com/46

Connection Pool 사용

기본적으로 제공되는 RestTemplate는 Connection Pool을 사용하지 않는다.

원한다면 추가적인 설정이 필요하다.

Connection Pool이 존재하지 않는다면, 

RestTemplate을 호출할 때마다, TCP 소켓을 열고, 3-way hand shaking이 발생하여 성능에 문제가 될 수 있고, 

요청량이 많아지게 되면 connection을 재활용할 수 없기 때문에 응답이 지연될 수 있다.

@RequiredArgsConstructor
@Configuration
public class WebConfig {
	
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
    
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); 
        factory.setReadTimeout(5000); // 읽기시간초과, ms 
        factory.setConnectTimeout(3000); // 연결시간초과, ms 
        HttpClient httpClient = HttpClientBuilder.create()
                .setMaxConnTotal(100) // connection pool 적용 
                .setMaxConnPerRoute(5) 
                .build(); 
        factory.setHttpClient(httpClient); // HttpClient 세팅 
        
        return new RestTemplate(factory);
    }
}

 

추가 참고 사항

RestTemplate 내부에서는 HttpURLConnection, HttpClient 등을 사용하는데,

해당 클래스들의 대한 내용( https://amagrammer91.tistory.com/65 )

LIST
SMALL

Customizing Response 

애플리케이션을 개발하는 과정에서

응답의 헤더에 특정 값을 추가하고 싶은 경우나

응답의 특정 데이터 값을 교체하고 싶은 경우가 있을 수 있다.

 

이러한 경우에 어떻게 해야 하는지 알아보자.

 

Interface ResponseBodyAdvice + @ControllerAdvice 

ResponseBody 데이터 접근 가능( 수정 가능 ) 

 

interceptor의 경우 postHandler method를 통해 controller 호출 이후에 접근이 가능하지만,

응답 객체에 접근 불가능 ( body에 접근 불가)

@ControllerAdvice
public class TestResponseBodyAdvice implements ResponseBodyAdvice<T> {

    // Implementation
}

 

메서드 설명 

 

@ControllerAdvice
public class TestResponseBodyAdvice implements ResponseBodyAdvice<T> {

    @Override
    public boolean supports(MethodParameter returnType, 
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public T beforeBodyWrite(T body, MethodParameter returnType, 
                    MediaType selectedContentType,
                    Class<? extends HttpMessageConverter<?>> selectedConverterType, 
                    ServerHttpRequest request,
                    ServerHttpResponse response) {
        
        return body;
    }
}

 

supports 

controller 작업이 끝난 response를 beforeBodyWrite 메서드에 보낼지 판단 

판단하는 기준으로는 해당 메서드의 파라미터인 controller의 returnType정보, messageConverter 정보가 있다.

 

beforeBodyWrite

controller 작업이 끝나고 어떠한 Converter를 통해 응답을 보낼지 결정된 후에 불린다.

다만, Converter를 통하지 않은 상태이다. ( 어떠한 converter를 사용할지는 정해졌지만, 아직 convert 하지는 않은 상태 )

이 메서드에서 실제 사용자가 원하는 body의 값을 교체 또는 response에 헤더 정보를 추가할 수 있다.

 

RequestBodyAdvice

ResponseBodyAdvice와 유사하게 RequestBodyAdvice interface 사용 가능

LIST
SMALL

 

기존 설정

# application.yml

# default
spring:
    profiles:
        active: test
---
spring:
    profiles: test
        include:
        - common
# 기타 설정 .. 
---
spring:
    profiles: prod
# 기타 설정 .. 
---
spring:
    profiles: dev
# 기타 설정 ..

spring.profiles 설정을 통해 어떠한 profile인지 표시 

spring.profiles.include 설정을 통해 추가적으로 포함할 profile 표시

 

2.4 버전 이후 설정

spring.profiles 설정은 duplicated 되었다

# application.yml

# default
spring:
  profiles:
    active: test
  group:
    dev: 
    - dev-t1
    - common
---
spring:
  config:
    activate:
      on-profile: test
# 기타 설정 .. 
---
spring:
  config:
    activate:
      on-profile: prod
# 기타 설정 ..       
---
spring:
  config:
    activate:
      on-profile: dev 
# 기타 설정 ..

spring.config.activate.on-profile 설정을 통해 어떠한 profile인 경우 해당 설정들이 활성화되는지 표시 

spring.profiles.group 설정을 통해 profile들을 그룹핑 

 

활성 profile 설정 

실제로 application을 실행할 때 spring.profiles.active 설정을 주어 어떠한 profile를 활성화할 것인지 정해주어야 한다.

해당 설정이 없을 시에는 default값으로 "default" profile이 실행된다.

 

위의 예시들처럼 yaml, properties file에 설정을 해주거나 

실행 시 argument로 넘겨도 된다.

java -jar myApp.jar --spring.profiles.active=dev

 

또는, 사용하는 개발 tool의 실행 argument에 설정해주면 된다.

( 이클립스의 경우 실행 프로젝트의 오른쪽 클릭 -> Run as.. -> Run Configurations ) 

 

추가적으로, 참고할만한 링크

spring-projects github에 2.4 관련 release note와 config data migration guide 

 

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes

 

GitHub - spring-projects/spring-boot: Spring Boot

Spring Boot. Contribute to spring-projects/spring-boot development by creating an account on GitHub.

github.com

 

 

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Config-Data-Migration-Guide

 

GitHub - spring-projects/spring-boot: Spring Boot

Spring Boot. Contribute to spring-projects/spring-boot development by creating an account on GitHub.

github.com

 

LIST

+ Recent posts