<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>BitBard</title>
    <link>https://bitbard-dongni.tistory.com/</link>
    <description>배우고 느낀 것들, 작은 코드 한 줄부터 일상의 순간까지, 성장의 흔적들을 기록하고자 합니다.</description>
    <language>ko</language>
    <pubDate>Mon, 6 Apr 2026 11:58:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Dongni</managingEditor>
    <image>
      <title>BitBard</title>
      <url>https://tistory1.daumcdn.net/tistory/6620811/attach/4db7ebb66bfe489588843c0c4e255fca</url>
      <link>https://bitbard-dongni.tistory.com</link>
    </image>
    <item>
      <title>[Database/TIL] Flyway 파일명 컨벤션 (템플릿화)</title>
      <link>https://bitbard-dongni.tistory.com/55</link>
      <description>&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오늘의 추천 곡&lt;br /&gt;&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;div id=&quot;code_1768923740114&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;iframe width=&amp;quot;1024&amp;quot; height=&amp;quot;576&amp;quot; src=&amp;quot;https://www.youtube.com/embed/dcKXmS_RD2E?si=3wnuURS-QctD3KDY&amp;quot; title=&amp;quot;YouTube video player&amp;quot; frameborder=&amp;quot;0&amp;quot; allow=&amp;quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&amp;quot; referrerpolicy=&amp;quot;strict-origin-when-cross-origin&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/dcKXmS_RD2E?si=3wnuURS-QctD3KDY&quot; width=&quot;1024&quot; height=&quot;576&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;소스 코드와 마찬가지로 DB의 형상 관리도 중요한 화두다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;CI/CD를 준비하는 과정에서 DB로 인해 EC2가 뻗지 않게끔 하기 위한 방법을 공부해보다가 알게된 기술인 Flyway에 대해서 설명해보고자 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;아니 사실은 Flyway에 대한 설명이라기 보다는 일종의 세팅 방법을 공유해보고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&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;h2 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size26&quot;&gt;Flyway를 사용할 때 생길 수 있는 문제&lt;/h2&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;보통 처음 Flyway를 쓰면 순차적인 숫자를 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;V1__init.sql&lt;/li&gt;
&lt;li&gt;V2__add_user.sql&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;혼자 개발할 땐 문제가 없지만, 여러 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;11&quot;&gt;팀원들이 각자의 기능을 개발한다면?&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;12,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;6&quot; data-path-to-node=&quot;12,0&quot;&gt;팀원 A:&lt;/b&gt; &quot;회원 기능 때문에 V3__add_member.sql 만들어서 푸시했어요!&quot;&lt;br /&gt;&lt;b data-index-in-node=&quot;61&quot; data-path-to-node=&quot;12,0&quot;&gt;팀원 B:&lt;/b&gt; &quot;어? 저도 게시판 때문에 V3__add_post.sql 만들었는데요?&quot;&lt;br /&gt;&lt;span style=&quot;color: #333333; letter-spacing: 0px;&quot;&gt;&lt;br /&gt;= Git 충돌(Conflict) 발생&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&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;이러한 상황을 방지하고자 간단한 타임스탬프 전략을 이용해볼 수 있겠다.&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;style5&quot; /&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;타임스탬프(Timestamp) 전략&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;Flyway는 버전명(파일명)을 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;17&quot;&gt;문자열 기준으로 정렬&lt;/b&gt;해서 실행한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;따라서 시간이 흐르는 순서(오름차순)대로 파일명을 지으면 충돌 없이 자연스럽게 순서를 보장받을 수 있는 것.&lt;/p&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;따라서 기존의 'Vx' 와 같은 네이밍 대신 아래의 네이밍 컨벤션을 따르면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;포맷:&lt;/b&gt; V{년월일시분초}__{설명}.sql&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;예시:&lt;/b&gt; V20260121143000__create_member_table.sql&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;주의사항:&lt;/b&gt; V 뒤에 숫자, &lt;b&gt;그리고 언더바 2개(__)는 필수&lt;/b&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-path-to-node=&quot;21&quot; data-ke-size=&quot;size26&quot;&gt;인텔리제이로 템플릿화 시키기&lt;/h2&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;사실상 이 기술을 도입했을 때 생길 수 있는 에러는 99% &lt;b&gt;휴먼 에러&lt;/b&gt;일 것이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;기존 DB 스키마에서 &lt;b&gt;변경&lt;/b&gt;이 생겼음에도 &lt;b&gt;sql 파일을 만들지 않거나, &lt;/b&gt;만들었는데 sql 파일에 &lt;b&gt;오타가 있다거나, &lt;/b&gt;&lt;b&gt;파일 네이밍&lt;/b&gt;을 할 때 &lt;b&gt;오타를 낸다거나&lt;/b&gt;..&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;이런 에러를 방지하기 위해서는 템플릿이 있으면 좋지 않을까?&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;개발 초기에 어떤 환경들을 얼만큼 세팅하면 좋을지 계속 고민하고 있는데, 이런 사소한 것들도 신경써서 챙겨주면 팀원들의 개발 효율이 대략 1.00000123배는 증가하지 않을까?&lt;/p&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size20&quot;&gt;1. 설정 메뉴 진입&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Windows: File &amp;rarr; Settings (Ctrl+Alt+S)&lt;/li&gt;
&lt;li&gt;Mac: IntelliJ IDEA &amp;rarr; Settings (Cmd+,)&lt;/li&gt;
&lt;li&gt;이동: &lt;b data-index-in-node=&quot;4&quot; data-path-to-node=&quot;24,2,0&quot;&gt;Editor&lt;/b&gt; &amp;rarr; &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;24,2,0&quot;&gt;File and Code Templates&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;변경: 상단&lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;24,2,0&quot;&gt; Scheme&lt;/b&gt;를&lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;24,2,0&quot;&gt; Default &amp;rarr; Project&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2355&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vu9da/dJMcabprrFA/xumgVAAlnbROS9VCkWjRS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vu9da/dJMcabprrFA/xumgVAAlnbROS9VCkWjRS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vu9da/dJMcabprrFA/xumgVAAlnbROS9VCkWjRS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvu9da%2FdJMcabprrFA%2FxumgVAAlnbROS9VCkWjRS0%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;2355&quot; height=&quot;1440&quot; data-origin-width=&quot;2355&quot; data-origin-height=&quot;1440&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;h4 data-path-to-node=&quot;25&quot; data-ke-size=&quot;size20&quot;&gt;2. 템플릿 추가&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,0,0&quot;&gt;Files&lt;/b&gt; 탭에서 + 버튼 클릭&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,1,0&quot;&gt;Name:&lt;/b&gt; 원하는 이름&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,2,0&quot;&gt;Extension:&lt;/b&gt; sql&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,3,0&quot;&gt;내용(File name):&lt;/b&gt; 아래 코드 붙여넣기 (본문에 넣는 거 아님)&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjAyPOIhJqSAxUAAAAAHQAAAAAQtgk&quot; data-hveid=&quot;0&quot;&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;V${YEAR}${MONTH}${DAY}${HOUR}${MINUTE}${SECOND}__${DESCRIPTION}&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1821&quot; data-origin-height=&quot;1358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8qloF/dJMcaaRBzVk/p2X2yRDhQuQSxHhKykdQ00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8qloF/dJMcaaRBzVk/p2X2yRDhQuQSxHhKykdQ00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8qloF/dJMcaaRBzVk/p2X2yRDhQuQSxHhKykdQ00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8qloF%2FdJMcaaRBzVk%2Fp2X2yRDhQuQSxHhKykdQ00%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;1821&quot; height=&quot;1358&quot; data-origin-width=&quot;1821&quot; data-origin-height=&quot;1358&quot;/&gt;&lt;/span&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;이후에는 프로젝트 트리 중 .idea 안을 확인해보면, 방금 만든 컨벤션이 생겨난 것을 알 수 있음.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCl57B/dJMcac9FmFt/m0K7065sdV3znCCMEwPN21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCl57B/dJMcac9FmFt/m0K7065sdV3znCCMEwPN21/img.png&quot; data-alt=&quot;사실 .ft로 파일이 생성되어야 하는데 아무리 해도 .sql로만 생기길래 .ft로 refactor 함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCl57B/dJMcac9FmFt/m0K7065sdV3znCCMEwPN21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCl57B%2FdJMcac9FmFt%2Fm0K7065sdV3znCCMEwPN21%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;858&quot; height=&quot;329&quot; data-origin-width=&quot;858&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사실 .ft로 파일이 생성되어야 하는데 아무리 해도 .sql로만 생기길래 .ft로 refactor 함&lt;/figcaption&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;또한 대부분의 프로젝트에서 .idea는 .gitignore에 지정되어 있을테니까 Git이 추적할 수 있게 따로 빼줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNx2wE/dJMcabiGcOe/cub8fDhZhnn5D4ubb7iv20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNx2wE/dJMcabiGcOe/cub8fDhZhnn5D4ubb7iv20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNx2wE/dJMcabiGcOe/cub8fDhZhnn5D4ubb7iv20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNx2wE%2FdJMcabiGcOe%2Fcub8fDhZhnn5D4ubb7iv20%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;608&quot; height=&quot;142&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&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;.gitignore에 해당 줄을 추가해주면 끝!&lt;/p&gt;
&lt;pre id=&quot;code_1768925706554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;!.idea/fileTemplates/&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;/div&gt;
&lt;h4 data-path-to-node=&quot;29&quot; data-ke-size=&quot;size20&quot;&gt;3. 사용 방법&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;31&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;db/migration 폴더 우클릭 &amp;rarr; &lt;b data-index-in-node=&quot;22&quot; data-path-to-node=&quot;31,0,0&quot;&gt;New&lt;/b&gt; &amp;rarr; &lt;b data-index-in-node=&quot;28&quot; data-path-to-node=&quot;31,0,0&quot;&gt;Flyway Migration&lt;/b&gt; 클릭&lt;/li&gt;
&lt;li&gt;나오는 창에 설명(Description)만 입력 (예: add_profile_image)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;1169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D53Ne/dJMcacBS3Bh/vSvJQqy79dknoLYSZWACA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D53Ne/dJMcacBS3Bh/vSvJQqy79dknoLYSZWACA0/img.png&quot; data-alt=&quot;템플릿 파일 테스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D53Ne/dJMcacBS3Bh/vSvJQqy79dknoLYSZWACA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD53Ne%2FdJMcacBS3Bh%2FvSvJQqy79dknoLYSZWACA0%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;1487&quot; height=&quot;1169&quot; data-origin-width=&quot;1487&quot; data-origin-height=&quot;1169&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;템플릿 파일 테스트&lt;/figcaption&gt;
&lt;/figure&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;1174&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zgI5K/dJMcac2UTcK/ak6334h3B70NZe7ExW0Mu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zgI5K/dJMcac2UTcK/ak6334h3B70NZe7ExW0Mu1/img.png&quot; data-alt=&quot;DESCRIPTION 작성만 하면 됨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zgI5K/dJMcac2UTcK/ak6334h3B70NZe7ExW0Mu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzgI5K%2FdJMcac2UTcK%2Fak6334h3B70NZe7ExW0Mu1%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;1174&quot; height=&quot;383&quot; data-origin-width=&quot;1174&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DESCRIPTION 작성만 하면 됨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&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;842&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRg0S9/dJMcahb6Gag/o7MVCB2zczyR8KTamk68Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRg0S9/dJMcahb6Gag/o7MVCB2zczyR8KTamk68Y0/img.png&quot; data-alt=&quot;대박슨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRg0S9/dJMcahb6Gag/o7MVCB2zczyR8KTamk68Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRg0S9%2FdJMcahb6Gag%2Fo7MVCB2zczyR8KTamk68Y0%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;842&quot; height=&quot;216&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;216&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대박슨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;이제 resources/db/migration 하위에 sql 파일을 생성할 때마다 해당 템플릿을 사용해서 행복 개발을 할 수 있게 됐다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;만약 New를 눌러서 확인해봤는데 템플릿이 없다면&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&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;953&quot; data-origin-height=&quot;1260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clfLiZ/dJMcabprrN2/7GG4kkYkBJXksxK7OIQpuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clfLiZ/dJMcabprrN2/7GG4kkYkBJXksxK7OIQpuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clfLiZ/dJMcabprrN2/7GG4kkYkBJXksxK7OIQpuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclfLiZ%2FdJMcabprrN2%2F7GG4kkYkBJXksxK7OIQpuk%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;953&quot; height=&quot;1260&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;1260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&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;1224&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vWTOU/dJMcacaPsnq/Vx6nP5v66AfNVOKo2bqbp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vWTOU/dJMcacaPsnq/Vx6nP5v66AfNVOKo2bqbp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vWTOU/dJMcacaPsnq/Vx6nP5v66AfNVOKo2bqbp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvWTOU%2FdJMcacaPsnq%2FVx6nP5v66AfNVOKo2bqbp0%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;1224&quot; height=&quot;669&quot; data-origin-width=&quot;1224&quot; data-origin-height=&quot;669&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;Invalidate and Restart&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;템플릿을 만드는 사람&lt;/b&gt;의&lt;b&gt; 입장&lt;/b&gt;에서, &lt;b&gt;.ft&lt;/b&gt;가 아니라 &lt;b&gt;.sql&lt;/b&gt;로만 생성된다면, 아까 &lt;b&gt;sql&lt;/b&gt; 을 입력했던 곳에&lt;b&gt; sql.ft&lt;/b&gt; 로 넣어주면 해결&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-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;여러분 저 됐어요!&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;됐어요!&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;ㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database/TIL</category>
      <category>flyway</category>
      <category>Flyway컨벤션</category>
      <category>intellij</category>
      <category>sql</category>
      <category>마이그레이션</category>
      <category>인텔리제이</category>
      <category>자동화</category>
      <category>컨벤션</category>
      <category>템플릿화</category>
      <category>파일컨벤션</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/55</guid>
      <comments>https://bitbard-dongni.tistory.com/55#entry55comment</comments>
      <pubDate>Wed, 21 Jan 2026 01:00:25 +0900</pubDate>
    </item>
    <item>
      <title>[Spring/TIL] 전역 예외 처리(@RestControllerAdvice) vs try-catch, 개념 확실히 잡기</title>
      <link>https://bitbard-dongni.tistory.com/54</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오늘의 추천 곡&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;div id=&quot;code_1768667170539&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;iframe width=&amp;quot;1024&amp;quot; height=&amp;quot;576&amp;quot; src=&amp;quot;https://www.youtube.com/embed/otFuVIyHWJE?list=RDsrEamVTM2OU&amp;quot; title=&amp;quot;하림 - 아마도 그건 [유희열의 스케치북/You Heeyeol&amp;rsquo;s Sketchbook] | KBS 201016 방송&amp;quot; frameborder=&amp;quot;0&amp;quot; allow=&amp;quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&amp;quot; referrerpolicy=&amp;quot;strict-origin-when-cross-origin&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/otFuVIyHWJE?list=RDsrEamVTM2OU&quot; width=&quot;1024&quot; height=&quot;576&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/div&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 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;br /&gt;처음에는&amp;nbsp;컨트롤러단에서&amp;nbsp;try-catch로&amp;nbsp;처리하는&amp;nbsp;게&amp;nbsp;당연한&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;그도 그럴게 대학교 4년간 많은 서적과 각종 AI가 그렇게 써왔기 때문에 그게 당연한 건줄 알았는데 &lt;b&gt;ResponseEntity&lt;/b&gt;에 대해서 찾아보다가 스프링의 &lt;b&gt;@RestControllerAdvice&lt;/b&gt;에 대해서 알게되었고, 전공자이지만 이런 걸 몰랐다는 나를 자책하면서 글을 작성한다.....&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;pre id=&quot;code_1768667443603&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/login&quot;)
public ResponseEntity&amp;lt;?&amp;gt; login(@RequestBody LoginRequest request) {
    try {
        authService.login(request);
        return ResponseEntity.ok(ApiResponse.success());
    } catch (PasswordMismatchException e) {
        return ResponseEntity.ok(ApiResponse.fail(&quot;PASSWORD_MISMATCH&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 위 코드는 문제 없이 잘 동작은 한다.&lt;br /&gt;&lt;br /&gt;예외도&amp;nbsp;잘&amp;nbsp;잡히고&amp;nbsp;응답도&amp;nbsp;원하는&amp;nbsp;대로&amp;nbsp;내려온다.&lt;br /&gt;&lt;br /&gt;내용을 살펴보면, &lt;b&gt;PasswordMismatchException&lt;/b&gt;이 발생했을 때 catch 블록에서 잡아서 실패 응답을 보내주고 있다.&lt;br /&gt;&lt;br /&gt;하지만 이 하나의 메서드만 볼 게 아니고 컨트롤러 전체에 이런 try-catch 구문이 반복된다면?..&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;즉, 컨트롤러와 메서드가 하나둘 늘어나면서 비즈니스 로직보다 예외 처리 코드가 더 길어지고, 똑같은 catch 블록이 여기저기 중복되는 현상이 발생한 것.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;@RestControllerAdvice&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에는 컨트롤러 전역에서 발생하는 예외를 한 곳에서 처리하는 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 바로 @RestControllerAdvice 이다.&lt;/p&gt;
&lt;pre id=&quot;code_1768667621016&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestControllerAdvice
public class GlobalExceptionHandler {
}&lt;/code&gt;&lt;/pre&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;MVC 패턴에서 컨트롤러 전과 후로 어디에 위치해야할지 감이 안잡혔었다.&lt;br /&gt;&lt;br /&gt;약간 서비스, 컨트롤러와 같은 명시적으로 부르는 명칭이 없었기 때문이다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;근데 사실 킹갓 스프링답게 그냥 정의만 해두면 스프링 컨테이너가 알아서 예외를 가로채서 처리해준다.&lt;br /&gt;&lt;br /&gt;우선&amp;nbsp;예외를&amp;nbsp;처리할&amp;nbsp;핸들러를&amp;nbsp;정의해보면&amp;nbsp;다음과&amp;nbsp;같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768667836916&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExceptionHandler(PasswordMismatchException.class)
public ResponseEntity&amp;lt;?&amp;gt; handlePasswordMismatch(PasswordMismatchException e) {
    return ResponseEntity.ok(ApiResponse.fail(&quot;PASSWORD_MISMATCH&quot;, &quot;비밀번호 불일치&quot;));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이렇게만&amp;nbsp;정의해두면,&amp;nbsp;서비스나&amp;nbsp;컨트롤러&amp;nbsp;어디서든&amp;nbsp;PasswordMismatchException이&amp;nbsp;터졌을&amp;nbsp;때&amp;nbsp;Spring이&amp;nbsp;이&amp;nbsp;메서드를&amp;nbsp;찾아서&amp;nbsp;실행한다.&lt;br /&gt;&lt;br /&gt;주입도,&amp;nbsp;호출도,&amp;nbsp;설정도&amp;nbsp;필요&amp;nbsp;없다.&amp;nbsp;그냥&amp;nbsp;예외&amp;nbsp;타입&amp;nbsp;기준으로&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;h3 data-ke-size=&quot;size23&quot;&gt;그럼 try-catch는 이제 안 쓰는 건가?&lt;/h3&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;어떻게&amp;nbsp;사용해야&amp;nbsp;하는건지에&amp;nbsp;대한&amp;nbsp;기준이&amp;nbsp;확립되지&amp;nbsp;않아서&amp;nbsp;더욱&amp;nbsp;그렇게&amp;nbsp;느낀&amp;nbsp;게&amp;nbsp;아닐까?&lt;br /&gt;&lt;br /&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;h3 data-ke-size=&quot;size23&quot;&gt;try-catch를 써야 하는 경우 (흐름 제어)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외가 발생해도 복구가 가능하거나, 다음 로직을 이어가야 할 때 사용한다.&lt;br /&gt;이건 예외 처리라기보단 프로그램의 흐름 제어(Flow Control)에 가깝다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 API 호출 실패 시 재시도(`retry`)&lt;/li&gt;
&lt;li&gt;특정 파일이 없으면 기본 파일 생성&lt;/li&gt;
&lt;li&gt;현재 시점의 날씨 데이터를 못가져와서 1시간 전 데이터랃 보여주기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 에러가 났지만 내가 흐름을 제어할 수 있어서 정상적으로 흘러가게 만들 수 있다면 try-catch문을 사용하면 된다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비즈니스 규칙을 위반해서 더 이상 처리할 게 없을 때, 즉 사용자에게 명시적으로 에러를 알리려고 할 때 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1768668087239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!passwordMatches) {
    throw new PasswordMismatchException();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;공통 APIResponse를 만들어서 사용하면 된다!&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;pre id=&quot;code_1768669165967&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;api_version&quot;: &quot;v1&quot;,
  &quot;status&quot;: &quot;success | fail | error&quot;,
  &quot;response_code&quot;: 200,
  &quot;error_code&quot;: &quot;ERROR_CODE&quot;,  // fail 또는 error일 때만 포함
  &quot;message&quot;: &quot;응답 메시지&quot;,
  &quot;count&quot;: 1, // data 개수 (배열은 배열의 길이를, 객체면 1, null이면 0
  &quot;data&quot;: [] // 실제 응답 데이터 (리스트의 경우 대괄호로 묶고 그렇지 않으면 중괄호로 단일 객체 전달)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;사실 이건 사족임&amp;nbsp;&lt;/s&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;&lt;br /&gt;&lt;b&gt;코드 전문 - &lt;/b&gt;&lt;b&gt;GlobalExceptionHandler.java&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768668117718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(PasswordMismatchException.class)
    public ResponseEntity&amp;lt;?&amp;gt; handlePasswordMismatch(PasswordMismatchException e) {
        // 로깅 등 추가 작업 가능
        return ResponseEntity.ok(ApiResponse.fail(&quot;PASSWORD_MISMATCH&quot;, e.getMessage()));
    }
    
    // 다른 예외들도 여기서 추가로 정의 가능
}&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;b&gt;Service:&lt;/b&gt; 로직 수행 중 문제가 생기면 예외를 &lt;b&gt;던짐(Throw)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Controller:&lt;/b&gt; 정상적인 흐름만 처리. &lt;b&gt;(try-catch 최소화)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GlobalExceptionHandler:&lt;/b&gt;&amp;nbsp;던져진 예외를 &lt;b&gt;잡아서&lt;/b&gt; 적절한 &lt;b&gt;응답(Response)&lt;/b&gt;으로 변환.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;처음에는&lt;b&gt; try-catch&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;/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;무조건적으로 전역 처리를 맹신하면 안 되고, 복구 가능한 건 잡고(catch), 의미 있는 실패는 던져서(throw) 전역에서 처리하자는 기준을 잡는 게 핵심인 것 같다.&lt;br /&gt;&lt;br /&gt;&lt;br /&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;h2 data-end=&quot;256&quot; data-start=&quot;225&quot; data-ke-size=&quot;size26&quot;&gt;지피티가 알려준 내용&lt;/h2&gt;
&lt;h3 data-end=&quot;256&quot; data-start=&quot;225&quot; data-ke-size=&quot;size23&quot;&gt;네가 알고 있던 세계 (전통적인 try-catch)&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768668359982&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/login&quot;)
public ResponseEntity&amp;lt;?&amp;gt; login() {
    try {
        service.login();
        return ResponseEntity.ok(&quot;성공&quot;);
    } catch (PasswordMismatchException e) {
        return ResponseEntity.badRequest().body(&quot;비밀번호 틀림&quot;);
    } catch (Exception e) {
        return ResponseEntity.status(500).body(&quot;서버 오류&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;601&quot; data-ke-size=&quot;size16&quot;&gt;이 구조에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;682&quot; data-start=&quot;610&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;682&quot; data-start=&quot;610&quot;&gt;❌ 문제점
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;682&quot; data-start=&quot;620&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;641&quot; data-start=&quot;620&quot;&gt;컨트롤러마다 try-catch 복붙&lt;/li&gt;
&lt;li data-end=&quot;664&quot; data-start=&quot;644&quot;&gt;비즈니스 로직 + 예외 처리 섞임&lt;/li&gt;
&lt;li data-end=&quot;682&quot; data-start=&quot;667&quot;&gt;컨트롤러가 점점 더러워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;687&quot; data-start=&quot;684&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;712&quot; data-start=&quot;689&quot; data-ke-size=&quot;size23&quot;&gt;스프링 방식 (네가 지금 배우는 거)&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768668390494&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/login&quot;)
public ResponseEntity&amp;lt;?&amp;gt; login() {
    service.login(); // 여기서 예외 터지면?
    return ResponseEntity.ok(ApiResponse.success());
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-end=&quot;898&quot; data-start=&quot;876&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;898&quot; data-start=&quot;878&quot; data-ke-size=&quot;size16&quot;&gt;❓ 여기서 예외 터지면 어디서 잡음?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-end=&quot;903&quot; data-start=&quot;900&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;923&quot; data-start=&quot;905&quot; data-ke-size=&quot;size23&quot;&gt;  스프링 컨테이너가 잡음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;944&quot; data-start=&quot;925&quot; data-ke-size=&quot;size20&quot;&gt;흐름을 정확히 그리면 이거임&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768668450293&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Controller
	&amp;darr;
Service
	&amp;darr;
예외 발생  
	&amp;darr;
DispatcherServlet (스프링 핵심)
	&amp;darr;
@RestControllerAdvice 탐색
	&amp;darr;
@ExceptionHandler 매칭
	&amp;darr;
ResponseEntity 반환&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1100&quot; data-start=&quot;1098&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;blockquote data-end=&quot;1143&quot; data-start=&quot;1102&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1143&quot; data-start=&quot;1104&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;try는 네가 썼고,&lt;br /&gt;catch는 스프링이 대신 써준 셈&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Spring/TIL</category>
      <category>RestControllerAdvice</category>
      <category>spring</category>
      <category>TIL</category>
      <category>try-catch</category>
      <category>예외처리</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/54</guid>
      <comments>https://bitbard-dongni.tistory.com/54#entry54comment</comments>
      <pubDate>Sun, 18 Jan 2026 01:48:17 +0900</pubDate>
    </item>
    <item>
      <title>[Spring/TIL] @RequestParam, @ModelAttribute, @RequestBody 차이</title>
      <link>https://bitbard-dongni.tistory.com/53</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;사실 이 내용을 어렴풋이 알고는 있었지만, 남들에게 술술 설명할 수 있는 정도로 잘 아냐고 물어봤을 땐 그닥... 이라고 할 게 분명해서 글로 한 번 정리해보고자 했다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Spring MVC에서 요청을 받을 때 사용하는 어노테이션은 여러 개가 있지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이 차이를 이해하는 데 필요한 개념은 되게 단순하다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;서버는 요청을 받으면 먼저 &amp;lsquo;종이 한 장&amp;rsquo;을 만든다&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 &lt;b&gt;종이 한 장&lt;/b&gt;으로 @RequestParam, @ModelAttribute, @RequestBody를 한 번 설명해보겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;미리보기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUTSZw/dJMcagjUJ8C/cvVXo0Qo2qpcg1GEBBvuy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUTSZw/dJMcagjUJ8C/cvVXo0Qo2qpcg1GEBBvuy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUTSZw/dJMcagjUJ8C/cvVXo0Qo2qpcg1GEBBvuy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUTSZw%2FdJMcagjUJ8C%2FcvVXo0Qo2qpcg1GEBBvuy1%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;1280&quot; height=&quot;720&quot; data-filename=&quot;그림1.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;흐름 설명&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트가 요청을 보낸다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;쿼리 파라미터 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /login?email=a@test.com&amp;amp;password=1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Form Data 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;POST /login
Content-Type: application/x-www-form-urlencoded

email=a@test.com&amp;amp;password=1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;요청 방식이 뭐든, 서버는 먼저 다음과 같이 행동한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;요청에 값들이 있네. 일단 적어두자.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 서버는 내부적으로 &lt;b&gt;종이 한 장&lt;/b&gt;을 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;email = &quot;a@test.com&quot;
password = &quot;1234&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 종이는 데이터가 URL에서 왔는지, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Body에서 왔는지 &lt;/span&gt;관심 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;값만 적혀 있으면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이게 흔히 말하는 &lt;b&gt;파라미터 맵&lt;/b&gt;이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;파라미터 맵(Parameter Map)이란?&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;파라미터 맵은 &lt;b&gt;요청으로 들어온 값들을 서버가 한 번에 정리해 둔 일종의 표&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;URL 쿼리스트링이든, 폼 데이터든 서버는 먼저 해당 값들을 &lt;b&gt;key-value 형태로 모아둔다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;username = kim
age = 20
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이런 식으로 해당 요청에는 이런 값들이 있다라고 &lt;b&gt;한 장의 메모처럼 정리해 둔 것&lt;/b&gt;이 바로 파라미터 맵이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;왜 굳이 파라미터 맵이 필요할까?&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;컨트롤러단에서는 URL 형식이 뭔지, 폼으로 왔는지 관심이 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;컨트롤러에 도착하기 전에 서버가 미리 해당 요청에서 쓸 수 있는 값 목록을 정리해서 결과만 넘겨주기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그 덕분에 @RequestParam, @ModelAttribute 같은 어노테이션은 &lt;b&gt;이 파라미터 맵에서 필요한 값만 꺼내 쓰는 역할&lt;/b&gt;만 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;자, 다시 그러면 들어온 요청이 뭐든 &lt;b&gt;종이 한 장&lt;/b&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;@RequestParam &amp;mdash; 종이에서 한 줄만 떼어낸다&lt;/h4&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;@RequestParam String email
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333; text-align: center;&quot;&gt; 종이에서 email 부분만 찢어서 주는 느낌.&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;/li&gt;
&lt;li&gt;값 하나만 필요할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;@ModelAttribute &amp;mdash; 종이 전체로 객체를 만든다&lt;/h4&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@ModelAttribute LoginReq req
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;407&quot; data-start=&quot;367&quot; data-ke-size=&quot;size16&quot;&gt;종이에 적힌 내용으로 객체를 만들어서 주는 느낌&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;407&quot; data-start=&quot;367&quot;&gt;이 종이 한 장(파라미터 맵)에는 요청 방식과 상관없이 이름과 값만 담겨 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768110269560&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;email = &quot;a@test.com&quot;
password = &quot;1234&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;493&quot; data-start=&quot;457&quot; data-ke-size=&quot;size16&quot;&gt;Spring은 먼저 LoginReq의 빈 객체를 하나 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1768110287682&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LoginReq req = new LoginReq();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;585&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;585&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;그 다음, 파라미터 맵을 한 항목씩 보면서 이름이 같은 필드를 객체에서 찾는다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;585&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;679&quot; data-start=&quot;587&quot; data-ke-size=&quot;size16&quot;&gt;email이라는 이름을 보면, setEmail(...)을 호출하고, password라는 이름을 보면, setPassword(...)를 호출한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;719&quot; data-start=&quot;681&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;719&quot; data-start=&quot;681&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 해서 종이에 적혀 있던 값들이 객체의 필드로 옮겨진다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-end=&quot;719&quot; data-start=&quot;681&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;여기서 중요한 점&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@ModelAttribute는 Form Data 자체를 읽는 게 아니다&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;읽는 건 오직 &lt;b&gt;종이&lt;/b&gt;다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /login?email=...&amp;amp;password=...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@GetMapping(&quot;/login&quot;)
public void login(@ModelAttribute LoginReq req)
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 Form Data를 읽는다고 하면 당연히 POST만 가능하겠구나 생각할 것인데, GET 요청도 가능하다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;GET인지 POST인지는 상관없이 &lt;b&gt;이미 파라미터 맵으로 만들어진 것&lt;/b&gt;을 읽기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;어쨌든 핵심은 GET이든 POST든 상관없다는 것.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;종이가 만들어지기만 하면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;그런데 JSON은?&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;POST /login
Content-Type: application/json

{
  &quot;email&quot;: &quot;a@test.com&quot;,
  &quot;password&quot;: &quot;1234&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;파라미터 맵은 key=value&amp;amp;key=value 쌍으로 이루어져 있을 때만 생성이 가능한데, JSON 규약과는 다르기 때문에 &lt;b&gt;종이가 만들어지지 않는다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@ModelAttribute LoginReq req&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 위와 같은 방식은 당연히 불가능.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; 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;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;@ModelAttribute 바인딩은 어떻게 이루어질까&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;앞에서 말했듯이, @ModelAttribute는 파라미터 맵(종이)을 보고 객체를 만든다.&lt;br /&gt;조금 더 정확히 말하면, &lt;b&gt;필드 단위로 값을 바인딩&lt;/b&gt;한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정은 대략 다음 순서로 이루어진다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Spring은 먼저 대상 타입(LoginReq)의 &lt;b&gt;빈 객체&lt;/b&gt;를 하나 생성한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;LoginReq req = new LoginReq();&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그 다음, 파라미터 맵을 하나씩 순회하면서 각 항목을 확인한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;email = &quot;a@test.com&quot;
password = &quot;1234&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Spring은 각 key 이름을 기준으로 객체 내부를 탐색한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;email이라는 이름의 필드가 있는지&lt;/li&gt;
&lt;li&gt;또는 setEmail(...) 형태의 setter가 있는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;찾아낸 뒤, &lt;b&gt;setter를 호출하는 방식으로 값을 주입&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;req.setEmail(&quot;a@test.com&quot;);
req.setPassword(&quot;1234&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;즉, @ModelAttribute의 바인딩은 객체 전체를 한 번에 매핑하는 것이 아니라 &lt;b&gt;필드 단위로, setter 기반으로&lt;/b&gt; 이루어진다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 때문에 일반적으로 @ModelAttribute를 사용하는 DTO는 자바 빈 규칙(기본 생성자 + setter)을 따르는 형태가 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;타입 변환은 어디서 일어날까&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;파라미터 맵에 들어 있는 값은 모두 문자열이다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;age = &quot;20&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 DTO의 필드는 문자열이 아닐 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;private int age;
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이때 &quot;20&quot;을 20으로 변환하는 작업은 &lt;b&gt;WebDataBinder&lt;/b&gt;가 담당한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;즉, @ModelAttribute 바인딩 과정에는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 &amp;rarr; 숫자&lt;/li&gt;
&lt;li&gt;문자열 &amp;rarr; enum&lt;/li&gt;
&lt;li&gt;문자열 &amp;rarr; 날짜&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;와 같은 &lt;b&gt;타입 변환 로직&lt;/b&gt;이 함께 포함되어 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 별도의 변환 코드를 작성하지 않아도 자연스럽게 DTO가 채워지는 것이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&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;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;@ModelAttribute vs @RequestBody 결정적인 차이&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 차이가 하나 생긴다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;@ModelAttribute는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파라미터 맵을 기반으로&lt;/li&gt;
&lt;li&gt;필드 이름을 기준으로&lt;/li&gt;
&lt;li&gt;setter를 호출해 값을 채운다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;반면 @RequestBody는 전혀 다른 방식으로 동작한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;@RequestBody는 JSON 문자열 전체를 대상으로 &lt;b&gt;역직렬화(deserialization)&lt;/b&gt; 를 수행한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 과정에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 생성자 + setter 방식이 아니라&lt;/li&gt;
&lt;li&gt;필드 접근 또는 생성자 기반 매핑&lt;/li&gt;
&lt;li&gt;Reflection을 이용한 객체 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 사용된다. 즉,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@ModelAttribute&lt;br /&gt;파라미터 맵 기반, &lt;b&gt;필드 단위 바인딩&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;@RequestBody&lt;br /&gt;JSON 원문 기반, &lt;b&gt;객체 전체 역직렬화&lt;/b&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;h3 style=&quot;text-align: left;&quot; data-path-to-node=&quot;58&quot; data-ke-size=&quot;size23&quot;&gt;그럼 이 일은 누가 다 하는 걸까?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;여기서 조금만 더 깊게 들어가 보자. 우리가 편하게 어노테이션만 붙이면 되는 이유는 Spring 내부에서 &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;59&quot;&gt;누군가&lt;/b&gt; 열심히 교통정리를 하고 있기 때문이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;59&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;60&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;HandlerMethodArgumentResolver&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;61&quot; data-ke-size=&quot;size16&quot;&gt;컨트롤러 메서드의 파라미터를 보고 &quot;누가 이 일을 처리할지&quot; 결정하는 관리자다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;62&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@RequestParam이나 @ModelAttribute가 붙어 있다?
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;62,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;62,0,1,0,0&quot;&gt;종이(파라미터 맵)를 보고 처리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@RequestBody가 붙어 있다?
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;62,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;3&quot; data-path-to-node=&quot;62,1,1,0,0&quot;&gt;이건 종이 말고, Body 내용 바로 읽어서 처리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&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;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;63&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;63&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;@ModelAttribute가 선택되었을 때 - WebDataBinder&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;65&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ArgumentResolver가 &quot;객체로 만들어야 돼&quot;라고 하면 WebDataBinder가 등장한다.&lt;/li&gt;
&lt;li&gt;이 친구가 **파라미터 맵(종이)**을 보고 객체의 Setter를 호출해서 값을 채워 넣는다.&lt;/li&gt;
&lt;li&gt;타입 변환(문자열 &quot;10&quot; &amp;rarr; 숫자 10)이나 검증(Validation)도 얘가 담당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;66&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; @RequestBody가 선택되었을 때 - HttpMessageConverter (Jackson)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;68&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종이가 없으므로, Body에 있는 JSON 데이터를 직접 읽어야 한다.&lt;/li&gt;
&lt;li&gt;이때 우리가 흔히 쓰는 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;68,1,0&quot;&gt;Jackson&lt;/b&gt; 같은 라이브러리가 동작해서 JSON 문자열을 자바 객체로 변환(Parsing)해준다.&lt;/li&gt;
&lt;li&gt;반대로 @ResponseBody를 써서 리턴할 때도 이 친구가 자바 객체를 JSON으로 바꿔서 내보낸다.&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 style=&quot;text-align: left;&quot; data-path-to-node=&quot;73&quot; data-ke-size=&quot;size26&quot;&gt;최종 정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;74&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;74,0,0&quot;&gt;@RequestParam&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;74,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종이(파라미터 맵)에서 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;74,0,1,0,0&quot;&gt;한 줄&lt;/b&gt;만 쏙 뺀다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;74,1,0&quot;&gt;@ModelAttribute&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;74,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;74,1,1,0,0&quot;&gt;WebDataBinder&lt;/b&gt;가 종이 전체 내용을 보고 Setter로 객체를 채운다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;74,2,0&quot;&gt;@RequestBody&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;74,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;종이가 없다. **HttpMessageConverter(Jackson)**가 JSON을 통째로 읽어서 객체로 바꾼다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;text-align: left;&quot; data-path-to-node=&quot;75&quot; data-ke-size=&quot;size16&quot;&gt;결국 &lt;b&gt;파라미터 맵(종이)&lt;/b&gt;을 &lt;b&gt;거치느냐&lt;/b&gt;, &lt;b&gt;아니냐&lt;/b&gt;와 그걸 &lt;b&gt;누가 처리&lt;/b&gt;해주느냐&lt;b&gt;(Binder vs Converter)&lt;/b&gt;의 차이만 알고 있으면 될 것 같다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/TIL</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/53</guid>
      <comments>https://bitbard-dongni.tistory.com/53#entry53comment</comments>
      <pubDate>Sun, 11 Jan 2026 15:16:10 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA-TIL] JWT, JJWT 구현</title>
      <link>https://bitbard-dongni.tistory.com/52</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT, JJWT란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 Json Web Token의 약자로, Json 형식의 데이터로 이루어진 인증 티켓 같은 거라고 생각해볼 수 있다.&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-filename=&quot;KakaoTalk_20251029_165034796.jpg&quot; data-origin-width=&quot;3837&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beIp5M/dJMcae62kFs/WtmsN165ok3HJvdUDlaSek/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beIp5M/dJMcae62kFs/WtmsN165ok3HJvdUDlaSek/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beIp5M/dJMcae62kFs/WtmsN165ok3HJvdUDlaSek/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeIp5M%2FdJMcae62kFs%2FWtmsN165ok3HJvdUDlaSek%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;3837&quot; height=&quot;1000&quot; data-filename=&quot;KakaoTalk_20251029_165034796.jpg&quot; data-origin-width=&quot;3837&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진과 같이 JWT는 Header, Payload, Signature로 이루어져 있다.&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;2464&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q7CWu/dJMcagX5IGK/c8YKjPY3cfi2CS1XC3diA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q7CWu/dJMcagX5IGK/c8YKjPY3cfi2CS1XC3diA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q7CWu/dJMcagX5IGK/c8YKjPY3cfi2CS1XC3diA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq7CWu%2FdJMcagX5IGK%2Fc8YKjPY3cfi2CS1XC3diA0%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;2464&quot; height=&quot;1000&quot; data-origin-width=&quot;2464&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&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;[Header]&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 style=&quot;width: 17.3256%;&quot;&gt;alg&lt;/td&gt;
&lt;td style=&quot;width: 82.6744%;&quot;&gt;서명(Signature)에 사용할 암호화 알고리즘 (예: HS256, RS256 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.3256%;&quot;&gt;typ&lt;/td&gt;
&lt;td style=&quot;width: 82.6744%;&quot;&gt;토큰의 타입 (대부분 &quot;JWT&quot;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;851&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;이 객체는 &lt;b&gt;Base64URL&lt;/b&gt; 방식으로 인코딩되어 JWT의 첫 번째 부분을 이룬다.&lt;/p&gt;
&lt;p data-end=&quot;851&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Payload]&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;sub&lt;/td&gt;
&lt;td&gt;토큰의 주제(Subject, 주로 사용자 ID)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;사용자 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;role&lt;/td&gt;
&lt;td&gt;권한(Role)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iat&lt;/td&gt;
&lt;td&gt;토큰 발급 시각 (issued at)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exp&lt;/td&gt;
&lt;td&gt;토큰 만료 시각 (expiration)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;851&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;851&quot; data-start=&quot;802&quot; data-ke-size=&quot;size16&quot;&gt;[Signature]&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 style=&quot;width: 50%;&quot;&gt;secret&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;서버가 가진 비밀 키 (절대 노출되면 안 됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;HMACSHA256&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;해싱 알고리즘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1878&quot; data-start=&quot;1815&quot;&gt;결과적으로 Signature는 &amp;ldquo;Header + Payload&amp;rdquo; 조합이 변조되지 않았다는 것을 증명해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체 구조&lt;/h3&gt;
&lt;pre id=&quot;code_1761822838025&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[Header]
{
  &quot;sub&quot;: &quot;1234567890&quot;,
  &quot;name&quot;: &quot;유동훈&quot;,
  &quot;role&quot;: &quot;user&quot;,
  &quot;iat&quot;: 1698652000,
  &quot;exp&quot;: 1698655600
}

[Payload]
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuyKteyViO2VnCIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNjk4NjUyMDAwLCJleHAiOjE2OTg2NTU2MDB9

[Signature]
HMACSHA256(
  base64UrlEncode(header) + &quot;.&quot; +
  base64UrlEncode(payload),
  secret
)&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 JWT 모습&lt;/h3&gt;
&lt;pre id=&quot;code_1761822886175&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuyKteyViO2VnCIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNjk4NjUyMDAwLCJleHAiOjE2OTg2NTU2MDB9.
TJVA95OrM7E2cBab30RMHrHDcEfxJoZQnQvV3u8bU8U&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT 적용 예시 with Servlet&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251029_165034796_01.jpg&quot; data-origin-width=&quot;2759&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btIWTI/dJMb99YXBUy/JSXXjpGWkmig5kdQrsTM81/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btIWTI/dJMb99YXBUy/JSXXjpGWkmig5kdQrsTM81/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btIWTI/dJMb99YXBUy/JSXXjpGWkmig5kdQrsTM81/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtIWTI%2FdJMb99YXBUy%2FJSXXjpGWkmig5kdQrsTM81%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;2759&quot; height=&quot;1000&quot; data-filename=&quot;KakaoTalk_20251029_165034796_01.jpg&quot; data-origin-width=&quot;2759&quot; data-origin-height=&quot;1000&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;h3 data-ke-size=&quot;size23&quot;&gt;컨트롤러 일부 발췌&lt;/h3&gt;
&lt;pre id=&quot;code_1761823115481&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(&quot;/user&quot;)
public class UserController extends HttpServlet implements ControllerHelper {
	private static final long serialVersionUID = 1L;
	
	private UserService userService;
	private RefreshTokenService refreshService;
	private JwtUtil jwt;
	
	@Override
    public void init() throws ServletException {
        // 1) Service 주입 (리스너가 context에 넣어줬다면 거기서 꺼내기)
        this.userService = (UserService) getServletContext().getAttribute(&quot;userService&quot;);
        this.refreshService = (RefreshTokenService) getServletContext().getAttribute(&quot;refreshTokenService&quot;);

        // 2) 시크릿 로딩: 환경변수 -&amp;gt; 없으면 web.xml context-param -&amp;gt; 그래도 없으면 예외
        String b64 = System.getenv(&quot;JWT_SECRET_BASE64&quot;);
        if (b64 == null) {
            b64 = getServletContext().getInitParameter(&quot;JWT_SECRET_BASE64&quot;);
        }
        if (b64 == null) {
            throw new ServletException(&quot;JWT_SECRET_BASE64 is missing. Set env or context-param.&quot;);
        }												
        byte[] secretBytes;
        try {
            secretBytes = java.util.Base64.getDecoder().decode(b64);
        } catch (IllegalArgumentException e) {
            throw new ServletException(&quot;JWT_SECRET_BASE64 is not valid Base64.&quot;, e);
        }
        this.jwt = new JwtUtil(secretBytes);
    }&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;코드 속 2번 항목처럼 환경 변수에서 시크릿 키를 받아오는 걸 확인할 수 있다.&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;이후에는 JwtUtil 클래스를 만들어서 JWT의 내부적인 동작을 정의해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761823260947&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.time.Instant;
import java.util.Date;

public class JwtUtil {
    private final Key key;

    public JwtUtil(byte[] secretBytes) {
        this.key = Keys.hmacShaKeyFor(secretBytes);
    }

    // 토큰 발급
    public String create(long userIdx, long ttlSeconds) {
        Instant now = Instant.now();
        return Jwts.builder()
                .setHeaderParam(&quot;typ&quot;, &quot;JWT&quot;)
                .claim(&quot;userIdx&quot;, userIdx)
                .setIssuedAt(Date.from(now))
                .setExpiration(Date.from(now.plusSeconds(ttlSeconds)))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    // 검증 + 클레임 추출
    public Jws&amp;lt;Claims&amp;gt; parse(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .setAllowedClockSkewSeconds(0)
                .build()
                .parseClaimsJws(token);
    }

    public long getUserIdx(String token) {
        return parse(token).getBody().get(&quot;userIdx&quot;, Number.class).longValue();
    }
    
    public long verifyAndGetUserId(String token) {
        return getUserIdx(token);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에는 JWT가 발급되는 순간들을 만들어줘야 한다.&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;pre id=&quot;code_1761823414274&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void signin(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    String email = request.getParameter(&quot;email&quot;);
    String password = request.getParameter(&quot;password&quot;);
    // TODO: password 암호화

    UserDto loginUser = userService.signin(email, password);
		
    if (loginUser == null) {
        redirect(request, response, &quot;/user?action=signin-form&quot;);
        return;
    }

    // 1) 토큰 발급
    String accessToken  = jwt.create(loginUser.getId(), 60 * 60 * 6);
    String refreshToken = jwt.create(loginUser.getId(), 60 * 60 * 24);

    // 2) 검증용 토큰 저장
    refreshService.save(loginUser.getId(), refreshToken, 60 * 60 * 24);

    // 3) 두 토큰을 HttpOnly 쿠키로 심기
    addHttpOnlyCookie(response, &quot;accessToken&quot;,  accessToken,  60 * 60 * 24);
    addHttpOnlyCookie(response, &quot;refreshToken&quot;, refreshToken, 60 * 60 * 24 * 7);

    redirect(request, response, &quot;/&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;JwtUtil을 이용해서 로그인에 성공하면 토큰을 jwt.create 해주고 재발급용 토큰인 refreshToken을 DB에 저장해주면 된다.&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;&lt;span style=&quot;color: #333333;&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;여기서 일반적으로 쿠키의 유효 기간은 JWT의 유효 기간보다 길게 설정해주어야 한다.&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;color: #333333; font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;쿠키 유효 기간 vs JWT 유효 기간&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: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;JWT는 서버가 유효성을 검증하는 토큰이고, 쿠키는 클라이언트가 그 토큰을 서버로 전달하는 수단이기 때문이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;만약 쿠키가 먼저 만료되어버리면, 아직 유효한 JWT가 남아 있더라도 클라이언트가 서버에 전달할 수 없게 된다. 즉, 서버는 토큰의 만료 여부조차 확인할 수 없게 된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; color: #333333;&quot;&gt;따라서 쿠키의 유효 기간은 JWT보다 길게 설정해야, 서버가 JWT 만료 여부를 정상적으로 판단할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Access Token vs Refresh Token&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251029_165034796_02.jpg&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brKIO8/dJMcacg40DI/bwRAQxXmlOkIOyz3vmMn01/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brKIO8/dJMcacg40DI/bwRAQxXmlOkIOyz3vmMn01/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brKIO8/dJMcacg40DI/bwRAQxXmlOkIOyz3vmMn01/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrKIO8%2FdJMcacg40DI%2FbwRAQxXmlOkIOyz3vmMn01%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;1867&quot; height=&quot;1000&quot; data-filename=&quot;KakaoTalk_20251029_165034796_02.jpg&quot; data-origin-width=&quot;1867&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯, JWT는 두가지 종류가 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Access Token&lt;/li&gt;
&lt;li&gt;Refresh Token&lt;/li&gt;
&lt;/ul&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;일반적으로 사용자는 로그인에 성공하면 서버로부터 Access Token과 Refresh Token, 두 가지를 함께 발급받는다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;532&quot; data-start=&quot;329&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;412&quot; data-start=&quot;329&quot;&gt;&lt;b&gt;Access Token&lt;/b&gt;은 실제로 요청 시 인증을 수행하는 토큰으로, 유효 기간이 짧게 설정되어 있다. (예: 30분 ~ 1시간)&lt;/li&gt;
&lt;li data-end=&quot;532&quot; data-start=&quot;413&quot;&gt;&lt;b&gt;Refresh Token&lt;/b&gt;은 Access Token이 만료되었을 때, 새로운 Access Token을 재발급받기 위한 용도로 사용된다. 보통 유효 기간이 훨씬 길다. (예: 2주 ~ 1달)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;627&quot; data-start=&quot;555&quot; data-ke-size=&quot;size16&quot;&gt;사진에서 볼 수 있듯, 서버는 로그인 시 AT와 RT를 함께 발급하고 이 토큰들을 클라이언트(브라우저)에 쿠키 형태로 전달한다. 이후 클라이언트는 요청을 보낼 때마다 Access Token이 담긴 쿠키를 서버로 함께 전송한다. 문제는 &lt;b&gt;Access Token이 만료되었을 때&lt;/b&gt;다. 이 경우 서버는 클라이언트의 요청을 처리하지 않고 &lt;b&gt;401 Unauthorized&lt;/b&gt; 상태 코드(만료 응답이자 재발급 신호)를 보낸다.&lt;/p&gt;
&lt;p data-end=&quot;627&quot; data-start=&quot;555&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;889&quot; data-start=&quot;805&quot; data-ke-size=&quot;size16&quot;&gt;이 시점에서 클라이언트는 자신이 가지고 있던 &lt;b&gt;Refresh Token&lt;/b&gt;을 서버로 다시 보내 새로운 Access Token을 발급받는다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;891&quot; data-ke-size=&quot;size16&quot;&gt;만약 Refresh Token까지 만료되었거나 위&amp;middot;변조되었다면, 이제는 더 이상 재발급이 불가능하므로 &lt;b&gt;다시 로그인&lt;/b&gt;을 해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;891&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;h2 data-end=&quot;970&quot; data-start=&quot;891&quot; data-ke-size=&quot;size26&quot;&gt;Signature 알고리즘 작동 방식&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251029_165034796_03.jpg&quot; data-origin-width=&quot;1419&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd3Gh4/dJMcabWMdfa/FhKhKS5FYUbRgyg7Su8qm0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd3Gh4/dJMcabWMdfa/FhKhKS5FYUbRgyg7Su8qm0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd3Gh4/dJMcabWMdfa/FhKhKS5FYUbRgyg7Su8qm0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd3Gh4%2FdJMcabWMdfa%2FFhKhKS5FYUbRgyg7Su8qm0%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;1419&quot; height=&quot;1000&quot; data-filename=&quot;KakaoTalk_20251029_165034796_03.jpg&quot; data-origin-width=&quot;1419&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;대칭키 방식 (HS256)&lt;/h3&gt;
&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-ke-size=&quot;size16&quot;&gt;서버 혼자 비밀키(secret key)를 가지고 서명과 검증을 모두 수행하는 구조&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비밀키 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 JWT에 서명할 때 사용할 비밀키(secret key) 를 생성한다.&lt;/li&gt;
&lt;li&gt;이 키는 오직 서버 내부에만 존재하며 외부로 노출되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트 로그인 요청
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 서버로 로그인 정보를 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로그인 성공 &amp;rarr; JWT 발급
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 로그인 정보를 검증한 뒤,&lt;/li&gt;
&lt;li&gt;사용자 정보를 포함한 JWT를 생성한다.&lt;/li&gt;
&lt;li&gt;이때 비밀키로 서명(Signature)을 추가하여 토큰을 완성한다.&lt;/li&gt;
&lt;li&gt;JWT는 응답으로 클라이언트에 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청 시 JWT 포함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 이후 API 요청마다&lt;/li&gt;
&lt;li&gt;이 JWT를&amp;nbsp;쿠키나 Authorization 헤더에 포함시켜 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버의 비밀키 검증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 요청을 받으면 자신의 비밀키로 서명을 검증한다.&lt;/li&gt;
&lt;li&gt;서명이 유효하다면 토큰이 위변조되지 않았음을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;응답 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증이 완료되면 서버는 정상적으로 요청을 처리하고 응답을 돌려준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251029_165034796_04.jpg&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;999&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uS4y7/dJMb99YXBUw/xLkd8XKdYuUe6pDWERaiS1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uS4y7/dJMb99YXBUw/xLkd8XKdYuUe6pDWERaiS1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uS4y7/dJMb99YXBUw/xLkd8XKdYuUe6pDWERaiS1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuS4y7%2FdJMb99YXBUw%2FxLkd8XKdYuUe6pDWERaiS1%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;1438&quot; height=&quot;999&quot; data-filename=&quot;KakaoTalk_20251029_165034796_04.jpg&quot; data-origin-width=&quot;1438&quot; data-origin-height=&quot;999&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비대칭키 방식 (RS256)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개키와 개인키를 분리하여 사용하는 방식&lt;br /&gt;(개인키로 서명하고, 공개키로 검증)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개인키 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 서명용 개인키(private key) 를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공개키 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개인키로부터 검증용 공개키(public key) 를 만들어낸다.&lt;/li&gt;
&lt;li&gt;이 공개키는 외부(다른 서버, 인증 서버, 클라이언트 등) 에 공유될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클라이언트 로그인 요청
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 서버로 로그인 요청을 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;로그인 성공 &amp;rarr; JWT 발급 및 전송
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버는 개인키로 JWT에 서명(Signature)을 추가하여 토큰을 완성하고, 그 JWT를 클라이언트에 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청 시 JWT 포함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 이후 요청마다 JWT를 포함시켜 서버로 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공개키로 검증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버(또는 인증 서버)는 JWT의 서명을 공개키로 검증한다.&lt;/li&gt;
&lt;li&gt;공개키로 검증이 성공하면 토큰이 정상 발급된 것임을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;응답 반환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검증이 끝나면 서버는 요청을 처리하고 정상 응답을 전송한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 80px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 0px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 14.3023%;&quot; rowspan=&quot;2&quot;&gt;&lt;b&gt; 구분 &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 35%;&quot; rowspan=&quot;2&quot;&gt;&lt;b&gt; HS256 (대칭키) &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 50.6977%;&quot; rowspan=&quot;2&quot;&gt;&lt;b&gt;RS256 (비대칭키) &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.3023%;&quot;&gt;키 구조&lt;/td&gt;
&lt;td style=&quot;width: 35%;&quot;&gt;하나의 비밀키로 서명+검증&lt;/td&gt;
&lt;td style=&quot;width: 50.6977%;&quot;&gt;개인키로 서명, 공개키로 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 14.3023%;&quot;&gt;검증 주체&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 35%;&quot;&gt;서버 자신만 가능&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 50.6977%;&quot;&gt;공개키를 가진 누구나 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 14.3023%;&quot;&gt;보안 수준&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 35%;&quot;&gt;비교적 단순, 빠름&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 50.6977%;&quot;&gt;더 안전하지만 복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 14.3023%;&quot;&gt;활용 예시&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 35%;&quot;&gt;단일 서버 환경&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 50.6977%;&quot;&gt;마이크로서비스 / OAuth / 외부 인증 연동&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;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클라이언트 - 서버 간의 JWT 시퀀스 다이어그램&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251029_165034796_05.jpg&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;999&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPfLQ3/dJMb99YXBUx/CKT0w0Vy1cRIG9Fe0R2a5k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPfLQ3/dJMb99YXBUx/CKT0w0Vy1cRIG9Fe0R2a5k/img.jpg&quot; data-alt=&quot;사실 이걸 시퀀스 다이어그램이라고 불러도 되는진 모르겠지만, 개념 상으로 시간의 흐름에 따른 요청, 응답의 흐름이 있기는 있으니까...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPfLQ3/dJMb99YXBUx/CKT0w0Vy1cRIG9Fe0R2a5k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPfLQ3%2FdJMb99YXBUx%2FCKT0w0Vy1cRIG9Fe0R2a5k%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;1986&quot; height=&quot;999&quot; data-filename=&quot;KakaoTalk_20251029_165034796_05.jpg&quot; data-origin-width=&quot;1986&quot; data-origin-height=&quot;999&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사실 이걸 시퀀스 다이어그램이라고 불러도 되는진 모르겠지만, 개념 상으로 시간의 흐름에 따른 요청, 응답의 흐름이 있기는 있으니까...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-end=&quot;691&quot; data-start=&quot;668&quot; data-ke-size=&quot;size23&quot;&gt;1~4. 로그인 및 토큰 발급&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client &amp;rarr; Server : 로그인 요청&lt;/b&gt;&lt;br /&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. Server &amp;rarr; DB : 사용자 조회&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버가 DB에서 해당 유저 정보를 조회.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. DB &amp;rarr; Server : 사용자 데이터 반환&lt;/b&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Server &amp;rarr; Client : JWT 발급 및 전달&lt;/b&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;975&quot; data-start=&quot;946&quot;&gt;&lt;b&gt;Access Token (단기 인증용)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1037&quot; data-start=&quot;979&quot;&gt;&lt;b&gt;Refresh Token (재발급용)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1037&quot; data-start=&quot;979&quot;&gt;&lt;b&gt; 두 가지 JWT를 생성해 클라이언트에 전달. &lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-end=&quot;1066&quot; data-start=&quot;1044&quot; data-ke-size=&quot;size23&quot;&gt;5~6. 요청 시 인증 처리&lt;/h3&gt;
&lt;p data-end=&quot;1066&quot; data-start=&quot;1044&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Client &amp;rarr; Server : API 요청 (JWT 포함)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1066&quot; data-start=&quot;1044&quot;&gt;클라이언트는 이후 요청마다 JWT(Access Token)를 쿠키나 헤더에 실어 보냄.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1066&quot; data-start=&quot;1044&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. Server : Access Token 검증&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1277&quot; data-start=&quot;1202&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1227&quot; data-start=&quot;1202&quot;&gt;토큰이 &lt;b&gt;유효하면 요청 정상 처리&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1277&quot; data-start=&quot;1231&quot;&gt;&lt;b&gt;만료되었다면&lt;/b&gt;, Refresh Token을 이용한 재발급 로직으로 분기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-end=&quot;1309&quot; data-start=&quot;1284&quot; data-ke-size=&quot;size23&quot;&gt;6-1~6-3. 재발급 및 재요청&lt;/h3&gt;
&lt;p data-end=&quot;1392&quot; data-start=&quot;1310&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6-1. Server &amp;rarr; DB : Refresh Token 조회&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1392&quot; data-start=&quot;1310&quot;&gt;DB에 저장된 Refresh Token을 조회해 유효한지 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1478&quot; data-start=&quot;1394&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6-2.&lt;/b&gt; &lt;b&gt;Server : 새로운 Access Token 생성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1478&quot; data-start=&quot;1394&quot;&gt;Refresh Token이 유효하면 새 Access Token을 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1566&quot; data-start=&quot;1480&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6-3. Server &amp;rarr; Client : 새로운 토큰 전달 후 응답&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1566&quot; data-start=&quot;1480&quot;&gt;새 토큰을 클라이언트에 전송하고, 요청을 다시 처리하여 응답 반환.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java/TIL</category>
      <category>Access Token</category>
      <category>HS256</category>
      <category>java</category>
      <category>jjwt</category>
      <category>jwt</category>
      <category>Refresh Token</category>
      <category>RS256</category>
      <category>대칭키</category>
      <category>비대칭키</category>
      <category>쿠키</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/52</guid>
      <comments>https://bitbard-dongni.tistory.com/52#entry52comment</comments>
      <pubDate>Thu, 30 Oct 2025 21:08:20 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 최단 거리 알고리즘 - 다익스트라 &amp;lt;Dijkstra&amp;gt;</title>
      <link>https://bitbard-dongni.tistory.com/51</link>
      <description>&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;최단 거리 알고리즘 - 다익스트라 &amp;lt;Dijkstra&amp;gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다익스트라 알고리즘&lt;/b&gt;은 흔히 알고있듯,&lt;b&gt; 최단 거리&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 BFS가 떠오를 것인데, BFS와의 차이점은 거리(가중치)가 1이 아니라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, BFS는 현재 칸에서 다음 칸으로 이동하면 거리(가중치)가 1이 증가하는데 반해 다&lt;b&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;b&gt;가중치가 있는 그래프&lt;/b&gt;에서&lt;b&gt; 하나의 시작 정점&lt;/b&gt;으로부터 &lt;b&gt;모든 정점들&lt;/b&gt;까지의 &lt;b&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;또 다른 이야기를 해보자면, 이전에 업로드했었던 Prim 알고리즘과도 다소 유사한 모습을 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프림 알고리즘와 다익스트라 알고리즘 모두 PriorityQueue를 사용하고 다소 그리디하게 동작하기 때문이다.&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;일 때는 다익스트라 &lt;b&gt;알고리즘을&lt;/b&gt; 사용하지 못한다.&lt;b&gt; 음수 가중치&lt;/b&gt;가&lt;b&gt; 개입&lt;/b&gt;되는 순간, 최단 거리라고 생각했던 것이 최단이 아닐 수 있듯, &lt;b&gt;논리적으로 심각한 문제가 발생하게 된다&lt;/b&gt;. 따라서 이러한 상황에서는 &lt;b&gt;벨만-포드&lt;/b&gt; 알고리즘을 사용하면 되고, 간선의 &lt;b&gt;양수 가중치&lt;/b&gt;만을 가지고 있을 때는 &lt;b&gt;다익스트라 알고리즘&lt;/b&gt;으로 쉽게 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-start=&quot;325&quot; data-end=&quot;413&quot; data-ke-style=&quot;style2&quot;&gt;다익스트라 알고리즘 동작 원리&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;1. 임의의 &lt;b&gt;출발 노드&lt;/b&gt;를 선택해서 &lt;b&gt;가중치 0&lt;/b&gt;으로 시작&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;2. 현재까지의 &lt;b&gt;최단 거리가 확정되지 않은 노드&lt;/b&gt; 중, &lt;b&gt;가장 짧은 거리&lt;/b&gt;를 가진 노드를 선택.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;3. 해당 노드와 연결된 &lt;b&gt;인접 노드들의 거리&lt;/b&gt;를 확인하고 &lt;b&gt;기존 거리보다 더 짧으면 갱신&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;4. 모든 노드가 확정될 때까지 2 ~ 3번의 과정을 반복.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-start=&quot;325&quot; data-end=&quot;413&quot; data-ke-style=&quot;style2&quot;&gt;핵심 자료 구조&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거리 배열 (int dist[]): 시작점에서 각 정점까지의 최소 비용 저장 (dp를 이용해서 갱신함)&lt;/li&gt;
&lt;li&gt;방문 배열 (boolean visited[]): 최단 거리가 확정된 노드 체크&lt;/li&gt;
&lt;li&gt;우선순위 큐 (PriorityQueue pq): 항상 &lt;b&gt;현재 가장 짧은 거리의 노드&lt;/b&gt;를 보장하기 위해서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;다른 알고리즘과의 차이점 by GPT&lt;/blockquote&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;b&gt;다익스트라&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최단 거리 (단일 시작점 &amp;rarr; 모든 정점)&lt;/td&gt;
&lt;td&gt;가중치 &amp;ge; 0&lt;/td&gt;
&lt;td&gt;가장 가까운 정점부터 거리 갱신 (Greedy)&lt;/td&gt;
&lt;td&gt;O(E log V)&lt;/td&gt;
&lt;td&gt;네비게이션, 네트워크 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;크루스칼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최소 신장 트리 (MST)&lt;/td&gt;
&lt;td&gt;무방향, 가중치 있음&lt;/td&gt;
&lt;td&gt;간선을 가중치 순 정렬 + 사이클 방지&lt;/td&gt;
&lt;td&gt;O(E log E)&lt;/td&gt;
&lt;td&gt;도로 건설, 통신망 비용 최소화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프림&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최소 신장 트리 (MST)&lt;/td&gt;
&lt;td&gt;무방향, 가중치 있음&lt;/td&gt;
&lt;td&gt;정점에서 시작해 최소 간선 확장&lt;/td&gt;
&lt;td&gt;O(E log V)&lt;/td&gt;
&lt;td&gt;전력망 연결, 네트워크 구축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;BFS&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;그래프 탐색, 최단 거리 (무가중치)&lt;/td&gt;
&lt;td&gt;무가중치 그래프&lt;/td&gt;
&lt;td&gt;레벨별 탐색 (큐)&lt;/td&gt;
&lt;td&gt;O(V + E)&lt;/td&gt;
&lt;td&gt;미로 탐색, 소셜 네트워크 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;느끼기에는 프림 알고리즘과 다익스트라가 매우 유사하게 느껴졌고 크루스칼과도 헷갈리기도 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;결국 크루스칼/프림 알고리즘은 최소 스패닝 트리를 구성하는 데에 목적이 맞춰져 있다는 걸 명심해야 한다.&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;/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;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;시작 정점에서 다른 모든 정점으로의 최단 거리가 저장되어 있다는 것이다!!&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;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;코드 전문&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BOJ 1916 - 최소비용 구하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758631686762&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class BOJ_1916 {
	
	public static class Vertex implements Comparable&amp;lt;Vertex&amp;gt;{
		int idx;
		int weight;
		
		public Vertex(int idx, int weight) {
			this.idx = idx;
			this.weight = weight;
		}

		@Override
		public int compareTo(Vertex o) {
			return Integer.compare(this.weight, o.weight);
		}
	}

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        
        int N = Integer.parseInt(br.readLine());
        int M = Integer.parseInt(br.readLine());
        
        ArrayList&amp;lt;Vertex&amp;gt;[] graph = new ArrayList[N + 1];
        for (int i = 1; i &amp;lt;= N; i++) graph[i] = new ArrayList&amp;lt;&amp;gt;();
        
        for (int i = 0; i &amp;lt; M; i++) {
        	st = new StringTokenizer(br.readLine());
        	
        	int start = Integer.parseInt(st.nextToken());
        	int end = Integer.parseInt(st.nextToken());
        	int weight = Integer.parseInt(st.nextToken());
        	
        	graph[start].add(new Vertex(end, weight));
        }

    	st = new StringTokenizer(br.readLine());
    	
    	int start = Integer.parseInt(st.nextToken());
    	int end = Integer.parseInt(st.nextToken());
    	
    	int[] dist = new int[N + 1];
    	Arrays.fill(dist, Integer.MAX_VALUE - 100_000);
    	
    	PriorityQueue&amp;lt;Vertex&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
    	boolean[] visited = new boolean[N + 1];
    	
    	pq.add(new Vertex(start, 0));
    	dist[start] = 0;
    	
    	while (!pq.isEmpty()) {
    		Vertex cur = pq.poll();
    		
    		if (visited[cur.idx]) continue;
    		visited[cur.idx] = true;
    		
    		for (Vertex next : graph[cur.idx]) {
    			if (dist[next.idx] &amp;gt; dist[cur.idx] + next.weight) {
    				dist[next.idx] = dist[cur.idx] + next.weight;
    				pq.add(new Vertex(next.idx, dist[next.idx]));
    			}
    		}
    	}
    	
    	System.out.println(dist[end]);
    	
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&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;pre id=&quot;code_1758632136328&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	public static class Vertex implements Comparable&amp;lt;Vertex&amp;gt;{
		int idx;
		int weight;
		
		public Vertex(int idx, int weight) {
			this.idx = idx;
			this.weight = weight;
		}

		@Override
		public int compareTo(Vertex o) {
			return Integer.compare(this.weight, o.weight);
		}
	}&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: 'Nanum Gothic';&quot;&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: 'Nanum Gothic';&quot;&gt;해당 정점 클래스의 필드로는 idx(정점의 번호), weight(현재 노드에 이르기까지 계산된 가중치의 합)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;PriorityQueue를 위해 구현해준 compareTo 메서드&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;추가로, 왜 여기선 시작 정점과 도착 정점을 담지 않고 정점의 idx를 필드로 가지고 있을까?&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: 'Nanum Gothic';&quot;&gt;다익스트라 알고리즘의 동작 과정이 특정 정점을 기준으로 점점 확장해나가는 형태이기 때문.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;시작 정점에서 그 다음 정점.. 그 다음 정점.. 쭉 확장되면서 나아감.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;크루스칼 알고리즘의 경우에는 간선에 포커스를 맞춘 알고리즘이기에 시작 정점과 도착 정점이 필요하기 때문에 Edge 클래스를 정의해주었음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프림 알고리즘의 경우에는 임의의 정점에서 이웃된 정점들 중 가장 가중치가 작은 정점들을 선택해나가기 때문에 다익스트라와 마찬가지로 현재 정점의 정보만을 가진 Vertex 또는 Node 클래스를 정의해준 것.&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: 'Nanum Gothic';&quot;&gt;프림 알고리즘 게시글을 보면 정점의 정보를 to로 정의해주었는데, 사실상 현재 정점의 idx과 다른 의미로 사용한거지만 로직 상으로는 동일한 의미를 가짐.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또 엄밀히 따지자면 다익스트라 알고리즘에서도 두가지 의미로 사용됨.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;문맥 1 (그래프 저장: graph[u].add(new Vertex(v, w)))&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;745&quot; data-start=&quot;361&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot; data-end=&quot;487&quot; data-start=&quot;361&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;487&quot; data-start=&quot;419&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;440&quot; data-start=&quot;419&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;idx = 도착 정점 v&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;487&quot; data-start=&quot;443&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;weight = &lt;b&gt;그 간선의 가중치&lt;/b&gt; w (edge weight)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;646&quot; data-start=&quot;488&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;문맥 2 (우선순위큐에 넣을 때: pq.add(new Vertex(v, dist[v])))&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;646&quot; data-start=&quot;551&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;572&quot; data-start=&quot;551&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;idx = 해당 정점 v&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;646&quot; data-start=&quot;575&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;weight = &lt;b&gt;그 정점까지의 누적 최단거리(현재 후보값)&lt;/b&gt; dist[v] (tentative distance)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;745&quot; data-start=&quot;647&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉 &lt;b&gt;같은 Vertex 타입을 사용하지만&lt;/b&gt; weight의 의미가 컨텍스트에 따라 달라짐 (edge weight vs. accumulated distance).&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758632326691&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        int N = Integer.parseInt(br.readLine());
        int M = Integer.parseInt(br.readLine());
        
        ArrayList&amp;lt;Vertex&amp;gt;[] graph = new ArrayList[N + 1];
        for (int i = 1; i &amp;lt;= N; i++) graph[i] = new ArrayList&amp;lt;&amp;gt;();
        
        for (int i = 0; i &amp;lt; M; i++) {
        	st = new StringTokenizer(br.readLine());
        	
        	int start = Integer.parseInt(st.nextToken());
        	int end = Integer.parseInt(st.nextToken());
        	int weight = Integer.parseInt(st.nextToken());
        	
        	graph[start].add(new Vertex(end, weight));
        }&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;/li&gt;
&lt;li&gt;이어서 M번동안 그래프의 연결 정보를 갱신해준다.&lt;/li&gt;
&lt;li&gt;이 문제에서는 무방향 그래프가 아니기 때문에 graph[start]에만 .add 해준 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758632548110&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;	int start = Integer.parseInt(st.nextToken());
    	int end = Integer.parseInt(st.nextToken());
    	
    	int[] dist = new int[N + 1];
    	Arrays.fill(dist, Integer.MAX_VALUE - 100_000);
    	
    	PriorityQueue&amp;lt;Vertex&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
    	boolean[] visited = new boolean[N + 1];
    	
    	pq.add(new Vertex(start, 0));
    	dist[start] = 0;
    	
    	while (!pq.isEmpty()) {
    		Vertex cur = pq.poll();
    		
    		if (visited[cur.idx]) continue;
    		visited[cur.idx] = true;
    		
    		for (Vertex next : graph[cur.idx]) {
    			if (dist[next.idx] &amp;gt; dist[cur.idx] + next.weight) {
    				dist[next.idx] = dist[cur.idx] + next.weight;
    				pq.add(new Vertex(next.idx, dist[next.idx]));
    			}
    		}
    	}&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;start: 시작 정점&lt;/li&gt;
&lt;li&gt;end: 도착 정점&lt;/li&gt;
&lt;li&gt;dist[]: dp 테이블, 특정 idx까지의 최단 거리를 기록함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음에는 모든 거리를 충분히 큰 수로 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;우선순위 큐에 시작 정점(가중치 0 -&amp;gt; 시작 지점이니까 거리가 0)을 추가&lt;/li&gt;
&lt;li&gt;시작 정점의 거리를 0으로 초기화(dist[start] = 0)&lt;/li&gt;
&lt;li&gt;이후 pq가 빌 때까지 다익스트라 핵심 로직 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1758633204941&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    	while (!pq.isEmpty()) {
    		Vertex cur = pq.poll();
    		
    		if (visited[cur.idx]) continue;
    		visited[cur.idx] = true;
    		
    		for (Vertex next : graph[cur.idx]) {
    			if (dist[next.idx] &amp;gt; dist[cur.idx] + next.weight) {
    				dist[next.idx] = dist[cur.idx] + next.weight;
    				pq.add(new Vertex(next.idx, dist[next.idx]));
    			}
    		}
    	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;502&quot; data-start=&quot;469&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. Vertex cur = pq.poll();&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;608&quot; data-start=&quot;503&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;542&quot; data-start=&quot;503&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;우선순위 큐에서 현재까지 비용이 가장 작은 정점을 꺼냄&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;608&quot; data-start=&quot;543&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;pq는 weight(현재까지의 비용) 기준으로 정렬되어 있어서 항상 &quot;가장 짧은 거리 후보&quot;가 먼저 나옴.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;656&quot; data-start=&quot;615&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. if (visited[cur.idx]) continue;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;750&quot; data-start=&quot;657&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;693&quot; data-start=&quot;657&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이미 방문했던 정점이면 다시 처리할 필요 없음&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;750&quot; data-start=&quot;694&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이유: 다익스트라에서는 한 번 최단거리가 확정된 노드는 더 이상 짧은 경로가 나올 수 없기 때문.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;791&quot; data-start=&quot;757&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. visited[cur.idx] = true;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;835&quot; data-start=&quot;792&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;835&quot; data-start=&quot;792&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 꺼낸 정점을 방문 처리 -&amp;gt; &quot;이 정점의 최단거리가 확정되었다&quot;는 뜻.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;894&quot; data-start=&quot;842&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. for (Vertex next : graph[cur.idx]) { ... }&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;940&quot; data-start=&quot;895&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;940&quot; data-start=&quot;895&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 정점(cur)과 연결된 &lt;b&gt;모든 인접 정점(next)&lt;/b&gt;을 확인.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1014&quot; data-start=&quot;947&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5. if (dist[next.idx] &amp;gt; dist[cur.idx] + next.weight) { ... }&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1165&quot; data-start=&quot;1015&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1165&quot; data-start=&quot;1015&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;조건 의미:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1165&quot; data-start=&quot;1028&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1074&quot; data-start=&quot;1028&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 next.idx까지 기록된 최단거리(dist[next.idx])&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1140&quot; data-start=&quot;1077&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;vs. cur까지 오는 최단거리 + cur &amp;rarr; next로 가는 간선 비용(next.weight)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1165&quot; data-start=&quot;1143&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;더 짧은 경로를 찾으면 갱신&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1227&quot; data-start=&quot;1172&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;6. dist[next.idx] = dist[cur.idx] + next.weight;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1288&quot; data-start=&quot;1228&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1228&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최단거리 배열 갱신.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1288&quot; data-start=&quot;1244&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&quot;현재까지 확인한 것보다 더 싸게(next.idx까지) 갈 수 있다&quot;는 뜻.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1350&quot; data-start=&quot;1295&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;7. pq.add(new Vertex(next.idx, dist[next.idx]));&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1416&quot; data-start=&quot;1351&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1374&quot; data-start=&quot;1351&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;갱신된 경로를 우선순위 큐에 넣음.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1416&quot; data-start=&quot;1375&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;나중에 이 next.idx도 꺼내서 그 주변 노드들을 확인하게 됨.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BOJ 1753 - 최단경로&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758633350259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class BOJ_1753 {
	public static class Vertex implements Comparable&amp;lt;Vertex&amp;gt; {
		int idx;
		int weight;
		
		public Vertex(int idx, int weight) {
			this.idx = idx;
			this.weight = weight;
		}

		@Override
		public int compareTo(Vertex o) {
			return Integer.compare(this.weight, o.weight);
		}
	}
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int V = Integer.parseInt(st.nextToken());
		int E = Integer.parseInt(st.nextToken());
		
		int start = Integer.parseInt(br.readLine());
		
		ArrayList&amp;lt;Vertex&amp;gt;[] graph = new ArrayList[V + 1];
		for (int i = 0; i &amp;lt;= V; i++) graph[i] = new ArrayList&amp;lt;&amp;gt;();
		
		for (int i = 0; i &amp;lt; E; i++) {
			st = new StringTokenizer(br.readLine());
			
			int u = Integer.parseInt(st.nextToken());
			int v = Integer.parseInt(st.nextToken());
			int w = Integer.parseInt(st.nextToken());
			
			graph[u].add(new Vertex(v, w));
		}
		
		int[] dist = new int[V + 1];
		Arrays.fill(dist, Integer.MAX_VALUE);
		
		PriorityQueue&amp;lt;Vertex&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
		boolean[] visited = new boolean[V + 1];
		
		pq.add(new Vertex(start, 0));
		dist[start] = 0;
		
		while (!pq.isEmpty()) {
			Vertex cur = pq.poll();
			if (visited[cur.idx]) continue;
			visited[cur.idx] = true;
			
			for (Vertex next : graph[cur.idx]) {
				if (dist[next.idx] &amp;gt; dist[cur.idx] + next.weight) {
					dist[next.idx] = dist[cur.idx] + next.weight;
					pq.add(new Vertex(next.idx, dist[next.idx]));
				}
			}
		}
		
		for (int i = 1; i &amp;lt;= V; i++) {
			if (dist[i] == Integer.MAX_VALUE) System.out.println(&quot;INF&quot;);
			else System.out.println(dist[i]);
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;코드 해설&lt;/blockquote&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;이전 문제는 시작 정점과 끝 정점을 입력 받아서 최종적으로는 끝 정점의 값만을 출력했다면 지금 문제는 dp 테이블의 모든 값들을 출력하는 문제임.&lt;/p&gt;</description>
      <category>Algorithm</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/51</guid>
      <comments>https://bitbard-dongni.tistory.com/51#entry51comment</comments>
      <pubDate>Tue, 23 Sep 2025 21:49:51 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 최소 신장 트리 - Prim 알고리즘 &amp;lt;Minimum Spanning Tree - Prim&amp;gt;</title>
      <link>https://bitbard-dongni.tistory.com/50</link>
      <description>&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;최소 신장 트리 - MST(Minimum Spanning Tree)&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;&lt;a href=&quot;https://bitbard-dongni.tistory.com/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.09.11 - [Algorithm] - [JAVA] 최소 신장 트리 - Kruskal 알고리즘 &lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757559329339&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;[JAVA] 최소 신장 트리 - Kruskal 알고리즘 &amp;lt;Minimum Spanning Tree - Kruskal&amp;gt;&quot; data-og-description=&quot;최소 신장 트리 - MST(Minimum Spanning Tree)최소 신장 트리란, 여러 스패닝 트리 중 간선들의 가중치의 총합이 가장 작은 트리를 이야기한다. 그렇다면 최소 신장 트리를 알려면 신장 트리(Spanning Tree)&quot; data-og-host=&quot;bitbard-dongni.tistory.com&quot; data-og-source-url=&quot;https://bitbard-dongni.tistory.com/47&quot; data-og-url=&quot;https://bitbard-dongni.tistory.com/47&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hnT2X/hyZIT7Sy7d/ZWuEpchqNbcxpk6vPgiHf1/img.jpg?width=800&amp;amp;height=754&amp;amp;face=0_0_800_754,https://scrap.kakaocdn.net/dn/dOLEit/hyZG6mFDNT/ljn1S6dxNudAOfL7RqNimK/img.jpg?width=800&amp;amp;height=754&amp;amp;face=0_0_800_754,https://scrap.kakaocdn.net/dn/bCjGhN/hyZIV5HnDJ/gUFM0M6XQNl4YawoHV9iYk/img.jpg?width=1105&amp;amp;height=999&amp;amp;face=0_0_1105_999&quot;&gt;&lt;a href=&quot;https://bitbard-dongni.tistory.com/47&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bitbard-dongni.tistory.com/47&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hnT2X/hyZIT7Sy7d/ZWuEpchqNbcxpk6vPgiHf1/img.jpg?width=800&amp;amp;height=754&amp;amp;face=0_0_800_754,https://scrap.kakaocdn.net/dn/dOLEit/hyZG6mFDNT/ljn1S6dxNudAOfL7RqNimK/img.jpg?width=800&amp;amp;height=754&amp;amp;face=0_0_800_754,https://scrap.kakaocdn.net/dn/bCjGhN/hyZIV5HnDJ/gUFM0M6XQNl4YawoHV9iYk/img.jpg?width=1105&amp;amp;height=999&amp;amp;face=0_0_1105_999');&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;[JAVA] 최소 신장 트리 - Kruskal 알고리즘 &amp;lt;Minimum Spanning Tree - Kruskal&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최소 신장 트리 - MST(Minimum Spanning Tree)최소 신장 트리란, 여러 스패닝 트리 중 간선들의 가중치의 총합이 가장 작은 트리를 이야기한다. 그렇다면 최소 신장 트리를 알려면 신장 트리(Spanning Tree)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bitbard-dongni.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;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot; data-end=&quot;413&quot; data-start=&quot;325&quot;&gt;Prim 알고리즘&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&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;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;쉽게 생각해보면, 크루스칼 알고리즘에서는 모든 간선의 정보를 오름차순으로 정렬해서 가중치가 가장 작은 간선들을 선택하는 방법이었지만 프림 알고리즘에서는 가까운 간선들 중에서도 특히 더 가까운 정점들을 위주로 선택해가면서 뻗어나가는 알고리즘이란 걸 생각하면 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;그림으로 예시를 확인해보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&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-filename=&quot;슬라이드1.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEH0Q3/btsQI4YT6t9/LrXh3dBGJvlN4ILvHAcDPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEH0Q3/btsQI4YT6t9/LrXh3dBGJvlN4ILvHAcDPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEH0Q3/btsQI4YT6t9/LrXh3dBGJvlN4ILvHAcDPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEH0Q3%2FbtsQI4YT6t9%2FLrXh3dBGJvlN4ILvHAcDPK%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드1.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;지금부터 한 단계씩 어떤 동작들이 일어나는지 확인해보면서 프림 알고리즘을 이해해보자.&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;b&gt;1번 노드&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드2.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AIu7r/btsQI8UxjQx/5AVkSAKhkTzl1oV7LuqZ8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AIu7r/btsQI8UxjQx/5AVkSAKhkTzl1oV7LuqZ8k/img.png&quot; data-alt=&quot;후보 간선은 우선순위 큐로, 가중치를 기준으로 삽입할 때 자동 정렬 된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AIu7r/btsQI8UxjQx/5AVkSAKhkTzl1oV7LuqZ8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAIu7r%2FbtsQI8UxjQx%2F5AVkSAKhkTzl1oV7LuqZ8k%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드2.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;후보 간선은 우선순위 큐로, 가중치를 기준으로 삽입할 때 자동 정렬 된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1번 노드를 선택했을 때, 1번 노드와 간선으로 연결된 노드들은 &lt;b&gt;2번, 3번, 4번 노드&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드3.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3gTOu/btsQIBCNurs/Ytvz2kYFgaKKfmcx58l11k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3gTOu/btsQIBCNurs/Ytvz2kYFgaKKfmcx58l11k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3gTOu/btsQIBCNurs/Ytvz2kYFgaKKfmcx58l11k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3gTOu%2FbtsQIBCNurs%2FYtvz2kYFgaKKfmcx58l11k%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드3.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;1번&lt;/b&gt;과 &lt;b&gt;3번&lt;/b&gt;을 연결하는 간선&lt;b&gt;(가중치 2)&lt;/b&gt; 이 선택된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 정렬 결과에 1번과 3번 노드를 잇는 간선을 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;(물론 여기서 임의로 간선을 추가해준건데, 실제로는 노드를 방문처리 한다거나 등의 방식들이 사용된다. 자세한건 후에 설명할 실제 코드 참고)&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 &lt;b&gt;1번&lt;/b&gt;과 &lt;b&gt;2번&lt;/b&gt;, &lt;b&gt;1번&lt;/b&gt;과 &lt;b&gt;4번&lt;/b&gt;을 연결하는 간선들은 선택할 수 없게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 1번 노드와 3번 노드의 방문 여부를 true로 업데이트 해야하기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드4.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bae9sJ/btsQKcPEuUf/vao8Fo00VHofxOp4VYzRPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bae9sJ/btsQKcPEuUf/vao8Fo00VHofxOp4VYzRPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bae9sJ/btsQKcPEuUf/vao8Fo00VHofxOp4VYzRPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbae9sJ%2FbtsQKcPEuUf%2Fvao8Fo00VHofxOp4VYzRPK%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드4.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제는&lt;b&gt; 3번&lt;/b&gt; 노드의 주위를 볼 차례이다. 마찬가지로&lt;b&gt; 3번&lt;/b&gt; 노드를 기준으로 &lt;b&gt;후보 간선&lt;/b&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-filename=&quot;슬라이드5.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w4KMK/btsQKRRVdqo/pgYawKJwE6MnTOfGlTriNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w4KMK/btsQKRRVdqo/pgYawKJwE6MnTOfGlTriNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w4KMK/btsQKRRVdqo/pgYawKJwE6MnTOfGlTriNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw4KMK%2FbtsQKRRVdqo%2FpgYawKJwE6MnTOfGlTriNK%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드5.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3번 노드의 후보 간선&lt;/b&gt;들 중, 가장 가중치가 낮은 &lt;b&gt;3번&lt;/b&gt; 노드와 &lt;b&gt;5번&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드6.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRHfCM/btsQIJAIQyA/JMlxqKa5kDZKvswsOC73cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRHfCM/btsQIJAIQyA/JMlxqKa5kDZKvswsOC73cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRHfCM/btsQIJAIQyA/JMlxqKa5kDZKvswsOC73cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRHfCM%2FbtsQIJAIQyA%2FJMlxqKa5kDZKvswsOC73cK%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드6.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 5번 노드를 기준으로 확인해보려고 하는데, 후보 간선이 될 수 있는 간선이 아예 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 다시 3번 노드를 기준으로 확인해야 한다.&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-filename=&quot;슬라이드7.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y7iyc/btsQIE0BUaI/m0RMKoiknssXx5INHn5Bx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y7iyc/btsQIE0BUaI/m0RMKoiknssXx5INHn5Bx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y7iyc/btsQIE0BUaI/m0RMKoiknssXx5INHn5Bx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy7iyc%2FbtsQIE0BUaI%2Fm0RMKoiknssXx5INHn5Bx1%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드7.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 &lt;b&gt;3번&lt;/b&gt; 노드를 기준으로, &lt;b&gt;2번&lt;/b&gt;과 &lt;b&gt;4번&lt;/b&gt;으로 연결되는 후보 간선이 이제는 2개가 존재한다.&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-filename=&quot;슬라이드8.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmc8Vw/btsQHQNPlHi/hGRQyR2ClQiJhZ1lO7iAA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmc8Vw/btsQHQNPlHi/hGRQyR2ClQiJhZ1lO7iAA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmc8Vw/btsQHQNPlHi/hGRQyR2ClQiJhZ1lO7iAA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmc8Vw%2FbtsQHQNPlHi%2FhGRQyR2ClQiJhZ1lO7iAA0%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드8.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 가중치이기에 임의로&lt;b&gt; 2번&lt;/b&gt; 노드로 가는&lt;b&gt; 후보 간선&lt;/b&gt;을 선택했다고 가정하고,&amp;nbsp; 정렬 결과에&lt;b&gt; 3번&lt;/b&gt; 노드와 &lt;b&gt;2번&lt;/b&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-filename=&quot;슬라이드9.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIljdI/btsQLaKt49Q/R8bqFRVzFbLrgVCSbwvZzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIljdI/btsQLaKt49Q/R8bqFRVzFbLrgVCSbwvZzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIljdI/btsQLaKt49Q/R8bqFRVzFbLrgVCSbwvZzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIljdI%2FbtsQLaKt49Q%2FR8bqFRVzFbLrgVCSbwvZzK%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드9.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드10.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhXIEO/btsQJBoKdt6/22YZDSL0jZ6bE0Anihke0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhXIEO/btsQJBoKdt6/22YZDSL0jZ6bE0Anihke0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhXIEO/btsQJBoKdt6/22YZDSL0jZ6bE0Anihke0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhXIEO%2FbtsQJBoKdt6%2F22YZDSL0jZ6bE0Anihke0K%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드10.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드11.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SHVNL/btsQJyr2V8F/YHoJzPMzxMCt3awzbMA5j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SHVNL/btsQJyr2V8F/YHoJzPMzxMCt3awzbMA5j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SHVNL/btsQJyr2V8F/YHoJzPMzxMCt3awzbMA5j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSHVNL%2FbtsQJyr2V8F%2FYHoJzPMzxMCt3awzbMA5j0%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드11.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마지막으로 &lt;b&gt;4번&lt;/b&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-filename=&quot;슬라이드12.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G6OXq/btsQLiPbC54/q513xHQrwA7jj2CQodBYt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G6OXq/btsQLiPbC54/q513xHQrwA7jj2CQodBYt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G6OXq/btsQLiPbC54/q513xHQrwA7jj2CQodBYt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG6OXq%2FbtsQLiPbC54%2Fq513xHQrwA7jj2CQodBYt0%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드12.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬라이드13.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXf6D3/btsQIzSyGHi/66OkOIcZpHIiMyEGGuANLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXf6D3/btsQIzSyGHi/66OkOIcZpHIiMyEGGuANLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXf6D3/btsQIzSyGHi/66OkOIcZpHIiMyEGGuANLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXf6D3%2FbtsQIzSyGHi%2F66OkOIcZpHIiMyEGGuANLk%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;1280&quot; height=&quot;720&quot; data-filename=&quot;슬라이드13.PNG&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간선의 개수가 (노드의 개수 - 1개)를 만족했기 때문에 여기서 종료되고 프림 알고리즘을 통한 MST 구현을 성공하게 된다.&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;사실, &lt;b&gt;엄밀히 따지자면&lt;/b&gt; 각 정점을 기준으로 PQ를 생성해서 사용하는 것이 아니기 때문에 &lt;b&gt;이전 간선들의 정보가 모두 저장이 되어있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위와 같은 방법으로 진행된다기 보다는 실제로는 &lt;b&gt;간선을 꺼낼 때, 간선과 연결된 노드가 이미 방문되었다면 해당 간선을 스킵하는 방식으로 구현이 되어있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사실 &lt;b&gt;5번&lt;/b&gt; 노드에서 &lt;b&gt;3번&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;결국엔, PQ에 이전까지의 모든 간선 정보가 담겨있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;3번 노드에서 5번 노드로 가는 간선&lt;/i&gt;을 선택 -&amp;gt; 5번 노드에서는 아무런 간선도 추가하지 않음 -&amp;gt; &lt;i&gt;3번 노드에서 다른 노드로 가는 간선&lt;/i&gt;을 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 5번 노드에서 아무런 간선도 추가하지 않았기 때문에 자연스럽게 다시 이어서 &lt;i&gt;3번 노드에서 다른 노드로 가는 간선&lt;/i&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;i&gt;3번 노드에서 다른 노드로 가는 간선&lt;/i&gt;보다 더 짧은 가중치의 간선들이 존재한다면 그 간선들이 꺼내져서 선택되겠지만, 실제로 그 간선들의 출발 노드와 도착 노드가 이미 방문 처리 되어있을 것이기 때문에 스킵되고 &lt;i&gt;3번 노드에서 다른 노드로 가는 간선&lt;/i&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Prim 알고리즘 동작 원리&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1.&lt;span&gt; 인접 리스트에 &lt;b&gt;연결 정보 저장&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;2.&lt;span&gt; 시작 노드를 기준으로 &lt;b&gt;시작 노드와 연결된 간선&lt;/b&gt;을 &lt;/span&gt;&lt;b&gt;PQ에 삽입&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. 해당 간선의 &lt;b&gt;도착 노드를 방문 처리&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;4.&lt;b&gt;&lt;span&gt;&lt;span&gt; 가중치 합 갱신, 선택된 정점 개수 갱신&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;5. &lt;b&gt;다음 노드&lt;/b&gt;와 &lt;b&gt;연결된&lt;/b&gt; &lt;b&gt;다다음 노드&lt;/b&gt;들에 대해서 &lt;b&gt;간선 후보를 PQ에 삽입&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;6. &lt;b&gt;모든 정점&lt;/b&gt;이&lt;b&gt; 선택&lt;/b&gt;&amp;nbsp;될 때까지 &lt;b&gt;3 ~ 5번 과정 반복&lt;/b&gt;&lt;/span&gt;&lt;/span&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;nbsp;&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;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;코드 전문&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SWEA 3124 - 최소 스패닝 트리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757559203101&quot; class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

/**
 * @link: https://swexpertacademy.com/main/talk/solvingClub/problemView.do?solveclubId=AZgruskKCQnHBIT9&amp;amp;contestProbId=AV_mSnmKUckDFAWb&amp;amp;probBoxId=AZjkGae6SiPHBIO0+&amp;amp;type=PROBLEM&amp;amp;problemBoxTitle=0826&amp;amp;problemBoxCnt=++3+
 * @performance: 2,018 ms, 165,272 kb
 * @note:
 * - 프림 알고리즘은 기본적으로 무방향 그래프인 걸 명심
 * 		- 진입, 진출 간선 중 하나라도 해당됐으면 정점 자체를 방문처리해야 함
 * - 정점도 굳이 간선 배열을 사용할 필요는 없음. -&amp;gt; 인접 리스트를 이용하면 됨
 * - 왜 프림은 인접 리스트고 크루스칼은 간선 배열을 썼을까?
 * 		- 크루스칼 알고리즘은 주어진 모든 간선을 오름차순 정렬해주어야 함.
 * 		- 프림 알고리즘은 시작 정점부터 가까운 정점마다의 간선을 차례대로 확인해야 함.
 * - 두 알고리즘 모두 MST 알고리즘이지만, 무엇을 중점적으로 보느냐가 다름.
 * - 크루스칼은 간선에게 포커싱을, 프림은 정점에게 포커싱을 맞춰서 둘의 구현 방법도 달라짐.
 * - 그냥 쉽게 생각해보면, 특정 정점에서 다른 정점들로 뻗어 나갈 때는 인접 행렬 또는 인접 리스트가 떠오르는데
 * - 인접 행렬보다 조금 더 공간적으로 활용하기 좋은 인접 리스트를 많이 사용하는 게 정석적임.
 * - Q.E.D.
 * @timecomplex: 
 */
public class SWEA_3124_PRIM {
	public static int V;
	public static int E;
	
	public static class Node implements Comparable&amp;lt;Node&amp;gt;{
		int to;
		long weight;
		
		public Node(int to, long weight) {
			this.to = to;
			this.weight = weight;
		}

		@Override
		public int compareTo(Node o) {
//			return this.weight - o.weight;
			return Long.compare(this.weight, o.weight);
		}
	}
	
	static long result;
	static boolean[] visited;
	static List&amp;lt;Node&amp;gt;[] graph;

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		StringTokenizer st;
		int T = Integer.parseInt(br.readLine());
		
		for (int test_case = 1; test_case &amp;lt;= T; test_case++) {
			st = new StringTokenizer(br.readLine());
			
			V = Integer.parseInt(st.nextToken());
			E = Integer.parseInt(st.nextToken());
			result = 0;
			
			visited = new boolean[V + 1];
			
			// 정점을 저장할 인접 리스트
			graph = new ArrayList[V + 1];
            for (int i = 1; i &amp;lt;= V; i++) {
            	graph[i] = new ArrayList&amp;lt;&amp;gt;();
            }
            
			// 입력
			for (int i = 1; i &amp;lt;= E; i++) {
				st = new StringTokenizer(br.readLine());
				int f = Integer.parseInt(st.nextToken());
				int t = Integer.parseInt(st.nextToken());
				int w = Integer.parseInt(st.nextToken());
                graph[f].add(new Node(t, w));
                graph[t].add(new Node(f, w));
			}

			long result = prim(1);
			
			sb.append(&quot;#&quot;).append(test_case).append(&quot; &quot;).append(result).append(&quot;\n&quot;);
		}
		System.out.print(sb);
	}


	private static long prim(int start) {
		PriorityQueue&amp;lt;Node&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
		pq.add(new Node(start, 0)); // 시작 노드는 가중치가 0
		
		long sum = 0;
		long cnt = 0;
		
		while(!pq.isEmpty()) {
			Node cur = pq.poll();
			if (visited[cur.to]) continue;
			
			visited[cur.to] = true;
			sum += cur.weight;
			cnt++;
			
			if (cnt == V) break;
			
			for (Node next : graph[cur.to]) {
				if (!visited[next.to]) {
					pq.add(next);
				}
			}
		}
		
		return sum;
	}
}&lt;/code&gt;&lt;/pre&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;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;코드 해설&lt;/blockquote&gt;
&lt;pre id=&quot;code_1757559203107&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static long prim(int start) {
		PriorityQueue&amp;lt;Node&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
		pq.add(new Node(start, 0)); // 시작 노드는 가중치가 0
		
		long sum = 0;
		long cnt = 0;
		
		while(!pq.isEmpty()) {
			Node cur = pq.poll();
			if (visited[cur.to]) continue;
			
			visited[cur.to] = true;
			sum += cur.weight;
			cnt++;
			
			if (cnt == V) break;
			
			for (Node next : graph[cur.to]) {
				if (!visited[next.to]) {
					pq.add(next);
				}
			}
		}
		
		return sum;
	}&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;PriorityQueue&amp;lt;Node&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;();
&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;b&gt;우선순위 큐(PQ)를 준비&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;pq.add(new Node(start, 0)); // 시작 노드는 가중치가 0
&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;시작 정점을 MST에 포함시키기 위해 &lt;b&gt;가중치 0으로 PQ에 삽입&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이렇게 하면 첫 단계에서 자동으로 시작 정점이 선택되게 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;while(!pq.isEmpty()) {
&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;b&gt;큐가 빌 때까지 반복&lt;/b&gt;하면서 MST를 확장&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Node cur = pq.poll();
&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;b&gt;현재까지 후보 간선 중 최소 가중치를 가진 간선&lt;/b&gt;을 꺼냄&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;if (visited[cur.to]) continue;
&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;꺼낸 간선이 이미 MST에 포함된 정점(이전에 이미 연결된 노드 정보)으로 향하면 &lt;b&gt;무시&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;중복 방문이나 사이클 생성을 막는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;visited[cur.to] = true;
&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;b&gt;MST에 포함시키고 방문 처리&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;sum += cur.weight;
cnt++;&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;/li&gt;
&lt;li&gt;선택된 정점의 개수(cnt)를 증가시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (cnt == V) break;
&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;모든 정점이 MST에 포함되었다면 MST 완성 (종료 조건)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;for (Node next : graph[cur.to]) {
    if (!visited[next.to]) {
        pq.add(next);
    }
}
&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;b&gt;갈 수 있는 모든 간선&lt;/b&gt;을 확인&lt;/li&gt;
&lt;li&gt;아직 MST에 포함되지 않은 정점으로 향하는 간선만 PQ에 넣어 &lt;b&gt;후보 간선으로 관리&lt;/b&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;</description>
      <category>Algorithm</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/50</guid>
      <comments>https://bitbard-dongni.tistory.com/50#entry50comment</comments>
      <pubDate>Mon, 22 Sep 2025 23:54:44 +0900</pubDate>
    </item>
    <item>
      <title>[HTTP] sendRedirect() vs forward() 차이점 간단 비교</title>
      <link>https://bitbard-dongni.tistory.com/49</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;sendRedirect() vs forward()&lt;/blockquote&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;밑바닥부터 쌓아 올라가는 느낌으로 한 번 업로드해보려고 한다.&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;아 근데 물론 리다이렉션 되는거 vs 안되는 거 처럼 막 너무 간단하고 추상적인 비교 말고 좀 더 깊게 비교해보려고 한다.&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;현재 서블릿을 학습 중이므로 서블릿을 기준으로 코드를 작성했지만, HTTP 웹 개발 전반에 해당되는 개념임을 명심하자.&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;blockquote data-ke-style=&quot;style2&quot;&gt;sendRedirect()&amp;nbsp;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1758380927892&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.sendRedirect(request.getContextPath() + &quot;/regist.html&quot;);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;710&quot; data-start=&quot;537&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;591&quot; data-start=&quot;537&quot;&gt;&lt;b&gt;HTTP 302 응답&lt;/b&gt;을 보내고, 브라우저가 다시 /regist.html로 요청.&lt;/li&gt;
&lt;li data-end=&quot;636&quot; data-start=&quot;592&quot;&gt;즉, &lt;b&gt;두 번의 요청&lt;/b&gt;이 발생한다. (redirect 응답 -&amp;gt; 새 요청)&lt;/li&gt;
&lt;li data-end=&quot;656&quot; data-start=&quot;637&quot;&gt;&lt;b&gt;주소창 URL이 바뀜.&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;710&quot; data-start=&quot;657&quot;&gt;request 객체가 초기화되므로 setAttribute()로 전달한 값은 사라짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;726&quot; data-start=&quot;712&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt; sendRedirect&lt;/span&gt;() 언제 쓰면 좋은가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;850&quot; data-start=&quot;727&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;770&quot; data-start=&quot;727&quot;&gt;&lt;b&gt;URL을 바꿔야 할 때&lt;/b&gt; (사용자에게 최종 주소를 보여주고 싶을 때)&lt;/li&gt;
&lt;li data-end=&quot;825&quot; data-start=&quot;771&quot;&gt;&lt;b&gt;PRG 패턴(Post-Redirect-Get)&lt;/b&gt; -&amp;gt; 폼 제출 후 새로고침 중복 전송 방지&lt;/li&gt;
&lt;li data-end=&quot;850&quot; data-start=&quot;826&quot;&gt;다른 컨텍스트나 외부 사이트로 이동할 때&lt;/li&gt;
&lt;li data-end=&quot;850&quot; data-start=&quot;826&quot;&gt;로그인 성공 후 메인 페이지로 이동하는 느낌 + 회원 가입 폼을 중복 제출하는 것을 방지하는 느낌&lt;/li&gt;
&lt;/ul&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;blockquote data-ke-style=&quot;style2&quot;&gt;forward()&lt;/blockquote&gt;
&lt;pre id=&quot;code_1758380940190&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.getRequestDispatcher(&quot;/regist.html&quot;).forward(request, response);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1273&quot; data-start=&quot;1096&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1118&quot; data-start=&quot;1096&quot;&gt;&lt;b&gt;서버 내부에서 요청을 넘김.&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1165&quot; data-start=&quot;1119&quot;&gt;주소창 URL 안 바뀜.&lt;/li&gt;
&lt;li data-end=&quot;1243&quot; data-start=&quot;1166&quot;&gt;request와 response가 그대로 유지 -&amp;gt; setAttribute()로 전달한 데이터 JSP에서 바로 꺼낼 수 있음.&lt;/li&gt;
&lt;li data-end=&quot;1273&quot; data-start=&quot;1244&quot;&gt;네트워크 요청은 한 번만 발생&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-end=&quot;1289&quot; data-start=&quot;1275&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;forward()&lt;/span&gt; &lt;/span&gt;언제 쓰면 좋은가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1367&quot; data-start=&quot;1290&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1318&quot; data-start=&quot;1290&quot;&gt;&lt;b&gt;데이터를 JSP로 넘겨서 출력해야 할 때&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1346&quot; data-start=&quot;1319&quot;&gt;같은 요청 안에서 결과 화면을 바로 보여줄 때&lt;/li&gt;
&lt;li data-end=&quot;1367&quot; data-start=&quot;1347&quot;&gt;주소창이 바뀌지 않아도 되는 경우&lt;/li&gt;
&lt;li data-end=&quot;1367&quot; data-start=&quot;1347&quot;&gt;로그인 실패 시에 같은 화면에 사용자가 입력한 값과 에러 메시지 등을 다시 그대로 화면에 뿌려주는 느낌&lt;/li&gt;
&lt;/ul&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;style2&quot; /&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;1916&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1fJue/btsQGDOiixo/hmh6UZfJxc7yp5kaMapl0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1fJue/btsQGDOiixo/hmh6UZfJxc7yp5kaMapl0K/img.png&quot; data-alt=&quot;왼쪽은 forward(), 오른쪽은 sendRedirect()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1fJue/btsQGDOiixo/hmh6UZfJxc7yp5kaMapl0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1fJue%2FbtsQGDOiixo%2Fhmh6UZfJxc7yp5kaMapl0K%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;1916&quot; height=&quot;746&quot; data-origin-width=&quot;1916&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;왼쪽은 forward(), 오른쪽은 sendRedirect()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 예시를 확인해보면, forward()의 경우에는 하나의 HTTP 요청만이 발생했고 sendRedirect()의 경우는 두 번의 요청이 발생했음을 확인할 수 있음.&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;또한 sendRedirect()의 경우에는 HTTP 302 상태 코드를 반환하는 것을 볼 수 있음.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt; 302는 Found 또는 임시 리디렉션(Temporary Redirect)을 나타내는 코드 &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;&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;차이를 잘 알고 사용하자.&lt;/p&gt;</description>
      <category>HTTP/TIL</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/49</guid>
      <comments>https://bitbard-dongni.tistory.com/49#entry49comment</comments>
      <pubDate>Sun, 21 Sep 2025 01:11:00 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Virtualization support not detectedDocker Desktop requires virtualization support to run. Contact your IT admin to enable virtualization or check system requirements. 오류 해결</title>
      <link>https://bitbard-dongni.tistory.com/48</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;도커 실습을 위해 docker desktop 프로그램을 실행해보았는데 아래와 같은 오류가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MiER1/btsQCOihFX8/8af4CqzyDCMzergaAchdI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MiER1/btsQCOihFX8/8af4CqzyDCMzergaAchdI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MiER1/btsQCOihFX8/8af4CqzyDCMzergaAchdI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMiER1%2FbtsQCOihFX8%2F8af4CqzyDCMzergaAchdI0%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;1270&quot; height=&quot;720&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;720&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; PC에서 하드웨어 가상화(virtualization)가 꺼져 있어서 Docker Desktop이 아예 실행되지 않는 상태&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;이를 해결하기 위해서는 일단 PC의 가상화 기능을 켜주어야 하는데, 우선 가상화가 켜져있는지를 확인하려면 Windows PowerShell에서 다음과 같은 명령어를 입력해보면 된다.&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;systeminfo&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-origin-width=&quot;935&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBR3Ek/btsQFM4k1v8/e8vBe4KCJ9AyJZEVjKJkB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBR3Ek/btsQFM4k1v8/e8vBe4KCJ9AyJZEVjKJkB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBR3Ek/btsQFM4k1v8/e8vBe4KCJ9AyJZEVjKJkB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBR3Ek%2FbtsQFM4k1v8%2Fe8vBe4KCJ9AyJZEVjKJkB1%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;935&quot; height=&quot;181&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;181&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;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;52&quot; data-end=&quot;130&quot;&gt;&lt;b&gt;Virtualization Enabled In Firmware: Yes&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; BIOS에서 가상화(VT-x/AMD-V) 켜져 있음&lt;/li&gt;
&lt;li data-start=&quot;131&quot; data-end=&quot;187&quot;&gt;&lt;b&gt;VM Monitor Mode Extensions: Yes&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; CPU가 가상화 기능 지원&lt;/li&gt;
&lt;li data-start=&quot;188&quot; data-end=&quot;262&quot;&gt;&lt;b&gt;Second Level Address Translation: Yes&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; WSL2, Hyper-V에 필요한 SLAT 지원&lt;/li&gt;
&lt;li data-start=&quot;263&quot; data-end=&quot;323&quot;&gt;&lt;b&gt;Data Execution Prevention Available: Yes&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; DEP 켜져 있음&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;color: #000000;&quot;&gt;WSL2(&lt;b&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; text-align: start;&quot;&gt;Windows Subsystem for Linux 2&lt;/span&gt;&lt;/b&gt;)&lt;/span&gt;환경이 세팅되어 있지 않다는 말이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Windows Home 에디션의 경우, Docker Desktop을 실행하려면 WSL2가 필요하기 때문에 WSL2가 준비되어 있어야 엔진이 켜지고 docker pull과 같은 명령어들을 사용할 수 있게 된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;해결 방법&lt;/blockquote&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 data-end=&quot;247&quot; data-start=&quot;89&quot;&gt;&lt;b&gt;관리자 권한으로 PoswerShell 실행&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;247&quot; data-start=&quot;89&quot;&gt;&lt;b&gt;wsl.exe --install Ubuntu 명령어 입력&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;247&quot; data-start=&quot;89&quot;&gt;wsl --install -d Ubuntu-22.04&lt;br /&gt;&amp;rarr; 자동으로 필요한 Windows 선택적 기능 설치 시작&lt;/li&gt;
&lt;li data-end=&quot;506&quot; data-start=&quot;370&quot;&gt;&lt;b&gt;&quot;가상 머신 플랫폼&quot; 기능 켜기&lt;/b&gt;
&lt;div&gt;&lt;span&gt;dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart&lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-end=&quot;792&quot; data-start=&quot;647&quot;&gt;&lt;b&gt;WSL 필수 구성 요소 켜기&lt;/b&gt;
&lt;div&gt;&lt;span&gt;dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart &lt;/span&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li data-end=&quot;817&quot; data-start=&quot;794&quot;&gt;명령어 모두 실행한 뒤 &lt;b&gt;재부팅&lt;/b&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1758212398669&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS C:\WINDOWS\system32&amp;gt; wsl --status &amp;gt;&amp;gt;
기본 버전: 2 현재 컴퓨터 구성에서는 WSL1이 지원되지 않습니다.
WSL1을 사용하려면 &quot;Windows Subsystem for Linux&amp;rdquo; 선택적 구성 요소를 사용하세요.
WSL2는 현재 컴퓨터 구성에서 지원되지 않습니다.
&quot;가상 머신 플랫폼&quot; 선택적 구성 요소를 사용하도록 설정하고
BIOS에서 가상화가 사용하도록 설정되어 있는지 확인하세요.

실행하여 &quot;가상 머신 플랫폼&quot;을 사용하도록 설정: wsl.exe --install --no-distribution
자세한 내용은 https://aka.ms/enablevirtualization 참조하세요. PS C:\WINDOWS\system32&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;위와 같이 WSL이 잘 설치가 되지 않은듯한 상태였음.&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;Hyper-V 계열 하이퍼바이저도 활성화 돼있고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU도 Intel VT-x를 지원하는 모델이고..&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;윈도우의 기능은 모두 켜져있는데 WSL2 커널이 올라오질 못했음.&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;WSL 엔진 초기화도 해보고 다른 버전으로 다운도 받아보고 WSL 패키지 자체를 삭제하고 재설치도 해봤는데 모두 실패.&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;VMware Workstation Player, VirtualBox 같이 하이퍼바이저 사용하는 프로그램이 있다면 충돌이 날 수도 있다는 내용을 보고 VMware 17 버전을 삭제해주었더니...&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;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crW0Du/btsQDucMySX/DWMF1aPIKiqTKnSw33vsNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crW0Du/btsQDucMySX/DWMF1aPIKiqTKnSw33vsNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crW0Du/btsQDucMySX/DWMF1aPIKiqTKnSw33vsNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrW0Du%2FbtsQDucMySX%2FDWMF1aPIKiqTKnSw33vsNK%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;1253&quot; height=&quot;702&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;702&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;&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;979&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TwHTa/btsQGRRoVDk/dO1hxgxA4upO0SsCSfZim1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TwHTa/btsQGRRoVDk/dO1hxgxA4upO0SsCSfZim1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TwHTa/btsQGRRoVDk/dO1hxgxA4upO0SsCSfZim1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTwHTa%2FbtsQGRRoVDk%2FdO1hxgxA4upO0SsCSfZim1%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;979&quot; height=&quot;392&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;392&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 VMware를 사용해본 적 있다면 삭제해보시길 권장.&lt;/p&gt;</description>
      <category>Docker/TIL</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/48</guid>
      <comments>https://bitbard-dongni.tistory.com/48#entry48comment</comments>
      <pubDate>Fri, 19 Sep 2025 01:24:37 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 최소 신장 트리 - Kruskal 알고리즘 &amp;lt;Minimum Spanning Tree - Kruskal&amp;gt;</title>
      <link>https://bitbard-dongni.tistory.com/47</link>
      <description>&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;최소 신장 트리 - MST(Minimum Spanning Tree)&lt;/blockquote&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;그렇다면 최소 신장 트리를 알려면 신장 트리(Spanning Tree)를 알아야 하는데, &lt;b&gt;스패닝 트리(신장 트리)&lt;/b&gt;란 &lt;b&gt;모든 정점&lt;/b&gt;을 연결했을 때 &lt;b&gt;간선의 수가 V - 1개&lt;/b&gt;이고 &lt;b&gt;사이클이 존재 하지 않는 무방향 그래프&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-5&quot; data-message-id=&quot;20ce2ee5-d4ff-4357-bcb7-673f941e6f24&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9FA51903-AF03-4AF1-BB49-3F02AA78A37E.jpg&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NdTHu/btsQskOK47m/itoJMwkFv9kA5LU5TN7wJ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NdTHu/btsQskOK47m/itoJMwkFv9kA5LU5TN7wJ1/img.jpg&quot; data-alt=&quot;신장 트리 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NdTHu/btsQskOK47m/itoJMwkFv9kA5LU5TN7wJ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNdTHu%2FbtsQskOK47m%2FitoJMwkFv9kA5LU5TN7wJ1%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;1061&quot; height=&quot;1000&quot; data-filename=&quot;9FA51903-AF03-4AF1-BB49-3F02AA78A37E.jpg&quot; data-origin-width=&quot;1061&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;신장 트리 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;모든 정점이 연결되었고, 5개의 정점에 대해서 4개의 간선만을 가지고 있으며, 사이클이 존재하지 않는 무방향 그래프임을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;간선에 적혀있는 숫자들은 간선의 &lt;b&gt;가중치&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&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-filename=&quot;6F838C9B-70CC-4C98-8755-2AF3ABAF59E4.jpg&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;999&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SAWWh/btsQuaRAS3T/JN8KaBBGQwz1IgTeFJUN4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SAWWh/btsQuaRAS3T/JN8KaBBGQwz1IgTeFJUN4K/img.jpg&quot; data-alt=&quot;최소 신장 트리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SAWWh/btsQuaRAS3T/JN8KaBBGQwz1IgTeFJUN4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSAWWh%2FbtsQuaRAS3T%2FJN8KaBBGQwz1IgTeFJUN4K%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;1105&quot; height=&quot;999&quot; data-filename=&quot;6F838C9B-70CC-4C98-8755-2AF3ABAF59E4.jpg&quot; data-origin-width=&quot;1105&quot; data-origin-height=&quot;999&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최소 신장 트리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 그림의 경우, 기존에 &lt;b&gt;1번 정점&lt;/b&gt;과 &lt;b&gt;3번 정점&lt;/b&gt;을 연결하는 &lt;b&gt;가중치 3의 간선&lt;/b&gt;대신&lt;b&gt; 2번 정점&lt;/b&gt;과 &lt;b&gt;3번 정점&lt;/b&gt;을 연결하는 &lt;b&gt;가중치 2&lt;/b&gt;의 간선을 선택해서 &lt;b&gt;이 경우보다 가중치가 작은 간선을 선택하는 경우가 없다&lt;/b&gt;고 가정해보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 상황이 바로 최소 신장 트리의 예시를 의미한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;최소 신장 트리의 구현&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;최소 신장 트리는 이렇게 쉬운 개념을 가지고 있으며, 구현하는 방법은 2가지가 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;Kruskal 알고리즘&lt;/li&gt;
&lt;li data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;Prim 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-start=&quot;325&quot; data-end=&quot;413&quot; data-ke-style=&quot;style2&quot;&gt;Kruskal 알고리즘&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;크루스칼 알고리즘의 경우 이전에 설명한 Union-Find 알고리즘을 활용해서 전개시킬 수 있다.&lt;/p&gt;
&lt;blockquote data-start=&quot;325&quot; data-end=&quot;413&quot; data-ke-style=&quot;style3&quot;&gt;왜 유니온 파인드 알고리즘을 적용시킬까?&lt;br /&gt;-&amp;gt; &lt;b&gt;유니온 파인드 알고리즘&lt;/b&gt;의 적용 예시 중 하나가 바로 &lt;b&gt;사이클 판별&lt;/b&gt;인데, 최소 신장 트리의 조건인 &lt;b&gt;무방향 그래프&lt;/b&gt;를 구현하기 위해서 &lt;b&gt;유니온 파인드 알고리즘&lt;/b&gt;을 적용시키는 것.&lt;br /&gt;-&amp;gt; 결국, &lt;b&gt;두 정점&lt;/b&gt;이 &lt;b&gt;이미 같은 트리(집합)&lt;/b&gt;라면 그 간선은 &lt;b&gt;선택하지 않는다&lt;/b&gt;는 의미를 함축함.&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;325&quot; data-end=&quot;413&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bitbard-dongni.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.09.05 - [분류 전체보기] - [JAVA] 유니온 파인드 알고리즘 &lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1757556703986&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;[JAVA] 유니온 파인드 알고리즘 &amp;lt;Disjoint Set&amp;gt;&quot; data-og-description=&quot;유니온 파인드 알고리즘유니온 파인드(Union-Find) 알고리즘은 서로소 집합(Disjoint Set Union, DSU) 자료구조를 이용해 원소들이 같은 집합에 속하는지 판별하거나, 두 집합을 하나로 합치는 연산을 효&quot; data-og-host=&quot;bitbard-dongni.tistory.com&quot; data-og-source-url=&quot;https://bitbard-dongni.tistory.com/46&quot; data-og-url=&quot;https://bitbard-dongni.tistory.com/46&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qVDF0/hyZIQXAK5S/u95p3oxmWHrSD0xbiB0xLK/img.jpg?width=800&amp;amp;height=263&amp;amp;face=0_0_800_263,https://scrap.kakaocdn.net/dn/c7EaER/hyZI5zEggv/tkCTE4DGo2ZzOA7Nf9URHk/img.jpg?width=800&amp;amp;height=263&amp;amp;face=0_0_800_263,https://scrap.kakaocdn.net/dn/ntUww/hyZIRPH6fo/S3vY4od31LVsXaOZHABY2K/img.jpg?width=3999&amp;amp;height=742&amp;amp;face=0_0_3999_742&quot;&gt;&lt;a href=&quot;https://bitbard-dongni.tistory.com/46&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bitbard-dongni.tistory.com/46&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qVDF0/hyZIQXAK5S/u95p3oxmWHrSD0xbiB0xLK/img.jpg?width=800&amp;amp;height=263&amp;amp;face=0_0_800_263,https://scrap.kakaocdn.net/dn/c7EaER/hyZI5zEggv/tkCTE4DGo2ZzOA7Nf9URHk/img.jpg?width=800&amp;amp;height=263&amp;amp;face=0_0_800_263,https://scrap.kakaocdn.net/dn/ntUww/hyZIRPH6fo/S3vY4od31LVsXaOZHABY2K/img.jpg?width=3999&amp;amp;height=742&amp;amp;face=0_0_3999_742');&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;[JAVA] 유니온 파인드 알고리즘 &amp;lt;Disjoint Set&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;유니온 파인드 알고리즘유니온 파인드(Union-Find) 알고리즘은 서로소 집합(Disjoint Set Union, DSU) 자료구조를 이용해 원소들이 같은 집합에 속하는지 판별하거나, 두 집합을 하나로 합치는 연산을 효&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bitbard-dongni.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;기본적으로 정점과 간선에 대한 정보를 이용해서 &lt;b&gt;간선&lt;/b&gt;에게 초점을 맞춰서 접근면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말이 지금은 별로 와닿지 않는데, Prim 알고리즘을 보고 나면 왜 하필 간선에 초점을 맞춰야 한다고 말했는지 느낄 수 있게 된다.&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;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Kruskal 알고리즘 동작 원리&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;간선 배열을 만들어서 입력 정보 전처리&lt;/b&gt;&lt;br /&gt;2. &lt;b&gt;유니온 파인드 로직 - make()&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. 간선 배열을 가중치를 기준으로 오름차순 정렬&lt;br /&gt;4.&lt;span&gt; 유니온 파인드 로직 - find(), union()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;코드 전문&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SWEA 3124 - 최소 스패닝 트리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757048687049&quot; class=&quot;java&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package src;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

/**
 * @link: https://swexpertacademy.com/main/talk/solvingClub/problemView.do?solveclubId=AZgruskKCQnHBIT9&amp;amp;contestProbId=AV_mSnmKUckDFAWb&amp;amp;probBoxId=AZjkGae6SiPHBIO0+&amp;amp;type=PROBLEM&amp;amp;problemBoxTitle=0826&amp;amp;problemBoxCnt=++3+
 * @performance: 2,184 ms, 115,960 kb
 * @note: Union-Find 복습 + Kruskal 복습
 * - Union-Find 로직에 Rank를 통한 최적화 및 경로 압축 최적화 적용
 * - 최대한 정석에 가깝게 구현했으니 해당 코드를 바탕으로 응용 연습해보면 될 듯.
 * - 물론 그냥 내 생각일 뿐
 * - 원래는 N개의 노드에 대해서 가장 저렴한 가중치를 가진 N - 1 개의 간선을 선택했을 때
 * - MST를 만족하지 못하는 경우가 있을거라는 이상한 착각에 빠졌었음 (혹시나 연결 안된 노드가 있다면? 노드에서 간선이 여러개 빠져 나가면 특정 노드는 연결 안되는 거 아님?)
 * @timecomplex: 아커만역함수 머시기 아무튼 빠름 근데 Arrays.sort() 써서 O(NlogN)일듯
 */
public class SWEA_3124 {
	// Union-Find
	public static int[] parents;
	
	public static void make() {
		for (int i = 0; i &amp;lt;= V; i++) {
			parents[i] = -1; // Union By Rank
		}
	}
	
	public static int find(int cur) {
		if (parents[cur] &amp;lt; 0) return cur; // 값이 -1이면 루트 노드를 의미, -2 이하이면 랭크가 증가한 부모 노드를 의미
		return parents[cur] = find(parents[cur]); // 경로 압축
	}
	
	public static boolean union(int a, int b) {
		int aP = find(a);
		int bP = find(b);
		
		// 이미 같은 집합
		if (aP == bP) return false;
		
		// 랭크가 서로 다를 때 (b가 더 큰 랭크) -&amp;gt; 왜 스왑? -&amp;gt; 자식으로 만드는 부분을 하나의 코드로 통일
		if (parents[aP] &amp;gt; parents[bP]) {
			// swap
			int temp = aP;
			aP = bP;
			bP = temp;
		}
		
		// 랭크가 서로 같을 때 -&amp;gt; a의 Rank를 증가 (-1, -2, -3 ... 작아질 수록 랭크가 큼)
		if (parents[aP] == parents[bP]) {
			parents[aP]--;
		}

		// 기본적으로는 b를 a의 자식으로 만듦.
		parents[bP] = aP;
		
		return true; // Union 연산 성공
	}
	
	// Kruskal -&amp;gt; 간선 배열 오름차순 정렬 + Union-Find로 간선 처리	
	public static class Edge implements Comparable&amp;lt;Edge&amp;gt; {
		int from;
		int to;
		int weight;
		
		public Edge(int from, int to, int weight) {
			this.from = from;
			this.to = to;
			this.weight = weight;
		}

		@Override
		public int compareTo(Edge o) {
			return Integer.compare(this.weight, o.weight);
		}
	}
	
	public static Edge[] edges;
	
	// ---
	public static int V;
	public static int E;
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		StringTokenizer st;
		int T = Integer.parseInt(br.readLine());
		
		for (int test_case = 1; test_case &amp;lt;= T; test_case++) {
			st = new StringTokenizer(br.readLine());
			
			V = Integer.parseInt(st.nextToken());
			E = Integer.parseInt(st.nextToken());
			
			parents = new int[V + 1];
			edges = new Edge[E];
			
			for (int i = 0; i &amp;lt; E; i++) {
				st = new StringTokenizer(br.readLine());
				int from = Integer.parseInt(st.nextToken());
				int to = Integer.parseInt(st.nextToken());
				int weight = Integer.parseInt(st.nextToken());
				edges[i] = new Edge(from, to, weight);
			}
			
			Arrays.sort(edges);
			make();
			
			int cnt = 0;
			long sum = 0;
			for (Edge e : edges) {
				int f = e.from;
				int t = e.to;
				int w = e.weight;
				
				if (!union(f, t)) continue; // union 결과가 !false = true -&amp;gt; 이미 트리 구조에 편입됨 또는 사이클?
				sum += w;
				if (++cnt == V - 1) break;
			}
			
			sb.append(&quot;#&quot;).append(test_case).append(&quot; &quot;).append(sum).append(&quot;\n&quot;);
		}
		System.out.print(sb);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;코드 해설&lt;/blockquote&gt;
&lt;pre id=&quot;code_1757558783651&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Kruskal -&amp;gt; 간선 배열 오름차순 정렬 + Union-Find로 간선 처리	
public static class Edge implements Comparable&amp;lt;Edge&amp;gt; {
    int from;
    int to;
    int weight;

    public Edge(int from, int to, int weight) {
        this.from = from;
        this.to = to;
        this.weight = weight;
    }

    @Override
    public int compareTo(Edge o) {
        return Integer.compare(this.weight, o.weight);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간선의 가중치로 정렬을 하기 위해서 Comparable 인터페이스를 구현해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757558963611&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (Edge e : edges) {
    int f = e.from;
    int t = e.to;
    int w = e.weight;

    if (!union(f, t)) continue; // union 결과가 !false = true -&amp;gt; 이미 트리 구조에 편입됨 또는 사이클?
    sum += w;
    if (++cnt == V - 1) break;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬된 간선 배열에서 간선을 하나씩 꺼낸 뒤, 저장된 간선 정보(시작 정점 -&amp;gt; 도착 정점)를 이용해서 union() 연산을 수행함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;union() 연산의 결과가 false일 경우에는 이미 같은 트리(집합)임을 시사하는 것이기 때문에 해당 간선은 선택하지 않음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;union() 연산이 성공하면 sum에 가중치를 더해주고 cnt가 V - 1개라면 종료해주면 된다. (간선의 개수는 정점 -1 개)&lt;/p&gt;</description>
      <category>Algorithm</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/47</guid>
      <comments>https://bitbard-dongni.tistory.com/47#entry47comment</comments>
      <pubDate>Thu, 11 Sep 2025 11:51:43 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 유니온 파인드 알고리즘 &amp;lt;Disjoint Set&amp;gt;</title>
      <link>https://bitbard-dongni.tistory.com/46</link>
      <description>&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;유니온 파인드 알고리즘&lt;/blockquote&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-author-role=&quot;assistant&quot; data-message-id=&quot;20ce2ee5-d4ff-4357-bcb7-673f941e6f24&quot; data-message-model-slug=&quot;gpt-5&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;0&quot; data-end=&quot;143&quot;&gt;유니온 파인드(Union-Find) 알고리즘은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서로소 집합(Disjoint Set Union, DSU)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;자료구조를 이용해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;원소들이 같은 집합에 속하는지 판별&lt;/b&gt;하거나,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;두 집합을 하나로 합치는 연산&lt;/b&gt;을 효율적으로 처리하는 알고리즘이다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background-color: #f4f4f6; color: #37373e; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;서로소 집합&lt;br /&gt;여러 개의 집합이 있을 때, 어떤 두 집합도 공통 원소를 갖지 않는 경우를 말함.&lt;br /&gt;즉, 집합끼리 겹치는 부분이 전혀 없는 상태&lt;br /&gt;{1, 2}, {3, 4}, {5, 6}&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;유니온 파인드 알고리즘 적용 예시&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프 이론 관련
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최소 신장 트리 (MST) - 크루스칼 알고리즘&lt;/li&gt;
&lt;li&gt;사이클 판별
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;간선을 하나씩 추가하면서 두 정점이 이미 같은 집합인지 확인(Union) -&amp;gt; 같은 집합이면 사이클 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;연결 요소 찾기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프에서 분리된 그룹 개수 세기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 말해도 사실상 와닿지 않으니까 한 마디로 정리해보면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;서로 연결된 덩어리&quot;를 관리할 때 고려해볼 수 있는 알고리즘&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-end=&quot;413&quot; data-start=&quot;325&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;유니온 파인드 동작 원리&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;자기 자신의 부모를 자신으로 초기화하는 make() 함수&lt;/b&gt;&lt;br /&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;특정 원소의 부모를 찾는 find() 함수&lt;/b&gt;&lt;br /&gt;3.&lt;span&gt;&amp;nbsp;&lt;b&gt;원소 a, 원소 b가 속한 집합을 합치는 union() 함수&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;4. 문제의 조건에 따라서 원소들을 입력받고 union() 진행&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;유니온 파인드 동작 미리보기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;make()&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;FE80F047-3D92-4FC4-90CF-E90EB8BB525E.jpg&quot; data-origin-width=&quot;3999&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xN2j7/btsQoBVz8Zp/pbhA9kkXHDtRwQfrgMAzl0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xN2j7/btsQoBVz8Zp/pbhA9kkXHDtRwQfrgMAzl0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xN2j7/btsQoBVz8Zp/pbhA9kkXHDtRwQfrgMAzl0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxN2j7%2FbtsQoBVz8Zp%2FpbhA9kkXHDtRwQfrgMAzl0%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;3999&quot; height=&quot;742&quot; data-filename=&quot;FE80F047-3D92-4FC4-90CF-E90EB8BB525E.jpg&quot; data-origin-width=&quot;3999&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;자신의 부모를 자기 자신으로 모두 초기화 해준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;해당 그림에서는 부모 노드로의 간선 표시를 생략해주었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;union(1, 2)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;088D65D2-D41D-44EE-AA1A-2DC987F96DBA.jpg&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UAngh/btsQlARYh6t/vvGEECfTsxTekouAwkk310/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UAngh/btsQlARYh6t/vvGEECfTsxTekouAwkk310/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UAngh/btsQlARYh6t/vvGEECfTsxTekouAwkk310/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUAngh%2FbtsQlARYh6t%2FvvGEECfTsxTekouAwkk310%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;2290&quot; height=&quot;1000&quot; data-filename=&quot;088D65D2-D41D-44EE-AA1A-2DC987F96DBA.jpg&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1번 원소와 2번 원소를 union 처리 -&amp;gt; 여기서는 임의로 union의 첫 번째 매개변수에 두 번째 매개변수를 편입시켰음.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;union(3, 4)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;EEEB874B-B8D2-47C5-81DF-FCC9DEB5C303.jpg&quot; data-origin-width=&quot;2509&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ90Ql/btsQoqGHMJS/JN5kYFubivGSiXDFvyrbn1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ90Ql/btsQoqGHMJS/JN5kYFubivGSiXDFvyrbn1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ90Ql/btsQoqGHMJS/JN5kYFubivGSiXDFvyrbn1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ90Ql%2FbtsQoqGHMJS%2FJN5kYFubivGSiXDFvyrbn1%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;2509&quot; height=&quot;1000&quot; data-filename=&quot;EEEB874B-B8D2-47C5-81DF-FCC9DEB5C303.jpg&quot; data-origin-width=&quot;2509&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3번 원소와 4번 원소를 union 처리&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;union(2, 3)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;8EF1A64F-B2CE-4DD7-8253-BC429273B679.jpg&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkGCfy/btsQl1V12nS/gMIoFSq8fRRadyt58w3Lxk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkGCfy/btsQl1V12nS/gMIoFSq8fRRadyt58w3Lxk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkGCfy/btsQl1V12nS/gMIoFSq8fRRadyt58w3Lxk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkGCfy%2FbtsQl1V12nS%2FgMIoFSq8fRRadyt58w3Lxk%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;1036&quot; height=&quot;1000&quot; data-filename=&quot;8EF1A64F-B2CE-4DD7-8253-BC429273B679.jpg&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1-&amp;gt;3, 3-&amp;gt;4 5, 6 과 같은 원소 집합이 있는 상태에서&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2번 원소와 3번 원소를 union 해주면 위 그림과 같은 모양이 된다고 생각하면 됨.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;유니온 파인드 최적화 방법&amp;nbsp;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;find(4) (경로 압축)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;23DDD2F9-4A98-403D-B0A4-C26BB84B1CB8.jpg&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL6hBA/btsQmMw1ggb/K2HdIAbxa7wFK8H42CJNok/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL6hBA/btsQmMw1ggb/K2HdIAbxa7wFK8H42CJNok/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL6hBA/btsQmMw1ggb/K2HdIAbxa7wFK8H42CJNok/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL6hBA%2FbtsQmMw1ggb%2FK2HdIAbxa7wFK8H42CJNok%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;1764&quot; height=&quot;1000&quot; data-filename=&quot;23DDD2F9-4A98-403D-B0A4-C26BB84B1CB8.jpg&quot; data-origin-width=&quot;1764&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;유니온 파인드 알고리즘은 트리의 구조가 중요한 것이 아니라 특정 집합에 속했냐 속하지 않았느냐, 어느 집합에 속했느냐와 같이 집합의 관점에서 접근해야 함.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 find() 연산을 실행했을 때, 해당 노드와 연결된 모든 노드들을 일종의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;평탄화&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;작업을 해준다면 다음 조회에서는 상수 시간에 조회할 수 있게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예시에서는 find(4)를 진행했을 때, 어떤 식으로 동작하게 되는지를 간략하게 표현해보았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;우선, find(4)를 호출하면 parents[4] = find(parent[4])가 반환되게 된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이후 재귀를 타고 들어가서 기저 조건을 만날 때까지 부모가 어느 집합에 속했는지를 찾는다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;parents[4] -&amp;gt; 4번이 가리키고 있는 부모 노드&lt;br /&gt;find() -&amp;gt; 어느 집합에 속했는지(누가 루트 노드인지, 누가 제일 높은 부모인지)&lt;br /&gt;find(parent[4]) -&amp;gt; 4번 노드의 부모가 어느 집합에 속했는지 찾음&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실행 결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9B4577BC-33EC-4E3A-9E1A-45DF1EC84804.jpg&quot; data-origin-width=&quot;2334&quot; data-origin-height=&quot;999&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYS1qX/btsQnuCXRjf/kw5ynwajJUmZ2TBj6Nia40/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYS1qX/btsQnuCXRjf/kw5ynwajJUmZ2TBj6Nia40/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYS1qX/btsQnuCXRjf/kw5ynwajJUmZ2TBj6Nia40/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYS1qX%2FbtsQnuCXRjf%2Fkw5ynwajJUmZ2TBj6Nia40%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;2334&quot; height=&quot;999&quot; data-filename=&quot;9B4577BC-33EC-4E3A-9E1A-45DF1EC84804.jpg&quot; data-origin-width=&quot;2334&quot; data-origin-height=&quot;999&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;find(4)를 통해서 4번 노드, 3번 노드, 2번 노드가 평탄화된 것을 알 수 있음.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Union-by-Rank&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4A3B8E46-4315-4BD5-99C6-A13A892D39CA.jpg&quot; data-origin-width=&quot;3036&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rS8rU/btsQmsMlbu0/ZXbGxgkQA4KwMfNAaj8fIK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rS8rU/btsQmsMlbu0/ZXbGxgkQA4KwMfNAaj8fIK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rS8rU/btsQmsMlbu0/ZXbGxgkQA4KwMfNAaj8fIK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrS8rU%2FbtsQmsMlbu0%2FZXbGxgkQA4KwMfNAaj8fIK%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;3036&quot; height=&quot;1000&quot; data-filename=&quot;4A3B8E46-4315-4BD5-99C6-A13A892D39CA.jpg&quot; data-origin-width=&quot;3036&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;또 다른 최적화 기법으로는 랭크를 이용한 최적화 방법이 존재한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;모든 노드의 부모 값을 -1로 초기화 해준다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이전 코드와 다른 점은 자기 자신을 부모로 초기화 해주었는데, 여기서는 기본 초기값으로 -1을 초기화해준 것.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;union(1, 2)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;F984E985-E690-465A-8556-82043868EA12.jpg&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QgtBB/btsQoq005Zz/LA3mqHIDGM8iTXMIaBn5G0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QgtBB/btsQoq005Zz/LA3mqHIDGM8iTXMIaBn5G0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QgtBB/btsQoq005Zz/LA3mqHIDGM8iTXMIaBn5G0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQgtBB%2FbtsQoq005Zz%2FLA3mqHIDGM8iTXMIaBn5G0%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;1668&quot; height=&quot;1000&quot; data-filename=&quot;F984E985-E690-465A-8556-82043868EA12.jpg&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 union 연산을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 노드와 2번 노드의 rank는 같기 때문에, 둘 중 하나를 골라서 rank를 1 감소시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 2번 노드의 부모 값을 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;union(1, 3)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;D5AEC526-6211-4E9B-9273-9280F467B50E.jpg&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wOJrP/btsQlCvueXr/uz68JcNKKjFLr5Zzn81e0K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wOJrP/btsQlCvueXr/uz68JcNKKjFLr5Zzn81e0K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wOJrP/btsQlCvueXr/uz68JcNKKjFLr5Zzn81e0K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwOJrP%2FbtsQlCvueXr%2Fuz68JcNKKjFLr5Zzn81e0K%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;1375&quot; height=&quot;1000&quot; data-filename=&quot;D5AEC526-6211-4E9B-9273-9280F467B50E.jpg&quot; data-origin-width=&quot;1375&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 union(1, 3) 연산을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 노드의 랭크는 -2, 3번 노드의 rank는 -1인데, 1번 노드의 랭크가 더 높기 때문에 (음수로 커질 수록 커짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 노드를 그대로 1번 노드의 자식으로 편입시켜주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 1번 노드의 rank의 변화는 없다.&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;rank의 변화가 발생하는 포인트는 union 연산하는 두 노드의 rank 값을 비교해서 만약 두 노드의 rank 값이 같다면 임의로 한 노드의 rank를 증가(단, 음수로 커지게) 시켜주면 된다.&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&gt;그래서 이게 왜 최적화냐?&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div id=&quot;model-response-message-contentr_445f2202300d6962&quot; style=&quot;color: #1f1f1f;&quot;&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;유니온 파인드에서 가장 무서운 건 트리가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;4&quot; data-index-in-node=&quot;23&quot;&gt;지팡이처럼 길어지는 편향 이진트리의 모습일 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;5&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;5,0,0&quot; data-index-in-node=&quot;0&quot;&gt;경로 압축만 있을 때:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;find를 호출하기 전까지는 트리가 얼마나 깊을지 모름. 만약 100만 개의 노드가 일렬로 연결되어 있다면, 첫 번째 find는 100만 번을 타고 올라가야 함.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;5,1,0&quot; data-index-in-node=&quot;0&quot;&gt;Union-by-Rank가 있을 때:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;합칠 때부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;5,1,0&quot; data-index-in-node=&quot;28&quot;&gt;&quot;키 큰 놈이 대장 해라&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;규칙을 적용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;위 그림처럼 작은 트리를 큰 트리 밑에 붙이면 전체 트리의 높이가 늘어나지 않습니다. 반대로 큰 트리를 작은 트리 밑에 붙이면? 전체 높이가 확 커지겠죠. 이걸 사전에 차단하는 게 목적입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;코드 전문&lt;br /&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SWEA 3289 - 서로소 집합&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745144957&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class SWEA_3289 {
	static int n;
	static int m;
	static int[] parents;
	
	private static void make() {
		for (int i = 0; i &amp;lt;= n; i++) {
			parents[i] = i; // make set: 자신을 자신의 부모로 초기화(자신이 곧 루트이자 대표자)
		}
	}
	
	private static int find(int a) { // a가 속한 집합(집합의 대표자) 찾기
		if (parents[a] == a) {
			return a;
		} else {
//			return find(parents[a]);
			return parents[a] = find(parents[a]); // 패스 압축 적용
		}
	}
	
	private static boolean union(int a, int b) { // 원소 a, 원소 b가 속한 집합을 합치기
		int aRoot = find(a);
		int bRoot = find(b);
		if (aRoot == bRoot) return false; // 같은 집합이니까 union할 필요 없음
		
		parents[bRoot] = aRoot; // b, a가 아닌, bRoot, aRoot를 사용해야 대표자끼리 연산하는 게 됨.
		return true; // union 성공
	}
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		int T = Integer.parseInt(br.readLine());
		
		for (int test_case = 1; test_case &amp;lt;= T; test_case++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			sb.append(&quot;#&quot;).append(test_case).append(&quot; &quot;);
			n = Integer.parseInt(st.nextToken());
			m = Integer.parseInt(st.nextToken());
			
			parents = new int[n + 1];
			make();
			
			for (int i = 0; i &amp;lt; m; i++) {
				st = new StringTokenizer(br.readLine());
				int oper = Integer.parseInt(st.nextToken());
				int a = Integer.parseInt(st.nextToken());
				int b = Integer.parseInt(st.nextToken());
				
				if (oper == 0) {
					union(a, b);
				} else {
					sb.append(find(a) == find(b) ? 1 : 0);
				}
			}

			sb.append(&quot;\n&quot;);
		}
		
		System.out.print(sb);
	}

}&lt;/code&gt;&lt;/pre&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;SWEA 3124 - 최소 스패닝 트리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1771745144961&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package src;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class SWEA_3124 {
	// Union-Find
	public static int[] parents;
	
	public static void make() {
		for (int i = 0; i &amp;lt;= V; i++) {
			parents[i] = -1; // Union By Rank
		}
	}
	
	public static int find(int cur) {
		if (parents[cur] &amp;lt; 0) return cur; // 값이 -1이면 루트 노드를 의미, -2 이하이면 랭크가 증가한 부모 노드를 의미
		return parents[cur] = find(parents[cur]); // 경로 압축
	}
	
	public static boolean union(int a, int b) {
		int aP = find(a);
		int bP = find(b);
		
		// 이미 같은 집합
		if (aP == bP) return false;
		
		// 랭크가 서로 다를 때 (b가 더 큰 랭크) -&amp;gt; 왜 스왑? -&amp;gt; 자식으로 만드는 부분을 하나의 코드로 통일
		if (parents[aP] &amp;gt; parents[bP]) {
			// swap
			int temp = aP;
			aP = bP;
			bP = temp;
		}
		
		// 랭크가 서로 같을 때 -&amp;gt; a의 Rank를 증가 (-1, -2, -3 ... 작아질 수록 랭크가 큼)
		if (parents[aP] == parents[bP]) {
			parents[aP]--;
		}

		// 기본적으로는 b를 a의 자식으로 만듦.
		parents[bP] = aP;
		
		return true; // Union 연산 성공
	}	
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #616164; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;코드 해설&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다른 말 할 필요없이, make, find, union 세 함수만 잘 알고있으면 아무 문제 없이 구현할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm</category>
      <author>Dongni</author>
      <guid isPermaLink="true">https://bitbard-dongni.tistory.com/46</guid>
      <comments>https://bitbard-dongni.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 5 Sep 2025 16:58:55 +0900</pubDate>
    </item>
  </channel>
</rss>