SMALL

Mybatis란

Mybatis는 Psersistence Framework 중에 하나로 SQL Mapper Framework에 해당한다.

https://joomn11.tistory.com/34

위의 글에서 해당 내용에 대한 정리를 해두었으니 참고하면 이해가 잘 될 것이다.

 

Mybatis 특징

  • 복잡한 쿼리나 다이나믹 쿼리에 유용하다
  • 비즈니스 코드와 SQL 쿼리를 분리해서 관리 가능하다 ( 간결성, 코드 유지 보수성 향상 )

 

SpringBoot + Mybatis 

spring-boot의 mybatis-spring-boot-starter를 활용하여 스프링 프로젝트에서 손쉽게 Mybatis를 사용할 수 있다. 

 

mybatis-spring-boot-starter의 장점

  • spring에서 추가된 mybatis bean 등록을 자동으로 해준다
    • SqlSessionFactoryBean, SqlSessionTemplate

 

 

pom.xml

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

 

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sample.dao.MapperDAO">
	<select id="selectAll"
		resultType="com.sample.dto.UserDto">
		SELECT *
		FROM user_test
	</select>
	<insert id="insert" parameterType="com.sample.dto.UserDto">
		INSERT INTO user_test (seq, name, country) values ( #{seq}, #{name},
		#{country})
	</insert>
</mapper>

 

Mapper interface

@Repository
@Mapper
public interface MapperDAO {
    List<UserDto> selectAll() throws Exception;
    void insert(UserDto user);
}

 

Service 

@Service
public class UserService {

    @Autowired
    private MapperDAO mapperDao;
    
    public List<UserDto> selectAll() throws Exception {
        return mapperDao.selectAll();
    }
    
    public void insert(UserDto user) {
        mapperDao.insert(user);
    }
}

 

application.yml 설정

mybatis:
  mapper-locations:
  - com/sample/mapper/*.xml
  config-location: classpath:mybatis-config.xml
  • mybatis.mapper-locations : mapper관련 xml 파일이 존재하는 디렉터리의 위치
  • mybatis.config-location : mapper configuration관련 xml을 사용한다면 해당 위치에 대한 정보

 

mybatis github sample

https://github.com/mybatis/spring/tree/master/src/test/java/org/mybatis/spring/sample

 

mybatis사이트 가이드

http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/#

 

LIST
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

+ Recent posts