/* 1. @Entity - 테이블과 링크될 클래스 - 기본값으로 클래스의 카멜케이스 이름을 언더스코어 네이밍(_)으로 테이블 이름을 매칭한다. - ex) SalesManager.java -> sales_manager table 아래 3개는 lombok 어노테이션 2. @NoArgsConstructor - 기본생성자 자동추가 - public Posts() {} 와 같음 3. @Getter - 클래스 내 모든 필드의 Getter 메소드 자동생성 4. @Builder - 해당 클래스의 빌더 패턴 클래스를 생성 - 생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함 */
/* 별다른 설정없이 @SpringBootTest를 사용할 경우 H2 데이터베이스를 자동으로 실행한다. */ @ExtendWith(SpringExtension.class) @SpringBootTest publicclassPostsRepositoryTest { @Autowired PostsRepository postsRepository; /* 1. @AfterEach - Junit에서 단위테스트가 끝날 때마다 수행되는 메소드를 지정한다. - 보통은 배포 전 전체 테스트를 수행할 때 테스트간 데이터 침범을 막기 위해 사용된다. - Junit4 -> 5로 넘어가면서 After -> AfterEach 로 변경되었다. */ @AfterEach publicvoidcleanup() { postsRepository.deleteAll(); } @Test publicvoid 게시글저장_불러오기() { //given Stringtitle="테스트 게시글"; Stringcontent="테스트 본문"; /* 2. postsRepository.save - 테이블 posts에 insert/update 쿼리를 실행한다. - id값이 있으면 update, 없다면 insert 쿼리가 실행된다. */ postsRepository.save(Posts.builder() .title(title) .content(content) .author("jojoldu@gmail.com") .build()); /* 3. postsRepository.findAll - 테이블 posts에 있는 모든 데이터를 조회해오는 메소드 */ //when List<Posts> postsList = postsRepository.findAll(); //then Postsposts= postsList.get(0); assertThat(posts.getTitle()).isEqualTo(title); assertThat(posts.getContent()).isEqualTo(content); } }
3.4 등록/수정/조회 API 만들기
API를 만들기 위해 총 3개의 클래스가 필요하다.
Request 데이터를 받을 Dto
API 요청을 받을 Controller
트랜잭션, 도메인 기능 간의 순서를 보장하는 Service
여기서 Service는 비지니스 로직을 처리하는 것이 아니라 트랜잭션, 도메인 간 순서 보장의 역할만 한다.
Web, Service, Repository, Dto, Domain 이 5가지 레이어에서 비지니스 처리를 담당해야 할 곳은 **Domain**이다.
/* 1. @ExtendWith(SpringExtension.class) - 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킨다. - 여기서는 SpringExtension 이라는 스프링 실행자를 사용한다. - 스프링 부트 테스트와 JUnit 사이에 연결자 역할 - JUnit4 -> 5로 넘어오면서 사용하는 어노테이션과 클래스가 각각 @RunWith -> @ExtendWith 로 SpringRunner -> SpringExtension 으로 변경되었다. 2. @WebMvcTest - 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션 - 선언할 경우 @Controller, @ControllerAdvice 등을 사용할 수 있다. - 단, @Service, @Component, @Repository 등은 사용할 수 없다. */
/* 3. AutoWired - 스프링이 관리하는 빈(Bean)을 주입 받는다. 4. private MockMvc mvc - 웹 API를 테스트할 때 사용한다. - 스프링 MVC 테스트의 시작점 - 이 클래스를 통해 HTTP GET, POST 등에 대한 API 테스트를 할 수 있다. */ @Autowired//3 private MockMvc mvc;//4
/* 5. mvc.perform(get("/hello")) - MockMvc를 통해 /hello 주소로 HTTP GET 요청을 한다. - 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언할 수 있다. 6. .andExpect(status().isOk()) - mvc.perform의 결과를 검증한다. - HTTP Header의 Status를 검증한다. - 우리가 흔히 알고 있는 200, 404, 500 emddml 상태를 검증한다. - 여기선 OK 즉, 200인지 아닌지를 검증한다. 7. .andExpect(content().string(hello)) - mvc.perform의 결과를 검증한다. - 응답 본문의 내용을 검증한다. - Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증한다. */ mvc.perform(get("/hello"))//5 .andExpect(status().isOk())//6 .andExpect(content().string(hello));//7 } }
2.3 롬북(Lombok)
자바 개발 시 자주 사용하는 코드 Getteer, Setter, 기본생성자, toString 등을 어노테이션으로 자동 생성해준다.
앞서 확인한 개요에서 언급했는데 함수형 프로그래밍에선 반복을 재귀를 통해서 구현한다고 했는데 재귀와 꼬리재귀에 대해서 간단히 알아보자.
재귀
함수 본문에서 자기자신을 호출하는 방식을 재귀호출(recursive call)이라고 부른다. 재귀는 다른 명령어가 방지할 때까지 계속된다.
예제
꼬리 재귀 최적화 in python
재귀호출의 경우 호출 스택의 깊이가 얕은 경우엔 큰 상관이 없으나 깊이가 깊어지면 오버플로우가 발생하는 문제가 있다. 여담으로 실행하는 시스템에 따라서 조금씩 다를수 있지만 파이썬에서 호출가능한 스택의 최대 깊이는 보통 1000 정도에서 RecursionError가 발생한다.
이를 해결하기 위한 방법으로 제시되는 해결책 중 하나가 꼬리 재귀Tail recursion이다.
간단히 말하자면 함수에서 마지막으로 호출하는 함수가 자기 자신이고, 재귀 호출의 값을 반환받은후 추가적인 연산이 필요하지 않는 방식을 말한다.
꼬리 재귀 적용 예제
위의 예제에서 사용한 팩토리얼 함수를 보자.
fact(n)을 호출했을 때 연산이 끝나지 않았는데 fact(n-1)을 호출하기 때문에 리턴 주소를 저장하기 위해서 시스템 콜스택을 사용하게 된다.
즉, 현재 함수(fact(n))에서 결과값을 반환하기 위해서는 현재 함수의 인자 값(n)을 스택에 가지고 있다가 그 다음 호출될 함수(fact(n-1))의 결과 값과 함께 연산을 해야 한다는 점이다.
이러한 방식은 꼬리 재귀를 만족하지 못한다고 본다.
예제를 꼬리 재귀로 바꾸려면 어떻게 해야할까? 재귀를 호출하는 부분에서 추가적인 연산이 필요없도록 만들면 되는데
이를 구현하기 위해선
return에서는 (언어 스펙에서 지정한 스택에 메모리를 쌓지 않는 연산자를 제외한) 연산자를 사용하면 안된다.
연산자의 사용없이 재귀 호출의 반환값을 그대로 return 해주면 된다.
한가지 주의할 점은 개발자가 꼬리재귀 구조로 코드를 짜더라도 사용하는 언어의 스펙에 따라서 꼬리재귀 최적화 보장여부가 다르기 때문에 확인이 필요하다.
요즘 python을 공부하고 싶어서 위 예시를 python으로 들었지만 python은 꼬리재귀 최적화를 보장하지 않는데 python의 창시자 귀도 반 로섬의 의견은 다음과 같다.