inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[인프런 워밍업 클럽 3기 백엔드 ]발자국 2주차

박윤영
0

해당 글은 ‘입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기(정보근)’ 강의 를 수강하고 작성한 내용입니다.

https://www.inflearn.com/course/입문자-spring-boot-kotlin-포트폴리오/dashboard

📝 강의 내용 정리


[실습] 데이터베이스 초기화

@component 어노테이션

스프링 빈에 등록시킬 클래스라고 알려줌

그외 @controller, @service, @repository도 컴포넌트 중 하나(원본코드가면 component가 붙어잇음)

@Profile(value=[”default”])

프로필이 디폴트일때만 해당 클래스를 빈으로 등록해라(개발용 프로필 데이터 용)

생성자 주입시

먼저 등록시켜야할 클래스들 먼저 등록시킴

예를들어 현재 클래스에서 생성자 주입으로 레포지토리 클래스들을 주입 받으면

스프링은 해당 레포지토리 클래스들을 먼저 만들고 그다음에 해당 클래스를 생성

@PostConstruct

스프링 빈 주입 후 해당 어노테이션이 붙은 메서드들을 한번 더 실행하면

스프링 초기 빌드 끝나는거임(찾아봐 인터넷에 확실X)

출력문으로 해당 메서드가 언제 타는지 확인해보면

→ 스프링 빈들 적용 완료됐다는 로그 뒤에 찍힘

→ 해당 메서드 실행되고 난후에 톰캣 열림

<aside> 📌

실제 프로젝트는 출력할때 print쓰지 말고 log 사용하셈

</aside>

레포지토리 클래스의 saveAll 메서드

인터페이스인 JpaRespository 에 정의되어있음 그래서 우리가 따로 만들지 않았더라도 쓸 수 있음

(매우 기본적인 쿼리문은 jpa가 미리 정의해놔서 가져다 쓰면 됨)

experienceRepository.saveAll()로 데이터를 저장하는데

이렇게 하면 experience랑 experienceDetails랑 연관관계라

저거 saveall만 해도 experienceDetails에도 삽입 쿼리가 날라감(이거가 엔티티에서 cascadeType을 지정해줬기 때문)

=⇒ 쿼리문 로그에서 확인 해보셈

[실습] 리포지토리 개발

레포지토리 리턴값이 여러개 올것같으면 List형으로 받아오기

하나만 들어올때면 Optional로 받아오기

override fun findById(id: Long): Optional<Project

이거 jpa에 기본있는 메서드 명인데 다시 만들려면 앞에 override 붙여줘야함

<aside> 📌

프로젝트 클래스랑 익스페리언스 클래스 같이 연관관계가 있는 클래스들은 현재 코드로는 성능이 좋지 않음 다음 시간에 성능 개선용 코드를 다시 짤 것 같음

</aside>

[실습] 리포지토리 테스트 코드 작성

테스트 코드 작성 이유

큰 서비스 일 수록 의도하지 않은 결과가 나올 수 있기 때문에 테스트 코드를 촘촘하게 작성하여 서비스 전체 기능에 미치는 사이드 이펙트를 사전에 감지하는 것이 좋음

테스트 코드 작성법

  1. 테스트 코드를 작성할 클래스를 오른쪽 마우스 클릭

  2. Generate → Test

  3. test 패키지에 해당 테스트 클래스가 생성됨

  1. 테스트 패키지 안에 직접 만들어줘야함

  2. 테스트할 인터페이스 이름에 +Test 이름으로 클래스 생성(코틀린 클래스)

  3. 생성된 클래스 위에 어노테이션 추가

    • @DataJpaTest : jpa 관련 테스트를 위한 설정 제공

    • @TestInstance: 테스트 인스턴스의 라이프 사이클 지정. 각 메서드 마다 인스턴스를 생성하는게 아닌 모든 테이스에 적용될 작업을 beforeall 로 한번만 수행할 것이기 때문(—> 이건 좀 더 찾아봐야될)

      https://devs0n.tistory.com/40

    • @BeforeAll : 테스트 클래스 내의 모든 @Test 메서드 실행 전에 한 번 실행됨

  4. 의존성 주입은 클래스() 안에 @Autowired로 주입

  5. 테스트할 메서드에 @Test 어노테이션 달아야 테스트로 인식

<aside> 📌

assertThat( import assertj) 사용: 더 직관적

ctrl+ r 로 replace로 코드 한번에 변경 가능

</aside>

[실습] 리포지토리 성능 개선

N+1 문제

현재 테스트 코드에서

findAllByIsActive() 테스트를 돌려보면 로그에 굉장히 많은 쿼리문이 나가는 것을 확인할 수 있다

⇒ JPA N+1 문제

부모꺼 부르고 자식 것도 다 불러오는 문제

⇒ jpa proxy가 fetch 타입이 lazy인 경우 가짜 객체 상태로 있다가, 해당 객체가 호출됬는데 데이터가 없을 경우 쿼리를 날려서 그때 값을 가져오는거

해결법

  1. fetch join

    jpa에 의존하지 않고 직접 jpql쿼리를 보냄

    → 쿼리가 하나만 나감.(Good~)

    하지만 해당 방법은 onetomany나 manyto many관계의 자식 엔티티가 여러개일 경우 하나만 조인할 수 있다는 한계가 존재함

    <aside> 📌

    조인 테스트

    한쪽에만 데이터가 있는경우엔 left join 사용

    그냥 join은 양쪽에 다 데이터가 있어야지만 가져올 수 있음

    </aside>

  2. batch fetch size

    ex) ProjectRepository는 여러개가 참조되어있어서 1로 안됨

    단. project랑 p.skill이랑 조인하고 여기서 s.skill이랑 조인은 됨

    일단 자료에 있는 쿼리문 적은다음에 테스트 실행해보면 쿼리문이 여러개 나감 → 프로젝트랑 스킬즈는 join이 되었는데 디테일즈는 조인이 안되서 detail 호출할때마다 쿼리가 날라가는거래(=lazy)

    ⇒ 즉 여러개 조인은 못가져옴

    이걸 해결하려면 properties에서 default_batch_fetch_size를 수정 해야함

    → 이렇게 하면 나가는 쿼리가 줄음, 단 in으로 한번에 가져옴

    <결론>

    이거는 n번 나갈 쿼리를 batch size를 키워서 한번에 in으로 여러개 가져옴

    1+N 이 1+(N/batch_ferch_size) 로 줄일 수 있다아

    결론적으로 쿼리문을 줄일 순 있다아

 

해당 페이지 어노테이션 정리

[실습] 클래스 생성

@Controller@RestController차이점

@GetMapping(”/test”)

= @RequestMapping(method = [RequestMethod.GET], name =”/test”)

[실습] DTO 개발

[실습] 리포지토리 개발

<결론>

해당 레포지토리 클래스는 도메인 패키지의 레포지토리들을 랩핑하는 역할이다

[실습] 서비스 개발

[실습] 서비스 테스트 코드 작성

[Mockito]

mockito를 사용하려면 클래스 위에

@ExtendWith(MockitoExtension::class)를 붙여야함

@InjectMocks

만든 mock을 주입할 대상(=테스트 할 대상)

lateinit

코틀린은 null을 허용하지 않기 때문에 초기화가 필순데, lateinit을 하면 초기화를 늦추는 작업. (mock이 생성된 다음에 초기화 할 목적)

@Mock이 붙은 레포지토리로 데이터를 만들어서 그걸로 서비스 테스트 진행

[실습] 컨트롤러 개발

[api 컨트롤러]

[view 컨트롤러]

[실습] 컨트롤러 테스트 코드 작성

API 컨트롤러 테스트

uri 호출 메서드

performGet()

MvcResult를 리턴

mockMvc빌더를 이용해서

.perform(MockMvcRequestBuilders.get(uri)

해당 uri를 get 방식으로 호출해줌

.andDo(MockMvcResultHandlers.print())

호출 후 결과를 출력

.andReturn()

결과를 mvcResult로 리턴

contentAsString

http Response 바디값을 String으로 파싱

jsonArray

스트링 값을 json배열로 파싱

⇒ resume같은 경우엔 애초에 객체로 return을 받기 때문에 jsonObject로 파싱

.optJSONArray를 써서 키값을 넣어주면 해당 객체의 value값을 가져옴

[실습] Thymeleaf - 부트스트랩 템플릿

[실습] Thymeleaf - 템플릿 수정(index)

html은 동적인 언어가 아니기 때문에 공통적인 내용을 여러 페이지에 표시할 때 같은 코드를 매번 중복해서 작성해줘야함

→ 이 중복을 줄이기 위해 타임리프의 프레그먼트 기능을 이용

현재 코드를 fragment화

About 섹션 수정

여기에 텍스트들은

introductions의 요소들을 한줄한줄 표시하는 형태로 진행

타임리프의 th:each 를 이용해 데이터베이스에서 받아온 값을 동적으로 html 에 표시

[about의 텍스트 데이터(introductions) 수정] <p *class*="text-muted" *th:each*="introduction : ${introductions}" *th:text*="${introduction.content}">텍스트값</p>

[about의 링크 데이터(links) 수정] <a *class*="text-gradient" *href*="#!" *target*="_blank" *th:each*="link : ${links}" *th:href*="${link.content}"><i *class*="bi bi-github" *th:class*="|bi bi-${link.name}|"></i></a>

타임리프 문법 미리보기

Experience카드를 fragment로 변경

공통사용 부분을 한번더 공통화

layout(~{::#content})

[실습] 인터셉터 개발

미션


[미션1] 깃허브 리포지토리에 프로젝트 올리기


뻔한 주제이기는 하지만 로그인을 한 유저들끼리 글을 쓰고 읽을 수 있는 커뮤니티 게시판을 프로젝트 주제로 잡았다.

일단 기본적인 틀은 모든 사용자가 글을 올릴 수 있는 자유글 있고, 관리자만 올릴 수 있는 공지글이 있을 예정이며, 관리자가 게시판의 글을 관리할 수 있도록 구현할 예정이다.

[미션2] 테이블 설계하기


테이블은 크게 유저, 게시판, 카테고리로 잡았다.

처음엔 아무생각없이 유저정보를 아이디 비번 닉네임으로만 구성을 했었는데, 관리자가 있으려면 role필드가 필요하다는것을 알고 유저테이블에 추가하였다.

그리고 공지글과 자유글을 카테고리로 구분하도록 테이블을 구성하였다.

추후 글 카테고리를 자유롭게 생성시킬 수 있도록 확장성을 염두해 두었다.

[미션3] REST API 설계하기


기본적인 api만 설계해 놓았다. 유저 관련은 회원가입, 로그인, 로그아웃, 정보 조회와 같이 기본적인 기능등에 대해 적어놓았고,

게시판 API도 기본적인 CRUD에 관한 내용만 적어두었다. 아마 개발을 시작하다보면 추가적으로 수정할 부분들이 생길 것 같다.

카테고리 테이블에 관련한 CRUD는 아직 개발 예정으로 빼놓았다. 위의 기능들을 구현한 후 추가할 예정이다

📅2주차 회고


2주 정도 코드를 봤더니 저번주 보다는 예제 코틀린 코드들이 어느정도 익숙해진 것 같다. 그리고 이번주가 본격적으로 기능을 구현하는 진도였다보니 저번주보다 더 흥미롭게 강의를 들었다. 평소에 알고는 개념적으로만 인지하고있었던 N+1문제도 직접 비교를 보여주시면서 강의를해주시니 더 와닿았던 것 같다. 테스트 코드 부분은 아직 익숙하진 않아서 아직 어렵지만 강의에서 배운것을 토대로 미션 코드에 적용해보면 이해가 좀더 될것 같다. 타임리프 부분은 진짜 처음 다뤄봤는데..확실히 html은 나한테 맞지않았다..(코드가 너무 많아..) 그래도 화면이 직접보여지다보니 뭘 만들고있다는 느낌은 확실히 들어서 재미있었다. 부트스트랩이라는 홈페이지를 처음알았는데 혼자 프로젝트 할때도 손쉽게 프론트를 구현할 수 있을 것 같아 자주 이용할 예정이다.

이번주에 주어진 미션들은 아직은 프로젝트 설계부분이라서 뭘 많이 하지는 않았는데, 간단한 테이블을 짜는 작업에도 혼자서 하려니 살짝 부담이 있었다. 이런작업들을 좀더 유연하게 할 수 있도록 여러번 해봐야겠다는 생각이 많이 들었다. 다음주부터는 정말 기능 구현을 시작해야하는데, 자신이 조금 없지만 차근차근 해 볼 예정이다.

답변 0