<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>후라이</title>
    <link>https://gaeran.tistory.com/</link>
    <description>좋아하는 일을 하기 안되면 지금 하는 일을 좋아하기</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 06:49:18 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>코꿀이</managingEditor>
    <image>
      <title>후라이</title>
      <url>https://tistory1.daumcdn.net/tistory/6319214/attach/f4435fb7b30744088fe6bfe653634e3a</url>
      <link>https://gaeran.tistory.com</link>
    </image>
    <item>
      <title>빅데이터분석기사 12회 실기 합격</title>
      <link>https://gaeran.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요 ?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;몇 달 전에 빅데이터분석기사 필기 합격 후기를 남겼었는데요 !&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;오랜 시간 끝에 6월 20일에 실기 시험을 응시하였습니다 .&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공부 방법, 시험 유형, 12회 실기 문제, 시험 후기 등을 남겨보겠습니다 :)&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;빅데이터분석기사 실기 시험 공부법&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;(1) 교재&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 필기 시험을 준비할 때부터 &quot;&lt;span style=&quot;color: #ef5369;&quot;&gt;이기적&lt;/span&gt;&quot; 교재를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QQXww/dJMcaf8hSzy/UKZEK6gzNbdQv6z90bYKkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QQXww/dJMcaf8hSzy/UKZEK6gzNbdQv6z90bYKkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QQXww/dJMcaf8hSzy/UKZEK6gzNbdQv6z90bYKkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQQXww%2FdJMcaf8hSzy%2FUKZEK6gzNbdQv6z90bYKkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;548&quot; height=&quot;3024&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱히 이유가 있어서 고른 책은 아니고 ,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교보문고에 빅데이터분석기사 책 치니까 제일 처음으로 뜨길래&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 저 책으로 공부했습니다_^^&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 전공자여서 저 책 한권으로 커버 가능했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비전공자 분들이 볼 땐 어쩔지 잘 모르겠습니다 ㅜㅜ.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 서치해보시고 ,&amp;nbsp; 더 좋은 책이 있다면 그 책으로 고르십시오 !!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 기출문제는 이기적 교재에 같이 들어있던 기출 문제입니다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 답지 확인하기 번거로워서 그냥 뜯었어요 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;(2) 강의&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실기 시험 문제는 상당히 &lt;u&gt;정형화&lt;/u&gt;되어 있는 편이라 유료 강의가 필요하지 않았던 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 저는 전공자지만 자바를 주로 사용하던 사람이라 파이썬이 익숙치 않아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에 함수 사용법, numpy, pandas 개념 익히는 용으로 유튜브 강의를 들었던 기억이 있습니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 &lt;b&gt;&quot;퇴근 후 딴 짓&quot;&lt;/b&gt; 유튜브 강의를 참고했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;퇴근 후 딴 짓 링크&quot; href=&quot;https://youtu.be/iOskHbBeV9o?si=yu-FMUwRrfxu13Ll&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://youtu.be/iOskHbBeV9o?si=yu-FMUwRrfxu13Ll&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 플레이리스트의 모든 강의를 들은 것은 아니고요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습환경 테스트와 &lt;u&gt;초반에 함수 익히는 용으로&lt;/u&gt; ? 띄엄띄엄 들었습니다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퇴근 후 딴짓 님께서 Kaggle로 실습 환경을 제공해주시는데다 , 다양한 문제를 풀어볼 수 있어서 정말 좋았습니다 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;(3) 공부기간&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 SQLD 시험과 NCS를 같이 병행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 일주일에 3번 정도 ? 실기 책을 정독했구요, 느낌상 실기 책 10회독 한 것 같습니다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필기 결과 나온 이후로 계속 공부했으니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넉넉히 2달 정도 꾸준히 공부한 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;(4) 공부 방법&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저는 교재에 나온 문제나 기출 문제는 5번 이상 반복하고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;퇴근 후 딴짓 님이 올리신 실습 문제는 2-3번 반복했어요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;실기는 코딩 많이 해본 사람이 장땡인 것 같아요 !&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;책에 나와있는 코드나, 강의에 나온 코드들을 &lt;b&gt;많이&lt;/b&gt; 그리고 &lt;b&gt;자주&lt;/b&gt; 따라해보시고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이해가 안된다 . 어떻게 접근해야할지 모르겠다 --&amp;gt; Chat gpt한테 물어서 꼬옥 해결하고 넘어가시길 ~~&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작업형 1유형&lt;/b&gt;은 진짜 문제 많이 풀어보면 어느새 해결책이 눈에 딱딱 들어오는 것 같고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2유형&lt;/b&gt;은 회귀/분류 하는 프로세스를 익히는 게 중요한 것 같아요 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 튜닝은 우선 패스하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터 전처리, 평가 지표&lt;/span&gt; 먼저 짚고 이후에 모델을 튜닝해보세요 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3유형&lt;/b&gt;도 1유형과 비슷하게 문제 많이 풀어보면 감이 올 거에요 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반엔 수학 개념이나 새로운 함수들이 너무 많아서 헤맬 수 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는 것이 생기면 역시나 Chat gpt에게 물어보심 됩니다 .&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;저는 T-검정 , F-검정 이론이 잘 이해가 안될 때 지피티를 끼고 살았답니다 ㅜ.ㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 추후에 문제에서 원하는 게 뭔지만 잘 파악하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;ttest_ind를 써야 하는지 ttest_rel을 써야하는지 등등..&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt; 12회 빅데이터분석기사 실기 문제&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억을 되살려 보자면 ..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1유형&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 단순 데이터 정렬 &amp;amp; 그룹별로 집계 후 최대/최솟값 뽑기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 열이 밀린 데이터 바르게 정렬해서 값 출력하기 &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(저 이거 못 풀었어요 .. ^^)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 과목에 파이썬 혹은 python이 들어간 데이터 갯수 출력 ? (단, 대소문자 구분 안함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;등등 ..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2유형&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;12회에서 논란이 많았던 문제라서 영상으로 대체합니다 ..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=nBu2GW84Yns&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=nBu2GW84Yns&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;사실 저는 이런 저런 모델 써보다가 그냥 랜덤포레스트 모델 쓰고 평가지표는 rmsle로 제출했는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;30점이었어요 . .&amp;nbsp; 오히려 좋아 . . .&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3유형&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기억이 잘 안 나는데 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ttest 나 chi2 쓰는 문제 나왔을 거고&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; statsmodels를 이용한 통계적 회귀/분류 분석 문제도 나왔던 듯 .. ? 해요&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(이 문제는 거의 고정 같아요)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;020&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/020.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/020.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdF3Va/dJMcahE3taS/FRAWqZbWhQsuKlHLI2hPm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdF3Va/dJMcahE3taS/FRAWqZbWhQsuKlHLI2hPm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdF3Va/dJMcahE3taS/FRAWqZbWhQsuKlHLI2hPm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdF3Va%2FdJMcahE3taS%2FFRAWqZbWhQsuKlHLI2hPm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;251&quot; data-origin-width=&quot;637&quot; data-origin-height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;잘한 거 맞나 ..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1유형에서 한 문제를 통째로 답을 못 내서 10점 까고 시작했습니다 ㅜㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1시간 동안 코드 짰는데 도저히 기억이 안 나서 ^^;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지는 어디서 틀렸는지도 모르겠네욤 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 첫트에 필기-실기까지 합격해서 기분은 좋습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;005&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/005.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/005.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여러분도 꼬옥 합격하시길 응원합니다 !!&lt;/p&gt;</description>
      <category>자격증/빅데이터분석</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/66</guid>
      <comments>https://gaeran.tistory.com/66#entry66comment</comments>
      <pubDate>Fri, 10 Jul 2026 19:59:34 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 [Lv.1] - 둘만의 암호</title>
      <link>https://gaeran.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;백준 알고리즘이 사라진다는 소식을 들었을 때 ,, 빅데이터분석을 준비하느라 파이썬만 주구장창 팔 때였다 .&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;내 오랜 자바 일기들이 담긴 곳이었는데, 아쉽지만 프로그래머스로 갈아탔다 !&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;두 문자열&lt;span&gt;&amp;nbsp;&lt;/span&gt;s와&lt;span&gt;&amp;nbsp;&lt;/span&gt;skip, 그리고 자연수&lt;span&gt;&amp;nbsp;&lt;/span&gt;index가 주어질 때, 다음 규칙에 따라 문자열을 만들려 합니다. 암호의 규칙은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;문자열&amp;nbsp;s의 각 알파벳을&amp;nbsp;index만큼 뒤의 알파벳으로 바꿔줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;index만큼의 뒤의 알파벳이&amp;nbsp;z를 넘어갈 경우 다시&amp;nbsp;a로 돌아갑니다.&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;skip에 있는 알파벳은 제외하고 건너뜁니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어&lt;span&gt;&amp;nbsp;&lt;/span&gt;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;= &quot;aukks&quot;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;skip&lt;span&gt;&amp;nbsp;&lt;/span&gt;= &quot;wbqd&quot;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;index&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 5일 때, a에서 5만큼 뒤에 있는 알파벳은 f지만 [b, c, d, e, f]에서 'b'와 'd'는&lt;span&gt;&amp;nbsp;&lt;/span&gt;skip에 포함되므로 세지 않습니다. 따라서 'b', 'd'를 제외하고 'a'에서 5만큼 뒤에 있는 알파벳은 [c, e, f, g, h] 순서에 의해 'h'가 됩니다. 나머지 &quot;ukks&quot; 또한 위 규칙대로 바꾸면 &quot;appy&quot;가 되며 결과는 &quot;happy&quot;가 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;두 문자열&lt;span&gt;&amp;nbsp;&lt;/span&gt;s와&lt;span&gt;&amp;nbsp;&lt;/span&gt;skip, 그리고 자연수&lt;span&gt;&amp;nbsp;&lt;/span&gt;index가 매개변수로 주어질 때 위 규칙대로&lt;span&gt;&amp;nbsp;&lt;/span&gt;s를 변환한 결과를 return하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제한사항&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #263747; color: #b2c0cc; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;5 &amp;le;&amp;nbsp;s의 길이 &amp;le; 50&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;1 &amp;le;&amp;nbsp;skip의 길이 &amp;le; 10&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;s와&amp;nbsp;skip은 알파벳 소문자로만 이루어져 있습니다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;skip에 포함되는 알파벳은&amp;nbsp;s에 포함되지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;1 &amp;le;&amp;nbsp;index&amp;nbsp;&amp;le; 20&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1782959474025&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    public String solution(String s, String skip, int index) {
        String answer = &quot;&quot;;
    
        for(int i=0;i&amp;lt;s.length();i++) {
            char c = s.charAt(i);
            
            int count = 0;
            
            while(count &amp;lt; index) {
                c++;
                
                if(c&amp;gt; 'z') { // 필수
                    c='a';
                }
                
                if(skip.indexOf(c)==-1) {
                    count++;
                }
            }
            
            answer +=c;
       
        }
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>프로그래머스/Lv.1</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/65</guid>
      <comments>https://gaeran.tistory.com/65#entry65comment</comments>
      <pubDate>Thu, 2 Jul 2026 11:34:45 +0900</pubDate>
    </item>
    <item>
      <title>빅데이터분석기사 12회 필기 합격</title>
      <link>https://gaeran.tistory.com/64</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;002&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/002.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/002.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이전 게시글에 &lt;u&gt;빅데이터분석기사&lt;/u&gt; 필기 시험 후기를 장황하게 남겼었는데요..ㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;약 2주정도 기다린 끝에 결과를 확인할 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d;&quot;&gt;(2주나 걸린다니.. 똥줄 타느라 혼났습니다ㅜㅜ)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4sPoD/dJMcabjQzeQ/AnFGFEp8NETKd6cQcTzlm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4sPoD/dJMcabjQzeQ/AnFGFEp8NETKd6cQcTzlm1/img.png&quot; data-alt=&quot;합격!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4sPoD/dJMcabjQzeQ/AnFGFEp8NETKd6cQcTzlm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4sPoD%2FdJMcabjQzeQ%2FAnFGFEp8NETKd6cQcTzlm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;262&quot; data-origin-width=&quot;503&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;합격!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;필기 시험은 만만하게 생각하고 있었는데, 지난번에 남긴 후기처럼 만만치 않았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;점수 확인할 때도 손이 벌벌 떨렸어요 ㅋㅋ 솔직히 간신히 합격한 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;제일 자신없던 과목이 1, 3 과목이었는데 역시나 3과목은 간당간당 붙은 것 같고&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1과목은 의외로? 꽤나 잘했더라구요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 12회부터 유형이 많이 달라진 건지, 13회도 이런식일런지 ..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;장담을 할 수 없어 조심스럽지만 !&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;개인적으로 &lt;b&gt;문제집에 나온 이론보다 더 깊게 문제가 출제&lt;/b&gt;된다고 느꼈으며, 응용/계산 문제도 많이 나옵니다 !&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가볍게 개념만 파악한다거나, 느낌만 알고서 시험 보면 저처럼 &quot;이게 뭐임?&quot; 하실 거에요..&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;심지어 1과목 조차 이론을 달달 외우고 갔는데 단순 암기로는 한계가 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #9d9d9d;&quot;&gt;ex) 분석 방법론 순서만 외우고 갔는데 .. 시험에서는 단단히 꼬아서 출제함&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그리고 &lt;b&gt;빅데이터분석 / 인공지능 관련 최신 기술 동향&lt;/b&gt;에 관한 문제가 꽤 깊게 나옵니다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;특히 12회에서는 LLM, 자연어 처리에 쓰이는 모델이나 알고리즘 (트랜스포머/어텐션 등등)에 대해서도 나오는 거 보니&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이름 좀 날린다 하는 모델들은 자세히 짚고 넘어가시는 게 좋을 듯 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래도 &lt;b&gt;기출 문제&lt;/b&gt;를 많이 풀다보면 자주 출제되는 문제 유형을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그런 문제들이라도 꽉 잡고 가시고, 빈출 유형은 시험에서도 꼭 출제되니 시험 전날 한번 더 복기하시길 !!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자세한 공부 방법은 이전 게시글에 다 써놨으니 참고하시면 좋을 듯 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #dddddd;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gaeran.tistory.com/63&quot;&gt;https://gaeran.tistory.com/63&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막으로, 제가 대학교 4학년 때 정처기 공부할 때는 &lt;b&gt;전공 지식 + 단기 암기&lt;/b&gt; 로 필기, 실기 다 커버했는데&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;빅분기는 필기조차도 벼락치기로 승부보기 어려운 것 같아요.&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;044&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/niniz/large/044.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/niniz/large/044.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;게다가 1년에 2번 보는 시험이라니..&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;텀이 너무 길어 투자해야하는 시간이 곱절로 커지는 기분입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래도 기출 문제 많이 푸시고 헷갈리는 것들 종이에 다시 정리하면서 개념 빡 잡고가면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;거뜬히 합격한다고 봅니다. 제가 그렇게 합격했습니다 !! (겨우)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;challenge&quot; data-emoticon-name=&quot;005&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/005.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/axz_keditor/emoticon/challenge/large/005.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;실기 준비하시는 분들 모두 화이팅하시고&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;13회 준비하시는 분들도 화이팅하세요~!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자격증/빅데이터분석</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/64</guid>
      <comments>https://gaeran.tistory.com/64#entry64comment</comments>
      <pubDate>Thu, 7 May 2026 16:49:54 +0900</pubDate>
    </item>
    <item>
      <title>[12회] 빅데이터분석기사 필기 시험 후기</title>
      <link>https://gaeran.tistory.com/63</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 4월 4일에 시행했던 빅데이터 분석 기사 필기 12회 시험을 리뷰해보도록 하겠습니다 !!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 저는 컴퓨터공학과를 졸업했고, 학부 연구생으로 1년간 AI를 공부하여 데이터 분석에도 살짝 걸친 정도의 레벨입니다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;lt;시험 준비&amp;gt;&lt;/b&gt;&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nVKEK/dJMcaiQlHEK/zdyIwgrSnUZGNWkYZCRbZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nVKEK/dJMcaiQlHEK/zdyIwgrSnUZGNWkYZCRbZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nVKEK/dJMcaiQlHEK/zdyIwgrSnUZGNWkYZCRbZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVKEK%2FdJMcaiQlHEK%2FzdyIwgrSnUZGNWkYZCRbZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;442&quot; height=&quot;155&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시험일은 4월 4일이었으며, 저는 3월 초에 시험공부를 시작해 약 한달 정도 공부했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 면접 등의 일정 때문에 매일, 매시간 공부한 것은 아니었어서 진지하게 집중했던 시간은 &lt;b&gt;보름~&lt;/b&gt;&amp;nbsp;정도.. 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qh2rZ/dJMcaciloT7/sY6z7ZBxpL4DaG636oJuk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qh2rZ/dJMcaciloT7/sY6z7ZBxpL4DaG636oJuk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qh2rZ/dJMcaciloT7/sY6z7ZBxpL4DaG636oJuk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqh2rZ%2FdJMcaciloT7%2FsY6z7ZBxpL4DaG636oJuk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1274&quot; height=&quot;385&quot; data-origin-width=&quot;1274&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 문제집은 &lt;u&gt;이기적 빅데이터분석기사 필기 기본서&lt;/u&gt; 입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교보문고에서 찾아본 바 후기가 가장 많아 구매하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 전공자라 개념을 익히는 데 큰 어려움은 없었지만, 비전공자라면 어느 정도 강의가 필요할 수도 있을 듯 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동영상 강의는 유튜브 영상으로, 개념에 깊이 파고 든다기 보다는 감을 익히는 정도의 강의인 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이기적 기본서를 4-5회 정도 정독하면서, 전체적인 흐름 안에 상세한 절차들을 이해하려고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수학적 지식이 많이 요구되는 2,3 과목의 경우에도 단순히 공식을 암기하기 보다는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 이런 분포가 만들어지는지, 왜 이런 검정을 써야하는지 등 상세한 개념들을 검색하고 찾아보면서 추가로 공부를 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 문제집을 푸시는 분도 계신 것 같은데, 저는 기본서 한 권만 공부했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제집 내에 지난 시험 기출문제와 실전 모의고사 편이 들어있어서 별도의 기출 문제집을 사서 풀진 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시험 막판에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cbt.youngjin.com/exam/index.php?no=69&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cbt.youngjin.com/exam/index.php?no=69&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775354533239&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;이기적 CBT, 영진닷컴&quot; data-og-description=&quot;빅데이터분석 기사 필기 답안 표기란 과목명 문항수 합격점수 1 과목 빅데이터 분석기획 20개 60점 2 과목 빅데이터 탐색 20개 60점 3 과목 빅데이터 모델링 20개 60점 4 과목 빅데이터 결과 해석 20개&quot; data-og-host=&quot;cbt.youngjin.com&quot; data-og-source-url=&quot;https://cbt.youngjin.com/exam/index.php?no=69&quot; data-og-url=&quot;https://cbt.youngjin.com/exam/index.php?no=69&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cbt.youngjin.com/exam/index.php?no=69&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cbt.youngjin.com/exam/index.php?no=69&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;이기적 CBT, 영진닷컴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;빅데이터분석 기사 필기 답안 표기란 과목명 문항수 합격점수 1 과목 빅데이터 분석기획 20개 60점 2 과목 빅데이터 탐색 20개 60점 3 과목 빅데이터 모델링 20개 60점 4 과목 빅데이터 결과 해석 20개&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cbt.youngjin.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 cbt 링크로 2-3번 기출 문제를 더 풀이하였습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;&amp;lt;시험 후기&amp;gt;&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;준비물&lt;br /&gt;&lt;/b&gt;신분증, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;검은 볼펜 혹은 싸인펜&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 시험장이 그런 건지는 모르겠는데 저희 시험장은 &lt;b&gt;화이트 사용 불가&lt;/b&gt;로, 답안지 자체를 바꿨어야 했습니다!&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l0xBA/dJMcacilqxy/0wCnJ4pDdOGFueWGEzjJq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l0xBA/dJMcacilqxy/0wCnJ4pDdOGFueWGEzjJq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l0xBA/dJMcacilqxy/0wCnJ4pDdOGFueWGEzjJq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl0xBA%2FdJMcacilqxy%2F0wCnJ4pDdOGFueWGEzjJq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;516&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수험표는 감독위원이 검사하진 않았지만, 수험번호 참고용으로 출력해갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mIWpF/dJMcaariGz7/aVqjkNoYSdpyqcpTxyXJt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mIWpF/dJMcaariGz7/aVqjkNoYSdpyqcpTxyXJt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mIWpF/dJMcaariGz7/aVqjkNoYSdpyqcpTxyXJt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmIWpF%2FdJMcaariGz7%2FaVqjkNoYSdpyqcpTxyXJt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1012&quot; height=&quot;448&quot; data-origin-width=&quot;1012&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 저는 &lt;b&gt;기출 문제&lt;/b&gt;를 풀면서도 4과목 중에 항상 1과목에서 오답률이 높았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 정보량이 많은데다 단순 암기 파트라 제 기억력이 안 따라 준 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 3, 4 과목은 많이 틀리면 2-3문제 틀리는 정도로 꽤나 탄탄하게 준비해 갔답니다 .. !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqlbt4/dJMcaaEQJkI/9LOMSW8ROKFPTghQbOC5J0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqlbt4/dJMcaaEQJkI/9LOMSW8ROKFPTghQbOC5J0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqlbt4/dJMcaaEQJkI/9LOMSW8ROKFPTghQbOC5J0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqlbt4%2FdJMcaaEQJkI%2F9LOMSW8ROKFPTghQbOC5J0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;319&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그런데&lt;/b&gt;&lt;/span&gt; 시험을 본 순간 많이 당황했던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시험 보기 전에 &lt;i&gt;학습한 개념들을 헷갈린다던가, 기억이 안 나서 헤맬 수 있다&amp;nbsp; &lt;/i&gt;정도는 각오했는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;처음 보는 개념들&lt;/b&gt;&lt;/span&gt;이 있어 놀랐습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 절사평균&lt;span style=&quot;color: #9d9d9d;&quot;&gt;, &lt;span style=&quot;color: #000000;&quot;&gt;은닉 마르코프 모델 내 알고리즘&lt;/span&gt; 등등..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 깊게 공부하지 않아 부족했던 저의 탓도 있지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 기출 문제에 비해 출제 방식이 사뭇 달랐던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 이전에는 연관성 분석 문제를 계산 문제로 나오더니 12회에서는 &lt;b&gt;발 없는 말이 천리 간다&lt;/b&gt;.. &amp;lt;&amp;lt; ?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로 나오더라구요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이기적 문제집에 출제된 기출 문제 중 비슷한 문제들이 꽤나 시험에 출제되기도 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이거 풀었던 문제 같은데? 어디서 많이 본 건데? 하던 문제들이 보였습니다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼, 저의 시험 리뷰는 이러하며, 다른 분들의 후기도 궁금합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나름 준비를 잘 했다고 생각했는데 쉽지 않았던 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찍은 문제가 많아, 합격할 수 있을런지 모르겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;i&gt;합격하게 된다면 합격 후기와 실기 준비도 남기겠습니다!&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘도 좋은 하루 보내세요~~&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;006&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/006.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/006.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>자격증/빅데이터분석</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/63</guid>
      <comments>https://gaeran.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 5 Apr 2026 11:30:51 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security + JWT</title>
      <link>https://gaeran.tistory.com/62</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. Spring Security vs. JWT Security 차이점&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Spring Security&lt;/b&gt;와 &lt;b&gt;JWT Security&lt;/b&gt;는 서로 대체 관계가 아니라, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JWT가 Spring Security의 인증(Authentication) 방식 중 하나로 사용될 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;목적&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애플리케이션 전반적인 &lt;b&gt;인증(Authentication)&lt;/b&gt;과 &lt;b&gt;권한(Authorization)&lt;/b&gt; 관리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JWT(JSON Web Token)&lt;/b&gt; 기반의 인증 방식&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;기본 인증 방식&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;세션 기반 인증 (Session, Cookie, HTTP Basic, OAuth 등)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;토큰 기반 인증 (Bearer Token 사용)&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;서버 상태&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;세션 저장소 필요 (Stateful)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;세션 없이 동작 (Stateless)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;사용 사례&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;내부 서비스, 관리자 페이지, 전통적인 웹 애플리케이션&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;REST API, 모바일 앱, 마이크로서비스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;보안 관리&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;세션/쿠키 기반 관리&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JWT 서명(Signature) 검증&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span&gt;2. &lt;/span&gt;토큰(Token)이란?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;인증 정보를 포함하는 문자열&lt;/b&gt;로, 주로 JSON Web Token(&lt;b&gt;JWT&lt;/b&gt;) 형태로 사용됨.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트가 로그인 후 토큰을 받아서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;요청마다 포함해서 전송&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;토큰을 검증하는 것만으로 인증이 가능&lt;/b&gt;하며, 세션을 저장할 필요가 없음 (&lt;b&gt;stateless)&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. JWT Security 개념 및 동작 방식&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt;JWT는 3가지 부분으로 구성&lt;/b&gt;됨 -&amp;gt; &lt;b&gt;Header.Payload.Signature&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.  // Header (알고리즘, 토큰 타입)
eyJ1c2VySWQiOiIxMjM0NTYiLCJyb2xlIjoiVVNFUiJ9.  // Payload (사용자 정보)
sfsdfIjd83jFksjf83JSdfLdsf9f8SDfdf  // Signature (서명)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Header&lt;/b&gt;&amp;nbsp;&amp;rarr; 어떤 알고리즘을 사용할지 (alg: HS256 등)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Payload&lt;/b&gt;&amp;nbsp;&amp;rarr; 사용자 ID, 권한 정보 등 (&lt;b&gt;Base64로 인코딩됨&lt;/b&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Signature&lt;/b&gt;&amp;nbsp;&amp;rarr; 변조 방지를 위한 서명 (&lt;b&gt;서버의 비밀 키로 서명됨&lt;/b&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt; &lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt; &lt;/span&gt; JWT 인증 과정&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;로그인 요청&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자가 ID/PW 입력 후 로그인 요청 (POST /login)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JWT 토큰 발급&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버가 ID/PW 검증 후 &lt;b&gt;JWT 토큰을 생성&lt;/b&gt;하여 클라이언트에 반환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;클라이언트 저장&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트(웹, 모바일)는 토큰을 &lt;b&gt;로컬 스토리지, 세션 스토리지 또는 쿠키에 저장&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;요청 시 토큰 포함&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이후 API 요청을 보낼 때, &lt;b&gt;Authorization 헤더에 토큰을 포함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Authorization: Bearer &amp;lt;JWT_TOKEN&amp;gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;서버 검증 및 처리&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버는 토큰의 서명을 검증하고, &lt;b&gt;유저 정보를 확인한 후 요청을 처리&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. JWT 생성 (JwtUtil = JwtProvider)&lt;br /&gt;2. JWT 필터 (JwtAuthenticationFilter)&lt;br /&gt;3. Spring Security 설정 (SecurityConfig)&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3-1. JwtProvider란?&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JwtProvider(혹은 JwtUtil, JwtTokenProvider)는 &lt;b&gt;JWT 토큰을 생성하고, 검증하는 역할&lt;/b&gt;을 하는 클래스이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(Spring Security에서 &lt;b&gt;JWT 기반 인증을 사용할 때 필수적인 유틸리티 클래스)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt;JwtProvider의 주요 역할&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JWT 토큰 생성&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자가 로그인하면, 사용자 정보를 포함한 &lt;b&gt;JWT 액세스 토큰을 생성&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;보통 만료 시간을 설정하고, 비밀 키(secret key)로 서명함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JWT 토큰 검증&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트가 보낸 토큰이 &lt;b&gt;유효한지 검증&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;변조 여부를 확인하고, 서명 검증을 수행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만료된 토큰인지 확인&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JWT에서 사용자 정보 추출&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;토큰에서 사용자 ID, 권한(role) 같은 정보를 가져와서 인증 객체를 생성.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;✔&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JwtProvider 예제 코드&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtProvider {

    private final String SECRET_KEY = &quot;mysecretkey&quot;; //   비밀 키 (보안 강화를 위해 .env로 관리 권장)
    private final long EXPIRATION_TIME = 1000 * 60 * 60; //   1시간 (토큰 유효 시간)

    // ✅ 1. JWT 토큰 생성
    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)  // 사용자 이름 (주로 userId 사용)
                .setIssuedAt(new Date()) // 발급 시간
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 만료 시간
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY) // 서명
                .compact();
    }

    // ✅ 2. JWT 토큰 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token); // 서명 검증
            return true;
        } catch (Exception e) {
            return false; // 유효하지 않은 토큰
        }
    }

    // ✅ 3. 토큰에서 사용자 이름(username) 추출
    public String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject(); // setSubject()에 저장한 값 가져오기
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;로그인 과정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트가 &lt;b&gt;ID/PW로 로그인 요청&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버가 사용자 인증 후 &lt;b&gt;JWT 액세스 토큰을 생성&lt;/b&gt; (JwtProvider.generateToken())하여 클라이언트에게 전달&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트는 이후 API 요청마다 Authorization: Bearer &amp;lt;JWT&amp;gt; 헤더에 &lt;b&gt;토큰을 포함&lt;/b&gt;해서 보냄&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JWT 검증 과정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트가 요청을 보낼 때 JWT를 헤더에 포함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JwtAuthenticationFilter가 &lt;b&gt;토큰을 추출&lt;/b&gt;하여 JwtProvider.validateToken()으로 검증&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;유효한 토큰이면 JwtProvider.getUsernameFromToken()으로 사용자 정보를 가져옴&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UserDetailsService를 통해 사용자 정보를 로드하고 인증을 완료&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3-2. JwtProvider와 JwtAuthenticationFilter&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Security에서 JWT를 사용할 때, &lt;b&gt;JwtAuthenticationFilter가 요청을 가로채서 인증을 수행&lt;/b&gt;하는데,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그 내부에서 JwtProvider를 이용해 &lt;b&gt;토큰을 검증하고 사용자 정보를 가져오는 것&lt;/b&gt;이 핵심 흐름이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; background-color: #dddddd;&quot;&gt;✔ &lt;b&gt;JwtAuthenticationFilter 예제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtProvider jwtProvider;
    private final UserDetailsService userDetailsService;

    public JwtAuthenticationFilter(JwtProvider jwtProvider, UserDetailsService userDetailsService) {
        this.jwtProvider = jwtProvider;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        
        String token = getTokenFromRequest(request);
        
        if (token != null &amp;amp;&amp;amp; jwtProvider.validateToken(token)) { // ✅ 토큰 검증
            String username = jwtProvider.getUsernameFromToken(token); // ✅ 토큰에서 사용자 정보 가져오기
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(auth); // ✅ 인증 완료
        }
        
        chain.doFilter(request, response); // 다음 필터로 이동
    }

    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(&quot;Authorization&quot;);
        if (bearerToken != null &amp;amp;&amp;amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
            return bearerToken.substring(7); // &quot;Bearer &quot; 제거 후 토큰만 추출
        }
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4. 전체 Spring Security + JWT 인증 흐름&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1️⃣ &lt;b&gt;로그인 요청 (UsernamePasswordAuthenticationFilter)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자가 &lt;b&gt;ID/PW로 로그인 요청&lt;/b&gt;을 보냄. (POST /login)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UsernamePasswordAuthenticationFilter가 요청을 가로채고, &lt;b&gt;UserDetailsService를 이용해 사용자 인증&lt;/b&gt; 수행.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인증이 성공하면 JwtProvider.generateToken()을 호출해 &lt;b&gt;JWT 액세스 토큰을 생성&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;생성된 토큰을 클라이언트에게 응답으로 반환. (헤더 또는 JSON 응답)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2️⃣ &lt;b&gt;JWT 기반 인증 (JwtAuthenticationFilter)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트는 이후 요청마다 &lt;b&gt;Authorization 헤더에 JWT 포함&lt;/b&gt;하여 보냄.&lt;/span&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Authorization: Bearer &amp;lt;JWT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JwtAuthenticationFilter가 요청을 가로채고, &lt;b&gt;헤더에서 JWT를 추출&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JwtProvider.validateToken()을 호출하여 &lt;b&gt;토큰의 유효성을 검증&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JwtProvider.getUsernameFromToken()을 호출하여 &lt;b&gt;사용자 정보(ID)를 추출&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UserDetailsService.loadUserByUsername()을 호출하여 &lt;b&gt;사용자 정보 조회&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인증이 완료되면 SecurityContextHolder에 인증 정보를 저장하고, &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;해당 요청을 계속 진행하도록 허용&lt;/b&gt;.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1️⃣ 로그인 요청 &amp;rarr; UsernamePasswordAuthenticationFilter &amp;rarr; 인증 성공 &amp;rarr; JwtProvider로 JWT 생성&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2️⃣ 클라이언트가 JWT 포함하여 요청 &amp;rarr; JwtAuthenticationFilter가 검증 &amp;rarr; 인증된 요청 처리&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/62</guid>
      <comments>https://gaeran.tistory.com/62#entry62comment</comments>
      <pubDate>Fri, 21 Feb 2025 16:58:21 +0900</pubDate>
    </item>
    <item>
      <title>Ports and Adapter 패턴 (Hexagonal)</title>
      <link>https://gaeran.tistory.com/61</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. 개요&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #ee2323;&quot;&gt;&lt;b&gt;Ports &amp;amp; Adapter 아키텍처란?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;: 비즈니스 로직을 외부 시스템(웹, DB, 메시지 큐 등)과 독립적으로 유지하기 위한 아키텍처 패턴&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심은 &lt;b&gt;어플리케이션의 중심에 핵심 비즈니스 로직을 두고, 외부 시스템과의 연결을 유연하게 한다는 것&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #ee2323;&quot;&gt;&lt;b&gt;왜 Ports &amp;amp; Adapter 아키텍처를 사용하나?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;: 전통적인 레이어드 아키텍처(Presentation - Domain - Persistence)의 문제점을 해결&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;레이어드 아키텍처의 의존성 방향은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;Controller -&amp;gt; Service -&amp;gt; Repository -&amp;gt; DataBase&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 구조에서 Service는 Repository에 강하게 의존하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, Service는 특정 구현체(JPA, JDBC, MongoDB 등)와 직접적으로 결합되어버린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;   &lt;b&gt;문제점 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;만약 JPA에서 MongoDB로 변경하려면?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;-&amp;gt; Service 내부 코드를 변경해야 함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;테스트하려고 하는데 DB가 필요하다면?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;-&amp;gt; Repository를 Mock으로 대체하기 어려움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;비즈니스 로직(Service)이 영속성 기술을 알아야 함&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;-&amp;gt; Repository가 특정 ORM/JDBC 기술에 종속되면서 유지보수가 어려워짐&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt; Ports &amp;amp; Adapter 적용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;비즈니스 로직과 외부 기술을 분리 &amp;rarr; DB, API, UI 변경에도 핵심 로직 영향 없음&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;테스트가 용이함 &amp;rarr; 가짜 Out-Port 구현(Mock Repository)으로 유닛 테스트 가능&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;확장성이 높음 &amp;rarr; JPA 대신 NoSQL, API 호출로 쉽게 변경 가능 &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 주요 개념&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; ✅&lt;b&gt; In-Port &amp;amp; Out-Port (포트) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;In-Port(입력 포트)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 애플리케이션 핵심 로직을 호출하기 위한 인터페이스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Controller, CLI,&amp;nbsp; 메시지 큐 등이 In-Port를 호출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Out-Port(출력 포트)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 애플리케이션이 외부 시스템(DB, API 등)에 접근할 때 사용하는 인터페이스&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Out-Port는 실제 구현을 모르고, 단지 &quot;이런 동작이 필요하다&quot;는 인터페이스만 정의(In-Port도 동일)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Repository, API Client 등이 Out-Port의 구현체가 됨&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; ✅&lt;b&gt; Adapter (어댑터) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Inbound Adapter(입력 어댑터)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자의 요청을 In-Port로 전달하는 역할 즉, In-Port의 구현체&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) Controller(Spring @RestController), CLI, 이벤트 리스너 등&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Outbound Adapter(출력 어댑터)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Out-Port를 구현해서 실제 외부 시스템과 통신하는 역할, Out-Port의 구현체&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;ex) JPA Repsitory, API 호출용 HTTP Client 등&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. 예제 코드로 이해&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;   &lt;b&gt;&quot;사용자가 회원 정보를 조회&quot;하는 기능을 Ports &amp;amp; Adapter 아키텍처로 구현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3.1) In-Port 정의&lt;/b&gt;(어플리케이션이 제공하는 기능을 정의하는 인터페이스)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739788358682&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface GetUserUseCase {
	UserResponse getUserById(Long userId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GetUserUseCase는 어플리케이션에서 제공하는 &quot;회원 조회&quot;기능을 정의한 인터페이스이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Controller 입장에서는 이 인터페이스를 호출하고, 내부 로직은 몰라도 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3.2) Out-Port 정의&lt;/b&gt;(외부 시스템과의 연결을 위한 인터페이스)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739788460184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface UserRepository {
	Optional&amp;lt;User&amp;gt; findById(Long userId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UserRepository는 DB에서 사용자 정보를 가져오는 역할이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실체 구현은 모르고, 단순히 findById를 호출하면 유저 데이터를 가져올 거야 라고만 인지한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;이제 인터페이스를 구현했으니 실제 서비스를 구현해보자.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3.3) In-Port 구현 &lt;/b&gt;(Service)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739788541173&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class GetUserService implements GetUserUseCase {
    private final UserRepository userRepository;

    public GetUserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserResponse getUserById(Long userId) {
        User user = userRepository.findById(userId)
                                  .orElseThrow(() -&amp;gt; new UserNotFoundException(userId));
        return new UserResponse(user);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GetUserService는 In-Port(GetUserUseCase)의 구현체&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UserRepository(Out-Port)를 사용하여 데이터를 가져온다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;핵심 비즈니스 로직이 이 서비스에 구현된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3.4) Out-Port 구현 &lt;/b&gt;(JPA Adapter)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739788682054&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public class UserJpaRepositoryAdapter implements UserRepository {
    private final JpaUserRepository jpaRepository;

    public UserJpaRepositoryAdapter(JpaUserRepository jpaRepository) {
        this.jpaRepository = jpaRepository;
    }

    @Override
    public Optional&amp;lt;User&amp;gt; findById(Long userId) {
        return jpaRepository.findById(userId);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UserRepository(Out-Port)를 실제 JPA Repository로 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어플리케이션 핵심 로직은 JPA의 존재를 몰라도 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3.5) Inbound Adapter &lt;/b&gt;(Controller - 외부 요청을 받아 In-Port 호출)&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739788746933&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/users&quot;)
public class UserController {
    private final GetUserUseCase getUserUseCase;

    public UserController(GetUserUseCase getUserUseCase) {
        this.getUserUseCase = getUserUseCase;
    }

    @GetMapping(&quot;/{userId}&quot;)
    public ResponseEntity&amp;lt;UserResponse&amp;gt; getUser(@PathVariable Long userId) {
        return ResponseEntity.ok(getUserUseCase.getUserById(userId));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;UserController는 Inbound Adapter이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;GetUserUseCase를 호출해서 비즈니스 로직을 수행하게 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제 DB에 어떻게 접근하는지는 신경쓰지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Outbound Adapter는 Out-Port를 구현하여 실제 외부 시스템과 연결하는 것으로 예) UserJpaRepsitoryAdapter를 의미&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;전체 구조 정리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1739788817850&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;┌────────────────────────────────┐
│        Web / API (Controller)  │  &amp;rarr;  Inbound Adapter
└────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────┐
│  Application (UseCase, Service)│  &amp;rarr;  In-Port 구현체
└────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────┐
│     Persistence (Repository)   │  &amp;rarr;  Out-Port 인터페이스
└────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────┐
│        Database (JPA, SQL)      │  &amp;rarr;  Outbound Adapter
└────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;그러나 단점,&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 코드량 증가 (더 많은 클래스 &amp;amp; 인터페이스)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 구현의 복잡성 증가 -&amp;gt; &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존에는 userRepository.findById(id) 이렇게 &lt;b&gt;바로 호출&lt;/b&gt;하면 됐는데, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;이제는 &lt;b&gt;Port를 통해 Adapter를 거쳐야 하는 구조&lt;/b&gt;가 됨. &lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/61</guid>
      <comments>https://gaeran.tistory.com/61#entry61comment</comments>
      <pubDate>Mon, 17 Feb 2025 20:04:45 +0900</pubDate>
    </item>
    <item>
      <title>[Web 게시판] MultipartFile로 게시판 첨부파일 업로드 | MultipartFile</title>
      <link>https://gaeran.tistory.com/60</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;   1. Multipart란?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Multipart는 웹 요청에서 &lt;b&gt;여러 부분(part)&lt;/b&gt;을 포함하는 데이터 형식으로 하나의 요청에 여러 개의 데이터를 포함시킬 수 있는 방식입니다. (&lt;b&gt;텍스트 데이터 (text/plain, application/json) +&amp;nbsp;&lt;/b&gt;&lt;b&gt;파일 데이터 (image/png, application/pdf 등)&lt;/b&gt; &lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;보통 파일 업로드와 같이 여러 가지 정보를 함께 전송할 때 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;HTTP에서 multipart/form-data는 파일 업로드를 지원하는 특수한 content-type입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 방식은 폼데이터와 파일을 한 번에 서버로 보내는 데 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 방식으로 텍스트 데이터(json)와 파일 데이터를 각각 분리해서 처리할 수 있도록 해주는 것입니다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;   2. MultipartFile은?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MultipartFile은 Spring에서 제공하는 인터페이스로, 클라이언트로부터 전송된 파일을 다루기 위해 사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MultipartFile 객체는 &lt;b&gt;파일의 내용, 이름, 크기, MIME 타입&lt;/b&gt; 등을 다룰 수 있는 메서드를 제공해줍니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring MVC에서 @RequestParam 또는 @RequestPart와 함께 사용되어 클라이언트에서 업로드한 파일을 서버에서 받을 수 있게 됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getName(): 업로드된 파일의 이름을 반환&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getOriginalFilename(): 원본 파일명을 반환&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getSize(): 파일의 크기를 반환 (byte 단위)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getContentType(): 파일의 MIME 타입(예: image/png)을 반환&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getBytes(): 파일 내용을 바이트 배열로 반환&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getInputStream(): 파일을 읽을 수 있는 InputStream을 반환&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;   3. MultipartFile로 게시판에 첨부파일 업로드 해보기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;File 엔티티- 첨부파일 관리를 위한 엔티티와 DTO 설계(추후에 다중 첨부 파일 관리를 편하게 하려고)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Post service 수정 - 작성 시 첨부파일 첨부 기능 추가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Post 작성 controller 수정&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;(1) PostFile Entity&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;PostFile 엔티티는 파일의 메타데이터를 저장합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터베이스에서 파일 정보를 저장하는 엔티티로, 파일의 이름, 경로를 저장하도록 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(게시글의 Id는 다중 파일 첨부를 위해 외래키로 가져오도록 한 것입니다!!)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739269629771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity(name = &quot;filepost&quot;)
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PostFile {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name=&quot;post_id&quot;)
    private Post post;

    private String filename;

    private String filepath;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;(2) Post 작성 서비스 수정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;파일 업로드 처리:&lt;/b&gt;&amp;nbsp;파일을 업로드한 후, PostFile 엔티티로 저장하는 과정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;파일 삭제 처리:&lt;/b&gt;&amp;nbsp;파일이 삭제될 때, 관련된 데이터베이스의 파일 정보를 삭제하고, 서버에서 실제 파일도 제거하는 로직&lt;br /&gt;(파일 삭제는 추후 구현)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1739324291065&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
    public Long write(String nickname, PostDto.Request postDto, MultipartFile file) {

        User user = userRepository.findByNickname(nickname);
        postDto.setUser(user);
        log.info(&quot;write post 실행&quot;);
        Post post = postDto.toEntity();
        postRepository.save(post);


        if(file!=null &amp;amp;&amp;amp; !file.isEmpty()) {
            try {
                //파일 저장 경로
                String uploadDir = System.getProperty(&quot;user.dir&quot;) + &quot;/src/main/resources/static/files/&quot;;
                String filePath = uploadDir + file.getOriginalFilename();

                File dest = new File(filePath);
                file.transferTo(dest);

                PostFile postFile = PostFile.builder()
                        .post(post)
                        .filename(file.getOriginalFilename())
                        .filepath(filePath)
                        .build();

                postFileRepository.save(postFile);
            }catch (IOException e) {
                log.error(&quot;파일 저장 오류 발생&quot;, e);
                throw new RuntimeException(&quot;파일 저장 실패&quot;);

            }
        }

        return post.getId();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;사실 파일 저장 경로를 저렇게 하드코딩하지 않는 것이 좋겠지만, 흐름을 파악하기 위해 따로 남겨두었습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;if문부터 참고하면, Post 작성 시 첨부파일이 첨부되지 않아도 Post 저장은 되어야 하므로&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제 파일이 업로드된 경우에만 파일 저장을 진행하도록 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739324744746&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;File dest = new File(filePath);
file.transferTo(dest);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;위 코드는 파일을 저장할 경로와 파일명을 지정한 File 객체를 생성하고 업로드된 파일을 지정한 경로로 저장하는 부분입니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗그러나, file.getOriginalFilename()을 그대로 사용하면 같은 이름의 파일이 업로드될 경우 덮어쓰기 문제가 발생할 수 있습니다. -&amp;gt; UUID를 추가하여 파일명을 변경하는 것이 더 안전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1739324860582&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PostFile postFile = PostFile.builder()
        .post(post)
        .filename(file.getOriginalFilename())
        .filepath(filePath)
        .build();

postFileRepository.save(postFile);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;파일 정보를 저장할 PostFile 엔티티를 생성하여 파일 정보를 DB에 저장합니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;(4) Post 작성 Controller 수정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&amp;gt; 이전 RestController&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739323111357&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/api/post/writePro&quot;)
public ResponseEntity&amp;lt;Long&amp;gt; write(
        @RequestBody @Valid PostDto.Request postDto, 
        @LoginUser UserDto.Response userDto) {

    Long postId = postService.write(userDto.getNickname(), postDto);
    return ResponseEntity.ok(postId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이전에는 Post 작성 폼에서 받은 입력 데이터를 json으로 변환하기 위해 @RequestBody로 PostDto.Request를 받도록 설계했지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;MultipartFile 데이터를 받기 위해서는 @RequestBody가 아닌 &lt;b&gt;multipart/form-data로 &lt;/b&gt;받아야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&amp;gt; MultipartFile을 받도록 수정한 RestController&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@PostMapping(value = &quot;/api/post/writePro&quot;, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity&amp;lt;Long&amp;gt; write(
        @RequestPart(&quot;post&quot;) @Valid PostDto.Request postDto, 
        @RequestPart(value = &quot;file&quot;, required = false) MultipartFile file,
        @LoginUser UserDto.Response userDto) {

    Long postId = postService.write(userDto.getNickname(), postDto, file);
    return ResponseEntity.ok(postId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;@RequestPart&lt;/b&gt;&lt;/span&gt; : multipart/form-data 요청에서 JSON 데이터(post 필드)와 파일(file 필드)을 함께 받을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;(파일이 없을 수도 있으므로 &lt;b&gt;required = false&lt;/b&gt; 설정하여&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;파일 첨부 없이도 글 작성 가능&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;하게 만듦)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a title=&quot;@RequestPart 알아보기&quot; href=&quot;https://gaeran.tistory.com/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://gaeran.tistory.com/59&lt;/a&gt;&lt;/span&gt;&lt;/u&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739325439842&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;@RequestPart 알아보기&quot; data-og-description=&quot; 1.&amp;nbsp; @RequestPart란?@RequestPart는 멀티파트 요청 (multipart request) 에서 특정 파일이나 JSON 데이터를 개별적으로 매핑할 때 사용되는 Spring MVC의 어노테이션입니다.2. @RequestParam vs @RequestBody vs @RequestPart&quot; data-og-host=&quot;gaeran.tistory.com&quot; data-og-source-url=&quot;https://gaeran.tistory.com/59&quot; data-og-url=&quot;https://gaeran.tistory.com/59&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iLRid/hyYfQjvdwR/iAVYYCL7ubxs29lGwc8kEk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/baUmIV/hyYf4B2iOJ/5q2gkimKJSF5VBFKUBHR2K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/5LeTk/hyYfXbRp8R/VFzYGg9kkLk9bbtCuWwXq1/img.jpg?width=1056&amp;amp;height=1120&amp;amp;face=0_0_1056_1120&quot;&gt;&lt;a href=&quot;https://gaeran.tistory.com/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gaeran.tistory.com/59&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iLRid/hyYfQjvdwR/iAVYYCL7ubxs29lGwc8kEk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/baUmIV/hyYf4B2iOJ/5q2gkimKJSF5VBFKUBHR2K/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/5LeTk/hyYfXbRp8R/VFzYGg9kkLk9bbtCuWwXq1/img.jpg?width=1056&amp;amp;height=1120&amp;amp;face=0_0_1056_1120');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;@RequestPart 알아보기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt; 1.&amp;nbsp; @RequestPart란?@RequestPart는 멀티파트 요청 (multipart request) 에서 특정 파일이나 JSON 데이터를 개별적으로 매핑할 때 사용되는 Spring MVC의 어노테이션입니다.2. @RequestParam vs @RequestBody vs @RequestPart&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gaeran.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt;프론트엔드 요청 예시&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;POST /api/post/writePro
Content-Type: multipart/form-data

------WebKitFormBoundary
Content-Disposition: form-data; name=&quot;post&quot;
Content-Type: application/json

{
  &quot;title&quot;: &quot;게시글 제목&quot;,
  &quot;content&quot;: &quot;게시글 내용&quot;,
  &quot;category&quot;: &quot;공지&quot;,
  &quot;writer&quot;: &quot;사용자1&quot;
}

------WebKitFormBoundary
Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;example.jpg&quot;
Content-Type: image/jpeg

(바이너리 파일 데이터)
------WebKitFormBoundary--
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/웹 게시판</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/60</guid>
      <comments>https://gaeran.tistory.com/60#entry60comment</comments>
      <pubDate>Wed, 12 Feb 2025 13:42:12 +0900</pubDate>
    </item>
    <item>
      <title>@RequestPart 알아보기</title>
      <link>https://gaeran.tistory.com/59</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 1.&amp;nbsp; @RequestPart란?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestPart는 &lt;b&gt;멀티파트 요청 (multipart request)&lt;/b&gt; 에서 특정 &lt;b&gt;파일&lt;/b&gt;이나 &lt;b&gt;JSON 데이터&lt;/b&gt;를 개별적으로 매핑할 때 사용되는 Spring MVC의 어노테이션입니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. @RequestParam vs @RequestBody vs @RequestPart 차이점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어노테이션 사용 대상 주요 목적 Content-Type&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 107.907%; height: 162px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestParam&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순 파라미터 (쿼리 스트링, 폼 데이터)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기본 데이터 타입 (String, int 등) 전송&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestBody&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JSON 요청 본문&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;객체로 변환 (DTO 등)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;application/json&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestPart&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;멀티파트 요청 (파일 + JSON)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파일과 JSON 데이터를 함께 받기&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;multipart/form-data&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. @RequestPart 사용 예제&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt;파일과 JSON 데이터를 동시에 받는 경우&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/posts&quot;)
public class PostController {

    @PostMapping(&quot;/write&quot;)
    public ResponseEntity&amp;lt;String&amp;gt; writePost(
            @RequestPart(&quot;postDto&quot;) PostDto postDto, // JSON 데이터
            @RequestPart(value = &quot;file&quot;, required = false) MultipartFile file // 파일 (선택)
    ) {
        log.info(&quot;게시글 작성: {}&quot;, postDto);
        
        if (file != null) {
            log.info(&quot;첨부된 파일: {}&quot;, file.getOriginalFilename());
        }

        return ResponseEntity.ok(&quot;게시글 작성 완료!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;요청 예시 (Postman)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;multipart/form-data로 요청&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;POST /posts/write
Content-Type: multipart/form-data

postDto: {&quot;title&quot;: &quot;Hello&quot;, &quot;content&quot;: &quot;This is a post&quot;}
file: (image.jpg 첨부)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt;Spring은 postDto를 JSON으로 변환하고, file을 MultipartFile로 처리&lt;/b&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4. @RequestPart가 필요한 이유&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@RequestBody로 JSON과 파일을 동시에 받을 수 없는 문제&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@PostMapping(&quot;/write&quot;)
public ResponseEntity&amp;lt;String&amp;gt; writePost(@RequestBody PostDto postDto, MultipartFile file) {
    // ❌ 실행되지 않음: @RequestBody는 JSON 요청만 처리할 수 있음
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestBody는 Content-Type: application/json을 기대하기 때문에 multipart/form-data를 처리하지 못함&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;파일과 JSON을 동시에 받으려면 @RequestPart를 사용해야 함&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- @RequestPart는 multipart/form-data 요청에서 &lt;b&gt;파일과 JSON 데이터를 함께 처리할 때 사용&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- @RequestParam과 달리 JSON 데이터를 DTO로 변환 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- @RequestBody는 application/json에서만 동작하므로 멀티파트 요청과 함께 사용할 수 없음&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- @RequestPart(value = &quot;file&quot;, required = false)로 파일을 선택적으로 받을 수도 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;nbsp;&lt;b&gt;즉, @RequestPart는 파일과 JSON을 동시에 받아야 할 때 가장 적절한 방법!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/59</guid>
      <comments>https://gaeran.tistory.com/59#entry59comment</comments>
      <pubDate>Wed, 12 Feb 2025 10:56:46 +0900</pubDate>
    </item>
    <item>
      <title>@RequestBody 와 &amp;lt;form&amp;gt; 태그</title>
      <link>https://gaeran.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;  1.&amp;nbsp; HTML &amp;lt;form&amp;gt; 태그의 기본 동작&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;HTML에서 &amp;lt;form&amp;gt; 태그를 사용하면 기본적으로 다음 두 가지 방식으로 데이터를 전송할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;application/x-www-form-urlencoded (기본값, POST 요청)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;키-값 쌍을 key1=value1&amp;amp;key2=value2 형태로 인코딩해서 전송&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;브라우저에서 자동으로 데이터를 URL 인코딩해서 보냄&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JSON이 아니라, @RequestParam이나 @ModelAttribute로 받아야 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;multipart/form-data (파일 업로드 시)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파일을 포함한 데이터를 보낼 때 사용&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JSON 데이터를 포함하지 않음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;  application/json 방식이 아님!&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;lt;form&amp;gt; 태그는 기본적으로 JSON 형식(application/json)을 지원하지 않는다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서, 폼 태그를 이용하면 &lt;b&gt;Spring은 JSON을 받을 준비? 따위 하지 않음&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;  2.&amp;nbsp; 실제 요청 방식 비교&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;❌ &amp;lt;form&amp;gt; 태그 기본 요청 (application/x-www-form-urlencoded)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/api/post/1&quot; method=&quot;POST&quot;&amp;gt;
  &amp;lt;input type=&quot;text&quot; name=&quot;title&quot; value=&quot;새로운 제목&quot;&amp;gt;
  &amp;lt;textarea name=&quot;content&quot;&amp;gt;새로운 내용&amp;lt;/textarea&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;수정&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;전송되는 데이터 (Request Body)&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;title=새로운+제목&amp;amp;content=새로운+내용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이렇게 전송되면, &lt;b&gt;Spring이 @RequestBody가 아닌 @RequestParam이나 @ModelAttribute로만 받을 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, JSON 형식이 아니라 단순한 키-값 쌍으로 넘어가는 거임.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;✅ fetch()를 사용한 JSON 요청 (application/json)&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;fetch(&quot;/api/post/1&quot;, {
    method: &quot;POST&quot;,
    headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
    body: JSON.stringify({
        title: &quot;새로운 제목&quot;,
        content: &quot;새로운 내용&quot;
    })
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;전송되는 데이터 (Request Body)&lt;/b&gt;:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;title&quot;: &quot;새로운 제목&quot;,
  &quot;content&quot;: &quot;새로운 내용&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 경우, Spring이 @RequestBody PostDto.Request postDto로 JSON을 받아서 처리할 수 있음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;  3.&amp;nbsp; 폼 태그로 JSON을 보낼 수 없을까?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;폼 태그로 JSON을 보내려면, 기본 Content-Type을 application/json으로 변경해야 한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 &amp;lt;form&amp;gt; 태그만으로는 이를 지정할 방법이 없음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;  &lt;b&gt;해결 방법&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;JavaScript fetch()를 사용해서 JSON으로 변환 후 전송&lt;/b&gt; (내가 주구장창 하고 있는 거)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;lt;form&amp;gt; 태그를 쓰고 싶다면 method=&quot;dialog&quot; 같은 커스텀 폼 처리 후 JS에서 변환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Hidden Input 필드를 JSON 문자열로 만들어서 전송 후, 컨트롤러에서 파싱&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 @RequestBody가 아니라 @RequestParam으로 받게 됨.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;lt;form&amp;gt; 태그에 onsubmit=&quot;return false;&quot; 추가 후 fetch() 실행&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;  4.&amp;nbsp; 그럼 &amp;lt;form&amp;gt; 태그는 무조건 쿼리스트링으로만 데이터를 보내나?&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대부분의 경우 &lt;b&gt;application/x-www-form-urlencoded (키-값 쌍) 방식으로만 동작&lt;/b&gt;하지만,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;lt;form&amp;gt; 태그로 application/json을 보낼 수도 있긴 함. 다만, &lt;b&gt;브라우저 기본 동작을 막고 JS를 이용해야 함&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;-&amp;gt; 잘 안 쓰는 듯.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;  결론&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&amp;lt;form&amp;gt; 태그는 기본적으로 JSON을 지원하지 않고, application/x-www-form-urlencoded 방식으로 보냄.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@RequestBody는 JSON을 받도록 설계되어 있어서, &amp;lt;form&amp;gt; 태그를 그대로 사용하면 매칭되지 않음.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Spring</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/56</guid>
      <comments>https://gaeran.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 5 Feb 2025 13:05:25 +0900</pubDate>
    </item>
    <item>
      <title>Spring Pageable 안 쓰면 바보</title>
      <link>https://gaeran.tistory.com/55</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. 개요&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약 Post(게시글)이 1,000개 존재한다고 하면, 이 1,000개의 데이터를 한 번에 가져올 시&amp;nbsp; 부하가 심하게 걸리게 됩니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면, 데이터를 한 번에 모두 가져오지 않고, 특정 크키만큼 데이터를 나눠서 조회하는 방식을 사용해야겠죠? &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이것이 &lt;b&gt;페이징(Pagination)&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. Pageable&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;페이징(Pagination) 기법을 스프링에서 사용자가 직접 구현해서 사용할 수도 있지만, Spring에서 제공하는 Pageable을 사용할 수도 있겠습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;Pageable&lt;/b&gt;은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring Data JPA에서 페이징과 정렬을 쉽게 처리할 수 있도록 제공하는 인터페이스&lt;/span&gt;입니다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKe5R/btsL4aPxK9X/bbfskEEAIZkO3qeKqaOQkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKe5R/btsL4aPxK9X/bbfskEEAIZkO3qeKqaOQkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKe5R/btsL4aPxK9X/bbfskEEAIZkO3qeKqaOQkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKe5R%2FbtsL4aPxK9X%2FbbfskEEAIZkO3qeKqaOQkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1177&quot; height=&quot;747&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;PageRequest가 실제 구현체인데, PageRequest의 생성자는 pageNumber, pageSize, sort를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;pageNumber&lt;/b&gt; &lt;/span&gt;: size를 기준으로 몇 번째 페이지의 데이터를 가져올지&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;pageSize&lt;/b&gt; &lt;/span&gt;: 한 페이지에 가져올 데이터의 개수&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;sort&lt;/b&gt; &lt;/span&gt;: 특정 컬럼을 기준으로 오름차순/내림차순 정렬&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;추가로, 자동으로 COUNT 쿼리를 실행하는데, Page&amp;lt;T&amp;gt; 객체를 사용할 경우 전체 데이터 개수를 자동으로 계산합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. Pageable의 핵심 메서드&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 122px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;메서드&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getPageNumber()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;현재 페이지 번호 반환(0부터 시작)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getPageSize()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한 페이지에 포함된 데이터 개수 반환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;getSort()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정렬 정보 반환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;isPaged()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;페이징이 적용되었는지 확인&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;next()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 페이지 정보 반환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;previousOrFirst()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이전 페이지 정보 반환&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;first()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;첫 번째 페이지 정보 반환&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4. Pageable 적용&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1738312267238&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @GetMapping(&quot;/welcome&quot;)
    public String welcomePage(Model model, @PageableDefault(sort = &quot;id&quot;, direction = Sort.Direction.DESC)
                              Pageable pageable, @LoginUser UserDto.Response user) {
        Page&amp;lt;Post&amp;gt; list = postService.pageList(pageable);

        if(user!=null) {
            model.addAttribute(&quot;user&quot;,user);
        }
        model.addAttribute(&quot;posts&quot;, list);
        model.addAttribute(&quot;previous&quot;, pageable.previousOrFirst().getPageNumber());
        model.addAttribute(&quot;next&quot;, pageable.next().getPageNumber());
        model.addAttribute(&quot;hasNext&quot;, list.hasNext());
        model.addAttribute(&quot;hasPrev&quot;, list.hasPrevious());

        return &quot;main&quot;;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨트롤러 인자에 Pageble이 들어간 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@PageableDefault&lt;/b&gt;(sort=&quot;id&quot;, direction = Sort.Direction.DESC) : 기본적으로 id 컬럼을 기준으로 내림차순(DESC) 정렬&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;즉, 최신 게시글이 제일 먼저 보이도록 설정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@PageableDefault란?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Data JPA에서 페이징 요청 시 기본값을 설정할 때 사용하는 어노테이션이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용자가 요청에서 number, size, sort 파라미터를 명시하지 않았을 경우, 기본값을 적용하게 된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;size : 한 페이지당 불러올 데이터 개수 (기본값 20)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;page : 기본 페이지 번호 (0부터 시작) (기본값 0)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;sort : 정렬 기준 컬럼 (지정하지 않으면 기본 정렬 없음)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;direction : 정렬 방향 (ASC, DESC)&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고, 앞서 말한 메서드를 통해 이전/다음 페이지 정보를 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;previous : 이전 페이지 번호&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;next : 다음 페이지 번호&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;hasNext : 다음 페이지 존재 여부&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;hasPrev : 이전 페이지 존재 여부&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1738312351948&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Transactional(readOnly = true)
    public Page&amp;lt;Post&amp;gt; pageList(Pageable pageable) {
        return postRepository.findAll(pageable);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서비스에서 비즈니스 로직을 수행하면서 Spring Data Repository에 Pageable을 또 인자로 함께 넘겨줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;게시글 전체를 페이징 처리하여 가져오기 위해 Page&amp;lt;Post&amp;gt;를 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Repository&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1738312410314&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface PostRepository extends JpaRepository&amp;lt;Post, Long&amp;gt; {
    Page&amp;lt;Post&amp;gt; findByTitleContaining(String keyword, Pageable pageable);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;findAll(Pageable pageable) 에서 전체 게시글을 페이징하여 조회하도록 하며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;findByTitleContaining(String keyword, Pageable pageble) 로 특정 키워드가 포함된 제목의 게시글만 페이징하여 조회하는 추가 기능을 통해 검색 로직을 구현할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5. 페이징 요청과 응답 예시&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738312970800&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET /welcome?page=1&amp;amp;size=5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;page : 1번 페이지 (두 번째 페이지) 요청&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;size : 한 페이지당 5개의 게시글 가져오기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738313009820&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM post 
ORDER BY id DESC 
LIMIT 5 OFFSET 5;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위와 같은 SQL이 실행된다고 생각하면 쉽습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1738313049729&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;content&quot;: [
        { &quot;id&quot;: 10, &quot;title&quot;: &quot;새로운 공지사항&quot; },
        { &quot;id&quot;: 9, &quot;title&quot;: &quot;이벤트 안내&quot; },
        { &quot;id&quot;: 8, &quot;title&quot;: &quot;서비스 점검 일정&quot; },
        { &quot;id&quot;: 7, &quot;title&quot;: &quot;업데이트 내용&quot; },
        { &quot;id&quot;: 6, &quot;title&quot;: &quot;회원가입 혜택&quot; }
    ],
    &quot;totalElements&quot;: 12,
    &quot;totalPages&quot;: 3,
    &quot;number&quot;: 1,
    &quot;size&quot;: 5,
    &quot;hasNext&quot;: true,
    &quot;hasPrevious&quot;: true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우리는 위와 같은 응답 JSON 데이터로 Page&amp;lt;Post&amp;gt;을 받을 수 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;content : 현재 페이지의 게시글 목록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;totalElements : 총 12개의 게시글&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;totalPages : 전체 페이지수 3개&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;number : 현재 페이지 번호&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;hasNext : 다음 페이지가 존재&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;hasPrevious : 이전 페이지가 존재&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;   참고&lt;/b&gt;&lt;br /&gt;Pageable과 메서드 네임 기반 정렬(OrderBy)을 함께 사용하게 되면 Spring Data JPA에서는 메서드 네임의OrderBy가 우선 적용되게 됩니다. createdAt 정렬이 강제 적용되기 때문에 Pageable의 정렬 조건(Sort.by(&quot;id).descending())은 무시됩니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 1️⃣ 메서드 네임에서 OrderBy를 사용하면, Pageable의 정렬이 무시됨.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2️⃣ 메서드 네임의 OrderBy를 제거하면 Pageable의 정렬이 정상적으로 동작.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3️⃣ 메서드에서 정렬을 강제하고 싶다면 OrderBy를 유지, 동적으로 변경하고 싶다면 Pageable을 활용.&lt;/span&gt;&lt;/blockquote&gt;</description>
      <category>Spring</category>
      <author>코꿀이</author>
      <guid isPermaLink="true">https://gaeran.tistory.com/55</guid>
      <comments>https://gaeran.tistory.com/55#entry55comment</comments>
      <pubDate>Fri, 31 Jan 2025 17:56:20 +0900</pubDate>
    </item>
  </channel>
</rss>