thumbnail

멋쟁이사자처럼 백엔드 자바 부트캠프 수강 후기

멋쟁이사자처럼 백엔드 자바 부트캠프 13기 수강후기

멋쟁이사자처럼 오늘 공부 후기 챌린지 - 백엔드 자바 편

이런 고민을 하고 있다면, 지금 이 순간에도 멋쟁이사자처럼에서 백엔드 자바 부트캠프를 수강하며 자신만의 속도로 성장 중인 수강생들을 주목해보세요.


배우고 잊어버리는 건 누구나 겪는 일이에요. 하지만 멋쟁이사자처럼 부트캠프는 단순히 배우는 것에 그치지 않고, 배운 내용을 내 것으로 만드는 방법까지 고민하고 있어요. 그렇다면, 배운 것을 가장 효과적으로 습득하는 방법은 무엇일까요? 바로 기록하고 정리하는 것이죠. 배운 내용을 정리하고 기록하면, 기억은 오래 남고 실전에서도 활용할 수 있으니까요!

멋쟁이사자처럼 부트캠프는 수강생들이 배운 내용을 정리하고 공유할 수 있도록 TIL (Today I Learned) 블로그 챌린지를 진행하고 있어요. 그래서 오늘, 강사님과 멘토님에게 배운 내용을 체계적으로 기록하며 성장하고 있는 수강생의 이야기를 가져왔어요. 작고 사소해 보일지라도 배운 내용을 정리하고, 기록하면서 성장한 이들의 소중한 경험을 지금 확인해 보세요.


멋쟁이사자처럼 백엔드 부트캠프 13기 

TIL 회고 - 44일차

44일차에는 실습했던 기존의 친구 목록 페이지를 리팩토링하여 페이지를 도입하고 페이징 처리를 통해 친구 목록이 페이지마다 특정 개수로 출력될 수 있도록 구현할 수 있었다.

학습 목표 : 페이징 처리에 익숙해질 수 있도록 관련 용어 및 활용 예시에 대해 공부

service/FriendService 추가

Plaintext
@Transactional(readOnly = true)public Page<Friend>findAllFriend(Pageable pageable){
Pageable pageable2 = PageRequest.of(pageable.getPageNumber(),
                                    pageable.getPageSize(),
                                    Sort.by(Sort.Direction.DESC, "id"));
// pageable2로 정보를 얻은 후 friendRepository의 메소드 findAll에 전달return friendRepository.findAll(pageable2);
}

PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())

getPageNumber() : 현재 원하는 페이지 번호가 무엇인지 얻어옴

getPageSize() : 한 페이지에 몇 개씩 가져올지 controller를 통해서 가져옴

Sort.Direction.DESC : 정렬 기준, ex. id를 기준으로 내림차순 정렬 내림차순 정렬 = Sort.Direction.DESC

service/FriendService 기존 코드

Plaintext
@Transactional(readOnly =true)
public Iterable<Friend> findAllFriend(){
return friendRepository.findAll();
}

  • 매개변수로 아무것도 받지 않는 findAllFriend()와 함께

service/FriendService - findAllFriend(Pageable pageable) 메소드 추가

  • 현재 원하는 페이지 번호가 무엇인지 얻어오는 것

Plaintext
@Transactional(readOnly = true)public Page<Friend>findAllFriend(Pageable pageable){
Pageable pageable2 = PageRequest.of(pageable.getPageNumber(),
                    pageable.getPageSize(),
                    Sort.by(Sort.Direction.DESC, "id"));

// pageable2로 정보를 얻은 후 friendRepository의 메소드 findAll에 전달return friendRepository.findAll(pageable2);
}
  • @Transactional(readnOnly=true)

 조회할 때는 읽기만 한다는 읽기 전용을 명시해 주어야 트랜잭션 수행 시 목적을 명확히 할 수 있음

  • findAllFriend(Pageable pageable)

pageable을 매개변수로 받는 findAllFriend()로 오버라이딩한 것

Pageable : Page를 추상화해놓은 객체

  • PageRequest.of : Pageable 객체를 생성

페이지에 대한 정보를 가져옴

Service로부터 정보를 가져와 모델에 넣어주며, 현재 페이지 또한 넘겨주게됨

controller/FriendController 기존 코드

Plaintext
// 수정 전 코드@GetMapping("/list")public Stringlist(Model model){
// 해야할 일
    model.addAttribute("friends", friendService.findAllFriend());
return "friends/list";
}
  • list를 보여주는 부분을 페이지로 보여줄 수 있도록 수정 해야 함

해결 방법 : Pageable 객체 활용

controller/FriendController 수정 코드

Plaintext
// 수정 : 페이지로 리스트를 보여주도록 처리@GetMapping("/list")public Stringlist(Model model,@RequestParam(name = "page", defaultValue = "1")int page,
@RequestParam(name = "size", required = false, defaultValue = "5")int size)){

Pageable pageable = PageRequest.of(page-1, size);

// 해야할 일
    model.addAttribute("friends", friendService.findAllFriend(pageable));
    model.addAttribute("currentPage", page);

return "friends/list";
}
  • @RequestParam(name = "page", defaultValue = "1") int page

name, required, defaultValue 정보를 page 변수에 담기

클라이언트가 보낸 page 값을 받아옴

페이지가 지정되지 않으면 기본값으로 1페이지를 사용

  • @RequestParam(name = "size", required = false, defaultValue = "5") int size

다른 name, required, defaultValue 정보를 size 변수에 담기

페이지 번호는 0부터 존재하는데, 일반적으론 1번 페이지부터 시작하므로 page-1을 해주어서

실제 원하는 페이지를 매핑 (DB의 인덱스가 0부터 시작)

클라이언트가 보낸 size 값을 받아옴

  • required = false : 클라이언트가 값을 보내지 않아도 동작하도록 설정

기본적으로 5개의 데이터를 가져오도록 설정

  • findAllFriend(pageable)

pageable 객체를 인자로 받을 수 있을 것, 기존 매개변수를 받지 않는 findAllFriend가 아닌

pageable객체를 넘길 수 있도록 findAllFriend(Pageable pageable) 메소드에 전달

"페이징된 친구목록을 조회"

결과를 "friends" 이름으로 모델에 추가 후 뷰에 전달

  • model.addAttribute("currentPage", page);

현재 사용자가 보고 있는 페이지가 몇 페이지인지 출력할 수 있도록 페이지 정보를 모델에 저장

  • 페이지의 사이즈 (=가져올 데이터 개수)는 defaultValue(=기본값)으로 5 지정 후 size 변수에 넣었으므로 친구 데이터 5개가 출력

  • Sort.Direction.DESC로 내림차순을 명시해 주었기 때문에 아이디가 숫자가 큰 친구부터 아이디가 숫자가 작은 친구까지의 순으로 출력이 되고 있음

Page 인터페이스

Plaintext
publicinterfacePage<T>extendsSlice<T> {
static <T> Page<T>empty() {
return empty(Pageable.unpaged());
    }
static <T> Page<T>empty(Pageable pageable) {
returnnewPageImpl(Collections.emptyList(), pageable, 0L);
    }
intgetTotalPages();
longgetTotalElements();
    <U> Page<U>map(Function<? super T, ? extends U> converter);
}
  • getTotalPages() 메소드를 가지고 있음 페이지를 나눌 때 한 페이지에 데이터를 몇 개씩 보여줄 것인지를 알고 전체 데이터가 몇 개인지 알려줌 (=getTotalPages()가 자동으로 알아서 해줌)

ex. 총 데이터가 14개, 한페이지에 5개(pagesize = 5)

총 페이지 수는 3인 것을 getTotalPages()가 미리 계산하는 것

  • JPA의 PageRequest.of()는 0부터 시작하는 인덱스를 사용

  • 반면, 사용자는 1페이지부터 시작한다고 인식

  • 따라서 사용자가 page=1을 요청하면, 실제로는 PageRequest.of(0, size)를 생성해야 올바른 데이터를 가져옴

  • 즉, 사용자의 직관적인 페이지 번호와 JPA의 0-based 인덱스 시스템을 맞추기 위해 page - 1을 수행함

사용자가 요청하는 페이지와 JPA에서 실제 가져오는 페이지를 비교

1페이지 (page=1)

PageRequest.of(0, 5)

0번 페이지

2페이지 (page=2)

PageRequest.of(1, 5)

1번 페이지

3페이지 (page=3)

PageRequest.of(2, 5)

2번 페이지

정리하자면

사용자는 1부터 시작하는 페이지를 요청하지만, JPA의 PageRequest는 0부터 시작하므로 page-1을 해야 함

list.html 수정

  • 페이지 번호가 친구목록의 밑에 출력이 될 수 있도록 구현

Plaintext
<div th:if="${friends.totalPages > 1}">
    <ul>
        <li th:each="i:${#numbers.sequence(1, friends.totalPages)}">
            <a th:href="@{/friends/list(page=${i})}" th:text="${i}"></a>
        </li>
    </ul>
</div>
  • if문을 사용하여 totalPages가 둘 이상일 때만 (=하나 초과일 때만) <div>를 만들어내도록 할 것

만약 페이지가 1개면 페이지 목록 번호를 만들어낼 필요가 없기 때문

  • 2페이지부터는 <div>로 페이지 번호를 만들어냄

  • th:each="i:${#numbers.sequence(1, friends.totalPages)}

numbers.sequence 객체(thymeleaf가 제공하는 문법)가 데이터 사이즈만큼 하나씩 (sequence) 증가하면서 1페이지부터 friends.totalPages 페이지까지 반복문을 돌면서 페이지 안 정보를 가져옴

여기서의 i는 <a>태그의 i에 값이 전달됨

  • <a th:href="@{/friends/list(page=${i})}" th:text="${i}"></a>

page의 번호는 ${i}로 sequence에서 만든 i를 가져옴

이 페이지 숫자를 화면에 보여주기 위해서 text를 지정**(${i})**

3페이지에 커서를 올렸을때 (localhost:8080/friends/list?page=3) 를 확인

페이지 쿼리가 제대로 전달되고 있는지 확인

Plaintext
<table th:if="${!friends.empty}">
<!--테이블 정보들...--></table>

  • !friends.empty

친구 목록이 없다면 테이블 또한 만들지 않도록 구현

학습 목표 : 친구 목록 페이지를 만들면서 프로그램의 흐름을 따라가면서 이해할 수 있었다.

혼자 새로운 프로젝트를 개발해낼 수 있을까 생각도 했지만 비슷한 구조로 생각하며, @GetMapping, @PostMapping 등을 활용하여 페이지에서의 요청이 발생했을 경우 매핑 시켜주는 과정을 천천히 이해해 보았다.

지금까지 배운 내용을 토대로 기능이 원활히 작동할 수 있도록 하나하나 뷰를 구현하는 것이 목표였다.

회고 결과 :이번 회고에서는 게시판 프로젝트를 직접 만들 수 있었다.

기존의 친구 목록 페이지를 참고하여 게시판 프로젝트의 구조 설계에 적용할 수 있었다.

시간이 오래 걸렸지만 뷰를 하나하나 구현해나가는 과정에서 시행착오가 많았다.

  • <p>태그와 <span>태그 사용으로 입력 필드 구분하기

  • Content 입력 필드는 textarea를 사용하는 것

  • 비밀번호 검증 로직을 추가하기 위한 방법

  • createdAt을 구현하기 위해 "게시글 수정"했을 때의 시간으로 적용되도록 구현

  • createdAt은 LocalDateTime으로 담아놓고 리스트를 보여주는 페이지에서는 날짜만 출력하도록 format 적용

  • 상세페이지에서는 날짜-시간까지 출력할 수 있도록 format 적용

  • 비밀번호는 type="password"로 입력되는 값들이 보이지 않도록 구현

  • 기존 친구 목록 삭제에서는 데이터가 바로 삭제되었다면 게시판의 삭제는 비밀번호로 유효성 검증을 한 후 삭제하도록 구현

느낀 점 :

확실히 요구사항을 보고 바로 구조와 구현해야 할 메소드가 떠오르기까지는 시간이 걸리는 것 같다.

또한 Controller - Service - Repository로 연결되어 메소드가 호출되는 과정이 여전히 익숙지 않았다.

향후 계획 :

  • 스프링 프레임워크의 페이징 처리 기술에 대해서 추가 공부 진행

  • 타임리프의 유용한 기능들을 추가로 알아보기 (이번 프로젝트의 개발에서 #temporals와 format() 사용 등)

  • 레이어드 아키텍처의 구조 공부


공부는 배울 때 끝나는 게 아니라, 되새기고 활용할 때 완성돼요. 그런 의미에서 오늘 소개한 이야기는 단순한 후기가 아니라, 학습을 더 효과적으로 만들고 나만의 것으로 만드는 과정이었을 거예요. 여러분도 배운 내용을 정리하고, 더 오래 기억하는 습관을 만들어 보세요. 작은 차이가 결국 큰 결과를 만들 테니까요!

내가 배운 것을 글로 정리하는 것이 처음에는 어렵게 느껴질 수도 있지만 멋쟁이사자처럼과 함께 꾸준히 기록하다 보면 스스로 변화하고 있다는 걸 깨닫게 될 거예요. 자! 이제 여러분 차례에요. 언제나 여러분의 곁에는 멋쟁이사자처럼이 있으니 고민하지 말고 배움을 기록하고, 성장의 발자취를 남겨보세요!

홈 : 멋사 부트캠프

멋쟁이사자처럼의 13년 교육 노하우로 IT 취업의 꿈을 현실로!

홈 : 멋사 부트캠프