첫 갭체크와 한 달 후 결과 비교 (Before & After)

처음 코딩테스트 공부를 시작했을 당시 알고리즘이나 문제 풀이에 대한 기반이 전혀 없는 완전한 노베이스 상태였다.

무엇을 어떻게 시작해야 할지 모르는 막막한 상태에서 학업 스케줄까지 겹치다 보니 학습의 연속성을 유지하기가 더욱 어려웠다.

이런 상황에서 코드트리의 첫 갭체크(Gap Check) 진단을 받았다. 당연히 첫 진단 결과는 처참했다.

개념 이해도가 부족했고, 아주 기본적인 구현 문제에서도 시간 초과와 오답이 속출했다.

첫 갭체크 진단시

1. 지난 갭체크 결과 (Before)

  • 조건문 (불안정): 기본적인 if 문은 사용 가능하나, 중첩 조건문이나 복잡한 예외 상황 처리가 미숙했다.
  • 단순 반복문 (부족): 루프 구성 및 종료 조건 설정 등 기초적인 제어 흐름 구현 자체에 어려움이 있었다.

진단 결과 요약: 반복문과 조건문 흔들림으로 인해 다음 단계인 배열이나 함수 학습으로 진입하기 어려운 수준이었다.

 

한달 후 갭체크 진

2. 이번 갭체크 결과 (After)

  • 1차원 배열 (불안정): 기본 구조 접근은 가능하나, 인덱스 순회 및 반복문 연계 연습이 필요한 단계로 진입했다.
  • 2차원 배열 (부족): 행과 열 구조를 활용한 데이터 처리 개념을 이제 막 익히기 시작하는 단계이다.

진단 결과 요약: 이전 진단에서 취약했던 제어문(조건문·반순 반복문)을 극복하고, 현재는 배열 구조를 다루는 단계로 명확히 레벨업했다. 

 

 

 

하지만 한 달 동안 갭체크 리포트가 객관적으로 짚어준 취약 구간과 학습 가이드를 명확히 따라갔다. 노베이스 상태에서 무작정 어려운 문제를 풀기보다, 시스템이 제안한 기초 공백을 메우는 데 집중했다. 그 결과 문제를 마주했을 때의 막막함과 오답률이 유의미하게 감소했다.

 

지난 한 달간 약점 보완을 위한 구체적인 노력

기초 제어 흐름이 부족한 상태에서 학업과 병행해야 했기에, 무작정 진도를 나가기보다 시스템이 짚어준 공백을 메우는 데 집중했다.

  • 반복 조건 및 제어 변수 기본기 훈련
    • 단순 반복문 부족 판정을 받은 후, 코드트리 트레일의 가장 기초적인 제어문 단원으로 돌아가 루프의 종료 조건과 논리 연산자 사용법을 반복 연습했다.

  • 북마크 기능을 통한 간헐적 복습
    • 평일 학업 중 예외 처리가 꼬였던 조건문이나 인덱스 접근 오류가 난 배열 문제들은 북마크 폴더별로 즉시 마킹하여 저장했다. 주말에 시간이 생겼을 때 대시보드를 열어 취약 단원 위주로 학습을 재개했다.
  • 코드 구조 시각화 및 비교
    • 복잡한 if-else 구조를 마주했을 때 코드트리 해설의 모범 답안과 내 코드를 나란히 비교하며 조건의 우선순위를 직관적으로 정돈하는 습관을 들였다.

 

약점 유형이 강점으로 바뀌기 시작

노베이스 상태에서 가장 막막했던 부분은 단순 반복 작업조차 루프로 깔끔하게 구현하지 못해 코드가 비효율적으로 길어진다는 점이었다. 중첩 조건문이 나오면 예외 상황을 놓치기 일쑤였다.

그러나 갭체크를 통해 제어문의 문제점을 인지하고, 코드트리의 Warmup-Challenge-Test 단계별 예제를 수행하면서 변화가 생겼다

 

반복 조건과 제어 변수의 역할을 명확히 이해하게 되자, 이전에는 손대기 어려웠던 1차원 배열의 선언과 데이터 순회 단계까지 자연스럽게 진입할 수 있었다. 기초 제어문이 안정되면서 다음 단원인 배열 구조를 학습할 수 있는 기반이 다져진 것을 체감했다.

 

코드트리에서 달성할 최종 학습 목표와 다짐

 

학업 등으로 인해 매일 일정량의 진도를 나가는 연속성(Streak)에 매몰되면 지치기 쉬운 환경이었다. 대신 연동해둔 깃허브(GitHub) 자동 기록 기능을 활용하여, 비록 간헐적이더라도 노베이스에서부터 차근차근 나만의 속도로 학습 흔적을 누적하는 데 집중했다.

  • 최종 학습 목표 : 코드트리 실력 진단에서 안정적인 점수대를 확보하고, 실전 코딩 테스트에서 기본 유형들을 제한 시간 내에 완벽히 구현해내는 역량을 갖춘다.

스트릭 숫자에 연연하며 스트레스를 받기보다, 갭체크 진단 리포트를 이정표 삼아 부족한 단원을 정밀하게 타격하는 방식을 유지하하고, 완전히 기초부터 시작했지만 시스템의 진단 기능을 적극 활용하여 학업과 균형을 맞추며 학습을 완료할 계획이다.

 

https://www.codetree.ai/ko/no-free-lunch-2026/?ref=5R8GAC

 

3년 만에 돌아온, 코드트리 청약 통장 챌린지 | 코드트리

매주 학습 납입하고 7주 만기 채우면 코드트리 8월까지 무료. 매주 추첨권을 모아 맥북·에어팟·애플워치 응모까지. 신청 인원에 따라 조기마감될 수 있어요.

www.codetree.ai

-> 무료 갭체크 진단부터 시작하기☺️

코딩 테스트 플랫폼을 이용하다 보면 학업 스케줄로 인해 학습 흐름이 자주 끊겼다. 며칠 만에 다시 접속하면 이전에 어디까지 공부했는지, 어떤 부분에서 막혔었는지 기억이 나지 않아 감을 잡는 데 시간을 허비했다.

 

코드트리의 북마크 기능은 끊어진 학습 흐름을 바로 연결해주는 시스템적 도구였다.

 

학습 도중 완벽히 이해하지 못했거나 나중에 다시 봐야 할 문제를 폴더별로 즉시 지정해 저장했다. 이 방식을 통해 시간이 부족한 평일에는 취약 지점을 마킹만 해두고, 여유가 생기는 시점에 북마크 대시보드만 열어 곧바로 복습을 재개할 수 있었다.

 

효율적인 복습을 위한 북마크 분류 기준

 

  • 완벽하지 않은 문제
    • 겨우 맞혔거나 구현 시간이 오래 걸려 추후 재도전이 필요한 문제
  • 다시 풀기
    • 시간적 여유가 있을 때 타이머를 켜고 혼자 힘으로 다시 풀어볼 문제

 

시간이 부족할 때는 새로운 문제를 무리하게 풀기보다, 북마크 폴더에 모아둔 문제들을 나란히 열어두고 구조적 차이점만 직관적으로 비교했다. 시스템의 모아보기 기능을 활용하는 것만으로도 복습의 밀도가 높아졌다.

 

시스템 기반의 유연한 복습 루틴

매일 일정량의 진도를 나가야 한다는 압박감에서 벗어나, 학업 일정에 맞춘 유연한 프로세스를 정립했다.

  1. 트레일 학습 및 마킹 (평일) : 새로운 개념을 학습하며 정체되었던 문제를 예외 없이 북마크에 저장했다.
  2. 북마크 대시보드 체크 (공백기) : 풀이 시간이 부족한 날에는 새로운 문제를 푸는 대신 북마크된 취약점 리스트를 눈으로 검토했다.
  3. 집중 복습 및 해결 (주말) : 학업 과제가 끝난 여유 시간에 북마크 시스템을 열어 저장된 문제들을 집중적으로 풀었다.

이번 주는 학업 스케줄로 인해 목표 XP를 채우지 못하는 날이 많았다. 하지만 북마크 시스템에 취약 문제들이 시각화되어 저장되어 있었기 때문에, 진도가 멈춘 상태에서도 불안감 없이 효율적으로 복습을 진행할 수 있었다.

 

 깃허브 연동을 통한 학습 기록

매일매일 빈틈없이 잔디를 심는 연속성 시스템에 얽매이지 않았다. 대신 연동해둔 깃허브(GitHub) 자동 기록 기능을 통해, 간헐적으로 공부하더라도 학습 흔적이 누적되는 것에 의미를 두었다.

주말이나 여유 시간에 몰아서 해결한 문제들이 깃허브에 한꺼번에 기록되는 것을 보며 나름의 학업 밸런스를 확인했다. 스트릭 기능에 스트레스를 받기보다, 코드트리의 시스템을 개인 라이프스타일에 맞춰 유연하게 변형하여 활용했다.

 

총평

코드트리는 매일 꾸준히 풀 수 있는 사용자뿐만 아니라, 학업 등으로 인해 간헐적으로 공부할 수밖에 없는 환경에서도 유용한 시스템을 제공했다.

내가 멈춘 지점을 기억해주는 트레일, 끊어진 흐름을 이어주는 북마크, 속도에 맞춰 누적되는 깃 연동 기능이 유기적으로 맞물려 독학 페이스를 유지하는 데 도움이 되었다. 앞으로도 시스템 기능을 적극 활용하여 나만의 속도로 학습을 이어갈 계획이다.

 

https://www.codetree.ai/ko/no-free-lunch-2026/?ref=5R8GAC

 

3년 만에 돌아온, 코드트리 청약 통장 챌린지 | 코드트리

매주 학습 납입하고 7주 만기 채우면 코드트리 8월까지 무료. 매주 추첨권을 모아 맥북·에어팟·애플워치 응모까지. 신청 인원에 따라 조기마감될 수 있어요.

www.codetree.ai

해당 링크로 코드트리 통약 챌린지 참여하기!

청약챌린지 3주차이다. 3회차 미션을 진행하며 가장 크게 느낀 점은, 코딩테스트 공부에서 중요한 것은 '매일 엄청난 양을 푸는 것'이 아니라 '흐름이 끊기더라도 다시 공부방으로 돌아오는 루틴을 만드는 것'이다. 그리고 코드트리가 제공하는 여러 동기부여 장치들이 이 루틴을 유지하는 데 매우 현실적인 도움이 되었다.

 

1. 학습 의지를 깨우는 카톡 리마인더

일과가 바쁘다 보면 "오늘 알고리즘 문제 풀어야지" 하는 생각조차 잊어버릴 때가 많다. 그때 도착하는 코드트리의 알림톡 리마인더는 공부 흐름이 완전히 끊기지 않도록 붙잡아주는 역할을 해준다.

 

설령 시간이 부족해 문제를 직접 풀지 못하는 날이더라도, 알림을 보고 접속하여 개념 설명을 다시 읽거나 이전 오답 해설을 복기하는 식으로 학습 밀도를 유지할 수 있다. 

 

이런식으로 알림이 온다.

 

2. 깃허브(GitHub) 잔디 심기 공식 연동

개발자로서 기술 역량을 기록하고 꾸준함을 증명하는 데 있어 GitHub의 초록색 잔디는 매우 직관적인 지표이다.

기존에는 외부 확장 프로그램을 사용하여 풀이를 연동하곤 했는데, 코드트리는 플랫폼 내부에서 공식적으로 Repository 연동 및 폴더명 커스텀 설정을 지원하여 굉장히 편리하다.

 

5월달은 코드트리 덕분이라 해도 과언이 아니다.

 

 

 

제출한 정답 코드가 자동으로 GitHub에 커밋되는 것을 보며, 내가 투자한 시간이 증발하지 않고 기록으로 남는다는 것을 확실히 느낄 수 있었다. 가장 좋은 점은 문제가 폴더별로 깔끔히 정리되어 내가 풀었던 문제들을 직관적으로 볼 수 있다는 점이다.

 

맨 왼쪽 사진처럼 문제가 폴더별로 정리되고, 해당 폴더에 들어가면 자동으로 README에 남겨진다.

 

내가 어떻게 풀었는지 자동으로 기록된다.

 

3. 고립감을 해소하는 라이브 챌린지 현황 및 단체 미션

 

혼자서 문제를 풀다 보면 쉽게 지치기 마련이다. 하지만 코드트리 대시보드에서는 다른 참여자들이 실시간으로 문제를 풀고 학습하는 현황이 라이브로 업데이트되어 자연스러운 자극을 받을 수 있다.

특히 이번 3회차에는 공동 경험치 10만을 모아 함께 보상을 받는 단체 미션이 존재하여, 개인 학습임에도 불구하고 '다 함께 참여하고 있다'는 유대감을 느낄 수 있었다.

 

 

이번 3회차를 하면서 느낀점은 코드트리의 시스템이 꾸준한 학습 습관을 유지하는데 분명히 도움되는 플랫폼이라는 점이다. 

학업과 과제, 프로젝트를 병행하다 보면 현실적으로 변수가 많아서 쉽지 않겠지만, 코드트리의 알림톡과 동기부여 장치들을 활용해 매일 작은 문제라도 풀어나가며 문제 푸는 습관을 형성할 계획이다. 4주차까지 아자아자!

 

https://www.codetree.ai/ko/no-free-lunch-2026/?ref=5R8GAC

 

3년 만에 돌아온, 코드트리 청약 통장 챌린지 | 코드트리

매주 학습 납입하고 7주 만기 채우면 코드트리 8월까지 무료. 매주 추첨권을 모아 맥북·에어팟·애플워치 응모까지. 신청 인원에 따라 조기마감될 수 있어요.

www.codetree.ai

🙌상기 링크로 코드트리 이용이 가능합니다!

코드트리 챌린지를 시작하면서 Sorting 챕터부터 학습을 진행했다.

솔직히 정렬은 쉬울 거라 생각했는데 생각보다 챙겨야 할 게 많았다.

 

코드트리 커리큘럼 구조

Trail 2. 여기서부터 알고리즘 학습이다.

Functions부터 시작해서 Recursive Functions → Sorting → Simulation → Exhaustive Search → Case Work → Ad-Hoc 순서로 이어진다.

Trail은 선택할 수 있어서 자신의 수준이나 목표에 맞게 시작점을 고를 수 있다.

 

단순히 문제만 나열하는 방식이 아니라, 각 챕터 안에서 기본 → 연습 → 테스트 순서로 문제가 구성되어 있어서 개념을 익히고 바로 적용해보는 흐름이 자연스럽게 만들어진다.

처음에는 이 구조가 그냥 문제 묶음처럼 보였는데, 직접 따라가보니 기본 문제에서 개념을 잡고, 연습 문제에서 조건이 조금씩 붙고, 테스트 문제에서 내가 진짜 이해했는지 확인하는 흐름이 꽤 잘 설계되어 있다는 걸 느꼈다.

 

Sorting 학습에서 달라진 풀이 감각

실제 학습 화면

 

기본 문제에서 Arrays.sort() 감각을 익혔다.

예를 들어 기본 문제인 오름내림차순 정렬은 이렇게 풀었다.

Arrays.sort(arr);
for (int i = 0; i < n; i++) {
    System.out.print(arr[i] + " ");
}
System.out.println();

for (int i = n - 1; i >= 0; i--) {
    System.out.print(arr[i] + " ");
}

 

처음엔 내림차순을 따로 다시 정렬해야 하는 줄 알았는데,

오름차순 정렬 후 역순으로 출력하면 된다는 걸 이때 정리했다.

 

정렬은 쉬워 보이지만 막상 문제에 적용하려고 하면 헷갈리는 부분이 생겼다. 단순히 오름차순, 내림차순 정렬만 하면 되는 줄 알았는데, 기준이 두 개 이상이거나 특정 조건에 따라 정렬 방식이 바뀌는 경우에는 처음 접근 자체가 달라져야 했다.

 

기본 문제에서는 Arrays.sort() 같은 내장 함수를 활용하는 감각을 익혔고, 연습 문제로 넘어가면서 comparator를 직접 구현하거나 정렬 기준을 세분화하는 연습을 했다. 테스트 문제에서는 정렬만으로 해결이 되는 문제인지, 아니면 정렬 이후 추가 처리가 필요한지를 판단하는 게 중요했다.

 

이 과정에서 정렬 문제를 볼 때 "뭘 기준으로 정렬할 것인가"를 먼저 정리하고 코드를 작성하는 습관이 조금씩 생겼다.

 

기본 → 연습 → 테스트를 따라가며

코드트리에서 좋았던 점은 같은 개념을 반복하되, 매번 조금씩 다른 방식으로 적용해볼 수 있다는 점이었다. 기본 문제만 풀었을 때는 "이거 알겠는데?" 싶었는데, 테스트 문제에서 막히는 경우가 있었다. 그때 내가 개념을 몰라서 틀린 건지, 구현을 실수한 건지를 구분할 수 있어서 다음에 뭘 보완해야 할지가 명확해졌다.

 

해설과 모범코드를 내 코드랑 비교해보는 것도 도움이 됐다. 같은 결과를 내더라도 훨씬 간결하게 쓰는 방식을 보면서 내 풀이에서 불필요한 부분이 어디인지 확인할 수 있었다.

 

다른 서비스와 비교했을 때

백준이나 프로그래머스는 문제는 많지만 내가 뭘 먼저 풀어야 할지 기준이 없어서, 결국 아무 문제나 풀다가 방향을 잃는 경우가 많았다. 코드트리는 커리큘럼 순서가 잡혀 있어서 일단 따라가기만 하면 된다는 점이 달랐다. 특히 어떤 유형이 약한지 갭체크로 확인하고 거기서부터 시작할 수 있다는 게 실질적으로 도움이 됐다.

 

https://www.codetree.ai/ko/no-free-lunch-2026/?ref=5R8GAC

 

3년 만에 돌아온, 코드트리 청약 통장 챌린지 | 코드트리

매주 학습 납입하고 7주 만기 채우면 코드트리 8월까지 무료. 매주 추첨권을 모아 맥북·에어팟·애플워치 응모까지. 신청 인원에 따라 조기마감될 수 있어요.

www.codetree.ai

해당 링크를 통해 갭체크 테스트가 가능하다. 😉

이제 슬슬 코딩테스트 준비해야하는데.. 백준으로 할까하다...

얼마전에 서비스 종료 되었다는 안타까운 소식을 듣게 되어

나 같은 초보는 어디서 실력을 쌓아야 차근차근 성장할 수 있을까 하고 한참을 방황하다 찾게 된 코드트리.

 

https://www.codetree.ai/ko

 

Codetree: Master Coding Interviews - Data Structures & Algorithms

Master algorithms, ace tech interviews, and elevate your coding skills with Codetree's systematic curriculum and expert-crafted problem sets.

www.codetree.ai

 

UI도 깔끔하고 무엇보다 들어가자마자 눈에 띈 '갭 체크 진단' 

 

코딩테스트는 1년전에 부트캠프 다닐때 테스트 겸 봤던게 처음이자 마지막이었던지라 내 (처참한)실력이 어느정도인지

궁금하기도 했고. 내 레벨에 따라 그에맞는 커리큘럼을 자동으로 추천해준다기에 거리낌없이 시도해봤는데

역시나!.. 결과는 처참했다. 어려운 문제는 당연하고 심지어 중간 난이도 조차 문제를 이해하기가 어려워서 (...)

나 지금 아주 심각하구나 하고 객관적인 내 실력과 마주할 수 있었다.

 

 

첫번째 문제

 

본인의 실력, 어떤 방향으로 향상시키고자 하는 방향을 선택 한 후 화면을 넘기면

첫 문제 테스트부터 시작한다.

 

여느 다른 코딩테스트 사이트와 마찬가지로 문제와 본인이 선택한 언어에 맞는 코드베이스를 제공해주고

내가 나머지 코드를 채우고 테스트한 뒤 제출하는 형식이다.

 

첫번째 문제는 단순 계산이었기에 간단한 연산을 입력하였고.

두번째 문제부터 반복문, 제어문이 나왔고

세번째 문제부터는 논리적으로 연산하는 부분이 시작되어

 

스스로 내 자신이 안타까움에 탄식을 자아냈다.. (나 고작 이것뿐이구나.. 알긴 알았다만..ㅎ)

 

 

정답을 맞추면 내가 문제를 푸는 과정의 화면과 함께 옆에 코멘트를 달아준다.

 

논리적 사고가 부족한 나는 3번째 문제부터 어려워져서 뭐.. 결국 거의 포기한 것과 다름 없고 결과는!..

 

네,,그래요 놀랍지 않군요

 

이런식으로 적나라하게(?) 내가 무엇이 부족하고 어떤 개념을 길러야하는지 가감없이 코멘트를 달아준다.

 

 

부족한 부분을 알아야 기본기 생략없이 탄탄히 실력을 쌓아올릴 수 있기에

이렇게 세밀하게 알려주는 방식이 굉장히 좋았다.

 

 

이렇게 테스트 한 결과를 기반으로 챕터를 추천해준다.

무엇보다 첫 난이도는 크게 어렵지 않게 시작하기 때문에 기본적인 개념을 쌓은 뒤 문제를 시작할 수 있다.

 

물론 진단결과를 토대로 시작하지 않고 원하는 챕터부터 시작이 가능하다.

 

하지만 나는 기본개념을 확실히 잡고 가길 원해서 진단결과 나온 약한 챕터부터 문제를 풀고 있는 중이다.

물론 그 개념이 if 문부터 시작이라 스스로 회의감이 들기는 하지만 ㅎㅎ..

이런들 어찌하리 어제보다 더 나은 내일이 내 모토기에 이렇게 차근차근 실력을 쌓다보면 

 

어려운 난이도도 쉽게 풀 수 있는 날이 언젠가 오지 않을까 하면서 매일 한시간씩 꾸준히 코드트리를 하는 중이다!

 

 

이런식으로 풀더별로 문제를 모아볼 수 있다.

 

 

무엇보다.. 깃허브 커밋까지 지원하여 내가 맞춘 문제만 자동커밋할 수 있다.

즉 꾸준히 문제만 풀면 깃허브 잔디로 내 성실함까지 증명이 가능하다!

 

정확히 4일전 부터 코드트리를 시작하여..

 

개발자라면 알것이다.. 깃허브에 잔디가 쌓일때 느끼는 그 뿌듯함이란..ㅎㅎ 

 

 

https://www.codetree.ai/ko/no-free-lunch-2026/?ref=5R8GAC

 

3년 만에 돌아온, 코드트리 청약 통장 챌린지 | 코드트리

매주 학습 납입하고 7주 만기 채우면 코드트리 8월까지 무료. 매주 추첨권을 모아 맥북·에어팟·애플워치 응모까지. 신청 인원에 따라 조기마감될 수 있어요.

www.codetree.ai

 

추천코드: 5R8GAC

 

링크를 통해 가입하고 추천코드를 입력하면 500포인트를 얻을 수 있습니다. 

 

추가로 문제를 풀때마다 추천인과 가입자 모두 +5 포인트를 받을 수 있는데,

전 매일 같이 문제를 풀고 있기 때문에 가입자에게도 좋은 추천인 제도입니다! ☺️

 

매일 출석체크 인증!


이 글을 읽고 온 초보개발자, 취업준비생, 신입개발자 혹은 성장하길 바라길 누군가!

모두 같이 열심히 준비하여 꼭 원하는 목표를 이루길 바랍니다 :)

어제보다 더 나은 내일의 나를 꿈꾸며

앞으로도 꾸준히 코딩테스트 공부한 내용을 적도록 하겠습니다!

 

배포경험은 처음이기에 초보자가 접근하기 쉽다는 AWS EC2로 개인프로젝트를 배포했다.

https://aws.amazon.com/ec2/

 

Amazon EC2 - 클라우드 컴퓨팅 용량 - AWS

Amazon EC2는 프로세서, 스토리지, 네트워킹, OS 및 구매 모델의 다양한 옵션을 제공하며, 클라우드에서 안전하고 크기 조정 가능한 컴퓨팅을 제공합니다.

aws.amazon.com

 

1. AWS EC2 인스턴스 생성

  • AWS EC2 인스턴스 생성 (t2.micro, Ubuntu, 서울 리전)
  • 보안 그룹 포트 오픈: 22(SSH), 80(HTTP), 443(HTTPS), 8080(Spring Boot)

1) AWS EC2 인스턴스 생성

6개월간은 Free plan이 가능하여 새로 가입을 했는데 region을 잘못보고 Sydney 로 설정하여

다시 Seoul로 설정한 뒤 재포를 했다. 헛수고 하지 않기 위해 꼭 Region 확인 후 'Launch instance' 

상단에 있는 'Asia Pacific(Seoul)

 

Instance Luanch 과정에서 생성된 .pem키는 꼭 잘 저장해놓도록 하자.

EC2 환경에 빌드할때 필요하다.

Public IP 또한 EC2에 올릴때 필요하다. 

 

2) 인바운드 규칙 탭 추가하기

  • HTTPS 포트 443 → 0.0.0.0/0
  • HTTP 포트 80 → 0.0.0.0/0
  • Custom TCP 포트 8080 → 0.0.0.0/0

2. EC2 환경 설정

  • SWAP 메모리 설정 (t2.micro 메모리 부족 대비)
  • Java 17 설치
  • MySQL 설치 및 DB 생성, 비밀번호 설정

최대한 free plan 한도에서 진행하려 하니 터미널이 자주 멈추는 현상이 발생했다.

찾아보니 t2.micro 메모리가 1GB밖에 안 돼서 Spring Boot를 띄우는 것만으로도 벅찬 것 같아

SWAP 메모리를 띠로 설정해 주었다.

*SWAP은 EC2 안에 있는 디스크를 메모리처럼 쓰는 것.

 

1) SWAP 메모리 설정

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

 

2) 프로젝트는 Java17 과 MySQL 로 설정 했기때문에 클라우드 환경에도 동일하게 설치해준다.

sudo apt update
sudo apt install -y openjdk-17-jdk
sudo apt install -y mysql-server

 

3) MySQL 접속하여 DB와 유저 설정을 한다.

sudo mysql

 

접속되면, root 비밀번호 설정

CREATE DATABASE workout_diary;
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword';
FLUSH PRIVILEGES;
EXIT;

3. DB 마이그레이션

  • MySQL Workbench로 로컬 DB export
  • scp로 EC2에 업로드
  • EC2에서 import

Windows에서 SQL 파일을 export하면 파일 앞에 보이지 않는 특수문자가 붙는다. MySQL이 이걸 읽으면 에러가 난다. mysqldump 명령어로 해결하려 했지만 계속 실패해서 결국 MySQL Workbench로 export하니 해결됐다.

 

1) DB export -> 파일을 export 해서 EC2 환경에 올린다. (MySQL Workbench 접속)

  1. Dump Data Only → Dump Structure and Data 로 변경
  2. Export to Self-Contained File 선택 (단일 파일로)
  3. Include Create Schema 체크
  4. Start Export 클릭!

2) scp로 EC2에 올리기

scp -i "C:\Users\dhwkd\workout server.pem" "C:\Users\dhwkd\OneDrive\문서\dumps\Dump20260511.sql" ubuntu@your-ec2-ip:~

 

3) EC2 터미널에서 DB import

mysql -u root -p'your-password' workout_diary < ~/Dump20260512.sql

4. Spring Boot 배포

  • application.properties 수정 (SSL 비활성화, 포트 8080)
  • CORS 설정 (배포 URL 추가)
  • mvnw clean package -DskipTests 빌드
  • scp로 jar 파일 업로드
  • nohup java -jar 백그라운드 실행

Spring Boot 배포전에 application.properties 포트번호 변경, CORS 설정에 배포 URL을 추가한다.

*도메인은 Duck DNS 의 무료 도메인을 이용했다. (계정 한개당 1개 무료 도메인 가능)

 

1) 포트번호, 배포 URL 추가

# Server web port
server.port=8080
configuration.setAllowedOrigins(Arrays.asList(
        "https://localhost:3000",
        "https://dailylift.duckdns.org"
));
private String[] theAllowedOrigins = {
        "https://localhost:3000",
        "https://dailylift.duckdns.org"
};

 

2) Spring Boot 빌드 >  jar파일 EC2에 업로드

cd C:\exercise-library\02-backend\workout-diary\workout-diary
.\mvnw.cmd clean package -DskipTests

 

scp -i "C:\Users\dhwkd\gymrat server.pem" "C:\exercise-library\02-backend\workout-diary\workout-diary\target\diary-0.0.1-SNAPSHOT.jar" ubuntu@your-ec2-ip:~

 

3) nohup java -jar 백그라운드 실행

sudo cp -r ~/build/* /var/www/html/
nohup java -jar ~/diary-0.0.1-SNAPSHOT.jar > ~/app.log 2>&1 &

5. React 배포

  • .env.production 생성 (배포 API URL)
  • npm run build 빌드
  • scp로 build 폴더 업로드
  • Nginx로 정적 파일 서빙

1) .env.production 생성

기존에 .env 파일만 존재했는데, 이럴 경우 배포 주소와 local 환경 테스트를 분리할 수가 없어서

매번 포트번호를 수정하는게 불편하여 .env.production 과 .env 파일로 나누었다.

 

[React 환경 변수 우선순위]

  • npm start (로컬) → .env.local > .env
  • npm run build (빌드) → .env.production > .env

따라서 파일을 나눠서 관리한다

 

2) npm run build 빌드 > PowerShell-EC2에 업로드

cd C:\exercise-library\03-frontend\react-library
npm run build
scp -i "C:\Users\dhwkd\workout server.pem" -r "C:\exercise-library\03-frontend\react-library\build" ubuntu@52.64.44.192:~

 

3) Nginx 설치 > React 파일 복사 > Nginx 설정

sudo apt install -y nginx
sudo cp -r ~/build/* /var/www/html/
sudo tee /etc/nginx/sites-available/default << 'EOF'
server {
    listen 80;
    server_name gymrat.duckdns.org;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name gymrat.duckdns.org;

    ssl_certificate /etc/letsencrypt/live/gymrat.duckdns.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/gymrat.duckdns.org/privkey.pem;

    location / {
        root /var/www/html;
        index index.html;
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://localhost:8080;
    }
}
EOF

6. 도메인 & HTTPS

  • DuckDNS 무료 도메인 생성
  • Let's Encrypt SSL 인증서 발급 (certbot)
  • Nginx 설정 (HTTP → HTTPS 리다이렉트, API 프록시)

1) DuckDNS 무료 도메인 생성

https://www.duckdns.org/

-> 도메인 설정 후, EC2 IP 주소를 입력한다.

 

DuckDNS는 certbot 자동 인증이 안 돼서 수동으로 TXT 레코드를 등록해서 인증했다.

certbot이 알려주는 값을 DuckDNS URL에 직접 입력하는 방식이다.

 

2) SSL 인증서 받기 > certbot이 TXT레코드 값 알려주면 이메일 주소 입력 > Y

sudo apt install -y certbot python3-certbot-nginx
sudo systemctl stop nginx
sudo certbot certonly --manual --preferred-challenges dns -d dailylift.duckdns.org

 

3) DuckDNS에 TXT 레코드 추가하기 > URL에 입력 후 OK 뜨면 bash Enter 누르기

https://www.duckdns.org/update?domains=dailylift&token=여기에토큰&txt=여기에TXT레코드

7. Auth0 설정

  • Allowed Callback URLs 추가
  • Allowed Logout URLs 추가
  • Allowed Web Origins 추가

배포하면서 okta.oauth2.audience 를 배포 도메인으로 바꿨더니 401 에러가 발생했다. Auth0 API의 Identifier(http://localhost:8080)와 설정값이 달라서 토큰 검증이 실패한 것이었다. Identifier는 변경이 불가능하므로 Spring Boot와 React 설정을 다시 http://localhost:8080 으로 맞춰서 해결했다.


8. 트러블슈팅

위 과정에서 겪은 주요 이슈들은 각 단계에 기재했으나 한눈에 보기 위해 정리했다.

  • SWAP 없으면 Spring Boot 실행 중 멈춤 (2번 참고)
  • BOM 문제로 SQL import 실패 → Workbench export로 해결 (3번 참고)
  • .env.production vs .env.local 우선순위 문제 (5번 참고)
  • SSL 인증서는 DuckDNS CAA 문제로 manual DNS 방식 사용 (6번 참고)
  • Auth0 audience 불일치 → http://localhost:8080 으로 맞춤 (7번 참고)

HTTP 요청코드
- 클라이언트가 보낸 요청이 성공했는지 실패했는지 알려주는 코드.
- 응답은 100~500번대까지 5개의 그룹으로 나뉘어져 있다.

상태 코드 설명
1XX(정보) 요청이 수신돼 처리 중입니다.
2XX(성공) 요청이 정상적으로 처리됐습니다.
3XX(리다이렉션 메시지) 요청을 완료하려면 추가 행동이 필요합니다.
4XX(클라이언트 요청 오류) 클라이언트의 요청이 잘못돼 서버가 요청을 수행할 수 없습니다.
5XX(서버 응답 오류) 서버 내부에 에러가 발생해 클라이언트 요청에 대해 적절히
수행하지 못했습니다.
  • 요청이 성공했을 때 200, 데이터 생성을 완료했을 때는 201 을 반환.
  • 요청한 정보를 찾을 수 없을 때 404, 서버에 오류가 났을 때 500 을 반환.

서버는 클랄이언트의 요청에 대한 응답으로 화면(view)가 아닌 데이터(data)를 사용하는데,
이때 사용하는 응답 데이터가 JSON(JavaScript Object Notation) 이다.

 

 

 

[JSON 데이터 예시]

{
    "id" : 1,
    "name" : "Park",
    // 키 값으로 또 다른 JSON 데이터와 배열 사용 가능
    address : {
    		"Street": "Nambu Street 151",
            "suite": "Central Villat 301",
              ...
  	},
    "likes" : {"singing", "reading", "writing"}
}
  • REST API 의 응답 표준으로 사용하는 JSON 은 키와 값의 쌍으로 된 속성으로 데이터를 표현한다.
  • JSON 의 값으로 또 다른 JSON 데이터나 배열을 넣을 수 있다.

 

 

 


REST API 의 구현 과정

  • REST : HTTP URL로 서버의 자원(resource) 을 명시하고, HTTP 메서드(POST, GET, PATCH/PUT, DELETE) 로 해당 자원에 대해 CRUD(생성, 조회, 수정, 삭제) 하는 것을 의미한다.
  • API : 클라이언트가 서버의 자원을 요청할 수 있도록 서버에서 제공하는 인터페이스(interface).
  • REST API : REST 기반으로 API를 구현한 것. -> 클라이언트가 기기에 구애받지 않고 서버의 자원을 이용할 수 있다.

 

REST API 를 구현하려면 URL (REST API의 주소) 을 설계해야 한다.

앞서 만든 Article 데이터를 CRUD(생성, 조회, 수정, 삭제) 하기 위해 REST API 주소를 다음과 같이 설계한다.

 

 

 

[REST API 설계]

HTTP Method REST API URL
GET (목록조회) /api/articles 
GET (단일조회) /api/articles/{id}
POST (생성) /api/articles
PATCH (수정) /api/articles/{id}
DELETE (삭제) /api/articles/{id}

 

설계를 끝냈다면, RestController를 생성한다.

 

 

 


 

 

 

[api/ArticleApiController]

@RestController
public class ArticleApiController {
    @Autowired
    private ArticleRepository articleRepository;
    // GET
    @GetMapping("/api/articles") 
    public List<Article> index() {
        return articleRepository.findAll();
    }
  • Article 묶음을 반환하므로 반환형이 List<Article>인 index() 메서드를 정의한다.
  • return 문에는 articleRepository 의 findAll() 메서드를 사용해 DB 에 저장된 모든 Article을 가져와 반환한다.
  • @Autowired : articleRepository 를 선언하기 위해 의존성을 주입한다.

 

 

[API tester]

 

GET 요청과 해당 URL 주소로 요청하면 JSON 형식으로 목록값이 잘 반환된다.

이제 단일값을 조회해보자.

 

 

 

[api/Article/ApiController]

    // POST
    @GetMapping("api/articles/{id}")
    public Article show(@PathVariable long id) {
        return articleRepostory.findById(id).orElse(null);
    }

 

 


[API tester]

단일값 조회 성공

 

 

 

 


조회에 이어 POST를 RestController에 구현해보자.

 

 

 

[api/ArticleApiController]

    @PostMapping("api/articles")
    public Article create(@RequestBody ArticleForm dto) {
        Article article = dto.toEntity();
        return articleRepository.save(article);
    }
  • @RequestBody : 요청 시 본문(Body)에 실어 보내는 데이터를 create() 메서드의 매개변수로 받기위해 선언.
  • 받은 dto를 DB에서 활용할 수 있도록 Entity로 변환해 article 에 넣고, articleRepository를 통해 DB에 저장.

 

 

[API tester]

POST 요청 성공.

 

 

 

 


PATCH, 수정 구현은 아래와 같다.

 

 

 

[api/ArticleApiController]

// PATCH
@PatchMapping("api/articles/{id}")
public ResponseEntity<Article> update (@PathVariable long id, @RequestBody ArticleForm dto) {
    // 1. DTO -> Entity 변환하기
    Article article = articleRepository.toEntity(); // 서비스 통해 게시글 수정
    // 2. 타깃 조회하기
    Article target = articleRepository.findById(id).orElse(null);
    // 3. 잘못된 요청 처리하기
    if (target == null || id !=article.getId()) {
             return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(null);
    // 4. 업데이트 및 정상 응답(200) 하기
    target.patch(article)
    Article updated = articleRepository.save(target);
    return ResponseEntity.status(HttpStatus.OK).body(updated);
}
  • ReponseEntity : Rest 컨트롤러의 반환형, 즉 RESP API 의 응답을 위해 사용하는 클래스이다.
    - REST API 요청을 받아 응답할 때 이 클래스에 HTTP 상태코드, 헤더, 본문을 실어 보낼 수 있다.
  • HTTPStatus : HTTP 상태 코드를 관리하는 클래스로, 다양한 Enum 타입과 관련한 메서드를 가진다.
    - 열거형으로, 여러 상수로 이루어진 고정 집합을 가진다.
    - 200 : HTTPStatus.OK,
    - 201 : HTTPStatus.CREATED
    - 400 : HTTPStatus.BAD_REQUEST 

수정할 내용이 있는 경우에만 수정 가능하도록 patch() 메서드를 만들어준다.

 

 

 

[entity/Article]

public void patch(Article article) {
    if (article.getTitle() != null) {
        this.title = article.title;
    if  (article.getContent() != null) {
        this.content = article.content;
        }
    }
}

 

if 문으로 article(수정 Entity) 의 title 이 null 이 아닐 경우에만, this(target) 의 title을 갱신해 준다.

*content 또한 동일.

 

 

 

[API tester]

PATCH 요청 성공.

  • PATCH URL 의 id('1') 변수와 BODY 부분의 id ('1')  요청이 일치해야 한다.

 

 

 


마지막으로 DELETE 이다.

 

 

 

[api/ArticleController]

    // DELETE
    @DeleteMapping("/api/articles/{id}")
    public ResponseEntity<Article> delete (@PathVariable long id) {
        // 1. 대상 찾기
        Article target = articleRepository.findById(id).orElse(null);
        // 2. 잘못된 요청 처리하기
        if (target == null) {
        return ResponseEntity.status(HTTPStatus.BAD_REQUEST).body(null);
        }
        // 3. 대상 삭제하기
        articleRepository.delete(target);
        return ReponseEntity.status(HTTPStatus.OK).build();
    }

 

  • 잘못된 요청일 경우 ResponseEntity 에 BAD_REQUEST, 본문(body) 에는 null 을 실어보낸다.
  • 잘못된 요청이 아닐 경우 대상 Entity 를 삭제한다.
  • 그리고 HTTPStatus.OK, 본문(body) 에는 null 대신 body 응답이 없는 객체를 생성한다. (즉, null과 동일)

 

 

 

[API tester]

DELETE 요청 완료.
DB 조회

 

DB 조회를 통해, id = 1 번열이 성공적으로 삭제 되었음을 확인할 수 있다.

 

 

 


 

**출처 : 스프링부트3 자바 백엔드 개발 입문 (홍팍 지음) 

게시판에서 글을 삭제하는 과정은 다음 순서로 이루어진다.

 

 

  1. 클라이언트가 HTTP 메서드로 특정 게시글의 삭제를 요청한다.
  2. 삭제 요청을 받은 컨트롤러는 repostiory를 통해 DB에 저장된 데이터를 찾아 삭제한다.(데이터가 있는 경우)
  3. 삭제가 완료됐다면 클라이언트를 결과 페이지로 리다이렉트 한다.
    - 클라이언트에 삭제 완료 메시지도 띄어주기 위해 RedirectAttributes 클래스를 사용한다.
    - 해당 클래스의 addFlashAttribute() 메서드는 리다이렉트 된 페이지에서 사용할 일회성 데이터를 등록할 수 있다.

 

 

[articles/show.mustache]

<a href="/articles/{{article.id}}/edit" class="btn btn-primary">Edit</a>
<a href="/articles/{{article.id}}/delete" class="btn btn-danger">Delete</a>
<a href="/articles">Go to Article List</a>

 

삭제 링크 URL를 추가한다.

 

 

 

[controller/ArticleController]

@GetMapping("/articles/{id}/delete")
    public String delete(@PathVariable Long id, RedirectAttributes rttr) {
        log.info("삭제 요청이 들어왔습니다");
        // 1. 삭제할 대상 가져오기
        Article target = articleRepository.findById(id).orElse(null);
        log.info(target.toString());
        // 2. 대상 엔터티 삭제하기
        if (target != null) {
            articleRepository.delete(target);
            rttr.addFlashAttribute("msg", "삭제됐습니다!");
        }
        // 3. 결과 페이지로 리다이렉트하기
        return "redirect:/articles";
    }
  • @PathVariable : 특정 게시글을 삭제하기 위해 대표값인 id 를 사용하기 위해 매개변수로 가져온다.
  • articleRepository.findById(id) : DB에 해당 id를 가진 데이터를 찾는다.
    - 찾으면 Article 타입의 target 변수에 저장하고, 못 찾으면 null을 반환한다.
  • articleRepository.delete(target) : 메서드로 대상을 삭제한다.
  • RedirectAttributes 객체의 addFlashAttribute() 메서드를 활용하여 리다이렉트 시점에 한번에 사용할 데이터를 등록.
    - 객체명.addFlashAttributes(넘겨_주려는_키_문자열, 넘겨_주려는_값_객체)

 

 

 

[layouts/header.mustache]

{{#msg}}
<div class="alert alert-primary alert-dismissible">
    {{msg}}
    <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{/msg}}

 

  • {{#msg}} , {{/msg}} : msg 창 사용범위 지정.
  • <div> 태그 : 메시지 창 작성.

 

 

 

리다이렉트 페이지와 함께 삭제 메시지 창이 잘 뜨게된다. 성공!

 

 

 


**출처 : 스프링부트3 자바 백엔드 개발 입문 (홍팍 지음) 

 

데이터 수정은 크게 2단계로 나눠진다.

 

1. <수정 페이지> 만들고 기존 데이터 불러오기

2. 데이터를 수정해 DB 에 반영한 후 결과를 볼 수 있게 <상세 페이지>로 리다이렉트하기.

 

 

1딘계 동작원리
2단계 동작원리

 

 

 


1단계부터 구현해보자. 

상세페이지에 edit(수정) 으로 이동하는 링크를 연결한다.

 

 

[articles/show.mustahce]

<a href="/articles/{{article.id}}/edit" class="btn btn-primary">Edit</a>
<a href="/articles">Go to Article List</a>

 

  •  href 속성 값의 URL을 보면 id가 article의 속성이므로 {{article.id}} 로 표시했다.

{{#article}, {{/article}} 형식으로 지정한 경우 -> {{id}} 만 써도 되지만 그게 아니므로 '.' (점)을 사용하여 표시한다.

 

이제 Edit 요청을 받을 컨트롤러를 연결한다.

 

 

 

[controller/ArticleController]

    @GetMapping("/articles/{id}/edit")
    public String edit(@PathVariable Long id, Model model) {
        // 수정할 데이터 가져오기
        Article articleEntity = articleRepository.findById(id).orElse(null);
        // 모델에 데이터 등록하기
        model.addAttribute("article", articleEntity);
        // 뷰 페이지 생성하기
        return "articles/edit";
    }

 

  • @PathVariable : id 값을 변수로 사용하여 가져오기 위해 annotation 설정.
  • Model : 객체를 담을 모델을 사용하기 위해 메서드의 매개변수로 설정.
  • 수정할 데이터 가져오기 : articleRepository의 findById(id) 메서드로 데이터를 찾아 가져온다.
    - 만약 데이터를 찾지 못하면 null을 반환하고, 데이터를 찾았다면 Aticle 타입의 articleEntity 로 저장한다.
  • 모델에 데이터 등록하기 : articleEntity를 article로 등록.

 

controller 를 생성했으니 이제 DB 에 저장된 데이터를  수정페이지로 가져와 화면에 출력해보자.

 

 

 

[articles/new.mustache]

{{#article}} <!--article 사용범위 지정 -->
<form class="container" action="/articles/update" method="post">
    <div class="mb-3">
        <label Class="form-label">제목</label>
        <input type="text" class="form-control" name="title" value="{{title}}">
    </div>
    <div class="mb-3">
        <label Class="form-label">내용</label>
        <textarea class="form-control" rows="3" name="content">{{content}}</textarea>
    </div>
    <a href="/articles/{{id}}">Back</a>
</form>
{{/article}}

 

<input> 태그로 생성한 입력 요소의 초기값은 value 속성으로 정의하지만, <textarea> 태그로 생성한 여러줄의 입력
요소는 콘텐츠 영역에 초깃값을 정의한다,

 

화면 출력 모습 (title, content 가 정상 출력된다.)

 


 

 

 

클라이언트와 서버 간 처리 흐름을 4가지로 나누어 생각해보자.

  • MVC(Model-View-Controller) : 서버 역할을 분담해 처리하는 기법
  • JPA(java Persistence API) : 서버와 DB 간 소통에 관여하는 기술
  • SQL(Structured Query Language) : DB 데이터를 관리하는 언어
  • HTTP(HyperText Transfer Protocol) : 데이터를 주고받기 위한 통신 규약

  • HTTP 메서드- 클라이언트와 서버 간에 데이터를 전송할 때는 통신 규약 즉, 프로토콜을 따른다.
    - 프로토콜(protocol) 은 기기 간에 각종 신호 처리 방법, 오류 처리, 인증 방식 등을 규정하고 있다.
    - 그 중 HTTP는 웹 서비스에 사용하는 프로토콜로, 클라이언트의 다양한 요청을 메서드를 통해 서버로 보낸다.
    - 대표적인 메서드는 POST(데이터 생성 요청), GET(조회), PATCH/PUT(수정), DELETE(삭제) 이다.
데이터 관리 SQL HTTP
데이터 생성(Create) INSERT POST
데이터 조회(Read) SELECT GET
데이터 수정(Update) UPDATE PATCH(PUT)
데이터 삭제(Delete) DELETE DELETE

 

 


 

 

이제 2단계이다. 

2단계는 데이터를 수정해 DB 에 반영한 후 결과를 볼 수 있게 <상세 페이지>로 리다이렉트 해야한다.

 

 

 

[articles/edit.mustache]

{{#article}} <!--article 사용범위 지정 -->
<form class="container" action="/articles/update" method="post">
    <input name="id" type="hidden" value="{{id}}">
    <div class="mb-3">
        <label Class="form-label">제목</label>
        <input type="text" class="form-control" name="title" value="{{title}}">
    </div>
    <div class="mb-3">
        <label Class="form-label">내용</label>
        <textarea class="form-control" rows="3" name="content">{{content}}</textarea>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
    <a href="/articles/{{id}}">Back</a>
</form>
{{/article}}

 

※ <form> 태그는 옛날에 만들어진 규격이라 PATCH 메서드를 지원하지 않기 때문에 POST로 요청한다.

  • 수정 폼에서 서버로 보낼 때는 id 값을 보내야 하므로 type="hidden" 속성으로 숨겨서 전송한다.

 

 

 

[dto/ArticleForm]

@AllArgsConstructor
@ToString
public class ArticleForm {
    private Long id;
    private String title; // 제목을 받을 필드
    private String content; // 내용을 받을 필드

    public Article toEntity() {
        return new Article(id, title, content);
    }
}

 

  • 수정 폼에서 id를 추가했으므로, DTO에도 id를 추가한다.

 

 

 

[controller/Articlecontroller]

@PostMapping("/articles/update")
    public String updateArticle(ArticleForm form) {
        log.info(form.toString());
        // 1. DTO를 엔터티로 변환하기
        Article articleEntity = form.toEntity();
        log.info(articleEntity.toString());
        // 2. 엔터티를 DB에 저장하기
        // 2.1 DB에서 기존 데이터 가져오기
        Article target = articleRepository.findById(articleEntity.getId()).orElse(null);
        // 2.2 기존 데이터값 갱신하기
        if (target != null) {
            articleRepository.save(articleEntity); // 엔터티를 DB에 저장(갱신)
        }
        // 3. 수정 결과 페이지로 리다이렉트하기
        return "redirect:/articles/" + articleEntity.getId();
    }

 

  • 수정 폼에서 전송한 데이터는 DTO로 받기 때문에, ArticleForm을 매개변수로 추가한다.
  • DTO-> Entity로 변환하는 form.toEntity() 메서드를 호출해 반환값을 articleEntity 이름으로 받는다.
  • 데이터 생성이 아닌, 수정하기 위해 기존 데이터를 가져온다.
    - 이때는 리파지토리를 이용하여 articleRepository.findById() 메서드를 호출한다.
    - findById() 메서드는 repository에서 자동 제공하는 메서드로, 괄호 안에는 찾는 id값을 작성한다.
    - findById(articleEntity.getId()) 메서드를 호출해 반환받은 데이터를 Article 타입의 target 변수에 저장한다.
    - 데이터가 없다면 null 을 반환한다.
  • 기존 데이터를 가져오고 나면 articleRepository.save() 메서드로 기존 데이터를 갱신한다.
  • 데이터를 수정하면 수정된 내용이 반영된 상세 페이지로 이동하도록 redirect 시켜준다.
    -URL의 id 부분은 Entity에 따라 매번 바뀌어야 하므로, articleEntity의 getId() 메서드를 호출한다.

 

 

디버깅 로그
'가가가가' -> '가나다라' 수정.

 

redirect 페이지인 /articles/1 로 이동하고, 제목과 내용도 수정한 대로 잘 바뀌었다. 성공!

 

 

 


 

**출처 : 스프링부트3 자바 백엔드 개발 입문 (홍팍 지음) 

웹 페이지에서 게시글을 등록하면 서버를 통해 DB 에 저장된다.

이렇게 저장된 데이터를 웹 페이지에 출력하는 과정은 다음과 같다.

 

로직을 이해했으면 코드를 통해 실제 데이터를 조회해보자.

 

 

 

[cotroller/ArticleController]

    @GetMapping("/articles/{id}")
    public String show(@PathVariable Long id, Model model) { // 매개변수로 id 받아오기
        log.info("id = " + id); // id 잘 받아오는지 확인

 

  • @PathVariable : URL 요청으로 들어온 전달값을 컨트롤러의 매개변수로 가져온다.

	// 1. id를 조회해 데이터 가져오기
       Article articleEntity = articleRepository.findById(id).orElse(null);
  • findById(id).orElse(null) : id 값으로 데이터를 찾을때 id값이 없으면 null을 반환.

@GetMapping("/articles/{id}")
    public String show(@PathVariable Long id, Model model) { // 매개변수로 id 받아오기
        log.info("id = " + id); // id 잘 받아오는지 확인

        // 1. id를 조회해 데이터 가져오기
        Article articleEntity = articleRepository.findById(id).orElse(null); 
        // 2. 모델에 데이터 등록하기
        model.addAttribute("article", articleEntity);
  • Model : MVC 패턴에 따라 조회한 데이터를 뷰 페이지에서 사용하기 위해 객체로 받는다.
  • model.addAttribute : 모델에 데이터를 등록한다.

 

@GetMapping("/articles/{id}")
    public String show(@PathVariable Long id, Model model) { // 매개변수로 id 받아오기
        log.info("id = " + id); // id 잘 받아오는지 확인

        // 1. id를 조회해 데이터 가져오기
        Article articleEntity = articleRepository.findById(id).orElse(null); 
        // 2. 모델에 데이터 등록하기
        model.addAttribute("article", articleEntity);
        // 3. 뷰 페이지 반환하기
        return "articles/show";
    }
  • return "articles/show";

가져온 데이터를 모델에 등록했으니 마지막으로 사용자에게 보여줄 뷰 페이지를 반환한다.


 

[articles/show.mustache]

{{#article}}
<tr>
    <th>{{id}}</th>
    <td>{{title}}</td>
    <td>{{content}}</td>
</tr>
{{/article}}

 

'#' 과 '/' 로 범위를 정하고, 모델 article에 담긴 객체를 클라이언트에서 출력하기 위해 '{{}}' 를 사용한다.

 

model 에 데이터가 담겨있다는 가정하에 해당 controller URL 로 조회하면

데이터가 성공적으로 조회된다.

 

 


 

이제 단일값이 아닌 list를 조회해보자.

위에서는 Entity를 반환했다면, 목록을 조회할 때는 엔티티의 묶음인 list를 반환한다.

 

 

[controller/ArticleController]

    @GetMapping("/articles") // 다중데이터 목록 조회
    public String index(Model model) {

        // 1. 모든 데이터 가져오기
        ArrayList<Article> articleEntityList = articleRepository.findAll();
        // 2. 모델에 데이터 등록하기
        model.addAttribute("articleList", articleEntityList);
        return "articles/index";
    }

 

※ List 를 사용할 경우, findAll() 메서드가 반환하는 데이터 타입은 Iterable 이기 때문에 오류 메시지가 발생한다.

 

Iterable, Collection, List 인터페이스의 관계.

 

따라서 익숙치 않은 Iterable 대신, ArrayList를 사용하기 위해 articleRepository 의 부모인
CurdRepository의 메서드를 오버라이딩 해준다.

 

 

 

[repository/ArticleRepository]

public interface ArticleRepository extends CrudRepository<Article, Long> {

    @Override
    ArrayList<Article> findAll();
}

 

[articles/index/mustache]

    {{#articleList}}
        <tr>
            <th>{{id}}</th>
            <td><a href="/articles/{{id}}">{{title}}</a></td>
            <td>{{content}}</td>
        </tr>
    {{/articleList}}

 

단일 조회와 마찬가지로 model 에 담긴 객체를 클라이언트에서 출력한다.

 

 

임시로 data.sql 에 담긴 객체들

 

목록 조회 성공!


**출처 : 스프링부트3 자바 백엔드 개발 입문 (홍팍 지음) 

+ Recent posts