Develope Me!

[SpringBoot] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - JPA 테스트 코드와 스프링 웹 계층 본문

Java/SpringBoot

[SpringBoot] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - JPA 테스트 코드와 스프링 웹 계층

코잘알지망생 2022. 2. 21. 18:38

Entity 클래스를 생성했다면 이제 테스트 코드를 작성해보자!

 

 

Spring Data JPA 테스트 코드 

 

domain.posts 패키지에 Posts 클래스를 작성했을 때와 동일하게 test 디렉토리에도 똑같이 패키지를 생성해준뒤

PostsRepositoryTest란 이름의 테스트 클래스를 작성했다. 

 

 

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {

    @Autowired
    PostsRepository postsRepository;

    @After //(1) 단위 테스트 끝난 후 수행되는 메소드 지정(데이터 침법 막기 위함)
    public void cleanup(){
        postsRepository.deleteAll();
    }

    @Test
    public void 게시글저장_불러오기(){

        //given
        String title = "제목 테스트";
        String content = "본문 테스트";

        //(2) 테이블 posts에 insert/update 쿼리 실행
        //id 값이 있다면 update, 없다면 insert 쿼리 실행
        postsRepository.save(Posts.builder()
                                    .title(title)
                                    .content(content).author("test1@gmail.com")
                                    .build());
        //when
        //테이블 post에 있는 모든 데이터 조회
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);
        Assertions.assertThat(posts.getTitle()).isEqualTo(title);
        Assertions.assertThat(posts.getContent()).isEqualTo(content);

    	}
    }

 

PostsRepositoryTes 클래스에서 save, findAll 기능을 테스트했다. 

(별다른 설정없이 @SpringBootTest 어노테이션을 사용하면 H2 DB를 자동으로 실행)

 

통과는 됐는데 실제로 실행된 쿼리가 어떤 형태인지 볼 수 없다.

쿼리 로그를 보기 위해서 resources 디렉토리에 application.properties 파일을 추가하여 옵션을 추가해줬다.

 

spring.jpa.show_sql=true

쿼리 로그에 옵션을 추가해준 뒤 다시 테스트 코드를 실행해보았다.

 

 

create table 쿼리에 'id bigint generated by default as identity~' 라는 옵션으로 생성됐다.

이는 H2 쿼리 문법이 적용됐기 때문이다.

H2는 MySQL 쿼리를 사용해도 정상적으로 작동되는데 이후 디버깅을 위해서 출력되는 쿼리 로그를 MySQL 버전으로 변경을 해볼 것이다. 

 

application.properties 파일에 해당 코드를 추가해준다.

 

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

위 코드는 스프링 버전 2.1.9까지는 적용 가능하다.

 

그 이후 2.1.10 이후 버전에는 아래와 같이직접 jdbc-url을 선언하여 ;MODE=MYSQL가 h2주소 뒤에 붙어야만 mysql 테이블 쿼리가 정상 작동된다고 한다. (참고: https://github.com/jojoldu/freelec-springboot2-webservice/issues/67)

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb
spring.datasource.hikari.jdbc-url=jdbc:h2:mem://localhost/~/testdb;MODE=MYSQL

 

 

H2 쿼리 로그를 MySQL 버전으로 변경하는 코드를 추가한 뒤 다시 한 번 테스트 코드를 수행했다.

 

옵션이 잘 적용되었음을 확인했다!

 

 

Spring web Layer

 

API를 만들기 위해서는 1. DTO 2. Controller 3. Service 클래스가 필요하다.

 

스프링의 웹 계층 / 출처: 구글 이미지&amp;amp;nbsp;

클래스를 작성하기 전에 스프링의 웹 계층을 정리해보자!

 

1. Web Layer 

@Controller와 JSP/Freemaker 등의 뷰 템플릿 영역이다.

이외에도 @Filter, Interceptor, @ControllerAdvice 등 외부 요청과 응답에 대한 전반적인 영역을 얘기한다.

 

2. Service Layer

@Service에 사용되는 서비스 영역이다.

Controller와 DAO의 중간 영역에서 사용되며 @Transactional이 사용되어야 하는 영역이기도 하다.

 

3. Repository Layer

데이터 저장소에 접근하는 영역이다.DAO(Data Access Obeject) 영역으로 이해하면 쉽다. 

 

4. DTOs

DTO는 계층간에 데이터 교환을 위한 객체이며 DTOs는 이들 영역을 의미한다.

 

5. Domain Model

도메인이라 불리는 개발 대상을 모든 사람이 동일한 관점에서 이해할 수 있고 공유할 수 있도록 단순화 시킨 것을 의미한다.

@Entity가 사용된 영역 역시 도메인 모델이라 할 수 있다.

반드시 DB 테이블과 관계가 있어야 하는 것은 아니고 VO처럼 값 객체들도 이 영역에 해당된다.

 

 

WEB(Controller), Service, Repository, DTO, Domain 이 영역 중 비즈니스 처리를 담당하는 곳은 바로 Domain이다.

기존에 서비스로 처리하던 방식은 '트랜잭션 스크립트'라고 한다. 

 

//트랜잭션 스크립트 
@Transactional
public Order cancelOrder(int orderId){
	//1. DB로 부터 주문/결제/배송 정보 조회
	OrdersDto order = ordersDao.selectOrders(orderId);
	BillingDto billing = billingDao.selectBilling(orderId);
	DeliveryDto delivery = deliveryDao.selectDelivery(orderId);
   
	String deliveryStatus = delivery.getStatus();
    
   	 //배송 중이라면 배송 취소로 변경
	if("IN_PROGRESS".equals(deliveryStatus)){
		delivery.setStatus("CANCEL");
		deliveryDao.update(delivery);
	}
    
    	//각 테이블 취소 상태 Update
	order.setStatus("CANCEL");
	ordersDao.update(order);
    
	billing.setStatus("CANCEL");
	billingDao.update(billing);

	return order;
}

서비스 클래스 내부에서 모든 로직을 처리하게 되면 서비스 계층이 무의미하며, 객체는 단순 데이터 덩어리 역할만 하게 된다.

 

만약 도메인에서 비즈니스 로직을 처리하게 되면 어떻게 될까?

 

@Transactional
public Order cancelOrder(int orderId){
	//주문/결제/배송 정보 조회
   	OrdersDto order = ordersRepository.findById(orderId);
	BillingDto billing = billingRepository.findByOrderId(orderId);
	DeliveryDto delivery = deliveryRepository.findByOrderId(orderId);
    
   	 //각자 본인 이벤트 취소
	delivery.cancel();
	order.cancel();
	delivery.cancel();
	
	return order;
}

주문, 결제, 배송 영역 별로 각자 본인의 취소 이벤트를 처리한다.

서비스 메소드는 트랜잭션과 도메인 간의 순서만 보장해준다.

 

 

 

다음으로는 도메인 모델을 다뤄 등록/수정/삭제 기능을 만들어볼 것이다.

 

 

 

 

 

 

 

Comments