<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>myh7754 님의 블로그</title>
    <link>https://myh7754.tistory.com/</link>
    <description>myh7754 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 08:43:54 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>myh7754</managingEditor>
    <item>
      <title>GC 동작 방식 및 원리 (2)</title>
      <link>https://myh7754.tistory.com/51</link>
      <description>&lt;h1 data-sourcepos=&quot;1:1-1:48;0-47&quot;&gt;GC &amp;mdash; Stop-the-World, Card Table, 그리고 알고리즘의 진화&lt;/h1&gt;
&lt;p data-sourcepos=&quot;3:1-3:148;49-196&quot; data-ke-size=&quot;size16&quot;&gt;지난 글에서는 JVM의 어떤 객체가 GC 대상이 되는지(3가지 가설, GC Root)를 다뤘다. 이번 글에서는 그 GC가 실제로 어떤 과정으로 동작하는지, 속도가 느려지는 원인인 STW는 무엇인지, 그리고 GC가 어떻게 발전해왔는지를 다룬다. 핵심 질문은 하나다.&lt;/p&gt;
&lt;blockquote data-sourcepos=&quot;5:1-5:52;198-249&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-sourcepos=&quot;5:3-5:52;200-249&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GC의 역사는 곧 Stop-the-World를 줄여온 역사다. 어떻게 줄여왔는가?&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-sourcepos=&quot;7:1-7:35;251-285&quot; data-ke-size=&quot;size26&quot;&gt;1 Stop-the-World &amp;mdash; GC 속도 저하의 원인&lt;/h2&gt;
&lt;p data-sourcepos=&quot;9:1-9:101;287-387&quot; data-ke-size=&quot;size16&quot;&gt;GC가 살아있는 객체를 판별하려면 GC Root에서 참조 그래프를 탐색(Mark)해야 한다. 문제는 탐색하는 동안 애플리케이션 스레드가 참조를 바꿔버리면 판정이 틀어진다는 것이다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;11:1-15:4;389-504&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;// GC가 A를 Mark하는 중에...
A.ref = B;      // 애플리케이션 스레드가 참조를 추가하면
C.ref = null;   // Mark 결과와 실제 그래프가 어긋난다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;17:1-17:138;506-643&quot; data-ke-size=&quot;size16&quot;&gt;가장 확실한 해결책은 &lt;b&gt;GC가 도는 동안 애플리케이션 스레드를 전부 멈추는 것&lt;/b&gt;이다. 이것이 Stop-the-World(STW)다. STW 동안 애플리케이션은 아무 요청도 처리하지 못하고, 힙이 클수록 탐색할 그래프가 커져 STW도 길어진다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;19:1-19:14;645-658&quot; data-ke-size=&quot;size23&quot;&gt;Safepoint&lt;/h3&gt;
&lt;p data-sourcepos=&quot;21:1-21:106;660-765&quot; data-ke-size=&quot;size16&quot;&gt;스레드는 아무 지점에서나 마음대로 멈추지 못한다. 객체 참조가 일관된 상태임이 보장되는 지점, 즉 &lt;b&gt;Safepoint&lt;/b&gt;에서만 멈춘다. JVM은 다음 위치에 Safepoint를 심어둔다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;23:1-26:11;767-844&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;23:1-23:12;767-778&quot;&gt;메서드 호출 직후&lt;/li&gt;
&lt;li data-sourcepos=&quot;24:1-24:44;779-822&quot;&gt;루프의 백엣지(back-edge, 루프 마지막에서 처음으로 돌아가는 지점)&lt;/li&gt;
&lt;li data-sourcepos=&quot;25:1-25:11;823-833&quot;&gt;예외 처리 지점&lt;/li&gt;
&lt;li data-sourcepos=&quot;26:1-26:11;834-844&quot;&gt;일부 반환 지점&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-sourcepos=&quot;28:1-33:4;846-927&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;actionscript&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;while (true) {
    i++;
    // &amp;larr; 매 반복마다 Safepoint 폴링 (GC가 요청했는지 검사)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;35:1-35:106;929-1034&quot; data-ke-size=&quot;size16&quot;&gt;만약 루프에 Safepoint가 없다면 이 스레드는 영원히 GC에 협조하지 않는다. GC가 필요하면 &lt;b&gt;모든 스레드가 Safepoint에 도달할 때까지&lt;/b&gt; 기다린 뒤에야 STW가 시작된다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;37:1-37:26;1036-1061&quot; data-ke-size=&quot;size23&quot;&gt;counted loop와 TTSP 문제&lt;/h3&gt;
&lt;p data-sourcepos=&quot;39:1-39:64;1063-1126&quot; data-ke-size=&quot;size16&quot;&gt;counted loop란 다음처럼 &lt;b&gt;반복 횟수가 명확하고, int 인덱스 기반이며, 종료가 보장되는 루프&lt;/b&gt;다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;41:1-45:4;1128-1190&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; 1_000_000; i++) {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;47:1-47:231;1192-1422&quot; data-ke-size=&quot;size16&quot;&gt;JIT 입장에서는 &quot;어차피 유한 루프인데 매 반복마다 Safepoint를 검사하면 느리다&quot;고 판단해, 최적화 과정에서 &lt;b&gt;루프 내부의 Safepoint 폴링을 제거&lt;/b&gt;할 수 있다. 문제는 이 루프가 거대할 때다. 이 루프를 도는 스레드 하나가 Safepoint에 도달하지 못하면, &lt;b&gt;이미 멈춘 나머지 모든 스레드가 그 스레드 하나를 기다린다.&lt;/b&gt; 이를 Time-To-Safepoint(TTSP) 문제라 부른다.&lt;/p&gt;
&lt;h3 data-section-id=&quot;liieuh&quot; data-end=&quot;747&quot; data-start=&quot;721&quot; data-ke-size=&quot;size23&quot;&gt;JVM도 나중에 문제를 인식했다.&lt;/h3&gt;
&lt;p data-end=&quot;797&quot; data-start=&quot;749&quot; data-ke-size=&quot;size16&quot;&gt;예전 HotSpot은 counted loop에서 polling 제거를 적극적으로 했다.&lt;/p&gt;
&lt;p data-end=&quot;820&quot; data-start=&quot;799&quot; data-ke-size=&quot;size16&quot;&gt;하지만 TTSP 이슈가 많이 발생해서:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&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;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;code-block-viewer&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;-XX:+UseCountedLoopSafepoints&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&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;/div&gt;
&lt;p data-end=&quot;875&quot; data-start=&quot;865&quot; data-ke-size=&quot;size16&quot;&gt;옵션이 추가되었다.&lt;/p&gt;
&lt;h3 data-section-id=&quot;88usnx&quot; data-end=&quot;907&quot; data-start=&quot;882&quot; data-ke-size=&quot;size23&quot;&gt;다만 요즘 JVM은 예전보다 똑똑하다.&lt;/h3&gt;
&lt;p data-end=&quot;939&quot; data-start=&quot;909&quot; data-ke-size=&quot;size16&quot;&gt;최신 HotSpot(C2)은 무조건 제거하지는 않는다.&lt;/p&gt;
&lt;p data-end=&quot;970&quot; data-start=&quot;941&quot; data-ke-size=&quot;size16&quot;&gt;루프 크기, 최적화 상태 등에 따라 다르게 처리한다.&lt;/p&gt;
&lt;p data-end=&quot;1019&quot; data-start=&quot;972&quot; data-ke-size=&quot;size16&quot;&gt;그래도 &lt;b&gt;아주 긴 계산 루프에서는 여전히 TTSP 문제가 발생할 가능성&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;49:1-49:32;1424-1455&quot; data-ke-size=&quot;size26&quot;&gt;2 Mark-Sweep-Compact와 세대별 GC&lt;/h2&gt;
&lt;p data-sourcepos=&quot;51:1-51:22;1457-1478&quot; data-ke-size=&quot;size16&quot;&gt;모든 GC 알고리즘의 뼈대는 3단계다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;53:1-57:67;1480-1703&quot;&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;Mark&lt;/td&gt;
&lt;td&gt;GC Root에서 참조 그래프 탐색, 살아있는 객체 표시&lt;/td&gt;
&lt;td&gt;그래프 크기에 비례한 시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sweep&lt;/td&gt;
&lt;td&gt;표시되지 않은 객체 수거&lt;/td&gt;
&lt;td&gt;&lt;b&gt;단편화(Fragmentation)&lt;/b&gt; 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compact&lt;/td&gt;
&lt;td&gt;살아남은 객체를 한쪽으로 밀어 단편화 제거&lt;/td&gt;
&lt;td&gt;객체 이동 &amp;rarr; 참조 주소 갱신 필요 &amp;rarr; 비용 큼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;59:1-59:163;1705-1867&quot; data-ke-size=&quot;size16&quot;&gt;Sweep만 하면 힙에 구멍이 숭숭 뚫린다. 총 여유 공간은 충분한데 연속된 공간이 없어 큰 객체를 할당하지 못하는 상황이 온다. 그래서 Compact가 필요하지만, 객체를 옮기면 그 객체를 가리키던 모든 참조를 갱신해야 해서 비싸다. 이 트레이드오프가 이후 모든 알고리즘 설계를 관통한다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;61:1-61:37;1869-1905&quot; data-ke-size=&quot;size23&quot;&gt;Copying Collector &amp;mdash; 약한 세대 가설의 구현&lt;/h3&gt;
&lt;p data-sourcepos=&quot;63:1-63:123;1907-2029&quot; data-ke-size=&quot;size16&quot;&gt;Mark-Sweep과 전혀 다른 접근이 하나 더 있다. &lt;b&gt;Copying Collector&lt;/b&gt;는 공간을 둘로 나눠두고, GC 때 &lt;b&gt;살아있는 객체만 반대편 공간으로 복사한 뒤, 원본 공간을 통째로 리셋&lt;/b&gt;하는 방식이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;65:1-65:44;2031-2074&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 것은 &lt;b&gt;죽은 객체를 '치우는' 동작 자체가 없다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;67:1-70:50;2076-2240&quot;&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;Sweep&lt;/td&gt;
&lt;td&gt;하나하나 방문해서 해제&lt;/td&gt;
&lt;td&gt;&lt;b&gt;전체 객체 수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copying&lt;/td&gt;
&lt;td&gt;아예 건드리지 않음 (영역 포인터만 리셋)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;생존자 수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;72:1-72:185;2242-2426&quot; data-ke-size=&quot;size16&quot;&gt;지난 글의 &lt;b&gt;약한 세대 가설&lt;/b&gt;을 떠올려보자. 대부분의 객체는 금방 죽는다. 즉 Young 영역의 생존자는 극소수다. 비용이 생존자 수에만 비례하는 Copying은 Young 영역에 압도적으로 유리하고, 복사 과정에서 객체가 한쪽에 몰리므로 &lt;b&gt;단편화도 원천 차단&lt;/b&gt;된다. Young 영역의 GC가 Copying 방식인 이유다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;74:1-74:22;2428-2449&quot; data-ke-size=&quot;size23&quot;&gt;Heap 구조와 Minor GC&lt;/h3&gt;
&lt;p data-sourcepos=&quot;76:1-76:118;2451-2568&quot; data-ke-size=&quot;size16&quot;&gt;힙은 Young 영역과 Old 영역으로 나뉘고, Young은 다시 &lt;b&gt;Eden&lt;/b&gt;과 &lt;b&gt;2개의 Survivor(S0, S1)&lt;/b&gt; 로 나뉜다. Copying을 위해 &lt;b&gt;Survivor 한쪽은 반드시 비워둔다.&lt;/b&gt;&lt;/p&gt;
&lt;div data-sourcepos=&quot;78:1-80:4;2570-2644&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;scheme&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;[Eden            ][S0 (From)][S1 (To, 비움)]     [Old              ]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;82:1-83:33;2646-2701&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;82:1-82:23;2646-2668&quot;&gt;새 객체는 &lt;b&gt;Eden&lt;/b&gt;에 할당된다&lt;/li&gt;
&lt;li data-sourcepos=&quot;83:1-83:33;2669-2701&quot;&gt;&lt;b&gt;Eden이 가득 차면 Minor GC가 시작&lt;/b&gt;된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-sourcepos=&quot;85:1-85:19;2703-2721&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Minor GC 동작 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-sourcepos=&quot;87:1-91:37;2723-3068&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-sourcepos=&quot;87:1-87:50;2723-2772&quot;&gt;Eden + 현재 사용 중인 Survivor(From)에서 살아있는 객체를 식별한다&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-88:69;2773-2841&quot;&gt;생존자를 비어있는 Survivor(To)로 복사하고, age 카운터를 +1 한다 (몇 번의 GC를 살아남았는지 기록)&lt;/li&gt;
&lt;li data-sourcepos=&quot;89:1-89:133;2842-2974&quot;&gt;이때 age가 임계값(-XX:MaxTenuringThreshold, 기본 15) 이상인 객체는 To가 아닌 &lt;b&gt;Old 영역으로 복사&lt;/b&gt;된다 &amp;mdash; 승격(Promotion)은 별도 단계가 아니라 &lt;b&gt;복사하는 순간의 목적지 판정&lt;/b&gt;이다&lt;/li&gt;
&lt;li data-sourcepos=&quot;90:1-90:57;2975-3031&quot;&gt;Eden과 옛 Survivor(From)는 죽은 객체를 개별 정리하지 않고 &lt;b&gt;통째로 비운다&lt;/b&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;91:1-91:37;3032-3068&quot;&gt;Survivor의 역할을 swap 한다 (From &amp;harr; To)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-sourcepos=&quot;93:1-93:27;3070-3096&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;객체가 Old 영역으로 가는 3가지 경로&lt;/b&gt;&lt;/p&gt;
&lt;div data-sourcepos=&quot;95:1-99:89;3098-3326&quot;&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;age 승격&lt;/td&gt;
&lt;td&gt;age &amp;ge; 임계값&lt;/td&gt;
&lt;td&gt;정상 승격&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조기 승격&lt;/td&gt;
&lt;td&gt;Survivor(To)가 가득 차 복사 공간 부족&lt;/td&gt;
&lt;td&gt;비정상 승격 (Survivor 크기 튜닝 신호)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;직행 할당&lt;/td&gt;
&lt;td&gt;처음부터 크기가 매우 큰 객체&lt;/td&gt;
&lt;td&gt;승격이 아니라 &lt;b&gt;Eden을 거치지 않는 직접 할당&lt;/b&gt; (G1에서는 Humongous Region으로)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;101:1-101:145;3328-3472&quot; data-ke-size=&quot;size16&quot;&gt;여기까지가 지난 글 가설들의 구현이다. &lt;b&gt;강한 세대 가설&lt;/b&gt;(오래 산 객체는 더 오래 산다)이 바로 age 기반 승격과 Old 분리의 근거다 &amp;mdash; 여기까지 살아남은 객체는 앞으로도 오래 살 가능성이 높으므로, Old로 보내 Minor GC 대상에서 제외한다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;103:1-103:76;3474-3549&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GC 범위에 따른 용어 정리&lt;/b&gt; (Major/Full GC는 공식 스펙 용어가 아니라 관례적 용어라 문서마다 정의가 조금씩 다르다)&lt;/p&gt;
&lt;div data-sourcepos=&quot;105:1-109:54;3551-3718&quot;&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;Minor GC&lt;/td&gt;
&lt;td&gt;Young 영역&lt;/td&gt;
&lt;td&gt;Eden이 가득 참&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Major GC&lt;/td&gt;
&lt;td&gt;Old 영역&lt;/td&gt;
&lt;td&gt;Old가 가득 참&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full GC&lt;/td&gt;
&lt;td&gt;Heap 전체 (+ Metaspace)&lt;/td&gt;
&lt;td&gt;Old 부족, 승격 실패 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h2 data-sourcepos=&quot;111:1-111:45;3720-3764&quot; data-ke-size=&quot;size26&quot;&gt;3 Card Table과 Write Barrier &amp;mdash; 세 번째 가설의 구현&lt;/h2&gt;
&lt;p data-sourcepos=&quot;113:1-113:30;3766-3795&quot; data-ke-size=&quot;size16&quot;&gt;가설 3개 중 2개의 구현을 봤다. 이제 마지막이다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;115:1-119:55;3797-3968&quot;&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;/td&gt;
&lt;td&gt;Young 분리 + Copying Collector + Minor GC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;강한 세대 가설&lt;/td&gt;
&lt;td&gt;age 기반 승격 + Old 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;세대간 참조 희소성&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Card Table + Write Barrier (이번 장)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;121:1-121:177;3970-4146&quot; data-ke-size=&quot;size16&quot;&gt;Minor GC의 GC Root는 지난 글에서 본 static 변수, 스택, 스레드만이 아니다. &lt;b&gt;Old 영역에서 Young 영역을 가리키는 참조&lt;/b&gt;도 (Minor GC 관점에서는) Root로 취급해야 한다. Old 객체가 살아있고 그 객체가 Young 객체를 참조한다면, 그 Young 객체도 살아있기 때문이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;123:1-123:136;4148-4283&quot; data-ke-size=&quot;size16&quot;&gt;그럼 Minor GC 때마다 Old 영역 전체를 스캔해야 하나? 그러면 Young/Old를 나눈 의미가 없다. 여기서 세대간 참조 희소성 가설이 등장한다. &lt;b&gt;Old &amp;rarr; Young 참조는 드물다. 드물다면, 그 드문 것만 기록해두면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;125:1-125:15;4285-4299&quot; data-ke-size=&quot;size23&quot;&gt;Card Table&lt;/h3&gt;
&lt;p data-sourcepos=&quot;127:1-127:63;4301-4363&quot; data-ke-size=&quot;size16&quot;&gt;Old 영역을 &lt;b&gt;512바이트 단위의 카드(Card)&lt;/b&gt; 로 쪼개고, 각 카드의 상태를 바이트 배열로 관리한다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;129:1-134:4;4365-4570&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;Old 영역:   [card 0][card 1][card 2][card 3] ...
Card Table: [clean ][dirty ][clean ][dirty ] ...
                       │                │
                       └─ 이 카드 안에 Young을 가리키는 참조가 있을 &quot;수&quot; 있음&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;136:1-136:99;4572-4670&quot; data-ke-size=&quot;size16&quot;&gt;Old 객체의 필드에 Young 객체 참조가 쓰이면, 해당 카드를 &lt;b&gt;dirty&lt;/b&gt;로 표시한다. Minor GC는 Old 전체가 아니라 &lt;b&gt;dirty 카드만&lt;/b&gt; 스캔하면 된다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;138:1-138:18;4672-4689&quot; data-ke-size=&quot;size23&quot;&gt;Write Barrier&lt;/h3&gt;
&lt;p data-sourcepos=&quot;140:1-140:75;4691-4765&quot; data-ke-size=&quot;size16&quot;&gt;그럼 dirty 표시는 누가 하는가? JVM이 &lt;b&gt;참조 대입 코드마다 자동으로 끼워 넣는 짧은 코드&lt;/b&gt;, Write Barrier다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;142:1-149:4;4767-4943&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;// 개발자가 쓴 코드
oldObject.field = youngObject;

// JVM이 실제로 실행하는 코드 (개념적으로)
oldObject.field = youngObject;
cardTable[addressOf(oldObject) &amp;gt;&amp;gt; 9] = DIRTY;  // 512B = 2^9&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;151:1-151:114;4945-5058&quot; data-ke-size=&quot;size16&quot;&gt;모든 참조 대입에 비용이 추가되지만(보통 수 ns), Minor GC 때 Old 전체 스캔을 피하는 이득이 훨씬 크다. &lt;b&gt;평상시에 조금씩 비용을 내고, GC 때 크게 아끼는&lt;/b&gt; 전형적인 트레이드오프다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;153:1-153:17;5060-5076&quot; data-ke-size=&quot;size26&quot;&gt;4 GC 알고리즘의 진화&lt;/h2&gt;
&lt;p data-sourcepos=&quot;155:1-155:45;5078-5122&quot; data-ke-size=&quot;size16&quot;&gt;이제 도구가 갖춰졌다. 각 알고리즘이 STW를 어떻게 줄여왔는지 순서대로 본다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;157:1-157:20;5124-5143&quot; data-ke-size=&quot;size23&quot;&gt;Serial GC &amp;mdash; 출발점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;159:1-162:21;5145-5268&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;159:1-159:39;5145-5183&quot;&gt;GC 스레드 &lt;b&gt;1개&lt;/b&gt;로 Mark-Sweep-Compact 수행&lt;/li&gt;
&lt;li data-sourcepos=&quot;160:1-160:12;5184-5195&quot;&gt;전 과정이 STW&lt;/li&gt;
&lt;li data-sourcepos=&quot;161:1-161:52;5196-5247&quot;&gt;힙이 작은 단일 코어 환경, 컨테이너 리소스가 극도로 제한된 환경 정도에서만 의미가 있다&lt;/li&gt;
&lt;li data-sourcepos=&quot;162:1-162:21;5248-5268&quot;&gt;-XX:+UseSerialGC&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-sourcepos=&quot;164:1-164:38;5270-5307&quot; data-ke-size=&quot;size23&quot;&gt;Parallel GC &amp;mdash; 처리량의 시대 (Java 8 기본)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;166:1-169:23;5309-5510&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;166:1-166:53;5309-5361&quot;&gt;GC 작업 자체를 &lt;b&gt;여러 스레드로 병렬화&lt;/b&gt;. STW는 여전히 발생하지만 길이가 짧아진다&lt;/li&gt;
&lt;li data-sourcepos=&quot;167:1-167:76;5362-5437&quot;&gt;목표가 &lt;b&gt;처리량(Throughput) 최대화&lt;/b&gt;: 전체 실행 시간 중 GC가 차지하는 비율을 최소화해 단위 시간당 처리량을 높인다&lt;/li&gt;
&lt;li data-sourcepos=&quot;168:1-168:50;5438-5487&quot;&gt;배치 처리처럼 &quot;잠깐 멈춰도 총 작업이 빨리 끝나면 되는&quot; 워크로드에 여전히 유효하다&lt;/li&gt;
&lt;li data-sourcepos=&quot;169:1-169:23;5488-5510&quot;&gt;-XX:+UseParallelGC&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-sourcepos=&quot;171:1-171:40;5512-5551&quot; data-ke-size=&quot;size23&quot;&gt;CMS &amp;mdash; Concurrent의 시초 (Java 14에서 제거)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;173:1-177:58;5553-5787&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;173:1-173:67;5553-5619&quot;&gt;Mark 단계 대부분을 애플리케이션 스레드와 &lt;b&gt;동시에(Concurrent)&lt;/b&gt; 수행해 STW를 크게 줄인 첫 시도&lt;/li&gt;
&lt;li data-sourcepos=&quot;174:1-176:29;5620-5729&quot;&gt;그러나 치명적 약점이 있었다
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;175:3-176:29;5640-5729&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;175:3-175:63;5640-5700&quot;&gt;Compact를 하지 않아 &lt;b&gt;단편화 누적&lt;/b&gt; &amp;rarr; 결국 Full GC(전체 STW Compact)로 폭발&lt;/li&gt;
&lt;li data-sourcepos=&quot;176:3-176:29;5703-5729&quot;&gt;동시 수행을 위한 CPU 경합, 복잡한 튜닝&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;177:1-177:58;5730-5787&quot;&gt;실패로 끝났지만, CMS가 개척한 Concurrent Marking은 이후 모든 GC의 토대가 됐다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-sourcepos=&quot;179:1-179:35;5789-5823&quot; data-ke-size=&quot;size23&quot;&gt;G1 GC &amp;mdash; 예측 가능한 멈춤 (Java 9+ 기본)&lt;/h3&gt;
&lt;p data-sourcepos=&quot;181:1-181:147;5825-5971&quot; data-ke-size=&quot;size16&quot;&gt;G1은 힙 구조 자체를 바꿨다. Young/Old를 &lt;b&gt;물리적으로 연속된 큰 덩어리&lt;/b&gt;로 두지 않고, 힙을 &lt;b&gt;동일 크기의 Region(기본 1~32MB)&lt;/b&gt; 수백~수천 개로 쪼갠다. 각 Region이 그때그때 Eden, Survivor, Old 역할을 맡는다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;183:1-186:4;5973-6049&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;markdown&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;[E][O][O][E][S][O][H][H][E][O][ ][O] ...
 └ Region 단위로 역할이 동적으로 배정된다&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;188:1-191:176;6051-6510&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;188:1-188:72;6051-6122&quot;&gt;&lt;b&gt;Garbage-First&lt;/b&gt;: 쓰레기 비율이 가장 높은 Region부터 골라서 수거한다. 적은 비용으로 많은 공간을 회수&lt;/li&gt;
&lt;li data-sourcepos=&quot;189:1-189:134;6123-6256&quot;&gt;&lt;b&gt;Pause 목표 기반 동작&lt;/b&gt;: -XX:MaxGCPauseMillis=200(기본 200ms)을 주면, G1이 목표 안에 끝낼 수 있는 만큼의 Region만 골라 수거한다. &quot;얼마나 오래 멈출지&quot;를 개발자가 협상할 수 있게 됐다&lt;/li&gt;
&lt;li data-sourcepos=&quot;190:1-190:78;6257-6334&quot;&gt;수거 = 생존자를 다른 Region으로 복사 &amp;rarr; &lt;b&gt;Region 단위 Compact가 자연스럽게 일어나&lt;/b&gt; CMS의 단편화 문제를 해결&lt;/li&gt;
&lt;li data-sourcepos=&quot;191:1-191:176;6335-6510&quot;&gt;&lt;b&gt;Humongous 객체&lt;/b&gt;: Region 크기의 50%를 넘는 객체는 전용 Region(H)에 연속 할당된다. 앞서 본 &quot;Old 직행 할당&quot;이 G1에서 구현되는 방식이다. 거대 배열을 자주 만들면 Humongous 할당이 잦아져 GC 압박이 커진다 &amp;mdash; 실무에서 대용량 byte[] 캐시가 문제 되는 지점이다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-sourcepos=&quot;193:1-193:51;6512-6562&quot; data-ke-size=&quot;size23&quot;&gt;ZGC &amp;mdash; 힙 크기와 무관한 정지 시간 (Java 15 정식, 21에서 세대 지원)&lt;/h3&gt;
&lt;p data-sourcepos=&quot;195:1-195:115;6564-6678&quot; data-ke-size=&quot;size16&quot;&gt;G1도 생존자 &lt;b&gt;복사(Evacuation)는 STW&lt;/b&gt;로 한다. ZGC는 이 마지막 벽을 넘었다. &lt;b&gt;객체 이동조차 애플리케이션과 동시에&lt;/b&gt; 수행해, 힙이 수 TB여도 STW를 1ms 미만으로 유지한다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;197:1-197:14;6680-6693&quot; data-ke-size=&quot;size16&quot;&gt;핵심 기술은 두 가지다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;199:1-199:114;6695-6808&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Colored Pointer&lt;/b&gt; &amp;mdash; 64비트 포인터의 남는 상위 비트에 객체의 GC 상태(Marked, Remapped 등)를 심는다. 객체가 아니라 &lt;b&gt;포인터 자체에 메타데이터&lt;/b&gt;를 넣는 발상이다.&lt;/p&gt;
&lt;p data-sourcepos=&quot;201:1-201:72;6810-6881&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Load Barrier&lt;/b&gt; &amp;mdash; Write Barrier가 &quot;쓸 때&quot; 끼어들었다면, ZGC는 &lt;b&gt;참조를 읽을 때&lt;/b&gt; 끼어든다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;203:1-212:4;6883-7073&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;axapta&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;// 개발자가 쓴 코드
Object o = container.field;

// ZGC가 실제로 실행하는 것 (개념적으로)
Object o = container.field;
if (포인터 색상이 현재 GC 단계와 안 맞으면) {
    o = 객체의 새 주소로 교정하고 포인터를 갱신;  // self-healing
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;214:1-214:156;7075-7230&quot; data-ke-size=&quot;size16&quot;&gt;GC가 객체를 옮기는 도중에 애플리케이션이 옛 주소를 읽으면, Load Barrier가 &lt;b&gt;그 자리에서 새 주소로 고쳐준다.&lt;/b&gt; 그래서 &quot;모든 참조를 한 번에 갱신하기 위한 STW&quot;가 필요 없다. 대가는 모든 참조 읽기에 붙는 미세한 비용과 다소 높은 CPU/메모리 오버헤드다.&lt;/p&gt;
&lt;h3 data-sourcepos=&quot;216:1-216:57;7232-7288&quot; data-ke-size=&quot;size23&quot;&gt;Tri-color Marking &amp;mdash; Concurrent Mark가 객체를 잃어버리지 않는 이유&lt;/h3&gt;
&lt;p data-sourcepos=&quot;218:1-218:40;7290-7329&quot; data-ke-size=&quot;size16&quot;&gt;CMS 이후의 모든 Concurrent GC는 삼색 마킹으로 동작한다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;220:1-224:26;7331-7452&quot;&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;/td&gt;
&lt;td&gt;아직 방문하지 않음 (Mark 종료 시 흰색 = 수거 대상)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;회색&lt;/td&gt;
&lt;td&gt;방문했지만 자식들은 아직 탐색 전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검은색&lt;/td&gt;
&lt;td&gt;자신과 자식 모두 탐색 완료&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;226:1-226:32;7454-7485&quot; data-ke-size=&quot;size16&quot;&gt;문제는 Mark 도중 애플리케이션이 이런 짓을 할 때다.&lt;/p&gt;
&lt;div data-sourcepos=&quot;228:1-231:4;7487-7593&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;ini&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;black.ref = white;   // 1. 검은 객체가 흰 객체를 새로 참조
gray.ref = null;     // 2. 회색에서 그 흰 객체로 가는 경로 제거&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;233:1-233:244;7595-7838&quot; data-ke-size=&quot;size16&quot;&gt;검은 객체는 &quot;탐색 완료&quot;라 다시 방문하지 않고, 흰 객체로 가는 다른 경로는 끊겼다. &lt;b&gt;살아있는 객체가 흰색인 채로 Mark가 끝나 수거돼버린다(lost object problem).&lt;/b&gt; 이를 막는 것도 Write Barrier다. 위 1번 같은 대입이 일어나면 Barrier가 감지해 해당 객체를 다시 회색으로 되돌리거나(Incremental Update, CMS 방식), 끊기기 직전의 참조를 기록해둔다(SATB, G1 방식).&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;235:1-235:17;7840-7856&quot; data-ke-size=&quot;size26&quot;&gt;5 그래서 뭘 써야 하나&lt;/h2&gt;
&lt;div data-sourcepos=&quot;237:1-242:38;7858-8106&quot;&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;/td&gt;
&lt;td&gt;Parallel GC&lt;/td&gt;
&lt;td&gt;멈춤보다 총 처리량이 중요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일반적인 웹 서버 (힙 수 GB)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;G1 (기본값)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;처리량과 지연의 균형, 튜닝 부담 적음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;초저지연 요구 (거래 시스템 등), 초대형 힙&lt;/td&gt;
&lt;td&gt;ZGC&lt;/td&gt;
&lt;td&gt;STW &amp;lt; 1ms, 힙 크기 무관&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;극소 리소스 컨테이너&lt;/td&gt;
&lt;td&gt;Serial GC&lt;/td&gt;
&lt;td&gt;오버헤드 최소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;244:1-244:128;8108-8235&quot; data-ke-size=&quot;size16&quot;&gt;대부분의 경우 정답은 &quot;기본값(G1)을 쓰고, 문제가 측정되면 그때 움직여라&quot;다. GC 로그(-Xlog:gc*) 없이 GC를 튜닝하는 것은 지난 글의 표현을 빌리면, 참조 그래프를 안 보고 메모리 누수를 잡겠다는 것과 같다.&lt;/p&gt;
&lt;h2 data-sourcepos=&quot;246:1-246:6;8237-8242&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;div data-sourcepos=&quot;248:1-258:79;8244-8900&quot;&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;Stop-the-World&lt;/td&gt;
&lt;td&gt;Mark 중 그래프 변경을 막기 위한 전체 정지. GC 발전 = STW 축소의 역사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safepoint&lt;/td&gt;
&lt;td&gt;스레드가 안전하게 멈출 수 있는 지점. counted loop의 TTSP 함정 주의&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copying (Young)&lt;/td&gt;
&lt;td&gt;약한 세대 가설의 구현. 죽은 객체는 건드리지 않고 생존자만 복사, 비용은 생존자 수에 비례, 단편화 원천 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;승격 (Promotion)&lt;/td&gt;
&lt;td&gt;강한 세대 가설의 구현. age 승격 / 조기 승격 / 직행 할당 3가지 경로로 Old행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Card Table / Write Barrier&lt;/td&gt;
&lt;td&gt;세대간 참조 희소성 가설의 구현. dirty 카드만 스캔해 Old 전체 스캔 회피&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serial &amp;rarr; Parallel&lt;/td&gt;
&lt;td&gt;STW 자체의 병렬화, 처리량 중심&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMS &amp;rarr; G1&lt;/td&gt;
&lt;td&gt;Concurrent Mark 도입 &amp;rarr; Region 구조 + Pause 목표 + 자연스러운 Compact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZGC&lt;/td&gt;
&lt;td&gt;Colored Pointer + Load Barrier로 객체 이동까지 동시 수행, STW &amp;lt; 1ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tri-color Marking&lt;/td&gt;
&lt;td&gt;Concurrent Mark의 이론적 기반, Write Barrier가 lost object 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-sourcepos=&quot;260:1-260:129;8902-9030&quot; data-ke-size=&quot;size16&quot;&gt;여기까지 GC 알고리즘의 진화와 STW, Card Table의 동작을 알아봤다. 다음 글에서는 GC 로그를 직접 읽고(-Xlog:gc*), 힙 덤프(jmap, MAT)로 지난 글의 누수 패턴들을 실제로 잡아내는 과정을 다룬다.&lt;/p&gt;</description>
      <category>GC</category>
      <category>Java</category>
      <category>jvm</category>
      <category>stw</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/51</guid>
      <comments>https://myh7754.tistory.com/51#entry51comment</comments>
      <pubDate>Wed, 15 Jul 2026 20:36:21 +0900</pubDate>
    </item>
    <item>
      <title>@Component가 Spring Boot에 등록되는 과정</title>
      <link>https://myh7754.tistory.com/50</link>
      <description>&lt;h1&gt;@Component는 어떻게 Spring Bean으로 등록될까&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component(혹은 @Service, @Repository 등)를 붙이면 Spring이 알아서 객체를 만들어 관리해 준다. 이 &quot;알아서&quot;의 내부 과정을 SpringApplication.run()부터 싱글톤 캐시에 올라가기까지 단계별로 따라가 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 들어가기 전에 Bean이 무엇인지부터 짚고 시작하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Bean이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 객체를 직접 new 해서 쓰지 않는다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;MemberService memberService = new MemberService();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코드를 &lt;b&gt;개발자 대신 Spring이 수행하고, 생명주기까지 관리&lt;/b&gt;한다. 이렇게 Spring 컨테이너가 생성&amp;middot;관리하는 객체를 &lt;b&gt;Bean&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 등록 과정의 시작점&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;java&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드가 실행되면 SpringApplication.run()이 호출되고, Spring Boot는 내부적으로 &lt;b&gt;ApplicationContext&lt;/b&gt; 를 생성한다. 이 ApplicationContext가 컨테이너의 핵심이며, 다음 역할을 담당한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bean 생성&lt;/li&gt;
&lt;li&gt;Bean 저장&lt;/li&gt;
&lt;li&gt;Bean 조회&lt;/li&gt;
&lt;li&gt;DI(의존성 주입) 수행&lt;/li&gt;
&lt;li&gt;생명주기 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. @SpringBootApplication 뜯어보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@SpringBootApplication은 사실 아래 세 어노테이션을 합쳐 놓은 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;java&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 Bean 등록과 직접 관련된 것은 &lt;b&gt;@ComponentScan&lt;/b&gt; 이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. @ComponentScan &amp;mdash; 후보 탐색&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 스캔은 @ComponentScan이 붙은 클래스의 패키지를 기준으로, 그 &lt;b&gt;하위 패키지의 클래스들을 전부 탐색&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 ClassPath를 스캔하는데, 핵심은 &lt;b&gt;클래스를 로딩하지 않는다&lt;/b&gt;는 점이다. 실제 클래스 파일(.class)을 읽되 &lt;b&gt;ASM&lt;/b&gt;으로 바이트코드를 파싱해서 어노테이션 정보만 빠르게 확인한다. JVM에 클래스를 적재(load)하지 않고 &quot;읽기만&quot; 하기 때문에 가볍고 빠르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 스캔하면서 @Component가 붙은 후보들을 찾아낸다. 대표적으로 다음이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Service&lt;/li&gt;
&lt;li&gt;@Repository&lt;/li&gt;
&lt;li&gt;@Controller&lt;/li&gt;
&lt;li&gt;@RestController&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들을 한 번에 잡아낼 수 있는 이유는, 이 어노테이션들이 내부적으로 &lt;b&gt;@Component를 메타 어노테이션으로 달고 있기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;less&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component   // &amp;larr; 이 메타 어노테이션 덕분에 스캔 대상이 된다
public @interface Service {
    // 실제로는 @AliasFor(&quot;value&quot;) 등이 더 있지만 핵심은 @Component
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. BeanDefinition 생성 &amp;mdash; 객체가 아니라 &quot;설계도&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후보를 찾았다고 바로 객체를 만들지는 않는다. 먼저 &lt;b&gt;BeanDefinition&lt;/b&gt; 을 만든다. BeanDefinition은 &lt;b&gt;Bean의 설계도&lt;/b&gt;라고 보면 된다. 담기는 정보는 대략 다음과 같다.&lt;/p&gt;
&lt;div&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;/td&gt;
&lt;td&gt;어떤 클래스로 만들지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;Singleton / Prototype 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;생성 방식&lt;/td&gt;
&lt;td&gt;생성자 / 팩토리 메서드 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;생성자 정보&lt;/td&gt;
&lt;td&gt;어떤 생성자를 쓸지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lazy 여부&lt;/td&gt;
&lt;td&gt;지연 초기화 대상인지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary 여부&lt;/td&gt;
&lt;td&gt;동일 타입 충돌 시 우선순위&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. BeanDefinitionRegistry 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들어진 BeanDefinition은 &lt;b&gt;BeanDefinitionRegistry&lt;/b&gt; 에 저장된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;isbl&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;memberService  &amp;rarr;  BeanDefinition(MemberService)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &quot;이 클래스를 &lt;b&gt;나중에&lt;/b&gt; Bean으로 만들 예정&quot;이라고 예약해 둔 상태다. 이때 Bean 이름은 기본적으로 &lt;b&gt;클래스명의 첫 글자를 소문자로&lt;/b&gt; 바꾼 형태가 된다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;nginx&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;MemberService  &amp;rarr;  memberService&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: URLParser처럼 앞 두 글자가 모두 대문자인 경우엔 그대로 URLParser로 유지된다. (Introspector.decapitalize() 규칙)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. BeanFactory가 객체를 생성한다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지(4~6번)는 &quot;설계도 등록&quot; 단계였고, 이제부터 실제 객체가 만들어진다. 컨테이너 초기화 흐름은 다음과 같다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;erlang&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;refresh()
 ├─ invokeBeanFactoryPostProcessors()
 │     └─ ConfigurationClassPostProcessor  &amp;larr; 여기서 @ComponentScan 실행 (4~6번)
 │
 └─ finishBeanFactoryInitialization()
       └─ preInstantiateSingletons()        &amp;larr; 여기서 객체 생성 (7~11번)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;preInstantiateSingletons()에서 싱글톤 Bean 생성이 시작된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 생성자 호출 (+ 생성자 주입)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에 필요한 파라미터를 분석하고, 해당 Bean들을 찾아 주입하면서 객체를 만든다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;oxygene&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;// 개념적으로는
new MemberService(memberRepository);

// 실제로는 Reflection 사용
Constructor.newInstance(찾은_Bean들);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;생성자 주입은 객체 생성 시점에 함께 이루어진다.&lt;/b&gt; 이 부분도 AutowiredAnnotationBeanPostProcessor가 쓸 생성자를 골라준다(determineCandidateConstructors). 여기까지 오면 객체 생성 자체는 완료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 의존성 주입 (필드 / setter)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 만들어진 뒤, &lt;b&gt;AutowiredAnnotationBeanPostProcessor&lt;/b&gt; 가 그 Bean을 훑어서 @Autowired가 붙은 &lt;b&gt;필드나 setter&lt;/b&gt;에 필요한 Bean을 찾아 주입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 &lt;b&gt;필드 주입은 안티패턴&lt;/b&gt;으로 분류되며, 생성자 주입이 권장된다. 이유는 다음과 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;① final을 못 붙인다 &amp;rarr; 불변성이 깨진다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드 주입은 객체를 만든 뒤에 값을 꽂는 방식이라 final을 쓸 수 없다. 생성자 주입은 final로 선언해서, 한 번 주입되면 바꾸지 못하게 막을 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 테스트 시 객체를 순수하게 못 만든다&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;haxe&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;MemberService s = new MemberService();  // repo가 null &amp;rarr; NPE&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드 주입은 생성자에 의존성이 없으니, 순수하게 new로 만들면 필드가 비어 있다. 테스트하려면 Spring 컨테이너를 띄우거나 Reflection을 써야 한다. 생성자 주입이면 new MemberService(mockRepo) 한 줄로 끝난다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;③ 순환참조를 (조용히) 허용할 수 있다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A&amp;harr;B 순환참조가 있을 때, &lt;b&gt;생성자 주입은 시작 시점에 무조건 에러로 터뜨려서&lt;/b&gt; &quot;설계가 잘못됐다&quot;고 바로 알려준다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;버전 주의:&lt;/b&gt; 흔히 &quot;필드 주입은 순환참조가 있어도 에러 없이 굴러간다&quot;고 설명하는데, 이는 &lt;b&gt;Spring Boot 2.6 미만&lt;/b&gt;에서의 이야기다. &lt;b&gt;Spring Boot 2.6 / Spring Framework 5.3부터는 spring.main.allow-circular-references 기본값이 false&lt;/b&gt; 라서, 필드 주입이어도 기본 설정에서는 시작 시점에 BeanCurrentlyInCreationException이 발생한다. 옛날처럼 통과시키려면 해당 옵션을 true로 켜거나 @Lazy를 써야 한다. 즉 필드 주입은 &quot;옵션을 켜면 순환참조를 숨길 수 있는&quot; 반면, 생성자 주입은 아예 끌 수조차 없이 항상 막힌다는 점이 차이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;④ 의존성이 숨겨진다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 주입은 &lt;b&gt;생성자 시그니처만 봐도&lt;/b&gt; 이 클래스가 무엇을 필요로 하는지 한눈에 드러난다. 필드 주입은 클래스 여기저기 흩어진 @Autowired를 전부 찾아봐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 초기화 후처리 (@PostConstruct &amp;rarr; AOP Proxy)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DI가 끝난 뒤 초기화 콜백과 프록시 처리가 이어진다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;less&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;@Autowired 주입 끝
   &amp;rarr; @PostConstruct 실행        (postProcessBeforeInitialization 시점)
   &amp;rarr; (InitializingBean.afterPropertiesSet / init-method)
   &amp;rarr; AOP Proxy 생성             (postProcessAfterInitialization 시점)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@PostConstruct&lt;/b&gt; : &quot;주입이 다 끝났으니 이제 초기화 로직을 돌려라&quot;는 콜백.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP Proxy&lt;/b&gt; : @Transactional, @Async 같은 게 걸려 있으면 원본 객체를 &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;h2 data-ke-size=&quot;size26&quot;&gt;11. Singleton Cache 저장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성된 Bean은 &lt;b&gt;1차 캐시&lt;/b&gt;에 들어간다. DefaultSingletonBeanRegistry의 singletonObjects인데, &lt;b&gt;&amp;nbsp;ConcurrentHashMap&amp;lt;String, Object&amp;gt; 구조&lt;/b&gt;다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dart&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;// Spring 내부 소스
private final Map&amp;lt;String, Object&amp;gt; singletonObjects = new ConcurrentHashMap&amp;lt;&amp;gt;(256);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;avrasm&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;singletonObjects: { &quot;memberService&quot; &amp;rarr; MemberService 객체 }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 getBean(&quot;memberService&quot;)를 호출하면 매번 새로 만드는 게 아니라 이 캐시에서 꺼내준다. 그래서 싱글톤이 &quot;딱 하나&quot;로 유지되는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;MemberService service = context.getBean(MemberService.class);  // 캐시에서 반환&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고: 싱글톤 3단계 캐시&lt;/b&gt; 사실 DefaultSingletonBeanRegistry에는 캐시가 세 개 있고, 이 구조가 9번에서 본 순환참조 해소의 핵심이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1차 singletonObjects&lt;/b&gt; : 완성된 Bean&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2차 earlySingletonObjects&lt;/b&gt; : 생성은 됐지만 아직 미완성인(주입 진행 중) Bean&lt;/li&gt;
&lt;li&gt;&lt;b&gt;3차 singletonFactories&lt;/b&gt; : 미완성 Bean을 반환할 수 있는 ObjectFactory&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 도중인 Bean을 3차 &amp;rarr; 2차로 &quot;조기 노출(early exposure)&quot;해서 순환참조를 풀어내는 구조다. (단, 10번에서 본 것처럼 기본 설정에서는 순환참조 자체가 막혀 있다.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름을 한 줄로 요약하면 이렇다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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;pre class=&quot;less&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;@ComponentScan (ASM 스캔)
   &amp;rarr; 후보 발견
   &amp;rarr; BeanDefinition 생성
   &amp;rarr; BeanDefinitionRegistry 등록
   &amp;rarr; preInstantiateSingletons()
   &amp;rarr; 생성자 호출 (+ 생성자 주입)
   &amp;rarr; 필드/setter 주입
   &amp;rarr; @PostConstruct &amp;rarr; AOP Proxy
   &amp;rarr; singletonObjects(1차 캐시) 저장&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component 하나가 컨테이너 안에서 거치는 길이 생각보다 길다. 특히 &lt;b&gt;&quot;BeanDefinition 등록 단계&quot;와 &quot;실제 객체 생성 단계&quot;가 분리되어 있다는 점&lt;/b&gt;, 그리고 &lt;b&gt;생성자 주입과 필드 주입의 시점 차이&lt;/b&gt;를 이해하면 DI 관련 동작 대부분이 설명된다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>@Component</category>
      <category>@Component등록</category>
      <category>spring</category>
      <category>생성자 주입</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/50</guid>
      <comments>https://myh7754.tistory.com/50#entry50comment</comments>
      <pubDate>Thu, 4 Jun 2026 01:21:08 +0900</pubDate>
    </item>
    <item>
      <title>GC 동작 방식 및 원리 (1)</title>
      <link>https://myh7754.tistory.com/49</link>
      <description>&lt;div class=&quot;jvm-post&quot;&gt;
&lt;h1&gt;GC &amp;mdash; 세대 가설과 GC Root&lt;/h1&gt;
&lt;p class=&quot;subtitle&quot; data-ke-size=&quot;size16&quot;&gt;GC가 객체의 생사를 판별하는 근거와, 실무에서 자주 마주치는 GC Root발 메모리 누수 패턴을 코드와 함께 파헤친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C와 C++에서는 메모리 할당과 해제를 개발자가 직접 관리해야 한다. Java는 이 과정을 자동화한 GC(Garbage Collector)를 제공한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;GC 알고리즘(G1, ZGC 등)과 Stop-the-World, Card Table, Write Barrier 등 세부 동작은 다음 글에서 다룬다. 이 글에서는 GC가 세대를 나누는 근거와 GC Root, 그리고 GC Root로 인한 메모리 누수 패턴까지만 짚는다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1 GC를 뒷받침하는 3가지 가설&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC는 아래 3가지 가설을 근거로 동작 방식을 설계한다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;가설&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;th&gt;근거가 되는 설계&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;약한 세대 가설&lt;br /&gt;&lt;span style=&quot;font-size: 13px;&quot;&gt;(Weak Generational Hypothesis)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;대부분의 객체는 생성된 직후 빠르게 사라진다&lt;/td&gt;
&lt;td&gt;Young 영역 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;강한 세대 가설&lt;br /&gt;&lt;span style=&quot;font-size: 13px;&quot;&gt;(Strong Generational Hypothesis)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;오래 살아남은 객체일수록 더 오래 산다&lt;/td&gt;
&lt;td&gt;Old 영역 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;세대간 참조 희소성&lt;br /&gt;&lt;span style=&quot;font-size: 13px;&quot;&gt;(Intergenerational Reference Rarity)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;Old 영역이 Young 영역을 참조하는 경우는 드물다&lt;/td&gt;
&lt;td&gt;Card Table로 그 드문 참조만 기록 &amp;rarr; Minor GC 시 Old 영역 전체를 스캔할 필요 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가설이 바로 Java GC가 Heap을 Young/Old로 나누고, Minor GC에서 Old 영역을 건너뛸 수 있게 하는 근거다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2 GC 대상은 어떻게 선별되는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC 대상을 판별하는 알고리즘은 크게 2가지다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reference Counting&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 참조될 때마다 카운트를 +1 하고, 참조가 끊기면 -1 한다. 카운트가 0이 되면 GC 대상으로 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점은 순환 참조다. 서로를 참조하는 두 객체는 외부에서 접근 불가능해도 카운트가 0이 되지 않아 영원히 수거되지 않는다. 이 문제를 해결하는 것이 Reachability다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reachability&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC Root를 기준으로 참조 그래프를 탐색해, GC Root에서 끝까지 도달 가능(Reachable)한 객체만 살아있다고 판단한다. 도달할 수 없는 객체는 GC 대상이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GC Root란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무조건 살아있다고 간주하는 시작점이다. GC는 이 지점들에서 출발해 참조 그래프를 탐색(Mark)하고, 방문하지 못한 객체를 수거(Sweep)한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GC Root의 종류&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;static 변수&lt;/li&gt;
&lt;li&gt;스택 프레임의 지역 변수 / 매개변수&lt;/li&gt;
&lt;li&gt;실행 중인 스레드&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;JVM에서 살아있는 모든 스레드 객체 자체가 GC Root다. 스레드가 살아있는 동안, 그 스레드의 스택 프레임과 내부 참조들은 모두 Reachable 상태를 유지한다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3 GC Root로 발생하는 메모리 누수 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC는 도달 가능한 객체를 정확히 판단하지만, 개발자가 의도치 않게 GC Root까지의 참조 체인을 계속 살려두면 메모리 누수가 발생한다. 실무에서 자주 만나는 3가지 패턴을 코드로 살펴본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;패턴 1: static 컬렉션에 지속적으로 추가&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;class EventBus {
    // static &amp;rarr; GC Root &amp;rarr; listeners의 모든 원소가 Reachable
    private static final List&amp;lt;EventListener&amp;gt; listeners = new ArrayList&amp;lt;&amp;gt;();

    public static void register(EventListener listener) {
        listeners.add(listener);
        // 제거 로직이 없으면 등록된 listener는 절대 GC되지 않음
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 1: 명시적 unregister() 제공&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;class EventBus {
    private static final List&amp;lt;EventListener&amp;gt; listeners = new ArrayList&amp;lt;&amp;gt;();

    public static void register(EventListener listener) {
        listeners.add(listener);
    }

    // 사용 측에서 반드시 호출해야 함
    public static void unregister(EventListener listener) {
        listeners.remove(listener);
        // listener &amp;rarr; GC Root 체인 끊김 &amp;rarr; GC 대상이 될 수 있음
    }
}

// 사용 예
EventListener listener = new MyListener();
EventBus.register(listener);
try {
    // ...
} finally {
    EventBus.unregister(listener);  // 반드시 해제
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 2: WeakReference 활용&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;class EventBus {
    // WeakReference로 감싸면 listener를 Strong 참조하는 곳이 없을 때 GC 가능
    private static final List&amp;lt;WeakReference&amp;lt;EventListener&amp;gt;&amp;gt; listeners = new ArrayList&amp;lt;&amp;gt;();

    public static void register(EventListener listener) {
        listeners.add(new WeakReference&amp;lt;&amp;gt;(listener));
    }

    public static void notify(Event event) {
        Iterator&amp;lt;WeakReference&amp;lt;EventListener&amp;gt;&amp;gt; it = listeners.iterator();
        while (it.hasNext()) {
            EventListener listener = it.next().get();
            if (listener == null) {
                it.remove();  // 이미 GC된 항목 정리
            } else {
                listener.onEvent(event);
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WeakReference 방식은 unregister를 강제하지 않아도 외부에서 Strong 참조가 없어지면 자동으로 GC된다. 다만 &lt;code&gt;get()&lt;/code&gt;이 null을 반환할 수 있으므로 null 체크가 필수다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;패턴 2: ThreadLocal 변수 미정리&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 스레드 풀 환경에서 스레드가 재사용되는 경우
private static final ThreadLocal&amp;lt;LargeObject&amp;gt; context = new ThreadLocal&amp;lt;&amp;gt;();

void handleRequest() {
    context.set(new LargeObject());
    process();
    // context.remove() 호출 안 하면?
    // 스레드가 풀로 반환된 후에도 ThreadLocal이 LargeObject를 참조
    // 해당 스레드가 살아있는 한(= GC Root) LargeObject도 살아있음
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결:&lt;/b&gt; finally 블록에서 반드시 &lt;code&gt;context.remove()&lt;/code&gt; 호출.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;void handleRequest() {
    context.set(new LargeObject());
    try {
        process();
    } finally {
        context.remove();  // 반드시 정리
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;패턴 3: 익명 클래스 / 람다의 외부 참조&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;class View {
    private byte[] largeData = new byte[1024 * 1024];  // 1MB

    void startAsyncTask() {
        // 익명 클래스는 암묵적으로 외부 클래스(View) 인스턴스를 참조
        new Thread(() -&amp;gt; {
            // 이 람다가 살아있는 동안 View 인스턴스도 GC 불가
            // largeData도 함께 메모리에 남음
            doLongRunningTask();
        }).start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 1: 필요한 데이터만 지역변수로 캡처&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class View {
    private byte[] largeData = new byte[1024 * 1024];  // 1MB

    void startAsyncTask() {
        // largeData에서 실제로 필요한 값만 추출해서 캡처
        // 람다는 View 인스턴스가 아닌 int 값만 참조 &amp;rarr; View는 GC 가능
        int targetValue = extractTargetValue(largeData);

        new Thread(() -&amp;gt; {
            doLongRunningTask(targetValue);  // View 참조 없음
        }).start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 2: static 중첩 클래스 사용&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;class View {
    private byte[] largeData = new byte[1024 * 1024];

    void startAsyncTask() {
        // static 중첩 클래스는 외부 클래스 인스턴스를 암묵적으로 참조하지 않음
        new Thread(new StaticTask()).start();
    }

    // static &amp;rarr; 외부 클래스(View)에 대한 암묵적 참조 없음
    private static class StaticTask implements Runnable {
        @Override
        public void run() {
            doLongRunningTask();  // View.this 참조 불가 &amp;rarr; View GC 가능
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 클래스(Inner Class)와 static 중첩 클래스(Static Nested Class)의 차이가 여기서 중요하다. 내부 클래스는 외부 인스턴스에 대한 암묵적 참조(&lt;code&gt;Outer.this&lt;/code&gt;)를 갖지만, static 중첩 클래스는 그렇지 않다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;개념&lt;/th&gt;
&lt;th&gt;핵심&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;세대 가설 3가지&lt;/td&gt;
&lt;td&gt;약한/강한 세대 가설로 Young&amp;middot;Old 분리, 참조 희소성으로 Card Table 기반 Minor GC 최적화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reference Counting&lt;/td&gt;
&lt;td&gt;참조 카운트 기반, 순환 참조 문제로 단독 사용 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reachability&lt;/td&gt;
&lt;td&gt;GC Root에서 도달 가능한 객체만 생존 판정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC Root&lt;/td&gt;
&lt;td&gt;static 변수, 스택 프레임의 지역변수/매개변수, 실행 중인 스레드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;누수 패턴&lt;/td&gt;
&lt;td&gt;static 컬렉션 미정리, ThreadLocal 미정리, 람다/익명 클래스의 외부 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 GC의 세대 가설과 GC Root를 알아봤다. 다음 글에서는 실제 GC 알고리즘(Serial, Parallel, G1, ZGC)과 Stop-the-World, Card Table의 동작을 이어서 다룬다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Java</category>
      <category>GC</category>
      <category>Java</category>
      <category>jvm</category>
      <category>세대가설</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/49</guid>
      <comments>https://myh7754.tistory.com/49#entry49comment</comments>
      <pubDate>Tue, 2 Jun 2026 19:44:13 +0900</pubDate>
    </item>
    <item>
      <title>JVM 정리 &amp;mdash; Java 코드가 실행되기까지</title>
      <link>https://myh7754.tistory.com/48</link>
      <description>&lt;div&gt;
&lt;style&gt;
  .jvm-post {
    font-family: 'Noto Sans KR', 'Apple SD Gothic Neo', sans-serif;
    font-size: 16px;
    line-height: 1.85;
    color: #111827 !important;
    word-break: keep-all;
    --bg-code: #eef0f3;
    --bg-pre: #1e1e2e;
    --bg-quote: #e8f4ff;
    --bg-table-head: #e9ecf0;
    --bg-table-even: #f3f5f7;
    --text: #111827;
    --text-muted: #1f2937;
    --text-code: #b91c1c;
    --text-pre: #f8f8f2;
    --text-quote: #1e3a8a;
    --text-quote-strong: #1e3a8a;
    --text-quote-code: #1d4ed8;
    --bg-quote-code: #bfdbfe;
    --border: #9ca3af;
    --border-quote: #2563eb;
    --border-table: #9ca3af;
    --placeholder-bg: #f3f4f6;
    --placeholder-border: #9ca3af;
    --placeholder-text: #6b7280;
    --section-num-bg: #111827;
    --section-num-text: #fff;
    --h-color: #0f172a;
  }

  @media (prefers-color-scheme: dark) {
    .jvm-post { color: #e2e2e2 !important; }
    .jvm-post h1, .jvm-post h2, .jvm-post h3, .jvm-post h4, .jvm-post strong { color: #f4f4f5 !important; }
    .jvm-post p, .jvm-post li, .jvm-post td { color: #e2e2e2 !important; }
    .jvm-post h2 { border-bottom: 2px solid #3f3f46 !important; }
    .jvm-post hr { border-top: 1px solid #3f3f46 !important; }
    .jvm-post code { background: #2d2d3a !important; color: #f87171 !important; }
    .jvm-post pre { background: #11111b !important; color: #f0f0f0 !important; }
    .jvm-post pre code { background: none !important; color: #f0f0f0 !important; }
    .jvm-post blockquote { background: #1a2744 !important; border-left: 4px solid #3b82f6 !important; color: #93c5fd !important; }
    .jvm-post blockquote strong { color: #bfdbfe !important; }
    .jvm-post blockquote code { background: #1e3a5f !important; color: #93c5fd !important; }
    .jvm-post th { background: #27272a !important; color: #a1a1aa !important; border: 1px solid #3f3f46 !important; }
    .jvm-post td { border: 1px solid #3f3f46 !important; }
    .jvm-post tr:nth-child(even) td { background: #222228 !important; }
    .jvm-post .subtitle { color: #a1a1aa !important; }
    .jvm-post .diagram-placeholder { background: #27272a !important; border: 2px dashed #52525b !important; color: #71717a !important; }
    .jvm-post .section-num { background: #e2e2e2 !important; color: #111 !important; }
  }
  .jvm-post h1 {
    font-size: 28px;
    font-weight: 800;
    margin: 0 0 8px 0;
    color: #0f172a !important;
    letter-spacing: -0.5px;
  }
  .jvm-post h2 {
    font-size: 22px;
    font-weight: 700;
    margin: 52px 0 16px 0;
    padding-bottom: 10px;
    border-bottom: 2px solid #64748b !important;
    color: #0f172a !important;
  }
  .jvm-post h3 {
    font-size: 18px;
    font-weight: 700;
    margin: 36px 0 12px 0;
    color: #111827 !important;
  }
  .jvm-post h4 {
    font-size: 16px;
    font-weight: 700;
    margin: 28px 0 8px 0;
    color: #1f2937 !important;
  }
  .jvm-post p {
    margin: 0 0 18px 0;
  }
  .jvm-post hr {
    border: none;
    border-top: 1px solid #64748b !important;
    margin: 40px 0;
  }
  .jvm-post code {
    font-family: 'JetBrains Mono', 'Fira Code', 'D2Coding', monospace;
    font-size: 14px;
    background: #eef0f3 !important;
    color: #b91c1c !important;
    padding: 2px 6px;
    border-radius: 4px;
  }
  .jvm-post pre {
    background: #1a1a2e !important;
    color: #f0f0f0 !important;
    font-family: 'JetBrains Mono', 'Fira Code', 'D2Coding', monospace;
    font-size: 13.5px;
    line-height: 1.7;
    padding: 20px 24px;
    border-radius: 10px;
    overflow-x: auto;
    margin: 16px 0 24px 0;
  }
  .jvm-post pre code {
    background: none !important;
    color: #f0f0f0 !important;
    padding: 0;
    border-radius: 0;
    font-size: inherit;
  }
  .jvm-post blockquote {
    background: #dbeafe !important;
    border-left: 4px solid #2563eb !important;
    margin: 20px 0;
    padding: 14px 20px;
    border-radius: 0 8px 8px 0;
    color: #1e3a8a !important;
    font-size: 14.5px;
  }
  .jvm-post blockquote strong {
    color: #1e3a8a !important;
  }
  .jvm-post blockquote code {
    background: #bfdbfe !important;
    color: #1d4ed8 !important;
  }
  .jvm-post ul {
    padding-left: 24px;
    margin: 0 0 18px 0;
  }
  .jvm-post li {
    margin-bottom: 6px;
  }
  .jvm-post strong {
    font-weight: 700;
    color: #0f172a !important;
  }
  .jvm-post table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14.5px;
    margin: 20px 0 28px 0;
  }
  .jvm-post th {
    background: #e2e8f0 !important;
    font-weight: 700;
    padding: 10px 16px;
    text-align: left;
    border: 1px solid #94a3b8 !important;
    color: #1f2937 !important;
  }
  .jvm-post td {
    padding: 10px 16px;
    border: 1px solid #94a3b8 !important;
    vertical-align: top;
    color: #111827 !important;
  }
  .jvm-post tr:nth-child(even) td {
    background: #f1f5f9 !important;
  }
  .jvm-post .subtitle {
    color: #1f2937 !important;
    font-size: 15px;
    margin: 0 0 32px 0;
  }
  .jvm-post .diagram-placeholder {
    background: #f1f5f9 !important;
    border: 2px dashed #94a3b8 !important;
    border-radius: 10px;
    padding: 40px;
    text-align: center;
    color: #6b7280 !important;
    font-size: 14px;
    margin: 20px 0 28px 0;
  }
  .jvm-post .section-num {
    display: inline-block;
    background: #0f172a !important;
    color: #ffffff !important;
    font-size: 13px;
    font-weight: 700;
    padding: 2px 10px;
    border-radius: 20px;
    margin-right: 8px;
    vertical-align: middle;
  }
&lt;/style&gt;
&lt;/div&gt;
&lt;div class=&quot;jvm-post&quot;&gt;
&lt;h1&gt;JVM &amp;mdash; Java 코드가 실행되기까지&lt;/h1&gt;
&lt;p class=&quot;subtitle&quot; data-ke-size=&quot;size16&quot;&gt;Class Loader &amp;rarr; Runtime Data Area &amp;rarr; Execution Engine 전체 흐름을 코드와 함께 깊이 파헤친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 코드는 어떻게 실행될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;main()&lt;/code&gt;을 실행하는 순간 JVM 안에서는 수십 가지 일이 동시에 벌어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Class Loader &amp;rarr; Runtime Data Area &amp;rarr; Execution Engine &amp;rarr; GC로 이어지는 JVM 실행 흐름 전체를 코드와 함께 깊이 다룬다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;GC는 분량상 별도 글에서 다룬다. 이 글에서는 구조와 기본 동작만 짚는다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java 실행 흐름 개요&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;.java 파일
  └─► javac (컴파일러)
        └─► 플랫폼 독립적인 .class (바이트코드)
              └─► JVM
                    ├─ Class Loader      &amp;rarr; .class를 메모리에 적재
                    ├─ Runtime Data Area &amp;rarr; 메모리 영역 구성
                    └─ Execution Engine  &amp;rarr; 바이트코드 &amp;rarr; 기계어 &amp;rarr; 실행
                                                  &amp;uarr;
                                              GC가 Heap 관리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이트코드는 OS에 종속되지 않는다. OS마다 구현된 JVM이 이를 받아서 해당 플랫폼의 기계어로 변환하며 실행한다. &quot;Write Once, Run Anywhere&quot;가 가능한 이유다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예시 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 기준으로 JVM의 각 영역이 어떻게 동작하는지 설명한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public interface Animal {
    String getName();
}

public class Dog implements Animal {
    public static int count = 2;    // static &amp;rarr; Method Area
    private String name;            // instance 필드 &amp;rarr; Heap

    public Dog(String name) { this.name = name; }

    @Override
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

public class Main {
    public static void main(String[] args) {
        int x = 7;                          // Stack: primitive 직접 저장
        int[] numbers = {1, 2, 3};          // Stack: 참조값(0x4000) &amp;rarr; Heap: int[]
        Animal animal = new Dog(&quot;coco&quot;);    // Stack: 참조값(0x1000) &amp;rarr; Heap: Dog①
        Dog d = new Dog(&quot;changed&quot;);         // Stack: 참조값(0x2000) &amp;rarr; Heap: Dog②

        Thread thread2 = new Thread(() -&amp;gt; {
            Dog sharedDog = (Dog) animal;   // Dog①을 두 스레드가 공유 &amp;rarr; Race Condition
            int localVar = 3;               // Thread 2의 Stack에만 존재
        });
        thread2.start();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 실행하면 메모리는 아래처럼 구성된다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷_28-5-2026_22939_.jpeg&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgQXsG/dJMcag6JFk8/NZA97TEFOpzou2mnhP4FIk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgQXsG/dJMcag6JFk8/NZA97TEFOpzou2mnhP4FIk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgQXsG/dJMcag6JFk8/NZA97TEFOpzou2mnhP4FIk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgQXsG%2FdJMcag6JFk8%2FNZA97TEFOpzou2mnhP4FIk%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;770&quot; height=&quot;924&quot; data-filename=&quot;스크린샷_28-5-2026_22939_.jpeg&quot; data-origin-width=&quot;770&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span class=&quot;section-num&quot;&gt;1&lt;/span&gt; Class Loader&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.class&lt;/code&gt; 파일을 JVM 메모리에 올리는 역할을 한다. 세 단계로 동작한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. Loading&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;.class&lt;/code&gt; 파일을 읽어 JVM 메모리에 적재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. Linking&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Verification&lt;/b&gt;&lt;br /&gt;바이트코드가 JVM 명세에 맞는지 검증한다. 손상된 클래스 파일이나 명세 위반 코드를 이 단계에서 차단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Preparation&lt;/b&gt;&lt;br /&gt;&lt;code&gt;static&lt;/code&gt; 변수에 타입별 기본값을 할당한다. (&lt;code&gt;int&lt;/code&gt; &amp;rarr; 0, &lt;code&gt;boolean&lt;/code&gt; &amp;rarr; false, 참조형 &amp;rarr; null)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Resolution&lt;/b&gt;&lt;br /&gt;클래스 파일 안의 심볼릭 참조(&lt;code&gt;#1&lt;/code&gt;, &lt;code&gt;#2&lt;/code&gt; &amp;hellip;)를 실제 메모리 주소로 치환한다. 이 심볼릭 참조들은 Method Area의 &lt;b&gt;Runtime Constant Pool&lt;/b&gt;에 저장되어 있다가 이 단계에서 실제 참조로 교체된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Runtime Constant Pool이란?&lt;/b&gt;&lt;br /&gt;클래스 파일 안에는 &lt;code&gt;&quot;coco&quot;&lt;/code&gt; 같은 상수, 메서드 시그니처, 필드 이름 등이 &lt;code&gt;#1&lt;/code&gt;, &lt;code&gt;#3&lt;/code&gt; 같은 심볼릭 인덱스로 저장되어 있다. Class Loader가 클래스를 메모리에 올리면 이 정보들이 Method Area의 Runtime Constant Pool에 적재되고, Resolution 단계에서 실제 메모리 주소로 교체된다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-3. Initialization&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;static&lt;/code&gt; 변수에 코드에서 정의한 실제 값을 할당하고, &lt;code&gt;static&lt;/code&gt; 블록을 실행한다.&lt;/p&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;public static int count = 2;
// Preparation:   count = 0  (기본값)
// Initialization: count = 2  (실제 값)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Lazy Loading&lt;/b&gt;: Class Loader는 필요한 시점에만 클래스를 로드한다. 처음 실행 시 모든 클래스를 올리지 않는다. 이 덕분에 애플리케이션 시작 비용이 줄어든다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Class Loader 계층 구조&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Bootstrap Class Loader    (C++로 구현, JVM 내장)
  └─ java.lang.String, Object 등 핵심 클래스
Platform Class Loader     (Java 9 이전: Extension Class Loader)
  └─ Java 확장 라이브러리 (java.sql, java.xml 등)
Application Class Loader
  └─ 내가 만든 클래스 + kafka, redis 등 외부 라이브러리&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Java 9 변경사항&lt;/b&gt;: Java 9에서 모듈 시스템(JPMS)이 도입되면서 &lt;code&gt;Extension ClassLoader&lt;/code&gt;가 &lt;code&gt;Platform ClassLoader&lt;/code&gt;로 이름이 바뀌고 역할도 재정의됐다. Java 8 이하 자료를 볼 때 참고하자.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부모 위임 모델 (Parent Delegation Model)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 로드 요청이 오면 자식 ClassLoader가 직접 처리하기 전에 부모에게 먼저 위임한다. 부모가 처리하지 못할 때만 자식이 처리한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;클래스 로드 요청 (Dog.class)
  &amp;rarr; Application ClassLoader: &quot;내가 먼저 로드할게&quot;
      &amp;rarr; Platform ClassLoader에 위임
          &amp;rarr; Bootstrap ClassLoader에 위임
              &amp;rarr; &quot;Dog는 모름&quot; &amp;rarr; 반환
          &amp;rarr; &quot;Dog는 모름&quot; &amp;rarr; 반환
      &amp;rarr; Application ClassLoader가 직접 로드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 &lt;code&gt;java.lang.String&lt;/code&gt;처럼 핵심 클래스를 외부에서 악의적으로 덮어쓰는 것을 방지할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span class=&quot;section-num&quot;&gt;2&lt;/span&gt; Runtime Data Area&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM이 OS로부터 할당받아 사용하는 메모리 공간이다. 스레드 공유 여부에 따라 두 가지로 나뉜다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드 비공유 영역 (Thread-private)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스레드가 독립적으로 보유하는 영역이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PC Register&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 스레드가 실행 중인 바이트코드의 위치(주소)를 저장한다. 스레드 스케줄링으로 CPU를 뺏겼다가 다시 받았을 때 어디서부터 재개할지 추적하는 역할이다. Native Method 실행 중에는 undefined 상태가 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JVM Stack&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 호출마다 &lt;b&gt;Stack Frame&lt;/b&gt;이 하나씩 쌓이고, 메서드가 종료되면 pop된다. GC 없이 자동 해제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Frame 내부 구조:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Local Variable Array&lt;/b&gt;: 지역 변수, 파라미터&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Operand Stack&lt;/b&gt;: 연산 중간 값 (바이트코드 명령어들이 이 스택을 읽고 씀)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Frame Data&lt;/b&gt;: 메서드 반환 주소, 상수 풀 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;main() Stack Frame
├─ x        = 7          (int, primitive &amp;rarr; 값 직접 저장)
├─ numbers  = 0x4000     (int[], 참조값만 저장 &amp;rarr; 실제 배열은 Heap)
├─ animal   = 0x1000     (Dog①의 Heap 주소)
└─ d        = 0x2000     (Dog②의 Heap 주소)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;StackOverflowError&lt;/b&gt;: 재귀 호출이 끝없이 쌓이면 Stack 공간이 초과된다. &lt;code&gt;-Xss&lt;/code&gt; 옵션으로 스택 크기를 조절할 수 있다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Native Method Stack&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C/C++로 작성된 Native Method 실행 전용 스택이다. JNI(Java Native Interface)와 함께 동작한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스레드 공유 영역 (Thread-shared)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 스레드가 접근 가능한 영역이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Heap&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;new&lt;/code&gt; 키워드로 생성된 객체 인스턴스가 저장된다. 모든 스레드가 공유하기 때문에 멀티스레드 환경에서 Race Condition이 발생할 수 있다. GC의 주요 대상이다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Heap
├── Young Generation
│   ├── Eden       &amp;larr; new 로 생성된 객체가 처음 올라오는 곳
│   └── Survivor (S0, S1)
└── Old Generation &amp;larr; 오래 살아남은 객체가 이동하는 곳&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;String Constant Pool&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 안에 존재하는 문자열 리터럴 전용 공간이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;String a = &quot;hello&quot;;             // Pool에 &quot;hello&quot; 생성 후 참조
String b = &quot;hello&quot;;             // Pool에 이미 있으므로 재사용 &amp;rarr; a == b: true
String c = new String(&quot;hello&quot;); // new 키워드 &amp;rarr; Pool 아닌 Heap에 별도 객체 생성
                                // a == c: false, a.equals(c): true&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;왜 Java 7부터 Pool이 Heap으로 이동했나?&lt;/b&gt;&lt;br /&gt;이전까지는 Pool이 PermGen(Permanent Generation) 영역에 있었다. PermGen은 크기가 고정되어 있어 문자열이 많이 쌓이면 &lt;code&gt;OutOfMemoryError&lt;/code&gt;가 발생하는 문제가 있었다. Java 7부터 Pool을 Heap으로 옮겨 GC 대상이 되도록 해 이 문제를 해결했다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;intern()&lt;/code&gt; 메서드를 사용하면 Heap에 생성된 String 객체를 Pool로 이동시키거나, 이미 Pool에 동일한 값이 있으면 그것을 재사용할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Method Area&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 자체의 메타데이터가 올라오는 곳이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장되는 정보:&lt;/p&gt;
&lt;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;code&gt;static&lt;/code&gt; 변수 (예: &lt;code&gt;Dog.count = 2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;메서드 바이트코드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Runtime Constant Pool&lt;/b&gt; (심볼릭 참조 &amp;rarr; Resolution 후 실제 주소로 교체)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;Java 8: PermGen &amp;rarr; Metaspace&lt;/b&gt;&lt;br /&gt;Java 8 이전에는 Method Area가 Heap 내부의 PermGen으로 구현되어 있었다. 크기가 고정되어 있어 클래스를 많이 로드하면 &lt;code&gt;java.lang.OutOfMemoryError: PermGen space&lt;/code&gt;가 발생했다. Java 8부터 Metaspace로 전환됐다. Metaspace는 JVM Heap이 아닌 Native Memory(OS가 관리하는 메모리)를 직접 사용하기 때문에 크기가 동적으로 조절된다. &lt;code&gt;-XX:MaxMetaspaceSize&lt;/code&gt;로 상한을 설정할 수 있다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span class=&quot;section-num&quot;&gt;3&lt;/span&gt; Execution Engine&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Class Loader가 올리고, Runtime Data Area가 저장하면, Execution Engine이 실행한다. 세 가지 컴포넌트로 구성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Interpreter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이트코드를 한 줄씩 읽어 기계어로 변환하며 실행한다. 구현이 단순하고 즉시 실행이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점: 같은 코드를 반복 실행해도 매번 새로 해석한다. 자주 호출되는 코드에서는 병목이 생긴다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JIT Compiler (Just-In-Time)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interpreter의 반복 해석 문제를 보완하기 위해 도입됐다. JVM이 실행 중에 &lt;b&gt;반복 호출되는 코드(핫스팟)&lt;/b&gt;를 감지하면, 해당 바이트코드를 통째로 네이티브 코드로 컴파일해 캐싱한다. 이후 동일한 코드 실행 시 컴파일 없이 캐시된 네이티브 코드를 바로 실행한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;처음 실행  &amp;rarr; Interpreter가 한 줄씩 해석
반복 감지  &amp;rarr; JIT가 네이티브 코드로 컴파일 + 캐싱
이후 실행  &amp;rarr; 캐싱된 네이티브 코드 바로 실행 (빠름)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;왜 처음부터 JIT를 안 쓰나?&lt;/b&gt; 한 번만 실행되는 코드까지 전부 컴파일하면 컴파일 비용 자체가 오히려 더 크다. Interpreter로 시작해서 자주 쓰이는 코드만 JIT가 가져가는 구조가 효율적이다.&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tiered Compilation &amp;mdash; C1 / C2&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 7부터 기본값. JIT를 두 단계로 나눠 점진적으로 최적화를 적용한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;호출 횟수          컴파일러
0회              &amp;rarr; Interpreter
~2,000회         &amp;rarr; C1 (가벼운 최적화)
~10,000회        &amp;rarr; C2 (공격적인 최적화)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;위 수치는 JVM 기본값이다. &lt;code&gt;-XX:CompileThreshold&lt;/code&gt; 옵션으로 직접 조절할 수 있다. JVM 버전이나 환경에 따라 실제 임계값은 다를 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C1 (Client Compiler)&lt;/b&gt;&lt;br /&gt;빠르게 컴파일하고 가벼운 최적화를 수행한다. 인라이닝, 기본 최적화가 주다. 목적은 컴파일 지연 최소화다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C2 (Server Compiler)&lt;/b&gt;&lt;br /&gt;컴파일이 느리지만 공격적인 최적화를 수행한다. 루프 최적화, 탈출 분석(Escape Analysis), 인라이닝 등을 적극 적용한다. 목적은 장기 실행 성능 극대화다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C2 주요 최적화&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;① 인라이닝 (Inlining)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 호출을 없애고 호출부에 코드를 직접 삽입한다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// 최적화 전
int result = add(a, b);
int add(int x, int y) { return x + y; }

// C2 인라이닝 후
int result = a + b;  // Stack Frame 생성/반환 오버헤드 제거&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 호출은 Stack Frame 생성 &amp;rarr; 인자 복사 &amp;rarr; 반환 주소 저장 등의 비용이 있다. 짧은 메서드가 루프 안에서 반복 호출될수록 인라이닝 효과가 크다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;② 루프 최적화 (Loop Optimization)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문 내부의 불필요한 연산을 제거하거나 재구성한다. 대표적인 예가 &lt;b&gt;루프 불변 코드 이동 (Loop Invariant Code Motion)&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 최적화 전: list.size()를 매 반복마다 호출
for (int i = 0; i &amp;lt; list.size(); i++) {
    process(list.get(i));
}

// C2 최적화 후: 루프 불변 값을 밖으로 꺼냄
int size = list.size();
for (int i = 0; i &amp;lt; size; i++) {
    process(list.get(i));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 &lt;b&gt;루프 언롤링(Loop Unrolling)&lt;/b&gt;(반복 횟수를 줄이고 본문을 펼침), &lt;b&gt;벡터화&lt;/b&gt;(SIMD 명령어 활용) 등도 수행한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;③ 탈출 분석 (Escape Analysis)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 생성된 메서드 밖으로 &quot;탈출&quot;하는지 분석한다. 탈출하지 않는 객체는 Heap 대신 Stack에 할당한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 탈출하는 경우 &amp;rarr; Heap 할당
Dog createDog() {
    Dog d = new Dog(&quot;coco&quot;);
    return d;  // 메서드 밖으로 나감 &amp;rarr; Heap
}

// 탈출하지 않는 경우 &amp;rarr; Stack 할당 가능
void process() {
    Dog d = new Dog(&quot;coco&quot;);
    System.out.println(d.getName());
    // d가 이 메서드 안에서만 쓰임 &amp;rarr; C2가 Stack에 할당
}  // 메서드 종료 시 자동 해제 &amp;rarr; GC 부담 없음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 할당은 GC 대상이 되어 Stop-the-World를 유발할 수 있다. Stack 할당은 메서드 종료 시 자동 해제되어 GC를 완전히 우회한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;④ 락 제거 (Lock Elision)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탈출 분석의 연장선이다. 객체가 단일 스레드 안에서만 사용된다고 분석되면, 해당 객체의 &lt;code&gt;synchronized&lt;/code&gt; 블록 자체를 제거한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;void method() {
    StringBuffer sb = new StringBuffer();  // synchronized 메서드를 가짐
    sb.append(&quot;hello&quot;);
    sb.append(&quot; world&quot;);
    System.out.println(sb.toString());
    // sb가 이 메서드 밖으로 나가지 않음
    // &amp;rarr; C2가 synchronized 자체를 제거
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;StringBuffer&lt;/code&gt;는 메서드마다 &lt;code&gt;synchronized&lt;/code&gt;가 걸려 있지만, 로컬에서만 쓰이는 경우 락 비용 없이 실행된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OSR (On-Stack Replacement)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JIT 설명에서 빠지기 쉬운 개념이다. 일반적으로 JIT는 메서드 단위로 컴파일하는데, 긴 루프가 있으면 문제가 생긴다. 루프가 끝나야 다음 메서드 호출이 발생하고, 그때서야 JIT가 적용되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OSR은 이미 실행 중인 루프를 &lt;b&gt;중간에 JIT 컴파일된 코드로 교체&lt;/b&gt;하는 기법이다. Interpreter로 루프를 돌다가 핫스팟으로 감지되는 순간, 루프가 끝날 때까지 기다리지 않고 즉시 최적화된 네이티브 코드로 전환한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[루프 실행 중, Interpreter]
  i=0  &amp;rarr; i=1  &amp;rarr; ... &amp;rarr; i=4999  &amp;larr; 핫스팟 감지
                               &amp;larr; JIT 컴파일 완료
  i=5000 &amp;rarr; 이 시점부터 네이티브 코드로 교체(OSR)
  i=5001 &amp;rarr; i=5002 &amp;rarr; ... [이후 JIT 속도로 실행]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deoptimization&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C2가 공격적 최적화를 수행했는데 이후에 그 가정이 틀렸음이 확인되면, JVM은 최적화를 취소하고 Interpreter로 되돌아간다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void process() {
    Dog d = new Dog(&quot;coco&quot;);
    // C2 판단: &quot;d는 탈출하지 않는다&quot; &amp;rarr; Stack 할당으로 최적화
    // 이후 리플렉션 등으로 d가 외부에 노출되는 경우가 생기면?
    // &amp;rarr; Deoptimization: 최적화 취소 &amp;rarr; Interpreter로 복귀
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deoptimization은 JVM이 안전하게 동작을 보장하기 위한 메커니즘이다. &lt;code&gt;-XX:+PrintCompilation&lt;/code&gt; 옵션을 켜면 JIT 컴파일과 Deoptimization 과정을 로그로 확인할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span class=&quot;section-num&quot;&gt;4&lt;/span&gt; GC (Garbage Collector)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap에서 더 이상 참조되지 않는 객체를 자동으로 제거한다. Java는 개발자가 직접 메모리를 해제하지 않아도 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 객체 생성        &amp;rarr; Eden에 할당
2. Eden이 꽉 참     &amp;rarr; Minor GC 발생
3. 살아남은 객체    &amp;rarr; Survivor로 이동 (age 증가)
4. age 임계값 초과  &amp;rarr; Old Generation으로 이동 (Promotion)
5. Old Gen이 꽉 참  &amp;rarr; Major GC (Full GC) 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GC 알고리즘(G1, ZGC 등), Stop-the-World, Card Table, Write Barrier 등 세부 동작은 다음 글에서 다룬다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;컴포넌트&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Class Loader&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.class&lt;/code&gt; 파일을 Loading &amp;rarr; Linking &amp;rarr; Initialization 순으로 메모리에 적재&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime Data Area&lt;/td&gt;
&lt;td&gt;Thread-private(PC, Stack, Native Stack) + Thread-shared(Heap, String Pool, Method Area)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution Engine&lt;/td&gt;
&lt;td&gt;Interpreter로 시작, 핫스팟 감지 시 JIT(C1&amp;rarr;C2)로 최적화. OSR, Deopt, Escape Analysis 등 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC&lt;/td&gt;
&lt;td&gt;Heap의 unreachable 객체를 자동 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 단순한 실행 환경이 아니다. Class Loader가 보안을 담당하고, Runtime Data Area가 메모리를 구조화하고, Execution Engine이 런타임에 코드를 분석해 최적화하는, 정교하게 설계된 시스템이다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Java</category>
      <category>Java</category>
      <category>JIT</category>
      <category>jvm</category>
      <category>JVM 동작</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/48</guid>
      <comments>https://myh7754.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 28 May 2026 17:17:31 +0900</pubDate>
    </item>
    <item>
      <title>Deboard 블로그 프로젝트 좋아요 동시성 및 데드락 문제 해결과정</title>
      <link>https://myh7754.tistory.com/46</link>
      <description>&lt;h1&gt;좋아요 동시성 문제 &amp;mdash; 데드락에서 Race Condition까지&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;toggleLike&lt;/code&gt; 구현 후 동시성 테스트를 작성하는 과정에서 단계적으로 발견한 두 가지 문제와 해결 과정을 정리한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;P &amp;mdash; 문제 발견 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;toggleLike 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 토글은 직관적으로 이렇게 동작한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. (userId, postId)로 좋아요 레코드 조회
2. 있으면 &amp;rarr; likeCount 감소 + 레코드 삭제
3. 없으면 &amp;rarr; likeCount 증가 + 레코드 삽입&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회와 삽입이 별도 쿼리로 나뉘어 있어 두 요청이 동시에 진입하면 정합성이 깨질 수 있다고 판단했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동시성 테스트 작성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CountDownLatch로 100개 스레드를 동시에 출발시키는 테스트를 작성했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(100);

for (int i = 0; i &amp;lt; 100; i++) {
    executorService.submit(() -&amp;gt; {
        startLatch.await(); // 100개 스레드 동시 출발 대기
        likeService.toggleLike(postId);
        doneLatch.countDown();
    });
}
startLatch.countDown(); // 동시 출발 신호
doneLatch.await();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 실행 &amp;rarr; 데드락 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상했던 race condition보다 먼저 데드락이 터졌다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException:
Deadlock found when trying to get lock; try restarting transaction&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;A &amp;mdash; 1차: 데드락 분석 및 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인: FK shared lock upgrade 충돌&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 구현 코드 흐름은 이랬다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Optional&amp;lt;Likes&amp;gt; likes = likesRepository.findByPostIdAndUserId(postId, userId);
if (likes.isPresent()) {
    likesRepository.delete(likes.get());       // Likes 조작
} else {
    likesRepository.save(Likes.toEntity(...)); // Likes INSERT &amp;larr; FK로 Post에 shared lock
}
int count = likesRepository.countByPostId(postId);
Post post = postService.getPostById(postId);   // Post SELECT
post.setLikeCount(count);                      // commit 시 Post UPDATE (exclusive lock 필요)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;likes 테이블은 post_id로 posts에 FK를 가지고 있다. InnoDB는 FK 참조 무결성 확인을 위해 &lt;b&gt;INSERT 시 참조 대상 행(posts)에 shared lock을 획득&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 commit 시점에 dirty checking으로 &lt;code&gt;UPDATE posts&lt;/code&gt;가 실행되는데 exclusive lock이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;TX A: INSERT likes &amp;rarr; posts shared lock 획득
      &amp;rarr; commit 시 posts exclusive lock 필요 (TX B가 shared lock 보유 중 &amp;rarr; 대기)

TX B: INSERT likes &amp;rarr; posts shared lock 획득
      &amp;rarr; commit 시 posts exclusive lock 필요 (TX A가 shared lock 보유 중 &amp;rarr; 대기)

&amp;rarr; 두 트랜잭션이 각각 shared lock을 쥔 채 upgrade를 기다림 &amp;rarr; Deadlock&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교착상태 4조건 중 &lt;b&gt;점유대기(hold-and-wait)&lt;/b&gt; 가 성립하고 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결: posts UPDATE를 먼저 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;posts에 대한 UPDATE를 likes 조작보다 먼저 실행하면 exclusive lock을 선점할 수 있다. 이후 INSERT likes의 FK shared lock 요청은 자신의 exclusive lock으로 만족되므로 순환 대기가 사라진다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;// 변경 후 &amp;mdash; posts UPDATE 먼저, likes 조작 나중
if (likes.isPresent()) {
    postRepository.decreaseLikeCount(postId);  // posts UPDATE 먼저
    likesRepository.delete(likes.get());        // Likes DELETE 나중
} else {
    postRepository.increaseLikeCount(postId);  // posts UPDATE 먼저
    likesRepository.save(...);                  // Likes INSERT 나중
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;code&gt;getReferenceById&lt;/code&gt;로 User/Post 프록시만 가져와 불필요한 SELECT를 제거했다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Post post = postService.getPostReferenceById(postId);  // SELECT 없이 프록시만 반환
User user = userService.getUserReferenceById(userId);  // SELECT 없이 프록시만 반환&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데드락 해결 후 재테스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데드락은 사라졌다. 하지만 같은 사용자가 동시에 요청을 보내면 likes 행이 중복 삽입되는 문제가 남아 있었다. 그리고 더 근본적인 문제가 보이기 시작했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;A &amp;mdash; 2차: Race Condition 분석 및 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 1: likeCount Lost Update&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 dirty checking 방식은 read &amp;rarr; modify &amp;rarr; write 3단계가 분리되어 있어 비원자적이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;TX A: post 읽음 (likeCount = 5) ──── +1 ──── likeCount = 6 commit
TX B: post 읽음 (likeCount = 5) ──── +1 ──── likeCount = 6 commit  &amp;larr; A 덮어씀
&amp;rarr; 기대값: 7, 실제값: 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 테스트로 확인했다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;[dirty checking] likeEntity 수: 100, likeCount: 6 &amp;rarr; 불일치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100명이 동시에 좋아요를 눌렀는데 likes 행은 100개가 정상 저장됐지만 likeCount는 6으로 기록됐다. 좋아요 자체는 되는데 &lt;b&gt;화면에 표시되는 숫자가 틀리는 문제&lt;/b&gt;다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 2: 중복 likes 행 삽입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 사용자가 수십 ms 간격으로 동시에 요청을 보내면 두 요청이 모두 &quot;없음&quot;을 읽고 INSERT를 시도한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;TX A: findByPostIdAndUserId &amp;rarr; 없음 &amp;rarr; INSERT likes
TX B: findByPostIdAndUserId &amp;rarr; 없음 &amp;rarr; INSERT likes  &amp;larr; 중복 행 삽입&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 더블탭, 네트워크 재전송 같은 상황에서 발생할 수 있는 구조적 취약점이다.&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;b&gt;방안 1: 비관적 락 (Pessimistic Lock)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query(&quot;SELECT p FROM Post p WHERE p.id = :postId&quot;)
Optional&amp;lt;Post&amp;gt; findByIdForUpdate(@Param(&quot;postId&quot;) Long postId);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인기 게시글에 트래픽이 몰릴수록 그 행 하나에 락 경합이 집중되어 처리량이 선형으로 떨어진다. 또한 데드락 해결을 위해 Post SELECT를 제거한 방향과 역행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방안 2: 낙관적 락 + @Retryable&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Retryable(value = {ObjectOptimisticLockingFailureException.class}, maxAttempts = 3)
public void toggleLike(Long postId) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lost update는 해결되지만 중복 INSERT 문제는 여전히 UNIQUE 제약이 필요하다. 결국 UNIQUE 제약을 쓰면서 낙관적 락을 추가하는 셈인데, Post 엔티티에 &lt;code&gt;@Version&lt;/code&gt;을 두면 RSS 업데이트, 조회수 등 Post를 건드리는 다른 모든 연산과 버전 충돌이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실측 검증: &lt;code&gt;@Version&lt;/code&gt;을 추가하고 100명 동시 좋아요 테스트를 돌렸다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;[낙관적 락] likeEntity 수: 12, likeCount: 12  &amp;larr; 88건 데이터 유실&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30회 재시도 설정에도 100명 중 12명만 커밋에 성공했다. 인기 게시글은 동시 트래픽이 한 행에 집중되는 특성상 낙관적 락은 재시도가 재시도를 부르는 구조가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방안 3: Redis 분산락&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Boolean acquired = redisTemplate.opsForValue()
    .setIfAbsent(&quot;lock:likes:&quot; + userId + &quot;:&quot; + postId, &quot;1&quot;, Duration.ofSeconds(5));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 문제를 모두 해결하지만 좋아요처럼 결국 DB에 써야 하는 작업에서는 Redis가 속도 이점을 제공하지 못한다. DB 왕복은 어차피 필요하고, Redis 락 획득/해제 왕복이 추가로 붙는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실측 결과: 비관적 락 2617ms &amp;rarr; Redis 분산락(spin-wait) 6167ms. DB만 쓰는 것보다 느렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 장애 시 좋아요 기능 전체가 마비된다. Redis 분산락이 유효한 케이스는 재고 차감처럼 UNIQUE 제약으로 막을 수 없거나, 외부 API 호출 제한처럼 DB가 없는 자원을 조정해야 하는 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;방안 4: synchronized&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public synchronized void toggleLike(Long postId) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;synchronized&lt;/code&gt;는 JVM 메모리의 모니터 락을 사용한다. Spring Bean은 싱글톤이라 서버 1대 안에서는 동일한 인스턴스를 공유하므로 락이 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 서버가 2대 이상이면 각 JVM은 독립된 프로세스라 서버 A의 락이 서버 B에 아무 영향을 주지 않는다. DB는 공유하고 있으므로 두 서버가 동시에 같은 행을 수정하는 상황이 그대로 발생한다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;서버 A (JVM 1)                서버 B (JVM 2)
┌─────────────────┐           ┌─────────────────┐
│  synchronized   │           │  synchronized   │
│  &amp;rarr; 락 획득 ✓   │           │  &amp;rarr; 락 획득 ✓   │  &amp;larr; 서로 모름
└─────────────────┘           └─────────────────┘
        &amp;darr;                             &amp;darr;
    DB (공유)  &amp;larr;─────────────────────┘&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;&lt;b&gt;방안 5: DB UNIQUE + @Modifying 원자적 SQL (채택)&lt;/b&gt;&lt;/p&gt;
&lt;p 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;&lt;b&gt;중복 INSERT 방지&lt;/b&gt;: &lt;code&gt;(user_id, post_id)&lt;/code&gt; UNIQUE 제약으로 DB 레벨에서 차단&lt;/li&gt;
&lt;li&gt;&lt;b&gt;lost update 방지&lt;/b&gt;: &lt;code&gt;UPDATE Post SET likeCount = likeCount + 1&lt;/code&gt; 으로 DB가 현재 값 기준 원자적 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 변경 전 &amp;mdash; read-modify-write, 간격 존재 &amp;rarr; lost update
post.setLikeCount(post.getLikeCount() + 1);

// 변경 후 &amp;mdash; DB가 원자적으로 처리, 간격 없음
@Modifying
@Query(&quot;UPDATE Post p SET p.likeCount = p.likeCount + 1 WHERE p.id = :postId&quot;)
void increaseLikeCount(@Param(&quot;postId&quot;) Long postId);&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;두 문제를 모두 해결하는 유일한 방안이 DB 제약 + 원자적 UPDATE 조합&lt;/li&gt;
&lt;li&gt;낙관적 락도 결국 UNIQUE가 필요하므로 @Version 추가 없이 @Modifying만으로 충분&lt;/li&gt;
&lt;li&gt;Redis 의존성 없이 DB만으로 정합성 보장&lt;/li&gt;
&lt;li&gt;정상 경로에서 추가 쿼리 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;A &amp;mdash; 구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. DB UNIQUE 제약&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(
    name = &quot;likes&quot;,
    uniqueConstraints = {
        @UniqueConstraint(columnNames = {&quot;user_id&quot;, &quot;post_id&quot;})
    }
)
public class Likes { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 원자적 likeCount 업데이트&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Modifying
@Query(&quot;UPDATE Post p SET p.likeCount = p.likeCount + 1 WHERE p.id = :postId&quot;)
void increaseLikeCount(@Param(&quot;postId&quot;) Long postId);

@Modifying
@Query(&quot;UPDATE Post p SET p.likeCount = p.likeCount - 1 WHERE p.id = :postId AND p.likeCount &amp;gt; 0&quot;)
void decreaseLikeCount(@Param(&quot;postId&quot;) Long postId);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AND p.likeCount &amp;gt; 0&lt;/code&gt; 조건으로 음수 방지도 함께 처리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 예외 처리&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Transactional
public void toggleLike(Long postId) {
    Long userId = userService.getCurrentUserId();
    try {
        Optional&amp;lt;Likes&amp;gt; like = likesRepository.findByPostIdAndUserId(postId, userId);
        if (like.isPresent()) {
            postRepository.decreaseLikeCount(postId);
            likesRepository.delete(like.get());
        } else {
            Post post = postService.getPostReferenceById(postId);
            User user = userService.getUserReferenceById(userId);
            postRepository.increaseLikeCount(postId);
            likesRepository.save(Likes.toEntity(user, post));
        }
    } catch (DataIntegrityViolationException e) {
        // UNIQUE 제약 위반 = 중복 요청
        // 트랜잭션 롤백으로 increaseLikeCount도 함께 원복 &amp;rarr; likeCount 정합성 유지
        log.error(&quot;중복 좋아요 요청 차단: userId={}, postId={}&quot;, userId, postId);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;DataIntegrityViolationException&lt;/code&gt; 발생 시 트랜잭션 전체가 롤백되므로 이미 실행된 &lt;code&gt;increaseLikeCount&lt;/code&gt;도 함께 원복된다. likeCount 정합성이 자동으로 유지된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;R &amp;mdash; 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CountDownLatch 기반 동시성 테스트 3종으로 검증했다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;테스트&lt;/th&gt;
&lt;th&gt;likeEntity 수&lt;/th&gt;
&lt;th&gt;likeCount&lt;/th&gt;
&lt;th&gt;결과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[비교] dirty checking 100명 동시&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;불일치 &amp;mdash; 문제 증명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@Modifying JPQL 100명 동시&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;일치 &amp;mdash; 정상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동일 사용자 100회 동시 요청&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;중복 차단 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비관적 락 대비 인기 게시글에서 락 경합 없이 동시 처리량을 유지하면서 데이터 정합성을 보장한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;R &amp;mdash; 성능 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정합성 검증 이후, 각 방식을 동일 조건(100명 동시, 단일 서버)에서 &lt;code&gt;TransactionTemplate&lt;/code&gt;으로 직접 실행해 처리시간을 측정했다.&lt;/p&gt;
&lt;table style=&quot;height: 121px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;th style=&quot;height: 21px;&quot;&gt;방식&lt;/th&gt;
&lt;th style=&quot;height: 21px;&quot;&gt;처리시간&lt;/th&gt;
&lt;th style=&quot;height: 21px;&quot;&gt;성공 건수&lt;/th&gt;
&lt;th style=&quot;height: 21px;&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;낙관적 락 (30회 재시도)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;1502ms&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;12/100&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;빠른 게 아니라 88건 포기 &amp;mdash; 데이터 유실&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;@Modifying JPQL&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;2339ms&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;100/100&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;b&gt;정합성 + 처리량 모두 확보&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;synchronized&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;2642ms&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;100/100&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;단일 JVM에서만 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;비관적 락&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;2617ms&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;100/100&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;완전 직렬화, lock 보유 시간 길어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Redis 분산락 (spin-wait)&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;6167ms&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;100/100&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;DB 왕복 + Redis 왕복 중복&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: 절대 수치는 JVM 워밍업 순서의 영향을 받는다. 방향성 참고용으로 해석해야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;낙관적 락 결과 해석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1502ms로 가장 빠르게 보이지만 12건만 실제로 저장됐다. &quot;빠른 것&quot;이 아니라 88개 스레드가 재시도를 포기하고 조기 종료한 것이다. 인기 게시글처럼 트래픽이 한 행에 집중되는 환경에서 낙관적 락은 성능 해법이 아니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Redis 분산락 결과 해석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis 자체는 마이크로초(&amp;mu;s) 단위의 인메모리 저장소다. 느린 이유는 Redis가 아니라 구조에 있다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;비관적 락:      앱 &amp;rarr; DB (1번 왕복)
Redis 분산락:   앱 &amp;rarr; Redis (SETNX) &amp;rarr; DB &amp;rarr; Redis (DEL)  (3번 왕복)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요는 결국 DB에 써야 하므로 DB 왕복을 줄일 수 없다. Redis 락 획득/해제 왕복이 추가로 붙어 항상 DB 단독보다 느리다. Redis 분산락이 의미 있는 케이스는 DB가 없는 자원(외부 API 제한, 이메일 중복 발송 방지 등)을 조정할 때다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&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;동시성 문제는 코드 리뷰만으로 발견하기 어렵다. CountDownLatch로 동시 실행을 강제하는 테스트를 작성하고 나서야 데드락이 드러났고, 데드락을 해결한 뒤에야 그 아래 숨어 있던 race condition이 보였다.&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;데드락을 해결하자 lost update가 드러났다. 첫 번째 문제가 더 깊은 구조적 취약점을 가리고 있었던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB를 신뢰하라&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 레이어의 check-then-act 로직은 동시성 환경에서 보장을 제공하지 못한다. DB UNIQUE 제약과 원자적 UPDATE는 어떤 레이어에서 실수가 나와도 최종 보루가 되어준다.&lt;/p&gt;</description>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/46</guid>
      <comments>https://myh7754.tistory.com/46#entry46comment</comments>
      <pubDate>Sun, 12 Apr 2026 13:34:17 +0900</pubDate>
    </item>
    <item>
      <title>브라우저에 url을 입력 후 과정</title>
      <link>https://myh7754.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 먼저 리다이렉트가 있다면 리다이렉트를 진행하고 없다면 그대로 해당 요청에 대한 과정이 진행됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 캐싱 브라우저 캐시, os 캐시를 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 없다면 DNS 서버에 &quot;&lt;a href=&quot;https://www.naver.com&quot; data-ved=&quot;0CAAQ_4QMahgKEwiHn7Wi-6iTAxUAAAAAHQAAAAAQoQI&quot; data-hveid=&quot;0&quot;&gt;www.naver.com&lt;/a&gt;의 IP 주소가 뭐야?&quot;라고 물어봄 -&amp;gt; 도메인을 ip 주소로 맵핑&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. DNS 서버로부터 223.130.195.200 같은 실제 IP 주소를 받아와.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. TCP/IP 연결 -&amp;gt; IP 주소를 알았으니 이제 서버와 연결 통로를 만들어야 해. 이때 신뢰성 있는 연결을 위해 3-Way Handshake 과정을 거쳐.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 -&amp;gt; 서버: 연결하자! (SYN)&lt;/li&gt;
&lt;li&gt;서버 -&amp;gt; 클라이언트: 그래, 연결하자! 나도 준비됐어. (SYN+ACK)&lt;/li&gt;
&lt;li&gt;클라이언트 -&amp;gt; 서버: 확인했어. 이제 데이터 보낼게! (ACK)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Http 요청 메시지 전송.&amp;nbsp; 연결이 완료되면 브라우저는 서버에 실제 데이터를 달라고 요청해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt;&amp;nbsp; &quot;GET /index.html HTTP/1.1&quot; 같은 메시지를 담은 HTTP Request를 보지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 서버의 응답 및 데이터 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버(WAS)는 요청을 분석해서 필요한 데이터(HTML, JSON 등)를 준비해. 성공적이라면 &quot;200 OK&quot; 상태 코드와 함께 HTML 파일을 브라우저로 다시 보내줘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8 여기서 랜더링 과정이 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://myh7754.tistory.com/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://myh7754.tistory.com/43&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773833553896&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;SSR, CSR, SPA 차이점 및 개념,  Virtual Dom&quot; data-og-description=&quot;SPA용자가 클릭을 할 때마다 서버에서 새로운 .html 파일을 새로 받아오는 게 아니라, 처음에 받은 페이지 하나에서 필요한 부분만 교체하며 보여주는 방식.CSR 클라이언트 사이드 랜더링 SPA라는 &quot; data-og-host=&quot;myh7754.tistory.com&quot; data-og-source-url=&quot;https://myh7754.tistory.com/43&quot; data-og-url=&quot;https://myh7754.tistory.com/43&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cEvW6K/dJMb8ZvAtb2/ynnkNfka4BHKxWMzRCWIPk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/tmyI5/dJMb8VNuncr/fxH2tkcQHAT83KuQKTWTz0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://myh7754.tistory.com/43&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://myh7754.tistory.com/43&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cEvW6K/dJMb8ZvAtb2/ynnkNfka4BHKxWMzRCWIPk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/tmyI5/dJMb8VNuncr/fxH2tkcQHAT83KuQKTWTz0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;SSR, CSR, SPA 차이점 및 개념, Virtual Dom&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SPA용자가 클릭을 할 때마다 서버에서 새로운 .html 파일을 새로 받아오는 게 아니라, 처음에 받은 페이지 하나에서 필요한 부분만 교체하며 보여주는 방식.CSR 클라이언트 사이드 랜더링 SPA라는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;myh7754.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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS지식</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/44</guid>
      <comments>https://myh7754.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 18 Mar 2026 20:42:57 +0900</pubDate>
    </item>
    <item>
      <title>SSR, CSR, SPA 차이점 및 개념,  Virtual Dom</title>
      <link>https://myh7754.tistory.com/43</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SPA&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;용자가 클릭을 할 때마다 서버에서 새로운 .html 파일을 새로 받아오는 게 아니라, 처음에 받은 페이지 하나에서 &lt;b data-index-in-node=&quot;69&quot; data-path-to-node=&quot;7,1,0&quot;&gt;필요한 부분만 교체&lt;/b&gt;하며 보여주는 방식.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSR 클라이언트 사이드 랜더링&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; SPA라는 구조를 가장 효율적으로 서비스하기 위해 선택하는 전략이 바로 CSR이다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;서버는 텅 빈 HTML과 JS 파일만 보내주고, 브라우저(클라이언트)가 JS를 실행해 동적으로 DOM을 생성하고 화면을 그리는 방식.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;React, Vue&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;장점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 로딩 이후에는 서버 부하가 적음.&lt;/li&gt;
&lt;li&gt;변경된 데이터만 받아오므로 화면 전환이 매우 빠름.&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,2,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;6,2,0&quot;&gt;단점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 로딩 속도(TTV)가 느림 (JS 파일이 무거울수록 심화).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSR 서버 사이드 랜더링&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 요청할 때마다 서버에서 즉석으로 HTML을 생성(렌더링)하여 완성된 페이지를 브라우저에 전달하는 방식.&lt;/li&gt;
&lt;li&gt;Thymeleaf, JSP등&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,0&quot;&gt;장점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,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;8,1,1,0,0&quot;&gt;초기 로딩 속도&lt;/b&gt;가 빠름 (이미 완성된 HTML을 받기 때문).&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;8,2,0&quot;&gt;단점&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8,2,1&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;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SSR + CSR)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 트렌드는 두 방식의 장점만 합치는 것이다. 첫 화면은 서버에서 빠르게 보여주고(SSR), 이후 동작은 부드럽게(CSR) 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;Next.js&lt;/b&gt;: 리액트 기반의 프레임워크지만, &lt;b data-index-in-node=&quot;26&quot; data-path-to-node=&quot;13,0,0&quot;&gt;SSR&lt;/b&gt;과 SSG(Static Site Generation)를 기본으로 지원한다. &quot;SEO가 중요한 리액트 서비스&quot;를 만들 때 필수적인 선택지다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;Nuxt.js&lt;/b&gt;: Vue.js 진영에서 Next.js와 같은 역할을 하는 프레임워크다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dom 이란?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;5,0,0&quot;&gt;개념&lt;/b&gt;: 웹 페이지(HTML 문서)를 브라우저가 이해할 수 있도록 &lt;b data-index-in-node=&quot;36&quot; data-path-to-node=&quot;5,0,0&quot;&gt;객체(Object)&lt;/b&gt; 형태로 만든 &lt;b data-index-in-node=&quot;54&quot; data-path-to-node=&quot;5,0,0&quot;&gt;모델&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML은 단순한 텍스트 파일일 뿐이야. 자바스크립트(JS)는 텍스트를 직접 수정할 수 없어. 그래서 브라우저가 HTML을 읽어서 &quot;이 버튼은 하나의 객체야&quot;, &quot;이 글자는 하나의 속성값이야&quot;라고 자바스크립트가 건드릴 수 있는 형태로 변환해 주는 거야.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dom의 한계와 문제점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 웹은 한 페이지에서 수만 번의 변화가 일어나. 그런데 DOM의 특정 요소를 하나 바꾸면, 브라우저는 그 변화 때문에 영향받는 다른 요소들의 위치를 다 다시 계산(&lt;b data-index-in-node=&quot;98&quot; data-path-to-node=&quot;20,0,0&quot;&gt;Reflow&lt;/b&gt;)하고 색칠(&lt;b data-index-in-node=&quot;111&quot; data-path-to-node=&quot;20,0,0&quot;&gt;Repaint&lt;/b&gt;)해야 해.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;성능 저하&lt;/b&gt;: 이 과정이 너무 자주 일어나면 화면이 버벅거리게 돼.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;해결책&lt;/b&gt;: 그래서 &quot;실제 DOM을 매번 건드리지 말고, 메모리에 가짜 DOM(Virtual DOM)을 만들어서 연습해본 뒤에 한 번에 반영하자!&quot;라는 아이디어가 나온 거지.&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;Vurtual Dom&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 DOM의 구조를 자바스크립트 객체 형태로 메모리에 복사해둔 가벼운 복사본이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 화면에 직접 그리는 것이 아니라, 메모리상에서 먼저 연산을 수행하기 때문에 훨씬 빠르다.&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size26&quot;&gt;동작 원리 (Diffing &amp;amp; Patching)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;변화가 발생했을 때 리액트가 화면을 업데이트하는 3단계 과정이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&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;11,0,0&quot;&gt;Render&lt;/b&gt;: 데이터가 변경되면 전체 UI를 Virtual DOM에 리렌더링한다. (메모리상의 가상 객체만 생성하는 것이라 매우 빠름)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;Diffing (비교)&lt;/b&gt;: 이전의 Virtual DOM 스냅샷과 방금 만든 새로운 Virtual DOM을 비교하여 &lt;b data-index-in-node=&quot;63&quot; data-path-to-node=&quot;11,1,0&quot;&gt;실제로 바뀐 부분&lt;/b&gt;만 찾아낸다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;Reconciliation (재조정/Patching)&lt;/b&gt;: 바뀐 부분만 실제 DOM에 적용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;State 상태값을 내려받아서 사용하기 까지의 과정&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;먼저 컴포넌트 내부에서 관리할 데이터를 선언해. 리액트의 경우 useState 같은 훅(Hook)을 사용해서 상태의 초기값을 설정. 이 단계에서 메모리상에 해당 상태를 위한 공간이 할당&lt;/li&gt;
&lt;li&gt;&lt;span&gt;사용자가 버튼을 클릭하거나 API로부터 데이터를 받아오는 등의 이벤트가 발생하면,&lt;/span&gt;&lt;span&gt; 상태를 변경하는 함수(예:&lt;/span&gt;&lt;span&gt; setState)를 호출&lt;/span&gt; &lt;br /&gt;( &lt;span&gt;중요한 점은 상태를 직접 수정(state = new)하는 게 아니라,&lt;/span&gt;&lt;span&gt; 반드시 제공된 함수를 써야 한다는 것, &lt;br /&gt;&lt;span&gt;그래야 프레임워크가 &quot;상태가 바뀌었으니 화면을 다시 그려야겠구나!&lt;/span&gt;&lt;span&gt;&quot;라고 인지할 수 있음)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt; 상태 변경 함수가 호출되면 컴포넌트는 리렌더링(Re-rendering) 단계에 진입 &lt;br /&gt;&lt;span&gt;가상 돔 생성:&lt;/span&gt;&lt;span&gt; 바뀐 상태값을 반영하여 새로운 가상 돔(Virtual DOM) 트리를 메모리에 새로 생성&lt;br /&gt;&lt;span&gt;디핑(Diffing):&lt;/span&gt;&lt;span&gt; 이전 단계에서 가지고 있던 기존 가상 돔과 방금 만든 새로운 가상 돔을 비교&lt;/span&gt; &lt;br /&gt;&lt;/span&gt; &lt;span&gt;재조정(Reconciliation):&lt;/span&gt;&lt;span&gt; 비교 결과,&lt;/span&gt;&lt;span&gt; 실제로 바뀐 요소가 무엇인지 찾아냄&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 계산이 끝나면 바뀐 부분만 실제 브라우저의 DOM에 적용 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 결과적으로 HTML 템플릿 안에 작성해두었던 변수 자리에 새로운 상태값이 채워지게&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS지식</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/43</guid>
      <comments>https://myh7754.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 18 Mar 2026 19:27:40 +0900</pubDate>
    </item>
    <item>
      <title>면접질문 RestAPI, 웹 기본(통신), 세션 쿠키</title>
      <link>https://myh7754.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;너무 오랜만이라 어떤 글을 쓸지 고민이 많았는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 하는 공부가 매일매일 해도 보여지지 않아서.. 억지로 git에 커밋하지 않는 한 보여지지 않는 게 아쉬워서 다시 조금이라도 꾸준히 써보려고 한다.&lt;br /&gt;우선 요즘 면접 때 들었던 질문에 대한 답, 그리고 기본적인 cs지식을 차근차근 정리해 보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 면접에서 실제 들었던 면접 질문 그리고 그 답들을 정리해 보려한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Rest API란?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; REST API는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestFul한 api를 말하며 일련의 특징과 규칙 등을 지키는 api를 일컸는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest는 웹서버에 있는 자원과 자원의 상태 등을 전송하는 방식을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http 같은 프로토콜 위에서 리소스를 uri로 구분하고, get, post, put, delete 등으로 작업을 표시하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Restful api의 특징&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Uniform Interface:&lt;/b&gt; HTTP 표준만 따르면 플랫폼에 상관없이 사용 가능한 일관된 인터페이스를 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Stateless (무상태성):&lt;/b&gt; 서버는 클라이언트의 상태를 저장하지 않습니다. 클라이언트의 요청만으로 모든 처리가 가능해야 하며, 이는 서버의 &lt;b&gt;확장성&lt;/b&gt;을 높여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Client-Server 구조:&lt;/b&gt; 자원을 가진 서버와 요청을 하는 클라이언트가 명확히 분리되어 있어 각각 독립적인 개발과 확장이 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cacheable (캐시 처리 가능):&lt;/b&gt; HTTP의 캐싱 기능을 적용할 수 있어 효율적인 네트워크 사용이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-1 Get vs Post차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Get : 리소스 조회, URL의 쿼리 스트링 명시적 (민감한 정보를 보내기엔 부적절),&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등성 O 여러번 해도 결과 동일 -&amp;gt; 여러번 조회해도 같은 값이 나옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Post : 리소스 생성, Http Body 숨켜짐,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멱등성 x -&amp;gt; &lt;b&gt;(카운터 버튼):&lt;/b&gt; 누를 때마다 숫자가 1, 2, 3...으로 올라갑니다. 누르는 횟수에 따라 결과가 매번 달라짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-2 Put vs Patch차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Put : 대상 리소스의 &lt;b&gt;전체&lt;/b&gt;를 업데이트, 만약 보내지 않은 필드가 있다면, 그 필드는 보통 null이나 기본값으로 &lt;b&gt;덮어씌워짐.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Patch : 리소스의 &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;1-3 GraphQL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 REST API의 한계를 극복하기 위해 만들어졌으며, 핵심은 &quot;클라이언트가 필요한 데이터의 구조를 직접 정의해서 요청 한다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리언어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 이유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API로 개발하다 보면 필연적으로 마주치는 두 가지 비효율을 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Over-fetching (오버페칭) : 사용자 이름만 필요한데 서버는 주소, 전화번호, 이메일까지 다 보냄.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Under-fetching (언더페칭) : 내 정보'와 '내가 쓴 댓글'을 보려면 /users/1 호출 후 /comments?user_id=1을 또 호출해야 함 (N+1 문제와 유사한 다중 호출).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;해결:&lt;/b&gt; 한 번의 쿼리로 유저 정보와 하위 댓글 리스트를 묶어서 가져옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;REST API vs GraphQL (면접용 비교)&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;REST API&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;GraphQL&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;엔드포인트&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자원마다 다름 (/users, /posts)&lt;/td&gt;
&lt;td&gt;단 하나 (/graphql)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터 결정권&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;서버&lt;/b&gt;가 정한 대로 줌&lt;/td&gt;
&lt;td&gt;&lt;b&gt;클라이언트&lt;/b&gt;가 원하는 대로 받음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;HTTP 상태 코드&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;200, 404, 500 등 다양하게 활용&lt;/td&gt;
&lt;td&gt;대부분 &lt;b&gt;200 OK&lt;/b&gt;로 오고, 에러는 Body에 담김&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;캐싱&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;HTTP 표준 캐시(브라우저 단) 활용 쉬움&lt;/td&gt;
&lt;td&gt;HTTP 캐시 활용이 어려움 (별도 라이브러리 필요)&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;p data-ke-size=&quot;size16&quot;&gt;2. HTTP 에러코드 (400 vs 500)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4xx (Client Error): &quot;요청이 잘못됨&quot;&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;b&gt;400 (Bad Request):&lt;/b&gt; 파라미터 타입이 틀리거나 필수 값이 없을 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;401 (Unauthorized):&lt;/b&gt; 누구인지 모름 (로그인 필요).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;403 (Forbidden):&lt;/b&gt; 누구인지는 아는데, 이 페이지를 볼 권한이 없음 (일반 유저가 관리자 페이지 접근).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;404 (Not Found):&lt;/b&gt; 요청한 URL 주소가 잘못됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5xx (Server Error): &quot;서버가 터짐&quot;&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;요청은 잘 왔는데, 서버 로직(Java 코드)에서 에러가 나거나 DB가 죽었을 때 발생해.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;500 (Internal Server Error):&lt;/b&gt; 가장 흔함. 코드에서 NullPointerException 같은 예외가 터졌을 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;503 (Service Unavailable):&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;3. 사용자 식별 (쿠키 vs 세션 vs JWT)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;로그인 유지를 어떻게 하나요?&quot;라는 질문에 대한 핵심 비교야.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;쿠키 (Cookie)&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;세션 (Session)&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;JWT (Token)&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;저장 위치&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클라이언트(브라우저)&lt;/td&gt;
&lt;td&gt;서버 메모리/DB&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;낮음 (탈취 쉬움)&lt;/td&gt;
&lt;td&gt;높음 (서버가 관리)&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;높음&lt;/td&gt;
&lt;td&gt;낮음 (서버 동기화 필요)&lt;/td&gt;
&lt;td&gt;매우 높음 (무상태성)&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&gt;&lt;b&gt;세션 방식:&lt;/b&gt; 서버가 &quot;철수&quot;라는 정보를 메모리에 들고 있고, 철수한테는 &quot;번호표(세션ID)&quot;만 줘. 보안은 좋지만, 서버가 여러 대(Scale-out)가 되면 모든 서버가 철수 정보를 공유해야 해서 복잡해져.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 방식:&lt;/b&gt; 아예 철수의 정보를 암호화해서 티켓(Token)으로 만들어줘. 서버는 저장 공간이 필요 없어서 무상태성(Stateless)을 유지하기 좋고, 마이크로서비스(MSA)에서 아주 유리해.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1 CSRF 공격&lt;/p&gt;
&lt;p 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;&lt;b&gt;정의:&lt;/b&gt; 사용자가 로그인된 상태에서, 공격자가 만든 가짜 사이트를 클릭했을 때 &lt;b&gt;사용자의 의도와 상관없이&lt;/b&gt; 서버에 위험한 요청(비밀번호 변경, 송금 등)을 보내는 공격.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;왜 가능할까?&lt;/b&gt; 브라우저는 해당 사이트로 요청을 보낼 때 &lt;b&gt;쿠키를 자동으로&lt;/b&gt; 실어 보내기 때문. (서버는 &quot;철수 쿠키가 왔네? 철수가 보낸 거구나!&quot; 하고 믿어버려.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대응 방법&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;15&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;15,0,0&quot;&gt;CSRF 토큰:&lt;/b&gt; 서버가 매번 임의의 토큰을 발행해서, 이 토큰이 없는 요청은 거부하는 방식.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;SameSite 설정:&lt;/b&gt; 쿠키를 구울 때 SameSite=Strict 옵션을 주면, 다른 사이트에서 보낸 요청에는 쿠키가 포함되지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS지식</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/42</guid>
      <comments>https://myh7754.tistory.com/42#entry42comment</comments>
      <pubDate>Wed, 18 Mar 2026 07:28:55 +0900</pubDate>
    </item>
    <item>
      <title>Redis와 사용법</title>
      <link>https://myh7754.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;set key value get key // 출력값은 value keys * // 모든 키값의 value 출력 del key // key값 삭제 set key value ex 시간 // ex하고 시간(초)를 넣으면 시간초 후 expired ttl key // 해당key값의 expired 남은 시간 출력 삭제되어있으면 -2 , 시간 제한이 없다면 -1 flushall // 모든 값 삭제&lt;/p&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/29</guid>
      <comments>https://myh7754.tistory.com/29#entry29comment</comments>
      <pubDate>Sun, 29 Jun 2025 14:37:36 +0900</pubDate>
    </item>
    <item>
      <title>코딩 테스트를 위한 Javascript 문법 정리</title>
      <link>https://myh7754.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  코딩 테스트를 위한 JavaScript 문법 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 Python으로 코딩 테스트를 준비해왔지만, 이번 기업은 &lt;b&gt;JavaScript만 사용 가능&lt;/b&gt;한 상황. 다른 기업들도 특정 언어만 허용하는 경우가 많기 때문에, 이번 기회에 JS로도 코테 문제를 풀 수 있도록 준비해보자.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  입력 처리&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;js
복사편집
const fs = require('fs');
let input = fs.readFileSync('/dev/stdin').toString().trim().split(' ');
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 입력&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;csharp
복사편집
string 5
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input 값:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;js
복사편집
['string', '5']
&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;code&gt;split(' ')&lt;/code&gt;&lt;/b&gt;: 공백 기준 분리&lt;/li&gt;
&lt;li&gt;문자열 &amp;rarr; 숫자 변환 필요 시 &lt;code&gt;Number(input[1])&lt;/code&gt; 등으로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  문자열 처리&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 문자열 반복&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;js
복사편집
&quot;abc&quot;.repeat(3);  // &quot;abcabcabc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 문자열 슬라이싱&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;js
복사편집
const str = &quot;HelloWorld&quot;;
str.slice(0, 5);  // &quot;Hello&quot;
str.slice(5);     // &quot;World&quot;
str.slice(2, 7);  // &quot;lloWo&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 대소문자 처리&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;js
복사편집
let s = &quot;Hello World&quot;;

s.toLowerCase(); // &quot;hello world&quot;
s.toUpperCase(); // &quot;HELLO WORLD&quot;

// 대소문자 반전
let swapped = &quot;&quot;;
for (let ch of s) {
  swapped += (ch === ch.toLowerCase()) ? ch.toUpperCase() : ch.toLowerCase();
}
// 결과: &quot;hELLO wORLD&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  반복문&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 기본 for문&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;js
복사편집
for (let i = 0; i &amp;lt; 5; i++) {
  console.log(i);  // 0 1 2 3 4
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ forEach&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;js
복사편집
arr.forEach((value, index, array) =&amp;gt; {
  // value: 요소 값
  // index: 요소 인덱스
  // array: 원본 배열
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ for-of&lt;/h3&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;js
복사편집
let arr = ['a', 'b', 'c'];
for (let value of arr) {
  console.log(value);  // a b c
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  정렬&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 문자열 정렬&lt;/h3&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;js
복사편집
let arr = ['banana', 'apple', 'cherry'];
arr.sort();
// 결과: ['apple', 'banana', 'cherry']
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 숫자 정렬&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 &lt;code&gt;sort()&lt;/code&gt;는 문자열 기준이라 숫자를 정렬하려면 비교 함수를 써야 함.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;js
복사편집
let nums = [10, 2, 33, 25];
nums.sort((a, b) =&amp;gt; a - b);  // 오름차순
// 결과: [2, 10, 25, 33]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바스크립트 자료구조&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  배열 (Array)&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;js
복사편집
let arr = [1, 2, 3, 4, 5];

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 309px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;메서드&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;설명&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;push()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;뒤에 요소 추가&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.push(6)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[1,2,3,4,5,6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;pop()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;뒤에서 요소 제거&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.pop()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[1,2,3,4], 반환값 5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;shift()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;앞에서 요소 제거&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.shift()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[2,3,4,5], 반환값 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;unshift()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;앞에 요소 추가&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.unshift(0)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[0,1,2,3,4,5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;slice(start, end)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;잘라내기 (원본 보존)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.slice(1, 3)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[2,3]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;splice(start, deleteCount, ...items)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;배열 중간 삭제/추가 (원본 변경)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;arr.splice(2, 1, 9)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;[1,2,9,4,5]즉, 인덱스 2의 값 1개를 지우고 그 자리에 9를 넣음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;sort((a,b)=&amp;gt;a-b)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;정렬 (숫자는 비교 함수 필수)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;arr.sort((a,b)=&amp;gt;a-b)&lt;/td&gt;
&lt;td style=&quot;height: 42px;&quot;&gt;[1,2,3,4,5]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;reverse()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;배열 뒤집기&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.reverse()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[5,4,3,2,1]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;map()&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;요소 변형&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;arr.map(x =&amp;gt; x * 2)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;[2,4,6,8,10]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 34px;&quot;&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;filter()&lt;/td&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;조건 필터링&lt;/td&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;arr.filter(x =&amp;gt; x % 2 === 0)&lt;/td&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;[2,4]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 34px;&quot;&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;reduce()&lt;/td&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;누적 합 등&lt;/td&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;arr.reduce((a, b) =&amp;gt; a + b, 0)&lt;/td&gt;
&lt;td style=&quot;height: 34px;&quot;&gt;15 &amp;larr; 파이썬의 sum()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  집합 (Set)&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;js
복사편집
let s = new Set([1, 2, 3]);

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;메서드&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;add(val)&lt;/td&gt;
&lt;td&gt;값 추가&lt;/td&gt;
&lt;td&gt;s.add(4)&lt;/td&gt;
&lt;td&gt;Set {1,2,3,4}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;delete(val)&lt;/td&gt;
&lt;td&gt;값 삭제&lt;/td&gt;
&lt;td&gt;s.delete(2)&lt;/td&gt;
&lt;td&gt;true, Set: {1,3}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;has(val)&lt;/td&gt;
&lt;td&gt;값 존재 여부&lt;/td&gt;
&lt;td&gt;s.has(3)&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size&lt;/td&gt;
&lt;td&gt;요소 개수&lt;/td&gt;
&lt;td&gt;s.size&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array.from(set)&lt;/td&gt;
&lt;td&gt;배열로 변환&lt;/td&gt;
&lt;td&gt;Array.from(s)&lt;/td&gt;
&lt;td&gt;[1,2,3]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  맵 (Map) - 파이썬의 딕셔너리&lt;/h2&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;js
복사편집
let m = new Map();
m.set('a', 1);
m.set('b', 2);

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;메서드&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set(key, val)&lt;/td&gt;
&lt;td&gt;값 저장&lt;/td&gt;
&lt;td&gt;m.set('c', 3)&lt;/td&gt;
&lt;td&gt;Map {'a' =&amp;gt; 1, 'b' =&amp;gt; 2, 'c' =&amp;gt; 3}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;get(key)&lt;/td&gt;
&lt;td&gt;값 얻기&lt;/td&gt;
&lt;td&gt;m.get('a')&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;has(key)&lt;/td&gt;
&lt;td&gt;키 존재 확인&lt;/td&gt;
&lt;td&gt;m.has('b')&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;delete(key)&lt;/td&gt;
&lt;td&gt;키 삭제&lt;/td&gt;
&lt;td&gt;m.delete('b')&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size&lt;/td&gt;
&lt;td&gt;요소 개수&lt;/td&gt;
&lt;td&gt;m.size&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object.fromEntries(m)&lt;/td&gt;
&lt;td&gt;객체로 변환&lt;/td&gt;
&lt;td&gt;{ a: 1, c: 3 }&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  객체 (Object) - 파이썬 dict 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map과의 차이점은 객체는 반드시 키값이 문자열이다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;js
복사편집
let obj = { a: 1, b: 2 };

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;메서드&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;obj['a']&lt;/td&gt;
&lt;td&gt;값 접근&lt;/td&gt;
&lt;td&gt;obj['a']&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;obj['c'] = 3&lt;/td&gt;
&lt;td&gt;값 추가&lt;/td&gt;
&lt;td&gt;{a:1, b:2, c:3}&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;delete obj['b']&lt;/td&gt;
&lt;td&gt;키 삭제&lt;/td&gt;
&lt;td&gt;{a:1}&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object.keys(obj)&lt;/td&gt;
&lt;td&gt;키 배열&lt;/td&gt;
&lt;td&gt;['a', 'b']&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object.values(obj)&lt;/td&gt;
&lt;td&gt;값 배열&lt;/td&gt;
&lt;td&gt;[1, 2]&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object.entries(obj)&lt;/td&gt;
&lt;td&gt;[key, val] 쌍 배열&lt;/td&gt;
&lt;td&gt;[ ['a', 1], ['b', 2] ]&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  슬라이딩 윈도우&lt;/h2&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;js
복사편집
let arr = [1, 2, 3, 4, 5];
let k = 3;
let maxSum = 0;
for (let i = 0; i &amp;lt;= arr.length - k; i++) {
    let window = arr.slice(i, i + k);
    let sum = window.reduce((a, b) =&amp;gt; a + b, 0);
    maxSum = Math.max(maxSum, sum);
}
console.log(maxSum); // 12

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  조합, 순열&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조합과 순열은 기본 문법에 없으므로 함수로 구현해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;js
복사편집
function combinations(arr, r) {
    if (r === 0) return [[]];
    if (arr.length &amp;lt; r) return [];
    const [head, ...rest] = arr;
    const withHead = combinations(rest, r - 1).map(c =&amp;gt; [head, ...c]);
    const withoutHead = combinations(rest, r);
    return withHead.concat(withoutHead);
}
combinations([1,2,3], 2); // [[1,2],[1,3],[2,3]]

&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  덱 (Deque)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JS에는 내장 Deque가 없지만 배열로 구현 가능.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;js
복사편집
let deque = [];
deque.push(1);        // 오른쪽 추가
deque.unshift(2);     // 왼쪽 추가
deque.pop();          // 오른쪽 제거
deque.shift();        // 왼쪽 제거

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScrpit</category>
      <category>javascript</category>
      <category>자바스크립트</category>
      <category>코딩테스트</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/28</guid>
      <comments>https://myh7754.tistory.com/28#entry28comment</comments>
      <pubDate>Sun, 8 Jun 2025 22:08:19 +0900</pubDate>
    </item>
    <item>
      <title>Json과 Object의 차이, Java에서 Json객체 사용법</title>
      <link>https://myh7754.tistory.com/14</link>
      <description>&lt;h1&gt;Object와 Json객체의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json은 데이터를 저장하거나 전송하기위한 &lt;b&gt;텍스트 기반의 데이터 포맷&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json은 key값과 value로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;obejct 객체는 프로그램에서 데이터를 조작하거나 관리하기 위한 자료구조 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 자바스크립트에서 http의 통신수단으로 사용되는 것은 Js의 Object가 아니라 Json형식으로 클라이언트와 데이터를 주고 받는 다는 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바 스크립트에서 JSON 객체를 다루는법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON.parse() : json을 js object로 파싱하기 위해서 메서드를 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON.stringify() : Js Object를 JSON으로 변환해주기 위해 필요한 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;json&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;Alice&quot;,
  &quot;age&quot;: 25,
  &quot;isStudent&quot;: true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;object로 파싱&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;const jsonString = '{&quot;name&quot;: &quot;Alice&quot;, &quot;age&quot;: 25, &quot;isStudent&quot;: true}';
const obj = JSON.parse(jsonString);

console.log(obj.name); // &quot;Alice&quot;
console.log(obj.age);  // 25
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JavaScript 객체&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;const obj = {
  name: &quot;Alice&quot;,
  age: 25,
  isStudent: true
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json으로 변환&lt;/p&gt;
&lt;pre class=&quot;capnproto&quot;&gt;&lt;code&gt;const obj = { name: &quot;Alice&quot;, age: 25, isStudent: true };
const jsonString = JSON.stringify(obj);

console.log(jsonString); // '{&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:25,&quot;isStudent&quot;:true}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java에서 Json 객체 다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java에서는 Jackson 라이브러리를 이용하여 json 객체를 파싱하여 사용한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;의존성 추가 (Gradle)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JSON &amp;rarr; Java 변환&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class Person {
	private String name;
	private int age;
	
	getter setter등등
}

public static void main(String[] args) throws Exception {
        String jsonString = &quot;{\\&quot;name\\&quot;:\\&quot;Alice\\&quot;,\\&quot;age\\&quot;:25,\\&quot;isStudent\\&quot;:true}&quot;;

        // ObjectMapper를 이용해 JSON 문자열을 Java 객체로 변환
        ObjectMapper mapper = new ObjectMapper();
        Person person = new Person();
        person.setName(&quot;Alice&quot;);
        person.setAge(25);

        // 변환된 객체 출력
        System.out.println(person.getName()); // Alice
        System.out.println(person.getAge());  // 25

        // Java 객체를 JSON 문자열로 변환
        jsonString = mapper.writeValueAsString(person);
        System.out.println(jsonString); // {&quot;name&quot;:&quot;Alice&quot;,&quot;age&quot;:25,&quot;isStudent&quot;:true}
    }
    
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/14</guid>
      <comments>https://myh7754.tistory.com/14#entry14comment</comments>
      <pubDate>Mon, 9 Dec 2024 01:48:53 +0900</pubDate>
    </item>
    <item>
      <title>OAuth2.0 google, kakao, naver</title>
      <link>https://myh7754.tistory.com/13</link>
      <description>&lt;h1&gt;OAuth2.0&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 인증 및 권한을 부여하기 위한 프로토콜이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 서비스에서 로그인할 때 사용 된다. &amp;rarr; 카카오, 네이버, 구글로 로그인하기 와 같은 기능을 구현하는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;531&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y1Hcl/btsK4XcuvIz/LPBC9Q9vCJsGvmDPCLyLS0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y1Hcl/btsK4XcuvIz/LPBC9Q9vCJsGvmDPCLyLS0/img.jpg&quot; data-alt=&quot;출처 : https://blog.naver.com/mds_datasecurity/222182943542&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y1Hcl/btsK4XcuvIz/LPBC9Q9vCJsGvmDPCLyLS0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY1Hcl%2FbtsK4XcuvIz%2FLPBC9Q9vCJsGvmDPCLyLS0%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;602&quot; height=&quot;531&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;531&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://blog.naver.com/mds_datasecurity/222182943542&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E2Tzd/btsK5NAffUk/qjUNujPjYsz1jULOqOxkc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E2Tzd/btsK5NAffUk/qjUNujPjYsz1jULOqOxkc1/img.png&quot; data-alt=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E2Tzd/btsK5NAffUk/qjUNujPjYsz1jULOqOxkc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE2Tzd%2FbtsK5NAffUk%2FqjUNujPjYsz1jULOqOxkc1%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;1699&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1699&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 일반적인 oauth2.0과 카카오의 oauth2.0의 동작 방식을 정의해 놓은 시퀀스 다이어그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 oauth2.0은 위와 비슷한 방식으로 처리 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;client&lt;/h3&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;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Resource Owner(리소스 소유자)&lt;/h3&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;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Authorization Server(인증서버)&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;Resource Server(리소스 서버)&lt;/h3&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;h3 data-ke-size=&quot;size23&quot;&gt;Access Token&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;client가 인증받은 후 리소스 서버에 접근할 수 있도록 발급되는 토큰&lt;/li&gt;
&lt;li&gt;이 토큰을 이용하여 리소스 서버에서 개인정보를 받을 수 있다.&lt;/li&gt;
&lt;li&gt;만료기간이 짧음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Refresh Token&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;access token의 유효 기간이 만료된 후 새로운 access token을 얻기 위해 사용하는 토큰&lt;/li&gt;
&lt;li&gt;만료기간이 Access Token에 비해 김&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring에서 google, kakao, naver oauth2.0설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링의 resource의 yml 파일 설정은 다음과 같이하고 client id와 sceret에 각 사이트의 develop에서 발급받은 id 비밀번호를 넣으면 된다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: YOUR_GOOGLE_CLIENT_ID
            client-secret: YOUR_GOOGLE_CLIENT_SECRET
            scope: profile, email
            redirect-uri: &quot;{baseUrl}/login/oauth2/code/google&quot;
            client-name: Google
          naver:
            client-id: YOUR_NAVER_CLIENT_ID
            client-secret: YOUR_NAVER_CLIENT_SECRET
            scope: name, email
            redirect-uri: &quot;{baseUrl}/login/oauth2/code/naver&quot;
            client-name: Naver
            provider: naver
          kakao:
            client-id: YOUR_KAKAO_CLIENT_ID
            client-secret: YOUR_KAKAO_CLIENT_SECRET
            scope: profile_nickname, account_email
            redirect-uri: &quot;{baseUrl}/login/oauth2/code/kakao&quot;
            client-name: Kakao
            provider: kakao
        provider:
          kakao:
            authorization-uri: &amp;lt;https://kauth.kakao.com/oauth/authorize&amp;gt;
            token-uri: &amp;lt;https://kauth.kakao.com/oauth/token&amp;gt;
            user-info-uri: &amp;lt;https://kapi.kakao.com/v2/user/me&amp;gt;
            user-name-attribute: id
          naver:
            authorization-uri: &amp;lt;https://nid.naver.com/oauth2.0/authorize&amp;gt;
            token-uri: &amp;lt;https://nid.naver.com/oauth2.0/token&amp;gt;
            user-info-uri: &amp;lt;https://openapi.naver.com/v1/nid/me&amp;gt;
            user-name-attribute: response&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.est.oauth2.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -&amp;gt; csrf.disable())
                .formLogin(form -&amp;gt; form.disable())
                .oauth2Login(Customizer.withDefaults()) // oauth2.0로그인 방식
                .authorizeHttpRequests(
                        auth -&amp;gt; auth.requestMatchers(&quot;/login&quot;)
                                    .anonymous()
                                .requestMatchers(&quot;/user/**&quot;)
                                    .hasAnyAuthority(&quot;USER&quot;)
                                .requestMatchers(&quot;/admin/**&quot;)
                                    .hasAnyAuthority(&quot;ADMIN&quot;)
                                .anyRequest()
                                    .authenticated()
                )
                .build();
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 마친 뒤 다음과 같이 OAuth2User 객체를 사용하여 정보를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class HomeController {

    @GetMapping(&quot;/home&quot;)
    public String home(@AuthenticationPrincipal OAuth2User oAuth2User, Model model) {
        model.addAttribute(&quot;name&quot;, oAuth2User.getAttribute(&quot;name&quot;)); // 사용자 이름
        model.addAttribute(&quot;email&quot;, oAuth2User.getAttribute(&quot;email&quot;)); // 이메일
        return &quot;home&quot;; // home.html
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/13</guid>
      <comments>https://myh7754.tistory.com/13#entry13comment</comments>
      <pubDate>Mon, 2 Dec 2024 23:57:29 +0900</pubDate>
    </item>
    <item>
      <title>JavaScrpit  Event, 동기, 비동기 통신</title>
      <link>https://myh7754.tistory.com/12</link>
      <description>&lt;h1&gt;JS&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var : 값을 다시 초기화 하는게 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호이스팅 : 선언이 스코프 최상단으로 끌어 올려지는 현상(초기화는 나중에 됨, undefined로 초기화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var a = 10;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var a = 40;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a = 40이 들어감&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hoisting&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;console.log(a); // undefined (호이스팅)
var a = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능하면 let을 사용하는게 좋음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let : 값을 초기화 다시 초기화하는게 불가 재할당은 가능 &amp;rarr; 일반 변수같은 느낌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let b =30;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let b = 40; &amp;rarr; 이렇게 다시 선언하여 초기화는 불가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b = 40; &amp;rarr; 이렇게 재할당은 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const : 상수 재할당은 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;undefiend : 변수는 선언되어 있지만 값이 정의 되지 않았다는 상태를 나타내줌 &amp;rarr; 시스템이 자동으로 부여&lt;/p&gt;
&lt;pre class=&quot;ceylon&quot;&gt;&lt;code&gt;let value;
console.log(value);  // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null : 의도적으로 값이 비어있다는 것을 개발자가 명시적으로 나타낼 때 사용하는 값 &amp;rarr; 개발자가 명시&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;let value = null;
console.log(value);  // null
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NaN : 숫자가 아닌값을 연산에 사용했을 때 발생하는 값들 &amp;rarr; 숫자가 아닌 값. type이 &amp;ldquo;number&amp;rdquo;로 반환됨&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;console.log(parseInt(&quot;Hello&quot;));  // NaN
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자기 자신과 같지 않다&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;console.log(NaN === NaN);  // false
console.log(isNaN(NaN));   // true
&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;==비교를 타입캐스팅 자동으로 하기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5 == &amp;lsquo;5&amp;rsquo;가 같은 true로 나온다 그래서 ===비교를 해야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5 === &amp;lsquo;5&amp;rsquo; 이렇게 해야 false가 나옴 타입까지 동일해야 true&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트는 변수에 함수를 할당하여 사용가능&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const hello = funciton() { console.log(&quot;hello world&quot;); }; 
hello(); // 이런식으로 사용 가능

function greet() {
	return funciton() {
		console.log(&quot;hello world&quot;);
	}
}
const hello = greet();
hello(); // hello world 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클로저&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수와 그 함수가 선언될 때의 스코프를 기억하는 기능&lt;br /&gt;&amp;rarr; 예를 들어 자바에서 함수를 호출하면 스코프가 그 안에서만 적용 되지만 자바스크립트는 밖에서도 함수가 살아있어서 아래와 같이 count가 유지됨&lt;/li&gt;
&lt;li&gt;클로저를 통해 함수는 자신의 외부 함수의 변수에 접근할 수 있고, 이 변수들은 함수가 실행된 후에도 유지된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function createCounter() {
    let count = 0;
    return function() {
        count += 1;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2 count가 계속 살아있음
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;키-값의 집합으로 이루어져 아래와 같이 사용 가능하다. 이것은 JSON형태라고함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;const myObj = { // JSON 형태 KEY: VALUE
	username: &quot;user1&quot;,
	password: &quot;1234&quot;,
	level: 50,
	isAdmin: false
}

console.log(myObj.username); // user1
console.log(myObj.isAdmin); // false

const userLevel = myObj.level;
console.log(userLevel + 100); // 150
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Event&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 사용자가 버튼을 클릭, 키보드를 누르거나 페이지가 로드되는 것을 이벤트라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mouse 이벤트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;click : 요소를 클릭할 때 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dblclick : 더블 클릭할 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mouseover : 마우스가 요소 위로 올라갈 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mouseout : 마우스가 요소를 벗어날 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키보드 이벤트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keydown : 키를 누를 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyup : 키를 뗄 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폼 이벤트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;submit : 폼이 제출될 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;change : 입력 값이 변경될 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등등 이 있다.&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;p data-ke-size=&quot;size16&quot;&gt;js에 내장되어 있는 객체인 document를 이용하여 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 html문서를 파싱하여 DOM이라는 구조를 만드는데 document는 이 DOM에 접근할 수 있도록 도와주는 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document.getElementById(&quot; &quot; ).addEventListener(&quot;이벤트&quot;, ()=&amp;gt; { 실행메서드});&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document.getElementById(&quot; &quot;).removeEventListener(&quot;이벤트&quot;, ()=&amp;gt; {실행메서드}); : 실행 메서드를 실행하고 해당 리스너를 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 html과 Event를 이용하여 사용할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mouse&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;div id=&quot;mouseBox&quot;&amp;gt;마우스 이벤트 테스트&amp;lt;/div&amp;gt;
&amp;lt;button id=&quot;mouseButton&quot;&amp;gt;클릭하세요&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;// 클릭 이벤트
document.getElementById(&quot;mouseButton&quot;).addEventListener(&quot;click&quot;, () =&amp;gt; {
  alert(&quot;버튼이 클릭되었습니다!&quot;);
});
const mouseBox = document.getElementById(&quot;mouseBox&quot;);

mouseBox.addEventListener(&quot;mouseover&quot;, () =&amp;gt; {
  mouseBox.textContent = &quot;마우스가 올라왔습니다!&quot;;
});

mouseBox.addEventListener(&quot;mouseout&quot;, () =&amp;gt; {
  mouseBox.textContent = &quot;마우스가 벗어났습니다!&quot;;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;keyboard&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;input id=&quot;keyboardInput&quot; type=&quot;text&quot; placeholder=&quot;키보드를 입력하세요&quot; /&amp;gt;
&amp;lt;p id=&quot;keyResult&quot;&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const input = document.getElementById(&quot;keyboardInput&quot;);
const keyResult = document.getElementById(&quot;keyResult&quot;);

// 키가 눌렸을 때 (keydown) 값이 event변수에 전달
input.addEventListener(&quot;keydown&quot;, (event) =&amp;gt; {
  keyResult.textContent = `눌린 키: ${event.key}`;
});

// 키가 떼어졌을 때 (keyup)
input.addEventListener(&quot;keyup&quot;, () =&amp;gt; {
  keyResult.textContent = &quot;키를 뗐습니다.&quot;;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;form&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;form id=&quot;formTest&quot;&amp;gt;
  &amp;lt;label for=&quot;formInput&quot;&amp;gt;이름:&amp;lt;/label&amp;gt;
  &amp;lt;input id=&quot;formInput&quot; type=&quot;text&quot; placeholder=&quot;이름을 입력하세요&quot; /&amp;gt;
  &amp;lt;button type=&quot;submit&quot;&amp;gt;제출&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;p id=&quot;formMessage&quot;&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;const form = document.getElementById(&quot;formTest&quot;);
const formMessage = document.getElementById(&quot;formMessage&quot;);

// 제출 이벤트
form.addEventListener(&quot;submit&quot;, (event) =&amp;gt; {
  event.preventDefault(); // 기본 동작 방지
  formMessage.textContent = &quot;폼이 제출되었습니다!&quot;;
});

// 값 변경 이벤트
const formInput = document.getElementById(&quot;formInput&quot;);
formInput.addEventListener(&quot;change&quot;, () =&amp;gt; {
  formMessage.textContent = `입력 값이 변경되었습니다: ${formInput.value}`;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DOM(Document Object Model)&lt;/h3&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;getElementById(&quot;Id&quot;) : id값으로 html내의 요소를 가져오는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document.getElementsByClassName(&quot;클래스이름&quot;) : 클래스 이름으로 요소를 가져오는 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;document.querySelector('css선택자') : 선택자를 이용하여 요소를 가져오는 함수&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동기통신&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴퓨터 프로세스에서 하나의 작업이 완료가 될 때 까지 작업을 기다렸다가 모든 작업을 &lt;b&gt;순차적&lt;/b&gt;으로 처리하는 방식을 말한다.&lt;/li&gt;
&lt;li&gt;장점은 단순하고 작은 규모 시스템에서 유리하게 작용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비동기 통신&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동기 통신과 반대로 서로 서로를 기다리지 않고 &lt;b&gt;독립적&lt;/b&gt;으로 진행되는 방식이다.&lt;/li&gt;
&lt;li&gt;문서의 각 요소를 객체로 나타내며, 이 객체를 통해 문서의 내용, 구조, 스타일 등을 동적으로 수정하고 접근할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fetch API&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;http요청을 비동기 방식으로 처리하여 도와주는 인터페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Promise&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch API는 http 요청을 비동기적으로 수행하기 위해 Promise 객체를 사용한다.&lt;/li&gt;
&lt;li&gt;Promise는 비동기 작업이 완료되었을 때의 결과를 나타내는 객체.&lt;/li&gt;
&lt;li&gt;fetch가 반환하는 Promise 객체는 &lt;b&gt;네트워크 요청의 상태를 관리&lt;/b&gt;하고, 요청이 완료된 후 응답을 처리하는 방법을 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise는 3가지 주요 상태를 가진다 대기(Pending), 이행(Fulfilled), 거부(Rejected)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pending상태에서 Fulfilled나 Rejected상태가 될 수 있지만 반대는 안된다. 즉 Fulfilled가 된 상태에서 Pending이 되지 못함&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대기(Pending)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promise가 생성된 &lt;b&gt;초기 상태,&lt;/b&gt; 비동기 작업이 &lt;b&gt;아직 완료되지 않았거나 결과가 아직 확정되지 않은 상태&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이행(Fulfilled)&lt;/h3&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;되어, Promise가 &lt;b&gt;결과값을 가진 상태&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이 상태로 변경되면, &lt;b&gt;then()&lt;/b&gt; 메서드에 전달된 성공 콜백 함수가 호출된다.&lt;/li&gt;
&lt;li&gt;결과값이 Promise 객체에 저장, Promise는 이 상태에 도달하면 더 이상 상태가 변경되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;거부(Rejected)&lt;/h3&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;하여, Promise가 &lt;b&gt;실패 상태를 나타내는 상태.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;catch()&lt;/b&gt; 메서드에 전달된 에러 처리 콜백 함수가 호출&lt;/li&gt;
&lt;li&gt;에러 정보가 Promise 객체에 저장되고, Promise는 이 상태에 도달하면 더 이상 상태가 변경되지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;fetch(url, 옵션)
  .then(onFulfilled =&amp;gt; { ... }) // 성공시
  .catch(onRejected =&amp;gt; { ... }); // 실해시
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch()의 인자로는 요청을 보낼 url , 메타데이터(Option)을 부여한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵션은 객체 형식으로부여 가능하고 요청의 **본문(body)**부분은 일반적으로 **JSON.stingify()**를 이용하여 객체를 &lt;b&gt;문자열화&lt;/b&gt; 하여 포함시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch(url, 옵션)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 옵션 값&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;{
	method: HTTP 메서드,
	headers: {
		헤더 키: 헤더 값,
	},
	body: JSON.stringify(본문 값),
	[credentials: 쿠키 및 인증정보 포함 여부]
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JavaScrpit</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/12</guid>
      <comments>https://myh7754.tistory.com/12#entry12comment</comments>
      <pubDate>Mon, 2 Dec 2024 17:22:54 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드7기] (Spring Security , Cors,CSRF)</title>
      <link>https://myh7754.tistory.com/11</link>
      <description>&lt;h1&gt;Spring Seucirty&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 웹 애플리케이션에서 인증 과 인가를 관리하는 보안 프레임워크이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXfnAl/btsK1gCYLnh/BcOK9nMBAuesflgMZJLDX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXfnAl/btsK1gCYLnh/BcOK9nMBAuesflgMZJLDX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXfnAl/btsK1gCYLnh/BcOK9nMBAuesflgMZJLDX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXfnAl%2FbtsK1gCYLnh%2FBcOK9nMBAuesflgMZJLDX0%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;1024&quot; height=&quot;382&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Filter&lt;/h2&gt;
&lt;p 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;톰켓에서 dispatcher servlet으로 전달하기 전에 실행&lt;/li&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;예시 : JWT 토큰 파싱 및 검증.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FilterChainProxy&lt;/b&gt;: Spring Security에서 필터 체인을 관리하는 클래스&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SecurityFilterChain&lt;/b&gt; 인터페이스를 통해 요청을 필터링할 필터 목록을 제공.&lt;/li&gt;
&lt;li&gt;Servlet의 기본 &lt;b&gt;Filter 인터페이스&lt;/b&gt;를 기반으로 동작.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Interceptor&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청한 사용자의 권한(Role)을 검증하여 특정 리소스 접근 제한.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DisPatcher Servlet 에서 컨트롤러로 전달하기 전에 실행&lt;/li&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;예시 : 관리자만 접근 가능한 URL 제어.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인증&lt;/h2&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;자신이 실제로 누구인지를 증명하도록 요구하는 과정이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security에서 인증 과정은 사용자가 제공하는 자격 증명, 보통 사용자 이름과 비밀번호를 기반으로 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 다양한 인증 방식(폼 로그인, OAuth2, JWT 등)을 지원.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증된 사용자가 &lt;b&gt;특정 자원&lt;/b&gt;에 &lt;b&gt;접근할 권한&lt;/b&gt;이 있는지를 결정 URL 기반 접근 제어, 메소드 수준 보안, 역할(Role) 기반 권한 부여 등을 통해 세부적인 권한 관리 가능.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SecurityContextHolder&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 정보를 저장하고 접근하는 클래스 현재 요청의 &lt;b&gt;SecurityContext&lt;/b&gt;를 관리하여 인증된 사용자 정보를 저장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SecurityContext&lt;/b&gt;는 또한 &lt;b&gt;인증(Authentication) 정보&lt;/b&gt;를 담고 있는 &lt;b&gt;인터페이스이다. SecurityContext&lt;/b&gt;는 인증 정보를 &lt;b&gt;Authentication 객체&lt;/b&gt;로서 저장하고, &lt;b&gt;Authentication&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;ex) SecurityContextHolder.getContext().getAuthentication()을 통해 인증 정보에 접근 가능.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SecurityContext&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SecurityContext&lt;/b&gt;는 &lt;b&gt;현재 인증된 사용자의 정보를 담고 있는&lt;/b&gt; 인터페이스.&lt;/li&gt;
&lt;li&gt;사용자가 어플리케이션에 로그인하면, 해당 사용자의 인증 정보가 &lt;b&gt;Authentication&lt;/b&gt; 객체로 생성되고, 이 객체는 &lt;b&gt;SecurityContext&lt;/b&gt;에 저장&lt;/li&gt;
&lt;li&gt;이후 애플리케이션의 어디에서든 &lt;b&gt;SecurityContext&lt;/b&gt;를 통해 현재 사용자의 인증 정보에 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;인증 상태와 권한 정보를 기반으로 보안 결정을 내리는 데 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Authentication&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Principal&lt;/b&gt;: 사용자의 식별자를 의미. &amp;rarr; UserDetail or 사용자명을 나타내는 String 등.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Credentials&lt;/b&gt;: 인증 자격(예: 비밀번호).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authorities&lt;/b&gt;: getAuthorities() 메서드를 통해 접근할 수 있으며, 사용자가 가지고 있는 권한(또는 역할)의 목록을 제공. 이 권한들은 GrantedAuthority 인터페이스의 구현체로 표현&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authenticated&lt;/b&gt;: isAuthenticated() 메서드를 통해 현재 사용자가 인증되었는지 여부를 반환. 인증이 성공적으로 이루어진 경우, 이 값은 true&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UserDetails&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 정보를 캡슐화하는 인터페이스.&lt;/li&gt;
&lt;li&gt;커스텀 구현 시 사용자 이름, 비밀번호, 권한, 계정 상태(활성화, 잠김 여부 등)를 정의.&lt;/li&gt;
&lt;li&gt;Spring Security는 이를 통해 인증 가능한 사용자 정보를 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션에서 추가적인 사용자 정보를 관리하거나 특수한 로직이 필요한 경우, UserDetails 인터페이스를 구현하여 커스텀할 수 있다.&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;lt; 표 1 &amp;gt; UserDetails 메서드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 반환 타입 설명 기본값&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;getAuthorities()&lt;/td&gt;
&lt;td&gt;Collection&amp;lt;?&amp;nbsp;extends&amp;nbsp;GrantedAuthority&amp;gt;&lt;/td&gt;
&lt;td&gt;사용자 권한 목록을 반환&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;getPassword()&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;사용자 비밀번호를 반환&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;getUsername()&lt;/td&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;사용자 고유한 값을 반환&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isAccountNonExpired()&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;계정의 만료 여부 반환&lt;/td&gt;
&lt;td&gt;true ( 만료 안됨 )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isAccountNonLocked()&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;계정의 잠김 여부 반환&lt;/td&gt;
&lt;td&gt;true ( 잠기지 않음 )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isCredentialsNonExpired()&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;비밀번호 만료 여부 반환&lt;/td&gt;
&lt;td&gt;true ( 만료 안됨 )&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isEnabled()&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;계정의 활성화 여부 반환&lt;/td&gt;
&lt;td&gt;true ( 활성화 됨 )&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

public class CustomUserDetails implements UserDetails {
    @Override
    public Collection&amp;lt;? extends GrantedAuthority&amp;gt; getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return UserDetails.super.isAccountNonExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
        return UserDetails.super.isAccountNonLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return UserDetails.super.isCredentialsNonExpired();
    }

    @Override
    public boolean isEnabled() {
        return UserDetails.super.isEnabled();
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UserDetailsService&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserDetailsService&lt;/b&gt;는 사용자명(username)을 기반으로 &lt;b&gt;UserDetails 객체를 로드하는 역할&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 과정에서 필요한 사용자 정보를 데이터베이스나 다른 저장소에서 가져오는 데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 반환 타입 설명&lt;/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;loadUserByUsername()&lt;/td&gt;
&lt;td&gt;UserDetails&lt;/td&gt;
&lt;td&gt;유저의 정보를 불러와서 UserDetails로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomUserDetailService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Security Config&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션의 보안 정책을 종합적으로 정의하고 관리하는 중심적 구성 요소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security를 활용하는 애플리케이션에서 반드시 필요하며 다음과 같은 설정을 할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인증(Authentication) 관리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;SecurityConfig는 &lt;b&gt;사용자 인증을 위한 설정을 담당한다. -&lt;/b&gt; 사용자 정보의 출처를 정의하는 UserDetailsService, 비밀번호 암호화 방식, 인증 요청 처리 방식을 설정하는 작업이 포함된다&lt;/li&gt;
&lt;li&gt;이 과정에서 개발자는 데이터베이스, LDAP, OAuth2 등 다양한 인증 방법을 선택하고 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권한 부여(Authorization) 관리 사용자가 애플리케이션 내의 자원에 접근할 수 있는지 여부를 결정하는 과정&lt;/b&gt; URL 기반 접근 제어와 메소드 기반 접근 제어를 설정하여, &lt;b&gt;특정 역할(Role)이나 권한(Authority)을 가진 사용자만이 특정 자원에 접근할 수 있도록 보장한다.&lt;/b&gt; 이를 통해 민감한 정보나 기능에 대한 비인가 접근을 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTP 보안 설정 CSRF보호 , 세션 관리 등 웹 애플리케이션의 보안과 관련된 다양한 HTTP 설정을 제공&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스터마이징과 확장&lt;/b&gt; 개발자는 자신만의 인증 방식이나 필터를 추가하거나, 기존의 보안 흐름을 재정의하여 애플리케이션의 보안 모델을 더욱 견고하게 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Form&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;form 로그인 방식은 사용자가 로그인 정보를 제공하여 이를 바탕으로 사용자의 신원을 확인하여 인증하는 방식이다. 주로 POST 요청을 통해 서버로 전송된 사용자 입력값을 서버에서 처리하게 된다. Spring Security는 이러한 로그인 폼을 매우 쉽게 설정하고 관리할 수 있는 기능을 제공.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과정은 다음과 같다&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 아이디 비밀번호 입력&lt;/li&gt;
&lt;li&gt;http요청을 통해 전달받아 Spring security는 요청을 UsernamePasswordAuthenticationFilter를 통해 처리.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 내일 예제코드 보고 수정
protected void configure(HttpSecurity http) throws Exception {
    http
        .formLogin() // Form 기반 로그인 활성화
        .loginPage(&quot;/login&quot;) // 커스텀 로그인 페이지 URL
        .defaultSuccessUrl(&quot;/home&quot;, true) // 로그인 성공 시 이동할 URL
        .permitAll() // 모든 사용자 접근 허용
        .and()
        .logout() // 로그아웃 설정
        .logoutSuccessUrl(&quot;/login?logout&quot;) // 로그아웃 성공 후 이동할 URL
        .permitAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UsernamePasswordAuthenticationFilter 처리:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;입력값을 UsernamePasswordAuthenticationToken 객체로 변환.&lt;/li&gt;
&lt;li&gt;이 객체는 인증되지 않은 상태로 생성됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AuthenticationManager로 인증 요청 전달
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 AuthenticationProvider를 순회하여 적합한 인증 제공자가 있는지 확인.&lt;/li&gt;
&lt;li&gt;보통 DaoAuthenticationProvider가 UserDetailsService를 통해 사용자 정보를 조회.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;비밀번호 검증&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PasswordEncoder를 사용하여 입력된 비밀번호와 저장된 암호화된 비밀번호를 비교.&lt;/li&gt;
&lt;li&gt;일치하면 인증 성공.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
            .userDetailsService(customUserDetailsService) // 사용자 정보 조회 서비스
            .passwordEncoder(passwordEncoder()) // 비밀번호 인코딩
            .and()
            .build();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Granted Authority 객체를 이용해 인가를 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인가 : 인증의 마지막 프로세스에서 관리되는 것&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인코딩 , 디코딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인코딩&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;li&gt;사용자로 부터 입력받은 비밀번호를 주로 해시함수를 사용하여 변환한 후 사용하는 방식이다.&lt;/li&gt;
&lt;li&gt;인코딩은 단방향으로 암호화하여 db 유출 시에도 사용자의 비밀번호가 보호되도록 한다.&lt;/li&gt;
&lt;li&gt;Spring Security는 여러 인코딩 알고리즘을 제공하는데 대표적으로 BCryptPasswordEncoder가 사용됨. &amp;rarr; 이 알고리즘은 해시화하고 추가적으로 Salt를 적용하여 같은 비밀번호라도 다른 해시값을 생성하도록 한다. 저장하는 시점에 매번 다른 Salt가 생겨 Salt도 함께 저장되는 것 디코딩할 때는 저장된 Salt도 가져와서 해시화 하여 인증. 디코딩할때도 Salt로 다른 해시값으로 인증하는게 아님.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디코딩&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;li&gt;사용자가 입력한 비밀번호를 저장된 인코딩된 비밀번호와 비교하여 해시값과 일치하는지 확인하여 인증한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;세션&lt;/h2&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;서버측에서 세션이 생성될 때 만들어진 세션ID로 식별한다.&lt;/li&gt;
&lt;li&gt;사용자 정보는 서버에 저장되고, 브라우저에는 세션ID만 있음.&lt;/li&gt;
&lt;li&gt;세션은 일정 시간 동안만 유지됨(ex 30분). 시간이 지나면 만료되 로그아웃 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쿠키&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠키는 HTTP 프로토콜의 stateless(상태 비저장) 특성을 보완하기 위한 기술.&lt;/li&gt;
&lt;li&gt;클라이언트(브라우저)가 관리하는 정보이다. ex) 사용자의 다크모드 설정, 선호 설정, 로그인 정보&lt;/li&gt;
&lt;li&gt;쿠키가 탈취되거나 변조되면 보안 문제가 생길 수 있음. 이를 막기 위해 httpOnly, Secure 과 같은 옵션을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Security의 Thymeleaf&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security와 Thymeleaf는 HTML 템플릿에서 인증 및 권한을 쉽게 처리할 수 있도록 돕는 속성을 제공한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;sec:authorize&lt;/b&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;예: sec:authorize=&quot;isAuthenticated()&quot; &amp;rarr; 로그인한 사용자만 볼 수 있음.&lt;/li&gt;
&lt;/ul&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;isAnonymous()&lt;/td&gt;
&lt;td&gt;비로그인 사용자에게만 콘텐츠 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;isAuthenticated()&lt;/td&gt;
&lt;td&gt;로그인된 사용자에게만 콘텐츠 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hasRole('ROLE_NAME')&lt;/td&gt;
&lt;td&gt;특정 권한을 가진 사용자만 콘텐츠 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hasAnyRole('ROLE1', 'ROLE2')&lt;/td&gt;
&lt;td&gt;여러 권한 중 하나를 가진 사용자에게 콘텐츠 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;permitAll&lt;/td&gt;
&lt;td&gt;모든 사용자에게 콘텐츠 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;denyAll&lt;/td&gt;
&lt;td&gt;누구에게도 콘텐츠 표시하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;sec:authentication&lt;/b&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;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;principal&lt;/td&gt;
&lt;td&gt;인증된 사용자의 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;principal.name&lt;/td&gt;
&lt;td&gt;인증된 사용자의 사용자명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;principal.authorities&lt;/td&gt;
&lt;td&gt;인증된 사용자의 권한 목록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. sec:authorize 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 템플릿에서 사용자 권한에 따라 콘텐츠를 표시&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 로그인된 사용자만 보기 --&amp;gt;
&amp;lt;div sec:authorize=&quot;isAuthenticated()&quot;&amp;gt;
    안녕하세요, &amp;lt;span sec:authentication=&quot;name&quot;&amp;gt;&amp;lt;/span&amp;gt;님!
&amp;lt;/div&amp;gt;

&amp;lt;!-- 관리자만 접근 가능 --&amp;gt;
&amp;lt;a href=&quot;/admin&quot; sec:authorize=&quot;hasRole('ADMIN')&quot;&amp;gt;관리자 페이지&amp;lt;/a&amp;gt;

&amp;lt;!-- 비로그인 사용자만 보기 --&amp;gt;
&amp;lt;div sec:authorize=&quot;isAnonymous()&quot;&amp;gt;
    &amp;lt;a href=&quot;/login&quot;&amp;gt;로그인&amp;lt;/a&amp;gt;
    &amp;lt;a href=&quot;/signup&quot;&amp;gt;회원가입&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;!-- 모든 사용자에게 콘텐츠 노출 --&amp;gt;
&amp;lt;div sec:authorize=&quot;permitAll&quot;&amp;gt;
    모든 사용자가 볼 수 있는 공지사항입니다.
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. sec:authentication 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿에서 로그인된 사용자 정보를 출력.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 사용자 정보 출력 --&amp;gt;
&amp;lt;p&amp;gt;현재 사용자: &amp;lt;span sec:authentication=&quot;name&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;!-- 사용자 권한 출력 --&amp;gt;
&amp;lt;p&amp;gt;권한: &amp;lt;span sec:authentication=&quot;principal.authorities&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSRF 공격&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Cross-Site Request Forgery) 로그인 되어 있는 인증정보를 가지고 공격하는 것 (쿠키, 세션으로 자주 발생)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 사용자의 인증 정보를 이용해 악의적인 요청을 보내는 방식으로 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSRF 공격을 방지하기 위한 주요 방법 중 하나는 &lt;b&gt;CSRF 토큰&lt;/b&gt;을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Security에서 CSRF의 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security를 사용하면 기본적으로 CSRF 보호가 활성화 된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -&amp;gt; csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // CSRF 토큰을 쿠키에 저장
            )
            .authorizeHttpRequests(auth -&amp;gt; auth
                .anyRequest().authenticated() // 모든 요청 인증 필요
            );

        return http.build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CORS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cors(Cross-Origin Resource Sharing)는 원하지 않는 출처에서 서버 리소스가 무단으로 접근되는 것을 방지하기 위한 보안 장치이다. 보안상의 이유로 &lt;b&gt;기본적으로 다른 출처의 리소스에 대한 요청을 차단&lt;/b&gt;하는데 이 때문에 다음과 같은 문제가 발생한다. CORS 문제는 일반적으로 클라이언트와 서버가 &lt;b&gt;다른 출처&lt;/b&gt;에 있을 때 발생한다. Cors는 두가지 주요 요청 유형을 관리한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;첫번째로 단순 요청 get,post,head와 같은 제한된 헤더를 사용하는 요청으로 Access-Control-Allow-Origin헤더만 포함하여 응답하면 된다.&lt;/li&gt;
&lt;li&gt;두번째는 프리 플라이트요청(preflight requests)은 OPTIONS 메서드를 사용하여 서버의 정책을 사전에 확인하는 요청으로 요청의 유효성을 검증해 응답한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p 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;&lt;b&gt;프론트엔드&lt;/b&gt;: http://localhost:3000&lt;/li&gt;
&lt;li&gt;&lt;b&gt;백엔드&lt;/b&gt;: http://localhost:8080&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트가 API 요청을 보내면 브라우저는 &lt;b&gt;CORS 정책&lt;/b&gt;을 확인.&lt;/li&gt;
&lt;li&gt;서버가 해당 요청을 허용하지 않는다면, 브라우저는 요청을 차단하고 &lt;b&gt;CORS 오류&lt;/b&gt;를 발생시킨다.&lt;/li&gt;
&lt;/ol&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;Spring Security에서 Cors의 설정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -&amp;gt; {}) // CORS 활성화 (CorsConfigurationSource 사용)
                .authorizeHttpRequests(auth -&amp;gt; auth
                        .anyRequest().authenticated() // 모든 요청 인증 필요
                );
        return http.build();
    }
    
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of(&quot;&amp;lt;http://localhost:3000&amp;gt;&quot;)); // 허용할 Origin
        configuration.setAllowedMethods(List.of(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;)); // 허용할 HTTP 메서드
        configuration.setAllowedHeaders(List.of(&quot;Authorization&quot;, &quot;Content-Type&quot;)); // 허용할 헤더
        configuration.setAllowCredentials(true); // 쿠키 허용
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(&quot;/**&quot;, configuration); // 모든 경로에 적용
        return source;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jwt (JSON Web Token)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 객체를 사용하여 정보를 안전하게 전달하기 위한 &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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/11</guid>
      <comments>https://myh7754.tistory.com/11#entry11comment</comments>
      <pubDate>Fri, 29 Nov 2024 01:33:22 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드7기](Spring 프록시객체, 지연로딩 ,즉시로딩, 영속성전이, N+1문제)</title>
      <link>https://myh7754.tistory.com/10</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;프록시 객체&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㅇ&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지연 로딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스에서&amp;nbsp;&lt;b&gt;필요한 데이터만을 필요 시점에 로딩&lt;/b&gt;함으로써 애플리케이션의&amp;nbsp;&lt;b&gt;성능을 최적화하는 데 큰 기여한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 로딩의 핵심 개념은 객체가 참조하고 있는 다른 객체나 컬렉션의 데이터를 처음부터 메모리에 로딩하지 않고,&amp;nbsp;&lt;b&gt;해당 데이터에 실제로 접근하려는 시점에 로딩을 수행하는 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 즉 커밋할 때 한번에 로드할 수 있도록 하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연 로딩의 동작 방식은 **프록시 패턴(Proxy Pattern)**을 기반으로 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 객체는 엔티티의 메타데이터와 식발자를 보유하여 엔티티 접근 시도가 있을 때 DB세션이 열려 있는지 확인하고 데이터베이스 쿼리를 실행하여 데이터를 로드한다. 데이터베이스 세션이 닫힌 상태에서 프록시 객체에 접근하면&amp;nbsp;&lt;b&gt;LazyInitializationException&lt;/b&gt;&amp;nbsp;발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지연로딩 설정은&amp;nbsp;&lt;b&gt;fetch에 값을 FetchType.LAZY로 설정하면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
public class BaseBallPlayer {
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = &quot;team_id&quot;)
	private BaseBallTeam team;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;즉시로딩&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 객체와 그 객체가 연관된 다른 객체들을 한꺼번에 로드하여 애플리케이션의 성능과 사용성을 향상시키는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, A 객체가 B 객체를 참조하고 있을 때, 즉시 로딩이 설정되어 있다면 A 객체를 로드하는 동시에 B 객체도 함께 로드된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 데이터를 메모리에 로드하게 되어 메모리 사용량이 증가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정 엔티티가 다수의 연관된 엔티티를 가지고 있을 때, 모든 연관 엔티티를 즉시 로딩하게 되면 시스템의 메모리 리소스를 과도하게 사용하게 된다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시로딩 설정 FetchType.EAGER&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
public class BaseBallPlayer {
	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = &quot;team_id&quot;)
	private BaseBallTeam team;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;영속성 전이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 엔티티에 대해 수행된 특정 작업이 연관된 자식 엔티티에도 자동으로 전파되도록 하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 부모 객체를 데이터베이스에 저장할 때, 연관된 자식 객체들도 자동으로 저장되도록 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Cascade&lt;/b&gt; 데이터베이스에서 cascade는 외래 키 제약조건(Foreign Key constraint)이 참조하는 기본키가 삭제되거나 업데이트 되었을 때 그 부모 레코드와 연관된 자식 테이블의 레코드도 자동으로 삭제되거나 업데이트되는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연관관계에서 CasCade를 설정은 다음과 같이 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@OneToMany, @ManyToOne, @OneToOne, @ManyToMany&lt;/b&gt;와 같은 관계 매핑 어노테이션에 &lt;b&gt;cascade = &lt;a href=&quot;http://CascadeType.XXX&quot;&gt;CascadeType.XXX&lt;/a&gt;&lt;/b&gt; 형식으로 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CascadeType 에는 &lt;b&gt;ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH&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;b&gt;PERSIST&lt;/b&gt; &lt;b&gt;CascadeType.PERSIST&lt;/b&gt;는 부모 객체를 영속화할 때, 연관된 자식 객체들도 함께 영속화하도록 설정. 예시 : 새로운 A 객체를 생성하면서 그와 연관된 B 객체들을 추가하고, A 객체를 데이터베이스에 저장할 때, B 객체들도 자동으로 데이터베이스에 저장하도록 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MERGE&lt;/b&gt; 부모 객체의 상태를 병합할 때, 연관된 자식 객체들도 함께 병합된다. 예시 : A 객체의 정보를 업데이트할 때, 그와 연관된 B 객체도 함께 업데이트 된다.&lt;/li&gt;
&lt;li&gt;REMOVE 부모 객체가 삭제될 때, 연관된 자식 객체들도 함께 삭제된다. 예시 : 특정 A 객체를 삭제할 때, 그와 연관된 모든 B 객체들도 자동으로 삭제되도록 설정하여 부모-자식 관계에서 불필요한 데이터가 남지 않도록 한다.&lt;/li&gt;
&lt;li&gt;REFRESH 부모 객체를 새로고침할 때, 연관된 자식 객체들도 함께 새로고침 된다. 데이터베이스에서 최신 데이터를 가져와 엔티티 상태를 동기화할 때 유용하다.&lt;/li&gt;
&lt;li&gt;DETACH 부모 객체가 비영속화될 때, 연관된 자식 객체들도 함께 비영속화된다. 세션의 관리를 효율적으로 처리할 수 있도록 도와준다. 예시 : 특정 A 객체를 detach할 때, 그와 연관된 B 객체도 함께 detach되어 세션 관리가 간소화된다.&lt;/li&gt;
&lt;li&gt;ALL 앞서 언급한 모든 전이 작업을 한꺼번에 적용하는 설정 부모 객체에 대해 수행된 모든 작업이 자식 객체에도 동일하게 적용되도록 설정하는 것이며, 이를 통해 부모와 자식 객체 간의 상태를 항상 동기화된 상태로 유지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;N+1 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORM을 사용하여 객체와 DB 테이블 간 매핑이 이루어지며, 이를 통해 개발자는 DB의 세부 사항을 신경쓰지 않고 객체지향적으로 코드를 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 ORM으로 생성된 코드는 자동화된 커리이므로 성능 최적화면에서 복잡하고 불필요한 코드가 발생할 수 있다 이 문제 중 가장 대표적인 것이 &lt;b&gt;N+1문제&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
@Entity
public class Board {

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

    @Column(nullable = false, length = 50)
    private String name;
    
    @OneToMany(mappedBy = &quot;board&quot;, fetch = FetchType.LAZY)
    private List&amp;lt;Post&amp;gt; postList = new ArrayList&amp;lt;&amp;gt;();

}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Entity
public class Post {

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

    private String title;
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;board_id&quot;)
    private Board board;

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제와 같이 1개의 게시판에 여러개의 글이 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 게시판에 10개의 글이 있다면 1개의 게시판을 조회하면 10개의 글을 조회하는 쿼리가 발생되어 성능저하가 발생된다. 즉 총 부모+자식의 쿼리수 1+10 = 11개의 쿼리가 발생하여 문제가 발생하는 것이 N+1 문제이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fetch join&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch join은 N+1문제를 해소할 수 있는 방법으로 사용된다. fetch join은 연관된 엔티티를 한 번의 쿼리로 모두 조회한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jpa에서 사용법은 @OneToMany 또는 @ManyToOne 관계에서 fetch = FetchType.EAGER를 설정하여 즉시 로딩을 사용 하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@OneToMany(fetch = FetchType.EAGER)
private List&amp;lt;Post&amp;gt; posts;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 fetch join또한 데이터의 양이 많아질 경우 , 하나의 쿼리에서 너무 많은 데이터를 가져와 성능이 저하 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 fetch join외에도 Batch Size와 같은 수단을 통해서 성능 튜닝을 유연히 수행할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배치 쿼리 (Batch Query)&lt;/b&gt;: N+1 문제를 해결하기 위한 또 다른 방법은 &lt;b&gt;배치 쿼리.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 여러 부모 엔티티에 대해 한 번에 자식 데이터를 가져오는 방식 JPA에서는 @Query 또는 @BatchSize를 사용하여 배치 처리를 할 수 있다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/10</guid>
      <comments>https://myh7754.tistory.com/10#entry10comment</comments>
      <pubDate>Mon, 25 Nov 2024 09:22:58 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드 7기] (Spring Entity 개념, 연관관계, 공통속성정의)</title>
      <link>https://myh7754.tistory.com/9</link>
      <description>&lt;h1&gt;Entity&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;엔티티(Entity)는&lt;/b&gt; 데이터베이스의 특정 테이블을 자바 클래스 형태로 표현한 것&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;create table boards(
    board_id bigint not null primary key auto_increment,
    name varchar(20) not null
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 sql문을 java의 Spring으로 표현하면 다음과 같다&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
@Entity
@Table(name = &quot;boards&quot;)
public class Board {
    
    private Long boardId;
    
    private String name;
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Id&lt;/h2&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;@GeneratedValue&lt;/b&gt;을 통해 자동으로 생성.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
@Entity
@Table(name = &quot;boards&quot;)
public class Board {
    
    @Id
    @GeneratedValue(strategy = GenerationType.사용할생성전략)
    private Long boardId;
    
    private String name;
    
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성 전략&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@GeneratedValue에도 종류가 있는데 일반적으로 &lt;b&gt;auto_increment&lt;/b&gt;와 &lt;b&gt;비슷&lt;/b&gt;한 &lt;b&gt;@GeneratedValue의&lt;/b&gt; &lt;b&gt;IDENTITY 전략&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;b&gt;(jakarta.persistence.GenerationType.AUTO)&lt;/b&gt; 데이터베이스의 기본키 생성 규칙을 자동으로 결정하며, 데이터베이스가 기본키를 생성하는 방식을 따르게 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시퀀스 전략 (jakarta.persistence.GenerationType.SEQUENCE)&lt;/b&gt; 기본키 값을 데이터베이스 시퀀스 객체를 사용하여 자동으로 생성하는 전략 @SequenceGenerator를 통하여 시퀀스의 이름과 시작 값 등을 자세히 정의하는 것도 가능 생성된 시퀀스를 사용하여 각 엔티티의 기본키 값을 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;식별자 전략 (jakarta.persistence.GenerationType.IDENTITY)&lt;/b&gt; 기본키 필드에 대한 자동 증가 속성(auto increment)과 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;&lt;b&gt;테이블 전략 (jakarta.persistence.GenerationType.TABLE)&lt;/b&gt; 시퀀스 전략과 유사하게 @TableGenerator를 통하여 기본키 값을 생성할 테이블과 관련된 설정을 직접 정의할 수 있다. 테이블 전략은 DBMS 제품군과는 상관 없이 개발 전략에 맞추어 취하는 방법&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UUID 전략 (jakarta.persistence.GenerationType.UUID)&lt;/b&gt; 기본키 값을 UUID로서 생성하여 할당하는 전략&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Column&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Column&lt;/b&gt;은 테이블 컬럼의 데이터 타입과 제약 조건을 정의할 때에도 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;nullable, columnDefinition, unique&lt;/b&gt;와 같은 제약조건 속성&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
@Entity
@Table(name = &quot;boards&quot;)
public class Board {
    
    private Long boardId;
	   
	  @Column(nullable = false, length = 50)
    private String name;
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EntityManager&lt;/h2&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;**영속성 컨텍스트(Persistence Context)**를 통해 엔티티 객체의 &lt;b&gt;상태&lt;/b&gt;를 관리를 한다.&lt;/li&gt;
&lt;li&gt;1차 캐시 역할을 하여 db의 불필요한 트랜잭션을 줄이고 성능을 향상시킨다&lt;/li&gt;
&lt;li&gt;트랜잭션 관리를 담당하며 트랜잭션의 **시작(BEGIN), 커밋(COMMIT), 롤백(ROLLBACK)**을 제어한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영속성 컨텍스트(Persistence Context)란?&lt;/h3&gt;
&lt;p 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;엔티티 객체의 상태를 추적하고, 해당 엔티티가 언제, 어떻게 데이터베이스와 상호작용 하는지를 결정한다.&lt;/li&gt;
&lt;li&gt;데이터베이스에 불필요한 접근을 줄이고, 성능을 최적화하며, 데이터의 일관성을 보장하는 역할을 수행한다.&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트는 기본적으로 세션(Session)이라고도 불리며, 이 세션은 엔티티 객체를 캐싱하는 역할을 수행한다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 시작되면 영속성 컨텍스트가 생성되며, 트랜잭션이 종료될 때까지 유지된다.&lt;/li&gt;
&lt;li&gt;1차 캐시로 작동되어 동일한 컨텍스트 내에서 여러번 조회할 때 캐시된 엔티티를 반환하여 성능을 최적화한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영속성 컨텍스트의 엔티티 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티는 컨텍스트 내에서 크게 4가지 상태로 관리된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;비영속 상태(transient)&lt;/b&gt; 엔티티 객체가 아직 영속성 컨텍스트에서 관리하지 않는 상태. 데이터 베이스에 저장이 되거나 하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;영속(관리)(managed)&lt;/b&gt; 영속성 컨텍스트에 의해 엔티티 객체가 관리되고 있는 상태. 엔티티의 변경사항을 추적(&lt;b&gt;변경감지&lt;/b&gt;)하며, 트랜잭션이 커밋 될 때 변경사항을 자동 반영한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;준영속(detached)&lt;/b&gt; 엔티티가 영속성 컨텍스트의 생명주기를 벗어나거나 명시적으로 분리 된 경우. 필드 변화가 일어난 후 커밋이 발생하더라도 관리를 받지 않고있기 때문에 변경사항이 반영 x 다시 반영해야 한다면 준영속 상태에서 병합(merge)를 통해 영속상태로 변경해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삭제(removed)&lt;/b&gt; 영속성 컨텍스트 뿐 아니라 db에서도 삭제하기 위한 상태. 제 상태에서 엔티티는 트랜잭션이 커밋될 때 데이터베이스에서 삭제된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변경 감지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영속성 컨텍스트에 의해 관리되는 엔티티의 상태변화를 영속성 컨텍스트가 자동으로 감지하고 db에 반영하는 것을 의미한다.&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;엔티티 초기 상태를 복사한 것&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트가 가장 최신의 스냅샷과 비교하여 어떤 필드가 변경되었는지 검사하고 컴밋할 때 db에 반영된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;연관관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db에서 테이블간의 관계(외래키와 같은)를 엔티티간의 관계로 표현한 개념이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일대일 연관관계(One to One)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@OneToOne 락커와 회원의 연관관계&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Entity
public class Locker {

	...
	
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Entity
public class Member {
	...
	private Grade memberType;
	
	@OneToOne
	private Locker locker;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일대다, 다대일 (One to Many, Many to One)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 팀에는 많은 선수들이 있고, 많은 선수들은 하나의 팀에 속해있다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Entity
public class BaseBallPlayer {
	...
	@ManyToOne
	private BaseBallTeam team;
	...	
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Entity
public class BaseBallTeam {
	...
	@OneToMany
	private List&amp;lt;BaseBallPlayer&amp;gt; players;
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일대다로 매핑할 경우에는 연관관계의 대상이 되는 객체를 List&amp;lt;&amp;gt; 타입의 필드로서 선언한다는것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 List&amp;lt;&amp;gt;일 필요는 없지만 반드시 Collection타입이어야 한다.&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;다대다 @ManyToMany는 자바는 쉽게 표현 가능한 상황이지만 db에서는 어렵기 때문에 잘 사용하지 않고 만약 필요하다면 다대다 관계를 풀어서 일대다 + 다대일 관계로 처리한다. -&amp;gt; 중간 테이블을 만들어서 관리한다&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@OneToOne, @OneToMany, @ManyToOne, @ManyToMany&lt;/b&gt;을 통해서 엔티티 간의 관계를 정의하였는데 여기서 속성 &lt;b&gt;mappedBy 속성&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;양방향 매핑 : 서로가 서로의 참조를 볼 수 있어 team에서도 선수를 가져와 사용할 수 있고 선수도 팀의 정보를 가져와 사용할 수 있는 관계로 보면 된다.&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;mappedBy 속성을 통해 Locker는 OneToOne으로 연결된 필드의 이름 locker의 이름을 통해 연결하고 JoinColumn을 통해 외래키 필드의 이름을 locker_id로 설정한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Entity
public class Locker {
	...
	@OneToOne(mappedBy = &quot;locker&quot;) // OneToOne으로 연결된 Locker필드의 이름
	private Member member;
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
public class Member {
	...
	private Grade memberType;
	@OneToOne
	@JoinColumn(name = &quot;locker_id&quot;) // 외래키의 이름 설정
	private Locker locker;
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다대일, 일대다 관계 @ManyToOne , @OneToMany&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 Post테이블은 Board테이블의 번호를 참조하여 외래키로 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt; 표 8 &amp;gt; 게시판(Board) 테이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번호 이름&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;공지 사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;자유 게시판&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt; 표 9 &amp;gt; 게시글(Post) 테이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번호 제목 내용 게시판 번호&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;첫 번째 공지사항&lt;/td&gt;
&lt;td&gt;첫 공지입니다. 안녕하&amp;hellip;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Spring은 즐거워요&lt;/td&gt;
&lt;td&gt;Spring이 이렇게&amp;hellip;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 테이블의 외래키 설정을 나타내면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Entity
public class Board {
	private int id;
	private String name;
	@OneToMany(mappedBy = &quot;board&quot;)
	private List&amp;lt;Post&amp;gt; post;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Entity
public class Post() {
	private int id;
	private String title;
	private String conetent;
	@ManyToOne
	@JoinColumn(name = &quot;board_id&quot;, nullable = false) // 외래키 설정
	private Board board;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공통속성 정의(@MappedSuperclass)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@MappedSuperclass&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;즉 @MappedSuperclass가 적용된 클래스는 엔티티(Entity)가 아니며, 데이터베이스 테이블로 매핑되지 않지만 이를 상속받는 자식 클래스는 부모 클래스의 필드를 자신의 테이블에 포함시키게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제와 같이 공통된 속성 createAt, id 등을 정의하여 베이스 테이블을 만들어 사용하면 속성은 공통되지만 독립적으로 각각 테이블에 적용되어 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@MappedSuperclass
public abstract class BaseEntity {

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

    @Column(name = &quot;created_at&quot;, nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @Column(name = &quot;updated_at&quot;)
    private LocalDateTime updatedAt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;board&quot;)
public class Board extends BaseEntity {

    @Column(nullable = false, length = 100)
    private String name;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;post&quot;)
public class Post extends BaseEntity {

    @Column(nullable = false, length = 100)
    private String title;

    @Column(length = 1000)
    private String contents;
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/9</guid>
      <comments>https://myh7754.tistory.com/9#entry9comment</comments>
      <pubDate>Mon, 25 Nov 2024 01:06:31 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드7기] (Spring JPA 기본,ORM이란? , JDBC)</title>
      <link>https://myh7754.tistory.com/8</link>
      <description>&lt;h1&gt;JPA&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SPRING에서 데이터베이스의 데이터를 꺼내 사용하기 위해 DBMS에 접근하는 기술&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DB 커넥터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램과 DB간 상호작용을 가능하게 하는 구성요소.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신을 관리하고 중재하는 핵심적인 역할.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 DBMS(PostgreSQL, MySQL)등 과 연결을 &lt;b&gt;표준화된 방식&lt;/b&gt;으로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커넥터는 보통 Query(질의)를 보내고 결과를 받아오며, &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;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;db와 연결을 효율적으로 사용하기 위해 &lt;b&gt;커넥션 풀링&lt;/b&gt;을 지원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db커넥터는 크게 2가지 Driver와 Client Library로 나눈다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*&lt;b&gt;풀링(Pooling):&lt;/b&gt; 자원을 필요할 때마다 생성하는 대신 미리 자원을 할당해 놓고 필요한 시점에 꺼내서 사용하는 방법.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 커넥션 풀링 : 데이터 베이스와 연결을 미리 생성하여 필요할 때 할당해 사용 후 다시 풀에 반환하는 방식. 장점&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;h3 data-ke-size=&quot;size23&quot;&gt;Driver&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 명령문을 전송하고 결과를 받아오는 역할을 수행한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Client Library&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 라이브러리는 드라이버를 추상화하여 개발자가 더 편리하게 사용할 수 있도록 제공.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JDBC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC는 &lt;b&gt;SQL 질의 실행, 데이터베이스 연결 관리, 트랜잭션 처리, 결과 집합(ResultSet) 처리&lt;/b&gt; 등 데이터베이스와 관련된 모든 작업을 &lt;b&gt;Java 애플리케이션에서 직접 수행할 수 있도록 기능을 제공.&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;rarr; 여러 종류의 DB 사용가능&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ORM&lt;/h2&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;ORM은 객체 지향 시스템의 객체를 관계형 데이터베이스의 레코드와 &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;&amp;lt; 표 1 &amp;gt; 게시판 테이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번호 이름&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;공지 사항&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;자유 게시판&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt; 표 2 &amp;gt; 게시글 테이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번호 제목 내용 게시판 번호&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;첫 번째 공지사항&lt;/td&gt;
&lt;td&gt;첫 공지입니다. 안녕하&amp;hellip;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Spring은 즐거워요&lt;/td&gt;
&lt;td&gt;Spring이 이렇게&amp;hellip;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 테이블의 join을 위해 게시글 테이블에 fk를 추가한 형태 이것을 스키마로 작성하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;create table boards(
    board_id bigint not null primary key auto_increment,
    name varchar(20) not null
);

create table posts(
    post_id bigint not null primary key auto_increment,
    title varchar(255) not null,
    contents text,
    board_id bigint not null,
    foreign key (board_id) references boards(board_id)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 때 post에서 board_id만으로 작성된 게시판을 알고 싶을 때 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db 입장에서는 문제없이 join으로 해결 가능하다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;select boards.name
    , posts.*
from posts
left join boards
    on boards.board_id = posts.board_id
;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 java입장에서는 다음과 같이 post 만으로 board객체의 이름을 알 수 없는 문제가 발생한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Getter
@Setter
public class Post {
    private Long postId;
    private String title;
    private String content;
    private Long boardId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 Post는 Board의 모든 값을 알기 위해 객체를 직접 할당하여 정보를 사용한다. 이러한 상황에서 객체와 테이블간 패러다임 불일치가 발생하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
public class Post {
    private Long postId;
    
    private String title;
    private String content;
    
    // private Long boardId;
    
    private Board board; // &amp;lt;- 객체를 직접 할당
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java Persistence API(JPA)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 ORM을 표준화 하기 위해 정의된 일종의 인터페이스.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 베이스와 테이블 간 매핑 설정을 하기위해 어노테이션 혹은 XML 기반의 메타데이터를 사용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 메타데이터로 Entity, 필드와 컬럼 간 매핑, 객체 간의 연관 관계 등을 정의.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Data Persistence&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 영속성은 시스템에서 생성된 데이터가 프로그램 실행 종료가 된 후에도 사라지지 않고 유지하는 특성을 의미한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hibernate&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jpa를 구현한 프레임워크 중 하나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍의 객체와 관계형 db 테이블 간 매핑을 자동화 하여 데이터 지속성 관리 기능을 제공한다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/8</guid>
      <comments>https://myh7754.tistory.com/8#entry8comment</comments>
      <pubDate>Fri, 22 Nov 2024 17:33:31 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드 7기] (Spring Controller, thymeleaf 실습)</title>
      <link>https://myh7754.tistory.com/7</link>
      <description>&lt;h1&gt;실습&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class TController {

//    @ResponseBody
//    @GetMapping(&quot;/params1&quot;)
//    String showPram1(HttpServletResponse req) {
//        String name =
//        System.out.println(&quot;name =&quot; + name);
//    }

    @ResponseBody
    @GetMapping(&quot;/params1&quot;)
    public String showParam1(HttpServletRequest req) {
        System.out.println(&quot;/param1&quot;);
        String name = req.getParameter(&quot;name&quot;);
        return &quot;&quot;;
    }
    // localhost:8080/params2?name=...
    @ResponseBody
    @GetMapping(&quot;/params2&quot;)
    public String showParams2(@RequestParam(name = &quot;name&quot;) String name ) {
        System.out.println(&quot;/params2&quot;);
        System.out.println(&quot;name = &quot; + name);
        return &quot;&quot;;
    }

    //required = false로 두면 name을 강요하지 않음
    //age에서 강요는 하지 않지만 값은 가지고 시작했으면 좋겠을 때defaultValue에 값
    @ResponseBody
    @GetMapping(&quot;/params3&quot;)
    public String showParams3(
            @RequestParam(name = &quot;name&quot;, required = false) String name,
            @RequestParam(name = &quot;age&quot;, required = false, defaultValue =  &quot;1&quot;)  int age
    ) {
        System.out.println(&quot;/params3&quot;);
        System.out.println(&quot;name = &quot; + name);
        System.out.println(&quot;age = &quot; + age);
        return &quot;&quot;;
    }

    // 로그인 할 때
    // id ,비밀번호 &amp;lt;- 객체로 관리 가능
    //@ModelAttribute를 붙히면 info에 값을 받기위한 값이라고 Spring이 인지해서 bean으로 등록
    // 값을 받기위해서는 SignForm클래스에서 주입받기 위한 Setter가 필요한데 @Data를 붙히면 그역할을 대신해줄 수 있음
    @ResponseBody
    @GetMapping(&quot;/params4&quot;)
    public String showParams4(@ModelAttribute SignForm info) {
        System.out.println(&quot;username = &quot;  + info.getUsername());
        return &quot;&quot;;
    }

    @Data
    class SignForm { // VO(Value Object)
        private String username;
        private int password;
    }
    //DTO vs VO

    //@ModelAttribute를 안붙혀도 info 자리에 들어가면 Spring이 알아서 인지함
    @ResponseBody
    @GetMapping(&quot;/params5&quot;)
    public String showParams5(SignForm info) {
        System.out.println(&quot;username = &quot;  + info.getUsername());
        return &quot;&quot;;
    }

    // 여러값을 받는 방법 객체로 받으면됨
    @ResponseBody
    @GetMapping(&quot;/params6&quot;)
    public String showParams6(@RequestParam(name = &quot;favorite&quot;) String[] favorites) {
        System.out.println(&quot;/params6&quot;);

        //
        for (String favorite : favorites) {
            System.out.println(&quot;favorite = &quot; + favorite);
        }
        return &quot;&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경로 변수&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// 경로 변수
@Controller
public class T2Controller {
    // REST API
    // /boards/공지사항/posts/1의 url을 받을 때 -&amp;gt; 게시글이 늘어가면 1에서 계속해서 늘어남 이때 해결방법
    // pathVariable -&amp;gt; @PathVariable
    @ResponseBody
    @GetMapping(&quot;/boards/공지사항/posts/{id}&quot;)
    public String showPathV(@PathVariable(name = &quot;id&quot;) int id) {
        System.out.println(&quot;id = &quot; + id);
        return &quot;&quot;;
    }

    //같은경로 요청이 2개가 들어오면 더 구체적인 쪽을 실행
    // ex) /path1/ 과 /path1/test가 있다면  /path1/test을 요청했을 때 /path1는 무시

    //path2/mango
    //path2
    // 이렇게하면 2가지 url을 받았을 때 쓰는 기능을 정의할 수 있음 /Dpath와 /path2/{name}
    @ResponseBody
    @GetMapping(value = { &quot;/Dpath&quot;, &quot;/path2/{name}&quot;})
    public String showPathV2(@PathVariable(name = &quot;name&quot;, required = true) String name) {
        System.out.println(&quot;name = &quot; + name);
        return &quot;&quot;;
    }

    //Optional을 사용해서 값이 비어있다면 null로 받고 있다면 받도록 설정할 수 있음
    @ResponseBody
    @GetMapping(value = {&quot;/Dpath2&quot;, &quot;/path3/{name}&quot;})
    public String showPathV3(
            @PathVariable(name = &quot;name&quot;) Optional&amp;lt;String&amp;gt; name
    ) {

        System.out.println(&quot;/path3 =&quot; + name.orElse(&quot;null&quot;));
        System.out.println(&quot;/name = &quot; + name.orElse(&quot;null&quot;));
        return &quot;&quot;;
    }

    // GetMapping의 {name} 과 @PathVariable의 변수 name이 같다면 자동으로 연결해 준다.
    @ResponseBody
    @GetMapping(&quot;/path4/{name}&quot;)
    public  String showPathV4(@PathVariable String name) {
        System.out.println(&quot;/path4/&quot; + name);
        System.out.println(&quot;name = &quot; + name);
        return &quot;&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Controller 와 service 연&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Controller
@RequiredArgsConstructor
public class T3Controller {
    private final TService service;
    
    @ResponseBody
    @GetMapping(&quot;/sum&quot;)
    public String result(Numbers numbers) {
        int result = service.sum(numbers);
        return &quot;&quot;;
    }

    @Data
    public class Numbers {
        private int a;
        private int b;
    }

}

@Service
public class TService {
//    public int sum(int a, int b) {
//        return a + b;
//    }

    public int sum(T3Controller.Numbers numbers) {
        return numbers.getA() + numbers.getB();
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thymeleaf&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thymeleaf 문법1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java파일&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class Thymeleaf1 {
    public String prefix = &quot;/thymeleaf&quot;;
    @GetMapping(&quot;/&quot;)
    public String index() {
        return &quot;index&quot;;
    }

    // Model로 model을 스프링이 자동으로 넣어줌
    @GetMapping(&quot;/1&quot;)
    public String page1(Model model) {
        // model을 key와 value로 값을 받음
        model.addAttribute(&quot;key&quot;, &quot;model의 값 입니다.&quot;);
        return prefix + &quot;/page1&quot;;
    }

    @GetMapping(&quot;/2&quot;)
    public String page2(Post post, Model model) {
        model.addAttribute(&quot;post&quot;, post);
        return prefix + &quot;/page2&quot;;
    }

    @Data
    class Post {
        private String title;
        private String content;
    }
    // utext가 위험한 이유 (Escape처리를 안한 text)
    @GetMapping(&quot;/3&quot;)
    public String page3(Post post, Model model) {
        String unsafe = &quot;&amp;lt;script&amp;gt;alert('해킹');&amp;lt;/script&amp;gt;&quot;;
        model.addAttribute(&quot;script&quot;, unsafe);
        return prefix + &quot;/page3&quot;;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html 파일&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;//index
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;div th:each = &quot;index : ${#numbers.sequence(1,11)}&quot;&amp;gt;
    &amp;lt;a th:href=&quot;@{~/thymeleaf/{idx}(idx=${index})}&quot;&amp;gt;viewpage[[${index}]]&amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

// 1번 model 객체로 받아온 key값에 해당하는 value값을 출력
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;page1&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;!--    model atrribute에서 받아온 해당하는 key값의 value를 가져옴--&amp;gt;
    &amp;lt;h1 th:text=&quot;${key}&quot;&amp;gt;나는 페이지1&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

// 2번 model 객체로 전달받은 post의 변수를 사용
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;page2&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1 th:text=&quot;${post.getTitle()}&quot;&amp;gt;게시물 제목&amp;lt;/h1&amp;gt;

    &amp;lt;div&amp;gt;
        &amp;lt;span th:text=&quot;${post.getContent()}&quot;&amp;gt; 내용&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

// 3번 escape 처리를 안한 utext에 자바스크립트 코드가 그대로 들어와서 변경됨
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;page3&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div th:text =&quot;${script}&quot;&amp;gt;1번&amp;lt;/div&amp;gt;
&amp;lt;!--    컨트롤러에서 만든 script코드가 반영되어 버림--&amp;gt;
    &amp;lt;div th:utext =&quot;${script}&quot;&amp;gt;2번&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thymeleaf 문법2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java파일&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
//@RequestMapping을 사용하면 항상 url Mapping할 때 맨 앞에 /view의 값이 붙음
@RequestMapping(&quot;/thymeleaf&quot;)
public class ThymeLeaf2 {
    public String prefix = &quot;/thymeleaf&quot;;

    @GetMapping(&quot;/5&quot;) // /view/1 과 같음
    public String syntaxPage1(Model model) {
        model.addAttribute(&quot;username&quot;, &quot;Hello World&quot;);
        return prefix+&quot;/page5&quot;;
    }

    @GetMapping(&quot;/6&quot;)
    public String syntaxPage2(Model model) {
        List&amp;lt;String&amp;gt; shopList = Arrays.asList(&quot;양파&quot;, &quot;돈&quot;);
        model.addAttribute(&quot;shopList&quot;, shopList);
        return prefix + &quot;/page6&quot;;
    }
    //post 값을 넣어 생성하여 view로 전달
    @GetMapping(&quot;/7&quot;)
    public String syntaxPage3(Model model) {
        Post post = new Post(1, &quot;안녕하세요&quot;, &quot;content&quot;);
        model.addAttribute(&quot;post&quot;, post);
        return prefix + &quot;/page7&quot;;
    }

    // view로 생성된 post를 전달
    @GetMapping(&quot;/8&quot;)
    public String syntaxPage4(Model model) {
        model.addAttribute(&quot;post&quot;,new Post());
        return prefix + &quot;/page8&quot;;
    }

    // view에서 post로 전달받은 객체를 전달받아 줄력
    @PostMapping(&quot;/9&quot;)
    public  String syntaxPage5(Post post, String content) {
        System.out.println(&quot;post = &quot; + post);
        System.out.println(&quot;content = &quot; + content);
        return  &quot;index&quot;;
    }

    @GetMapping(&quot;/10&quot;)
    public String syntaxPage6() {
        return prefix + &quot;/thpage1&quot;;
    }
    @GetMapping(&quot;/11&quot;)
    public String syntaxPage7(int target, Model model) {
        model.addAttribute(&quot;target&quot;, target);
        return prefix + &quot;/thpage2&quot;;
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Post {
    private int id;
    private String title;
    private String content;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html 파일&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;// 5번 th:if문 조건을 만족하면 실행 thLunless문 조건을 불만족하면 실행하지 않음
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;span th:if=&quot;username != null&quot;&amp;gt; [[${username}]] 회원님! 반갑습니다.&amp;lt;/span&amp;gt;
        &amp;lt;span th:unless=&quot;username != null&quot;&amp;gt; 회원이 없습니다.&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

//6번 컬렉션도 전달 가능
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;page6&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h2&amp;gt;물건&amp;lt;/h2&amp;gt;
    &amp;lt;ul&amp;gt;
        &amp;lt;li th:each = &quot;item : ${shopList}&quot; th:text=&quot;${item}&quot;&amp;gt;&amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

//7번 th:object를 상속받은 자식 태그부터 getTitle,post.title을 하지 않아도 해당 객체에 전달받은 필드 값을 바로 사용가능
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;문법 페이지 3&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;!-- th:object : wrapper클래스 하위 태그에 post라는 객체를 적용시켜줄꺼야--&amp;gt;
&amp;lt;div class=&quot;wrapper&quot; th:object=&quot;${post}&quot;&amp;gt;
    &amp;lt;h2 th:text=&quot;*{title}&quot;&amp;gt;제목란&amp;lt;/h2&amp;gt;
    &amp;lt;hr /&amp;gt;
    &amp;lt;h4&amp;gt; [[ *{id} ]] 번 게시글,  작성자 : &amp;lt;/h4&amp;gt;
    &amp;lt;div class=&quot;contents&quot;&amp;gt;
        &amp;lt;p th:text=&quot;*{content}&quot;&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

//8번 
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;게시글 작성&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt; 게시물 작성&amp;lt;/div&amp;gt;
    &amp;lt;form th:action=&quot;@{/page/5}&quot; method =&quot;post&quot;&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;label for=&quot;title&quot;&amp;gt;제목 : &amp;lt;/label&amp;gt;
            &amp;lt;input id=&quot;title&quot; type=&quot;text&quot; name=&quot;title&quot;&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;textarea name = &quot;content&quot; cols=&quot;30&quot; rows=&quot;10&quot;&amp;gt;&amp;lt;/textarea&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
            &amp;lt;button type=&quot;submit&quot;&amp;gt;작성&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
//9번 8번에서 전달받은 post에 값을 담아 다시 Post방식으로 넘겨준다.

//10번
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;a th:href=&quot;@{~/page/1}&quot;&amp;gt; page1&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;

&amp;lt;!--  #numbers.sequence  [1,2,3,4] 배열 자동으로 생성--&amp;gt;
    &amp;lt;li th:each = &quot; idxx : ${#numbers.sequence(1,4)}&quot;&amp;gt;
      &amp;lt;a th:href=&quot;@{~/view/{idx}(idx=${idxx})}&quot; &amp;gt;view[[${idxx}]]&amp;lt;/a&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

//11번
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;!--html 내부에서 변수를 만들고 싶을 때 th:with--&amp;gt;
  &amp;lt;div th:with=&quot;name = '구구단'&quot;&amp;gt;
    &amp;lt;h2 th:text=&quot;${name}&quot;&amp;gt;&amp;lt;/h2&amp;gt;
    &amp;lt;h3&amp;gt;[[${target}]]&amp;lt;/h3&amp;gt;
    &amp;lt;div&amp;gt;
&amp;lt;!--     th:block 어떤 태그를 써야할지 애매할 때 넣는다--&amp;gt;
      &amp;lt;th:block th:each=&quot;idx : ${#numbers.sequence(1,9)}&quot;&amp;gt;
        &amp;lt;p&amp;gt;[[ ${target} ]] X [[ ${idx}]] = [[ ${target} * ${idx}]]&amp;lt;/p&amp;gt;
      &amp;lt;/th:block&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/7</guid>
      <comments>https://myh7754.tistory.com/7#entry7comment</comments>
      <pubDate>Tue, 19 Nov 2024 21:32:59 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드 7기](POJO, SPRING @Controller, Thymeleaf + CS지식(CSR, SSR))</title>
      <link>https://myh7754.tistory.com/6</link>
      <description>&lt;h1&gt;POJO&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 프레임워크, 라이브러리, 기술에 종속, 의존하지 않고 순수 자바 객체만을 사용해서 만든 자바 프로그램이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링은 POJO를 따르는 프레임워크로 주요 기술인 IoC/DI, AOP, PSA는 애플리케이션을 POJO로 개발할 수 있게 해주는 기술이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;POJO의 규칙&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프레임 워크, 특정 환경과 규약에 종속되지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring,Hibernate와 같은 프레임워크에 의존하지 않는 객체&lt;/li&gt;
&lt;li&gt;특정 프레임워크나 라이브러리의 클래스를 상속받거나 인터페이스를 구현하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;간단한 구조&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요한 필드 , 생성자, Getter/Setter 메서드만 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SRP 단일 책임 원칙을 지키는 클래스여야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;POJO의 장점&lt;/h2&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;&lt;b&gt;객체지향적인 설계를 제한 없이 적용할 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SPRING에서 POJO를 적용한 예&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring의 어노테이션은 강한 의존성을 만들지 않고 Spring이 어노테이션을 읽고 처리하기 때문에 POJO를 위반하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring이 POJO를 관리하는 코드&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import org.springframework.stereotype.Service;
@Service  // Spring 컨테이너에 ProductService를 빈으로 등록
public class ProductService {
    public String getProductName() {
        return &quot;Laptop&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 컨테이너에서 POJO를 사용하는 코드&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 컨테이너를 통해 객체를 생성하고 의존성을 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class ProductController {
    private final ProductService productService;

    @Autowired
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    public void printProductName() {
        System.out.println(productService.getProductName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 컨테이너에서 POJO를 사용하는 코드&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class ProductController {
    private final ProductService productService;

    @Autowired
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    public void printProductName() {
        System.out.println(productService.getProductName());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;@Controller&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring 에게 해당 어노테이션이 붙은 클래스가 컨트롤러임을 선언하여 반환하는 값은 뷰 이름이나 데이터를 반환한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@RequestMapping&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;method에 요청할 방식 get, post를 정하고, path에 경로를 적는다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;@RequestMapping(method = RequestMethod.GET, path = &quot;/hello-world&quot;)
    public String receiveRequestFormBrowser() {
        System.out.println(&quot;hello&quot;);
        return &quot;index&quot;;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@GetMapping&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestMapping의 Get방식을 합쳐놓은게 @GetMapping&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@GetMapping(&quot;/print&quot;)
    public void receiveGetRequest(HttpServletResponse response) throws IOException {
        // 한국어가 ??로 나오고 인식을 못함 ?? 123 hello가 출력됨
        PrintWriter writer = response.getWriter();
        writer.println(&quot;할로 123 hello&quot;); //  ?? 123 hello

        response.setContentType(&quot;text/html;charset=UTF-8&quot;); //이걸 쓰고 아래와 같이 쓰면 한국어 사용가능
        PrintWriter writer = response.getWriter();
        writer.println(&quot;할로 123 hello&quot;); // 할로 123 hello
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@ResponseBody&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;return 값에 html이 아니라 뷰렌더링 없이 리턴값을 직접 페이지에 랜더링하여 전송하는 어노테이션이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@ResponseBody
    @GetMapping(&quot;/print2&quot;)
    public String receive2() {
        return &quot;안녕하세요&quot;;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Thymeleaf&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html에 java코드를 사용하기 위해 java코드를 html로 변환해주는 템플릿 엔진&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf는 HTML 속성에 th: 접두사를 붙여 동작을 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;th:text&lt;/td&gt;
&lt;td&gt;텍스트를 출력 (HTML 이스케이프 처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:utext&lt;/td&gt;
&lt;td&gt;텍스트를 출력 (HTML 이스케이프 미처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:href&lt;/td&gt;
&lt;td&gt;링크의 href 속성을 동적으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:src&lt;/td&gt;
&lt;td&gt;이미지 등의 src 속성을 동적으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:alt&lt;/td&gt;
&lt;td&gt;이미지 태그의 alt 속성을 동적으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:action&lt;/td&gt;
&lt;td&gt;폼 태그의 action 속성을 동적으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:onclick&lt;/td&gt;
&lt;td&gt;클릭 이벤트 속성 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:value&lt;/td&gt;
&lt;td&gt;입력 필드의 기본값 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:style&lt;/td&gt;
&lt;td&gt;style 속성을 동적으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:class&lt;/td&gt;
&lt;td&gt;class 속성을 동적으로 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:if&lt;/td&gt;
&lt;td&gt;조건에 따라 렌더링 (True일 때만 출력)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:unless&lt;/td&gt;
&lt;td&gt;조건에 따라 렌더링 (False일 때만 출력)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:each&lt;/td&gt;
&lt;td&gt;반복문 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:object&lt;/td&gt;
&lt;td&gt;폼의 기본 객체 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:field&lt;/td&gt;
&lt;td&gt;폼 필드 바인딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th:remove&lt;/td&gt;
&lt;td&gt;요소를 제거 (all, tag, body 등 옵션)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Thymeleaf 문법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서 전달받은 값을 기준으로 사용가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex)&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Controller
public class TestController {
    @GetMapping(&quot;/test&quot;)
    public String test(Model model) {
        model.addAttribute(&quot;msg&quot;, null); // null 값 전달
        return &quot;test&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thymeleaf에서 변수를 사용할 때는 ${}를 사용 ${variable}: 현재 컨텍스트에 있는 변수 출력 @{url}: URL을 동적으로 생성&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;th:text = &quot;${전달 받은 메시지 값}&quot; &amp;lt;div th:text=&quot;${msg}&quot;&amp;gt;기본값&amp;lt;/div&amp;gt; &amp;rarr; 기본값과 상관없이 msg출력&lt;/li&gt;
&lt;li&gt;만약 msg값이 null 이라면 그냥 기본 div 태그가 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:text=&quot;${name}&quot;&amp;gt;Default Name&amp;lt;/p&amp;gt; &amp;lt;!-- name 변수 값 출력 --&amp;gt;
&amp;lt;a th:href=&quot;@{/home}&quot;&amp;gt;Home&amp;lt;/a&amp;gt;         &amp;lt;!-- /home URL 생성 --&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;th:href = &amp;ldquo;@{url}&amp;rdquo; : url 생성 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적 url&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;a th:href=&quot;@{/home}&quot;&amp;gt;Home&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 url (경로 변수)&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;a th:href=&quot;@{/user/{id}(id=${userId})}&quot;&amp;gt;User Profile&amp;lt;/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 파라미터&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;a th:href=&quot;@{/search(query=${searchTerm})}&quot;&amp;gt;Search&amp;lt;/a&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;th:each = &quot;item :${items}&quot; : 리스트나 배열을 순회하여 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
  &amp;lt;li th:each=&quot;item : ${items}&quot; th:text=&quot;${item}&quot;&amp;gt;Sample Item&amp;lt;/li&amp;gt;
&amp;lt;/ul&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;th:if = &quot;${user &amp;ne; null}&quot; : 널이 아니면 이 태그가 출력&lt;/li&gt;
&lt;li&gt;th:unless = &quot;${user &amp;ne; null}&quot; : 널이면 이 태그가 출력 unless는 else처럼 사용하려면 else와 다르게 if와 같은조건을 한번 더 입력해줘야함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;p th:if=&quot;${user.isAdmin}&quot;&amp;gt;관리자&amp;lt;/p&amp;gt;
&amp;lt;p th:unless=&quot;${user.isAdmin}&quot;&amp;gt;일반 사용자&amp;lt;/p&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;th: fragment =&amp;rdquo;&amp;rdquo; : 웹페이지에 메뉴 탭이나 네비바와 같이 공통으로 반복되는 영역을 정의할 때 사용 특히 header와 footer에 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;div th:fragment=&quot;header&quot;&amp;gt;
  &amp;lt;h1&amp;gt;Header Content&amp;lt;/h1&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의한 fragement를th:include 또는 th:replace로 가져온다.&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;&amp;lt;div th:include=&quot;fragments.html :: header&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div th:replace=&quot;fragments.html :: header&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;th:include: HTML 내부에 삽입. th:replace: 기존 HTML을 대체.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;thymeleaf5.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOyg0B/btsKMnRf7yq/inKw7QjInDJBb0c4e5Vxk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOyg0B/btsKMnRf7yq/inKw7QjInDJBb0c4e5Vxk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOyg0B/btsKMnRf7yq/inKw7QjInDJBb0c4e5Vxk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOyg0B%2FbtsKMnRf7yq%2FinKw7QjInDJBb0c4e5Vxk0%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;1400&quot; height=&quot;547&quot; data-filename=&quot;thymeleaf5.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form th:object=&quot;${user}&quot; : form 태크에서 데이터를 보낼 때 object에서 지정한 객체에 값을 담아 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;thymelaf4.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YiudL/btsKN4JC5Wk/fHfe1ybzeffc172KKIMj6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YiudL/btsKN4JC5Wk/fHfe1ybzeffc172KKIMj6k/img.png&quot; data-alt=&quot;이미지와 같이 User Data에 있는 필드 name과 age가 담겨 object로 전달받아 name과 age를 사용가능.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YiudL/btsKN4JC5Wk/fHfe1ybzeffc172KKIMj6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYiudL%2FbtsKN4JC5Wk%2FfHfe1ybzeffc172KKIMj6k%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;1360&quot; height=&quot;747&quot; data-filename=&quot;thymelaf4.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지와 같이 User Data에 있는 필드 name과 age가 담겨 object로 전달받아 name과 age를 사용가능.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;th:inline : Javascript , css 코드 안에서 데이터를 동적으로 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;lua&quot;&gt;&lt;code&gt;&amp;lt;script th:inline=&quot;javascript&quot;&amp;gt;
    var username = [[${user.name}]];  // Alice
    var items = [[${items}]];        // [&quot;Item1&quot;, &quot;Item2&quot;, &quot;Item3&quot;]
&amp;lt;/script&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSR, SSR (CS지식)&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSR (클라이언트 사이드 랜더링)&lt;/h3&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;클라이언트(브라우저)에서 웹 페이지의 렌더링을 담당하는 방식&lt;/li&gt;
&lt;li&gt;서버는 HTML 껍데기와 함께 필요한 JavaScript 파일을 클라이언트에 전달하고, 클라이언트에서 JavaScript가 실행되어 동적으로 페이지를 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;작동 방식
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저가 서버에 요청&lt;/li&gt;
&lt;li&gt;서버는 기본적인 html과 js 파일을 반환&lt;/li&gt;
&lt;li&gt;클라이언트에서 js가 실행되어 DOM을 구성하고 데이터를 받아와 화면을 동적으로 렌더링&lt;/li&gt;
&lt;/ol&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;처음 로딩 속도가 느릴 수 있음 (js 실행 대기)&lt;/li&gt;
&lt;li&gt;이후 페이지 간 이동은 빠름 (SPA 방식)&lt;/li&gt;
&lt;li&gt;데이터를 동적으로 받아오기 때문에 인터랙티브한 UI 구현에 유리.&lt;/li&gt;
&lt;li&gt;API 호출을 통해 필요한 데이터만 받아와 클라이언트에서 렌더링.&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;더 나은 ux 제공&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;seo(검색 엔진 최적화)에 불리함&lt;/li&gt;
&lt;li&gt;초기 로딩 속도가 느릴 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSR 서버사이드 랜더링 &amp;rarr; html의 조립이 다 끝난 상태로 넘어감&lt;/h3&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;서버에서 HTML을 완성하여 브라우저에 전달하는 방식. 브라우저는 전달받은 HTML을 바로 화면에 렌더링. 모든 페이지 요청마다 서버에서 새로운 HTML이 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;작동방식
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저가 서버에 요청을 보냄.&lt;/li&gt;
&lt;li&gt;서버에서 HTML을 조립하고 완성된 페이지를 반환.&lt;/li&gt;
&lt;li&gt;브라우저는 받은 HTML을 바로 렌더링.&lt;/li&gt;
&lt;/ol&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;첫 로딩 속도가 빠름 (완전한 HTML 제공).&lt;/li&gt;
&lt;li&gt;페이지 이동 시마다 서버에 새 요청을 보냄&lt;/li&gt;
&lt;li&gt;SEO(검색 엔진 최적화)에 유리 (HTML이 이미 준비된 상태&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;SEO에 최적화된 구조.&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;li&gt;동적인 UI 구현이 번거로울 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/6</guid>
      <comments>https://myh7754.tistory.com/6#entry6comment</comments>
      <pubDate>Mon, 18 Nov 2024 16:34:52 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드7기] (Spring DI, IOC, AOP, Annotation)</title>
      <link>https://myh7754.tistory.com/5</link>
      <description>&lt;h1&gt;DI , 의존성주입&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;의존성 주입은 객체가 자신이 의존하는 다른 객체를 직접 생성하거나 관리하는 것이 아니라 외부에서 주입받는 방식으로 제어의 흐름을 반전시키는 제어의 역전 IOC(Inversion of Control, IoC)원칙을 기반으로 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;외부에서 의존성을 주입받아&lt;/strong&gt; 객체의 생명주기와 의존성 관리는 프레임워크에 맡기고 개발자는 비즈니스 로직에만 집중한다.&lt;/li&gt;
&lt;li&gt;크게 3가지 생성자주입, setter주입, 필드주입이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;생성자 주입 : 필드 내에서 boardRepository를 생성하지 않고 밖에서 생성된 repository를 생성자에서 주입받아 사용&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BoardService {
    private final BoardRepository boardRepository;

    // 생성자 주입
    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    public void addBoard(Board board) {
        boardRepository.addBoard(board);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Setter주입 : 객체가 생성된 후 세터 메서드를 통해 주입받는 방식 기본적으로 생성자 주입을 할 수 있다면 생성자 주입&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class BoardService {
    private BoardRepository boardRepository;

    // 세터 주입
    public void setBoardRepository(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    public void addBoard(Board board) {
        if (boardRepository != null) {
            boardRepository.addBoard(board);
        } else {
            throw new IllegalStateException(&amp;quot;BoardRepository is not set.&amp;quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;필드 주입 : @Autowired 어노테이션과 함께 직접 필드안에 주입하는 방식 -&amp;gt; 필드 주입은 테스트가 어렵고, 리팩토링 시 문제가 발생할 가능성이 높아 안티패턴으로 간주&lt;br&gt;@Autowired는 Bean컨테이너에서 등록되어 있는 객체를 찾아 타입에 맞게 자동으로 등록된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;

public class BoardService {
    @Autowired
    private BoardRepository boardRepository;  // 필드 주입

    public void addBoard(Board board) {
        boardRepository.addBoard(board);
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;IOC&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;ioc는 객체의 생성, 초기화, 의존성 관리 등의 제어 흐름을 ioc컨테이너로 넘겨 관리하도록 하는 것. -&amp;gt; 객체의 제어권을 프레임워크로 넘기는 것.&lt;/li&gt;
&lt;li&gt;스프링 컨테이너가 관리하는 객체를 Bean이라고 하고 이 Bean 컨테이너를 빈 팩토리라고 부른다.&lt;/li&gt;
&lt;li&gt;IoC가 구현하는 주요 방법이 DI 즉 외부에서 의존성을 주입받는 방식이고, IoC 컨테이너라 불리는 것을 Spring Container가 구현하는 것.&lt;/li&gt;
&lt;li&gt;크게 2종류 BeanFactory , ApplicationContext로 나뉜다.BeanFactory&lt;/li&gt;
&lt;li&gt;bean이라 불리는 객체를 생성, 관리 의존성 주입한다.&lt;/li&gt;
&lt;li&gt;빈이 동일한 의존성을 공유할 수 있도록 &lt;strong&gt;싱글톤 빈&lt;/strong&gt;을 보장한다.ApplicationContext&lt;/li&gt;
&lt;li&gt;BeanFactory에서 확장한 컨테이너로 애플리케이션 레이어 통합을 지원하며, 데이터베이스 연동, 트랜잭션 관리, 웹 애플리케이션 설정 등을 하나의 컨텍스트에서 통합 관리할 수 있도록 도와준다.&lt;/li&gt;
&lt;li&gt;다양한 설정 방식을 지원. XML 설정 파일을 사용하는 ClassPathXmlApplicationContext, 파일 시스템에서 XML 설정 파일을 읽어오는 FileSystemXmlApplicationContext, 그리고 어노테이션 기반의 설정을 지원하는 AnnotationConfigApplicationContext 등&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Spring Container = IoC Container&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DI와 IOC를 기반으로 객체를 관리하고 의존성을 주입하며 객체간 상호작용을 조율하는 역할을 한다.&lt;/li&gt;
&lt;li&gt;핵심기능은 Bean객체의 생성, 초기화, 설정, 의존성 관리이다.&lt;/li&gt;
&lt;li&gt;BeanFacktory와 ApplicationContext라는 두 인터페이스가 포함되어 있으며 이를통해 빈 객체와 스프링 어플리케이션의 객체를 각각 관리한다.&lt;/li&gt;
&lt;li&gt;ApplicationContext: BeanFactory를 확장한 형태 → 메시지 소스 접근, 이벤트 처리, 트랜잭션 관리 등 고급 기능을 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;AOP&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;관점 지향 프로그래밍으로, 핵심 비즈니스 로직과 공통 기능(예: 로깅, 트랜잭션 관리 등)을 분리해주는 프로그래밍 패러다임.&lt;/li&gt;
&lt;li&gt;Spring에서는 @Aspect, @Before, @After, @Around 등의 어노테이션으로 AOP 기능을 설정&lt;/li&gt;
&lt;li&gt;@Aspect : 클래스가 AOP 기능을 제공하는 관점(aspect)임을 나타낸다.&lt;/li&gt;
&lt;li&gt;@Before: 메서드 실행 전에 특정 동작을 수행.&lt;/li&gt;
&lt;li&gt;@After: 메서드 실행 후에 특정 동작을 수행.&lt;/li&gt;
&lt;li&gt;@Around: 메서드 실행 전후에 특정 동작을 수행하며, 메서드 실행 자체를 제어할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;PSA&lt;/h1&gt;
&lt;h1&gt;Annotation&lt;/h1&gt;
&lt;h2&gt;@Configuration , @bean 컨테이너&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;스프링 컨테이너는 @Configuration 클래스 내부의 @Bean 메서드를 호출하여 반환된 객체를 싱글톤 빈으로 관리&lt;/li&gt;
&lt;li&gt;@Configuration : 해당 클래스가 Spring 설정 파일임을 나타낸다.&lt;/li&gt;
&lt;li&gt;@Bean : 직접 객체를 생성하고 Bean으로 등록할 때 사용@Component, @ComponentScan&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Configuration public class AppConfig { @Bean public BoardService boardService(BoardRepository boardRepository) { return new BoardService(boardRepository); } @Bean public BoardRepository boardRepository() { return new BoardRepository(); } }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;클래스에 @Component를 붙이면 별도 @Bean 설정 없이 자동으로 스캔&lt;/li&gt;
&lt;li&gt;@Component: 클래스를 Spring Bean으로 등록 → 이를 확장한 어노테이션으로 @Service, @Repository, @Controller 등이 있다.&lt;/li&gt;
&lt;li&gt;@ComponentScan: 지정한 패키지를 스캔하여 @Component가 붙은 클래스를 Bean으로 등록@Autowired -&amp;gt; 예전에는 주입받기 위해 사용하는 건데 지금은 그냥 자동으로 해줌 spring이&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@Component public class BoardRepository { // Bean으로 자동 등록 }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;@Autowired: Bean을 자동으로 주입합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;@AllArgsConstructor&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;모든 필드의 인자 있는 생성자 생성@NoArgsConstructor&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@AllArgsConstructor public class Board { private Long id; private String title; private String content; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;인자 없는 생성자 생성&lt;/li&gt;
&lt;li&gt;@RequiredArgsConstructor&lt;/li&gt;
&lt;li&gt;@RequiredArgsConstructor: 모든 final 필드 및 @NonNull로 표시된 필드를 포함하는 생성자를 자동 생성 -&amp;gt; 즉 반드시 생성되어야 하는 final 필드만 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public class BoardService { private final BoardRepository boardRepository; // 반드시 생성자를 통해 주입 public void addBoard(Board board) { boardRepository.addBoard(board); } }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/5</guid>
      <comments>https://myh7754.tistory.com/5#entry5comment</comments>
      <pubDate>Thu, 14 Nov 2024 17:17:57 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드 7기] (database 실습2)</title>
      <link>https://myh7754.tistory.com/4</link>
      <description>&lt;pre&gt;&lt;code&gt;show databases ;
drop database if exists `practice`;
create database `practice`;
use `practice`;
create table air_logistics (
    #unsigned 절대값
    id int unsigned not null primary key auto_increment,
    category varchar(10) not null,
    special_baggage Boolean not null comment &amp;#39;true = 1 , false = 0&amp;#39;,
    send varchar(5) not null,
    receive varchar(5) not null,
    airline varchar(10) not null,
    distance int not null,
    special_rate int unsigned not null,
    rate int unsigned not null,
    send_date datetime default CURRENT_TIMESTAMP
);

insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;식품&amp;#39;,0,&amp;#39;중국&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;대한항공&amp;#39;,1,0,1500,&amp;quot;2024-11-30&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;기호품&amp;#39;,0,&amp;#39;한국&amp;#39;,&amp;#39;호주&amp;#39;,&amp;#39;아시아나&amp;#39;,2,0,2000,&amp;quot;2024-11-30&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;전자제품&amp;#39;,1,&amp;#39;일본&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;낫폰항공&amp;#39;,1,1000,1500,&amp;quot;2024-11-30&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;의학품&amp;#39;,1,&amp;#39;미국&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;델타항공&amp;#39;,4,1000,1500,&amp;quot;2024-12-01&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;식품&amp;#39;,0,&amp;#39;인도&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;고우에어&amp;#39;,3,0,1500,&amp;quot;2024-12-01&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;일바누편&amp;#39;,0,&amp;#39;한국&amp;#39;,&amp;#39;캐나다&amp;#39;,&amp;#39;대한항공&amp;#39;,4,0,1500,&amp;quot;2024-12-01&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;의류&amp;#39;,1,&amp;#39;한국&amp;#39;,&amp;#39;일본&amp;#39;,&amp;#39;아시아나&amp;#39;,1,1000,1500,&amp;quot;2024-12-02&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;전자제품&amp;#39;,1,&amp;#39;미국&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;델타항공&amp;#39;,4,1000,1500,&amp;quot;2024-12-02&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;주류&amp;#39;,1,&amp;#39;칠레&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;라탐항공&amp;#39;,4,0,1000,&amp;quot;2024-12-03&amp;quot;);
insert into air_logistics(category, special_baggage, send, receive, airline, distance, special_rate, rate, send_date) values (&amp;#39;주류&amp;#39;,1,&amp;#39;독일&amp;#39;,&amp;#39;한국&amp;#39;,&amp;#39;대한항공&amp;#39;,4,0,1000,&amp;quot;2024-12-03&amp;quot;);
select * from air_logistics;
# 1.우리 공항에서 보내는 곳과 그 보낸 곳의 항공편 수가 어떻게 되는지 전체 데이터를 알고 싶습니다.
select
    send as &amp;#39;보내는 곳&amp;#39;,
    count(`send`) as &amp;#39;항공편 수&amp;#39;
from
    air_logistics
-- 보내는 곳과 count를 묶어주기 위해 group by 사용
group by
    send;

# 1-1. 추가 한국의 항공편만 보고싶습니다.
# where을 사용한 경우
select send as &amp;#39;보내는곳&amp;#39;, count(`send`) as &amp;#39;항공편수&amp;#39;
from air_logistics
where send = &amp;#39;한국&amp;#39;
group by send
;
# having을 사용한경우 where 로 할 수 있다면 성능상 where이 좋습니다.
select
    send as &amp;#39;보내는곳&amp;#39;, count(`send`) as &amp;#39;항공편수&amp;#39;
from air_logistics
group by send
having send = &amp;#39;한국&amp;#39;
;
# 1-3 미국에서 우리 공항으로 들어오는 물류의 항공편이 몇건인지
select
    count(`send`) as &amp;#39;미국에서 들어오는 항공편 수&amp;#39;
from air_logistics
where send = &amp;#39;미국&amp;#39; and receive = &amp;#39;한국&amp;#39;;

-- 품류 관리표
create table `category_tb`(
    id int unsigned not null primary key auto_increment,
    category varchar(10) not null,
    special_baggage Boolean not null comment &amp;#39;true = 1 , false = 0&amp;#39;
);
insert into category_tb(category, special_baggage) values (&amp;#39;식품&amp;#39;,false);
insert into category_tb(category, special_baggage) values (&amp;#39;기호품&amp;#39;,false);
insert into category_tb(category, special_baggage) values (&amp;#39;전자제품&amp;#39;,true);
insert into category_tb(category, special_baggage) values (&amp;#39;의학품&amp;#39;,true);
insert into category_tb(category, special_baggage) values (&amp;#39;식품&amp;#39;,false);
insert into category_tb(category, special_baggage) values (&amp;#39;일바누편&amp;#39;,false);
insert into category_tb(category, special_baggage) values (&amp;#39;의류&amp;#39;,true);
insert into category_tb(category, special_baggage) values (&amp;#39;전자제품&amp;#39;,true);
insert into category_tb(category, special_baggage) values (&amp;#39;주류&amp;#39;,true);
insert into category_tb(category, special_baggage) values (&amp;#39;주류&amp;#39;,true);
select * from category_tb;
-- 원본 테이블에 품류 번호 컬럼 추가
alter table air_logistics add column category_id int unsigned not null after id;


desc air_logistics;
select * from air_logistics;
select * from category_tb;
-- 두 테이블을 join하여동일한 컬럼 값으로 변경
# update 값을바꿀테이블 join 엮을테이블 on 공통된컬럼 set 바꿀테이블에수정할값;
update air_logistics a
join
    category_tb b
on a.category = b.category
set a.category_id = b.id;
-- 외래키 추가
alter table air_logistics add foreign key (category_id) references category_tb(id);
-- 이제 엮였으므로 원본에서 불필요한 catgory와 special_beggage 삭제
alter table air_logistics
drop column category,
drop column special_baggage;

# 1-4특별수하물이 0일때는 O 아닐때는 x 조회
-- case를 사용할 경우 반드시 end를 쓴다 -&amp;gt; 이 sql은 switch case문처럼 작동
select
    a.category as &amp;#39;품류&amp;#39;,
    case
        -- case의 조건
        when a.special_baggage = true
        -- 참이라면
        then &amp;#39;O&amp;#39;
        -- 거짓이라면 (생략가능)
        else &amp;#39;X&amp;#39;
        -- 종료했을 때 결과 (생략가능)
    end as &amp;#39;특별_수하물_여부&amp;#39;
from category_tb a;
# 쪼개놓은 테이블 2개를 다시 원본테이블로 표시하는 방법
-- select from 왼쪽테이블 join 오른쪽테이블 on 공통된 컬럼
# join은 left join 혹은 right join 중 하나를 선택할 수 있으며
# 만약 left join을 한다면 on에 있는 공통된 컬럼에서 왼쪽값을 기준으로 일치하는 행을 넣고 없다면 null 값을 넣는다.
select
    a.id as &amp;#39;번호&amp;#39;,
    b.category as &amp;#39;품류&amp;#39;,
    b.special_baggage as &amp;#39;특별 수하물 여부&amp;#39;,
    a.send as &amp;#39;보내는 곳&amp;#39;,
    a.receive as &amp;#39;받는 곳&amp;#39;,
    a.airline as &amp;#39;항공편&amp;#39;,
    a.distance as &amp;#39;거리&amp;#39;,
    a.special_rate as &amp;#39;특별운임비&amp;#39;,
    a.rate as &amp;#39;운임비&amp;#39;,
    a.send_date as &amp;#39;보내는 날짜&amp;#39;
from
    air_logistics a
right join
    category_tb b
on a.category_id = b.id ;
select * from air_logistics;
select * from category_tb;

-- 거리탈 운임 표
CREATE TABLE `distance_charge` (
    id int unsigned not null primary key auto_increment,
    `culture_code` VARCHAR(5) NOT NULL UNIQUE,
    distance int not null,
    rate int unsigned not null
);

-- 데이터 추가
INSERT INTO `distance_charge`
SET `culture_code` = &amp;#39;아시아&amp;#39;,
    `distance` = 1,
    `rate` = 1500
;

INSERT INTO `distance_charge`
SET `culture_code` = &amp;#39;오세아니아&amp;#39;,
    `distance` = 2,
    `rate` = 2000
;

INSERT INTO `distance_charge`
SET `culture_code` = &amp;#39;인도&amp;#39;,
    `distance` = 3,
    `rate` = 2500
;

INSERT INTO `distance_charge`
SET `culture_code` = &amp;#39;유럽-미국&amp;#39;,
    `distance` = 4,
    `rate` = 3000
;

-- 문화권 테이블
CREATE TABLE `culture` (
    `id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    `culture_code` VARCHAR(5) NOT NULL,
    `nation` VARCHAR(5) NOT NULL UNIQUE
);
-- 데이터추가

INSERT INTO `culture`
SET `culture_code` = &amp;#39;아시아&amp;#39;,
    `nation` = &amp;#39;한국&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;아시아&amp;#39;,
    `nation` = &amp;#39;일본&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;아시아&amp;#39;,
    `nation` = &amp;#39;중국&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;오세아니아&amp;#39;,
    `nation` = &amp;#39;호주&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;인도&amp;#39;,
    `nation` = &amp;#39;인도&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;유럽-미국&amp;#39;,
    `nation` = &amp;#39;독일&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;유럽-미국&amp;#39;,
    `nation` = &amp;#39;칠레&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;유럽-미국&amp;#39;,
    `nation` = &amp;#39;미국&amp;#39;
;

INSERT INTO `culture`
SET `culture_code` = &amp;#39;유럽-미국&amp;#39;,
    `nation` = &amp;#39;캐나다&amp;#39;
;

alter table air_logistics
drop column distance,
drop column rate;
select * from air_logistics;

-- 외래키 추가

# alter table
#     draft
#     add constraint
#         fk_export_nt foreign key(target_nation)
#             references
#                 culture(nation)
# ; 이코드는fk_export_nt 제약조건 이름
alter table air_logistics add foreign key(receive) references  culture(nation);
# constraint를 넣는 이유가 뭐지? 없어도 똑같지 않나?
alter table culture add constraint foreign key(culture_code) references distance_charge(culture_code);


-- 특별 수하물 운임표
CREATE TABLE `special_charge_table`(
    id int unsigned not null primary key auto_increment,
    special_baggage Boolean not null comment &amp;#39;true = 1 , false = 0&amp;#39;,
    special_rate int unsigned not null
);
-- 데이터 추가
INSERT INTO `special_charge_table`
SET special_baggage = true,
    `special_rate` = 1000
;

INSERT INTO `special_charge_table`
SET special_baggage = false,
    `special_rate` = 0
;
drop table special_charge_table;
-- 외래키 추가
alter table category_tb add constraint foreign key (special_baggage) references special_charge_table(special_baggage);

-- 칠레로 가는 전자제품의 운임(특별운임 포함)
select
    (
        select st.category
        from category_tb st
        where st.category = &amp;#39;전자제품&amp;#39;
    ) as &amp;#39;품류&amp;#39;
     , c.nation
     , ((
            select dc.rate
            from distance_charge dc
                     left join culture c
                               on dc.culture_code = c.culture_code
            where c.nation = &amp;#39;칠레&amp;#39;
              and
                c.culture_code = dc.culture_code
        )
    +
        (
            select sct.special_rate
            from special_charge_table sct
                     left join category_tb s
                               on sct.special_baggage = s.special_baggage
            where s.category = &amp;#39;전자제품&amp;#39;
        ))
      as &amp;#39;운임&amp;#39;
from
    culture c
where
    c.nation = &amp;#39;칠레&amp;#39;
;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;후기&lt;/h3&gt;
&lt;p&gt;중간에 놓쳐서 뒤에가 조금 더 이해가 안갔던 것 같습니다. 혼자 차근차근 하니 막상 그렇게 많은 내용은 아니였던 것 같고 강사님이 뒤에 문제가 더 많았다고 들었던 것 같은데 강의 때 조금더 잘 따라갔다면 더 많이 풀 수 있지 않았을까 하는 아쉬움이 남습니다.&lt;/p&gt;</description>
      <category>DataBase</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/4</guid>
      <comments>https://myh7754.tistory.com/4#entry4comment</comments>
      <pubDate>Thu, 14 Nov 2024 02:53:03 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드7기] (Database실습1, ERD)</title>
      <link>https://myh7754.tistory.com/3</link>
      <description>&lt;h1&gt;ERD (엔티티 관계 다이어그램, Entity Relationship Diagram)&lt;/h1&gt;
&lt;p&gt;데이터 베이스 설계 과정에서 사용되는 모델링 기법 중 하나이다.&lt;br&gt;데이터 베이스에 저장될 데이터 엔티티(Entity)들과 엔티티 간의 관계를 시각적으로 표현하는 기법.&lt;br&gt;Entity는 정의 가능한 사물 또는 개념을 의미하고 아래와 같이 표현된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chm7jJ/btsKHtWvW3t/tk7AGdWB9L0IFrmjK1kLo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chm7jJ/btsKHtWvW3t/tk7AGdWB9L0IFrmjK1kLo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chm7jJ/btsKHtWvW3t/tk7AGdWB9L0IFrmjK1kLo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchm7jJ%2FbtsKHtWvW3t%2Ftk7AGdWB9L0IFrmjK1kLo1%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;405&quot; height=&quot;187&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;테이블 이름 , pk정보,컬럼이름 ,자료형 ,제약조건&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YX89i/btsKFjBx8yU/sSor6CXwKhpqKVv4SyL5RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YX89i/btsKFjBx8yU/sSor6CXwKhpqKVv4SyL5RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YX89i/btsKFjBx8yU/sSor6CXwKhpqKVv4SyL5RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYX89i%2FbtsKFjBx8yU%2FsSor6CXwKhpqKVv4SyL5RK%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;302&quot; height=&quot;345&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;|(필수, mandatory ) or O(Optional, 선택).&lt;/p&gt;
&lt;h2&gt;관계&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1:1 관계&lt;/strong&gt; : 하나의 인스턴스 엔티티가 다른 하나의 인스턴스와 관계를 맺는 것/&lt;br&gt;ex) 한 사람은 하나의 여권을 가질 수 있고, 한 여권은 한 사람에게만 발급된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1:n 관계&lt;/strong&gt; : 하나의 엔티티가 여러개의 인스턴스와 관계를 맺는 것&lt;br&gt;ex) 하나의 사용자는 여러개의 전화번호를 가질 수 있지만 여러명의 유저가 하나의 전화번호를 가질 수는 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;n:m 관계&lt;/strong&gt; : 양쪽 엔티티가 서로 여러 관계를 맺고, 두 엔티티 모두 여러 인스턴스가 서로 연결되는 구조&lt;br&gt;ex) 한명의 학생은 여러 수업을 수강할 수 있고, 수업은 여러 학생 등록할 수 있다.&lt;/p&gt;
&lt;h1&gt;sql 실습&lt;/h1&gt;
&lt;h2&gt;DDL&lt;/h2&gt;
&lt;h3&gt;CREATE&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;열 : 열의 이름
데이터 타입 : 정수, 문자열, 날짜
제약조건 : NOT NULL, DEFAULT, PRIMARY KEY 등의 옵션
COMMENT : 별명&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;CREATE TABLE [데이터베이스이름].[테이블이름] (
    [열] [데이터타입] [제약조건] COMMENT [별명],
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;실제 예시&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE test_db.students (
    name     VARCHAR(255) NOT NULL, // NOT NULL은 해당 열에 NULL값을 허용하지 않는 의미
    age INT NOT NULL,
    address VARCHAR(255) NOT NULL
)&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;UNIQUE 제약조건 : 중복 없이 유일하게 들어가야하는 정보 ex) id, 이메일&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE 테이블명 (
        컬럼명 데이터타입 UNIQUE 
        ...
)&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;DEFAULT 제약조건 : 컬럼에 값을 입력하지 않을 경우 기본값이 적용된다.&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE 테이블명 (
        컬럼명 데이터타입 DEFAULT 기본값
        ...
)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;실제예시&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE profile (
    id VARCHAR(255) NOT NULL,
    create_date DATE DEFAULT now()
)

INSERT INTO profile (id) VALUES (45)&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;PRIMARY KEY (기본키) : 데이터를 식별하기 위한 기준. 기본키는 NULL 값을 가질 수 없으며 중복된 값을 가져서도 안된다 즉 NOT NULL + UNIQUE -&amp;gt; ID같은 속성&lt;/h4&gt;
&lt;h4&gt;auto_increment : 자동증가 옵션과 함께 많이 사용&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE 테이블명 (
        컬럼명 데이터타입 PRIMARY KEY auto_increment.
        ...
) &lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;FOREIGN KEY (외래키) : 테이블간 관계를 나타낼 때 사용. 다른 테이블의 기본키를 참조해 외래키로 사용한다. 즉 외래키는 다른 테이블의 기본키 중 하나&lt;/h4&gt;
&lt;h4&gt;RESTRICT : 대부분의 DBMS 시스템은 DEFAULT 설정으로 RESTRICT가 적용된다. 데이터 무결성을 유지하기 위한 가장 안전한 옵션인데, 외래키 제약 조건이 설정된 경우 참조된 행이 삭제되는 것을 방지한다.&lt;/h4&gt;
&lt;p&gt;이 제약조건을 사용하게 될때 자식의 모든 레코드를 삭제해야 부모 테이블을 삭제할 수 있다.&lt;/p&gt;
&lt;h4&gt;CASECADE : 부모테이블에 대한 DELETE를 허용. 참조된 테이블의 레코드가 삭제될 때 해당 레코드를 참조하는 다른 테이블의 레코드도 함께 삭제도니다. 즉 부모의 행이 삭제되면 관련된 자식 테이블 행도 자동삭제된다.&lt;/h4&gt;
&lt;p&gt;SET NULL : 부모 테이블의 정보가 삭제되었을 때 참고하던 자식 테이블의 FK 컬럼값이 NULL로 변경&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE customer (
        id INT PRIMARY KEY, 
        name VARCHAR(10) NOT NULL, 
        address VARCHAR(200), 
        contact VARCHAR(100)
);
CREATE TABLE orders (
        id INT PRIMARY KEY,
        customer_id INT, 
        date TIMESTAMP DEFAULT now(),
        payment VARCHAR(50),
        amount INT,
        delivery_amount INT,
        -- 참조한 외래키
        FOREIGN KEY (customer_id) REFERENCES customer(id) [ON DELETE CASCADE|ON DELETE SET NULL] 
)&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;ALTER : 테이블의 구조 변경을 위해 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE students ADD grade VARCHAR(20) // varchar속성의 grade 추가
ALTER TABLE students RENAME COLUMN grade TO grea // students의 grade이름을 great로 수정
ALTER TABLE students MODIFY COLUMN address VARCHAR(100) // 타입을 varchar로 수정
ALTER TABLE students DROP COLUMN grade // grade 열삭제&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;DML&lt;/h1&gt;
&lt;h1&gt;DCL&lt;/h1&gt;</description>
      <category>DataBase</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/3</guid>
      <comments>https://myh7754.tistory.com/3#entry3comment</comments>
      <pubDate>Wed, 13 Nov 2024 01:39:05 +0900</pubDate>
    </item>
    <item>
      <title>[오르미 백엔드 7기] (DataBase)</title>
      <link>https://myh7754.tistory.com/2</link>
      <description>&lt;h1&gt;데이터 베이스란?&lt;/h1&gt;
&lt;p&gt;데이터 베이스는 방대한 데이터를 체계적으로 저장, 관리, 검색할 수 있도록 도와주는 기술.&lt;br&gt;단순한 저장소를 넘어 데이터의 무결성을 유지하고, 효율적인 검색과 처리를 가능하게 하고, 복잡한 데이터 구조를 논리적으로 표현할 수 있는 도구.&lt;/p&gt;
&lt;h1&gt;데이터 베이스의 종류&lt;/h1&gt;
&lt;h2&gt;관계형 데이터베이스 (Relational Database)&lt;/h2&gt;
&lt;p&gt;MariaDB , MySQL , PostSQL, OracleDB , Microsoft SQL Server&lt;br&gt;데이터와 데이터간 관계를 정의하는 구조를 가진다.&lt;br&gt;행(Row)와 열(Column)로 이루어져 있다. SQL을 사용해서 다룰 수 있다.&lt;/p&gt;
&lt;h2&gt;NoSQL 데이터 베이스&lt;/h2&gt;
&lt;p&gt;MongoDB(문서형), Redis(키-값)&lt;br&gt;비관계형 db로 sql을 사용하지 않고 당양한 데이터 모델을 지원하여 관리한다.&lt;br&gt;주로 비정형 데이터를 효율적으로 처리 가능하고 고성능, 높은 확장성을 제공하는 것이 특징이다.&lt;br&gt;문서, 키-값, 열, 그래프 등 다양한 형태로 데이터를 저장한다.&lt;/p&gt;
&lt;h2&gt;객체지향 데이터 베이스(Object-Oriented Database)&lt;/h2&gt;
&lt;p&gt;객체지향 프로그래밍의 개념을 db에 적용한 시스템.&lt;br&gt;객체와 객체 간의 관계를 저장하고, 관리한다.&lt;br&gt;복잡한 구조를 관리하는데 유리하고, 상속, 다형성 등 개념을 db에서도 사용가능하다,&lt;/p&gt;
&lt;h2&gt;그래프 데이터베이스(Graph Database)&lt;/h2&gt;
&lt;p&gt;노드(Node), 간선(Edge), 속성(Property)으로 구성된 그래프 구조를 기반으로 데이터를 저장 관리한다.&lt;br&gt;복잡한 관계와 연결성을 가진 데이터를 처리하는데 적합하며, 소셜네트워크, 추천시스템, 경로 탐색 등에서 유용하게 사용된다.&lt;/p&gt;
&lt;h2&gt;분산 데이터베이스(Distributed Database)&lt;/h2&gt;
&lt;p&gt;여러 네트워크에 분산된 db를 하나의 통합된 db처럼 관리하는 db 시스템이다.&lt;br&gt;고가용성, 확장성, 성능 향상을 목표로 한다. 클리우드 환경에서 자주 사용되고 대규모 데이터 처리와 실시간 데이터 접근에 적합하다.&lt;/p&gt;
&lt;h2&gt;데이터베이스 관리 시스템 (DBMS)&lt;/h2&gt;
&lt;p&gt;db를 생성하고 관리하며 조작할 수 있게 해주는 소프트웨어&lt;br&gt;db에 대해 데이터의 저장,검색,수정,삭제 등의 작업을 지원하고 데이터를 효율적으로 사용할 수 있도록 지원한다.&lt;br&gt;DB는 데이터 저장소, DBMS는 그 데이터를 관리하고 조작하는 도구&lt;br&gt;주요 기능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;동시성 제어 : 여러 사용자가 동시에 데이터베이스에 접근하여도 데이터의 일관성을 유지&lt;/li&gt;
&lt;li&gt;무결성 유지 : 데이터의 정확성과 신뢰성을 보장. ex) 잘못된 데이터가 저장되는 것을 방지&lt;/li&gt;
&lt;li&gt;보안관리 : 권한을 통해 특정 사용자만 db에 접근 가능하도록 설정&lt;/li&gt;
&lt;li&gt;백업과 복구 : 데이터 손실 시 복구할 수 있는 기능을 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;관계형 데이터베이스 시스템 (RDBMS)&lt;/h1&gt;
&lt;h2&gt;스키마&lt;/h2&gt;
&lt;p&gt;데이터 베이스에서 데이터 구조와 제약 조건을 정의한 것. 테이블, 뷰, 인덱스, 권한 등 데이터 베이스의 객체들이 어떻게 구성될지를 명세&lt;/p&gt;
&lt;h2&gt;테이블&lt;/h2&gt;
&lt;p&gt;행과 열로 구성. 데이터를 모아놓은 표라고 생각하면 됨.&lt;br&gt;행 : 튜플, 레코드. 차수: 속성의 개수&lt;br&gt;열 :&lt;/p&gt;
&lt;h2&gt;키&lt;/h2&gt;
&lt;h3&gt;기본키(Primary Key)&lt;/h3&gt;
&lt;p&gt;주키라고도 불리며 테이블에서 각 행을 고유하게 식별할 수 있는 열이다.&lt;br&gt;&lt;strong&gt;기본키&lt;/strong&gt;는 &lt;strong&gt;중복된 값&lt;/strong&gt;이나 &lt;strong&gt;Null 값&lt;/strong&gt;을 허용하지 않으며, 각 행을 유일하게 식별해야한다.&lt;/p&gt;
&lt;h3&gt;왜래키(Foreign Key)&lt;/h3&gt;
&lt;p&gt;한 테이블의 열이 &lt;strong&gt;다른 테이블&lt;/strong&gt;의 &lt;strong&gt;기본 키&lt;/strong&gt;를 &lt;strong&gt;참조&lt;/strong&gt;하는 제약 조건.&lt;br&gt;테이블 간 관계를 정의하고 참조 무결성을 보장한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;참조 무결성 : 왜래 키로 참조된 데이터가 항상 유요하게 유지되도록 하는 것&lt;br&gt;ex) 두 테이블 A와 B가 있을 때, B 테이블의 어떤 열이 A 테이블의 기본 키(Primary Key)를 참조한다면, B 테이블에 들어가는 외래 키 값은 반드시 A 테이블에 존재하는 값이어야 한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;유니크 키(Unique Key)&lt;/h3&gt;
&lt;p&gt;열의 값이 테이블 내에서 중복되지 않도록 보장하는 제약조건&lt;br&gt;기본키와 비슷하지만 &lt;strong&gt;Null값&lt;/strong&gt;을 &lt;strong&gt;허용&lt;/strong&gt;하는 차이점 존재.&lt;/p&gt;
&lt;h3&gt;복합 키(Composite Key)&lt;/h3&gt;
&lt;p&gt;두개 이상의 열을 조합하여 테이블의 각 행을 고유하게 식별하는 키&lt;/p&gt;
&lt;h1&gt;행(Row)&lt;/h1&gt;
&lt;p&gt;데이터 베이스의 기본 구성 단위로 저장된 실제 데이터를 나타냄 튜플(Tuple)라고 불린다.. ex) 실제 사용자의 정보들&lt;/p&gt;
&lt;h1&gt;열(Column)&lt;/h1&gt;
&lt;p&gt;데이터의 특정 속성을 나타냄 ex) 카테고리 분류 : 이름, 번호, id, 주민등록번호 ...&lt;br&gt;데이터 타입 정수(INT), 문자열(CHAR), 날짜(DATE)등이 존재&lt;br&gt;열에는 제약조건(Constraint) 및 인덱스(index)를 설정할 수 있다.&lt;/p&gt;
&lt;h1&gt;제약조건&lt;/h1&gt;
&lt;p&gt;데이터 베이스에서 데이터의 무결성과 일관성을 보장하기 위해 설정하는 규칙&lt;br&gt;제약조건은 데이터가 테이블에 삽입, 수정, 삭제될 때 데이터의 정확성과 일관성을 유지하도록 돕는다.&lt;br&gt;-&amp;gt; 이상현상이 발생하지 않도록 한다.&lt;/p&gt;
&lt;h2&gt;기본 키 제약조건&lt;/h2&gt;
&lt;p&gt;기본 키는 중복된 값이나 NULL 값을 허용하지 않는다.&lt;/p&gt;
&lt;h2&gt;외래 키 제약조건&lt;/h2&gt;
&lt;p&gt;한 테이블의 열이 다른 테이블의 기본 키를 참조하도록 설정한다.&lt;/p&gt;
&lt;h2&gt;유니크 제약조건&lt;/h2&gt;
&lt;p&gt;열의 값이 테이블 내에서 중복되지 않도록 보장. 기본키는 자동으로 유니크 제약조건을 가진다.&lt;br&gt;데이터의 중복을 방지하고, 특정 열의 값이 유일하도록 유지&lt;/p&gt;
&lt;h2&gt;Null 제약조건&lt;/h2&gt;
&lt;p&gt;열이 NULL 값을 가질 수 없도록 설정합니다. 이 제약조건은 데이터가 반드시 존재해야 하는 열에 적용&lt;/p&gt;
&lt;h2&gt;기본값 제약조건&lt;/h2&gt;
&lt;p&gt;명시적으로 값이 제공되지 않을 때 사용할 기본값을 설정&lt;/p&gt;
&lt;h2&gt;검사 제약조건(Check Constraint)&lt;/h2&gt;
&lt;p&gt;열의 값이 특정 조건을 만족해야 한다고 설정&lt;br&gt;이 제약조건은 데이터가 특정 규칙을 따르도록 강제&lt;/p&gt;
&lt;h1&gt;인덱스(INDEX)&lt;/h1&gt;
&lt;p&gt;데이터 검색의 성능을 향상시키기 위해 사용되는 구조. 특정 열에 대해 생성되어, 해당 열의 값고 그에 대응하는 데이터의 위치를 효율적으로 매핑한다. 이를 통해 DB는 쿼리 실행시 테이블의 전체 스캔 없이 인덱스를 통한 데이터 접근이 가능하다.&lt;br&gt;하지만 인덱스를 생성하고 유지하는데 추가적인 저장 공간이 필요하고 데이터 삽입, 수정, 삭제 시 인덱스도 함께 갱신해야 하므로 성능 오버헤드가 발생할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;오버헤드 : 인덱스를 생성하고 유지하는 과정에서 발생하는 추가적인 처리 비용을 의미.&lt;br&gt;구체적으로 오버헤드는 성능에 부담을 주는 추가 작업이나 추가적인 자원 소모를 가리킨다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h1&gt;SQL&lt;/h1&gt;
&lt;p&gt;RDBMS에서 데이터정의 조작 제어, 검색을 수행하는데 사용되는 표준화된 프로그래밍 언어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DESC [테이블 이름]; // 테이블의 구조(스키마) 확인&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;데이터 정의 언어 (DDL)&lt;/h2&gt;
&lt;p&gt;주요 목적은 DB의 객체를 생성, 수정, 삭제하는 것&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE 테이블이름(필드이름 TYPE 제약조건, ... KEY (필드이름) REPERENCES target_tb(필드이름) ) ;
ex )
CREATE TABLE Users (
    user_id INT NOT NULL,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(100) NOT NULL,
    email VARCHAR(100),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id),
    FOREIGN KEY (email) REFERENCES EmailList(email) -- 예시로 외래키 설정
);

테이블 생성시 부여 가능 옵션
AUTO_INCREMENT
NOT NULL
UNIQUE
DEFAULT
CHECK
PRIMARY KEY
FOREIGN KEY&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AUTO_INCREMENT:&lt;/strong&gt; 숫자형 컬럼(Column)에 고유한 값을 증가시켜 삽입할 때 사용. 주키(Primary Key)에 주로 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NOT NULL:&lt;/strong&gt; 해당 컬럼(Column)에 null값을 허용하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UNIQUE:&lt;/strong&gt; 컬럼(Column)에 저장된 값이 테이블 내에서 고유한 값이 되도록 보장. 이 옵션이 추가되면 중복된 로우(Row)을 허용하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DEFAULT:&lt;/strong&gt; 컬럼(Column)의 기본 값을 지정. 만일 INSERT 구문에서 값이 없이 로우(Row)이 추가되었다면 기본값을 채워 추가.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CHECK:&lt;/strong&gt; 추가되는 로우(Row)의 컬럼(Column)의 값이 CHECK의 조건에 해당하는지 확인.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PRIMARY KEY&lt;/strong&gt;: 테이블의 주키가 될 컬럼(Column)을 설정.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FOREIGN KEY:&lt;/strong&gt; 다른 테이블의 주키(Primary Key)를 참조하여 두 테이블 간의 관계(Relation)를 설정.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE 테이블이름;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;ALTER TABLE [테이블 이름] ADD COLUMN [컬럼 이름] [자료형] [옵션];
ALTER TABLE [테이블 이름] MODIFY COLUMN [컬럼 이름] [자료형] [옵션];
ALTER TABLE [테이블 이름] CHANGE COLUMN [이전 컬럼 이름] [변경할 컬럼 이름] [자료형] [옵션];
ALTER TABLE [테이블 이름] DROP COLUMN [컬럼 이름];
ALTER TABLE [테이블 이름] RENAME [변경할 테이블 이름];&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;ADD COLUMN : 테이블에 컬럼을 추가&lt;/li&gt;
&lt;li&gt;MODIFY COLUMN : 테이블의 컬럼 명세를 변경.&lt;/li&gt;
&lt;li&gt;CHANGE COLUMN : 테이블에 컬럼 명세를 이름과 함께 변경.&lt;/li&gt;
&lt;li&gt;DROP COLUMN : 테이블의 컬럼을 삭제.&lt;/li&gt;
&lt;li&gt;RENAME COLUMN : 테이블 이름을 변경.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;특정 테이블의 모든 데이터를 삭제하지만 테이블의 구조는 유지하는 명령어
TRUNCATE TABLE 테이블이름;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;데이터 조작 언어 (DML)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;SELECT 필드이름 [as 별칭], FROM 테이블이름 [CONDITIONS];
SELECT 필드이름1, 필드이름2, ...
FROM 테이블이름
WHERE 조건;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;INSERT INTO 테이블이름 (필드이름1, 필드이름2, ...)
VALUES (값1, 값2, ...);
INSERT INTO [테이블 이름] VALUES (값1, 값2, 값3, ...);
INSERT INTO [테이블 이름] (컬럼1, 컬럼2, ...) VALUES (값1, 값2, ...);
INSERT INTO [테이블 이름] SET [컬럼1] = [값1], [컬럼2] = [값2] ... ;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;첫번째 방식을 사용할 때에는 VALUES 에 기재하는 값들의 순서를 열의 순서와 동일하게 하여야 한다. 생략된 열이 있다면 DEFAULT나 NULL로서 값이 채워진다,&lt;/li&gt;
&lt;li&gt;두번째 방식은 열과 값이 일치하게 기재하여야 한다. 생략된 열이 있다면 DEFAULT나 NULL로서 값이 채워진다,&lt;/li&gt;
&lt;li&gt;세번째 방식은 특정 열에만 값을 추가할 때 유리. 생략된 열이 있다면 DEFAULT나 NULL로서 값이 채워진다,&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;UPDATE 테이블이름
SET 필드이름1 = 값1, 필드이름2 = 값2, ...
WHERE 조건;&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;DELETE FROM 테이블이름
WHERE 조건;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;데이터 제어 언어 (DCL)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;계정생성
CREATE USER &amp;#39;ID&amp;#39;@&amp;#39;HOST&amp;#39; IDENTIFIED BY&amp;#39;PWD&amp;#39;;
권한 부여
GRANT 권한 ON database.table TO &amp;#39;ID&amp;#39;@&amp;#39;HOST&amp;#39;
권한 삭제
REVOKE 권한 ON database.table To &amp;#39;ID&amp;#39;@&amp;#39;HOST&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;sql 실행 순서&lt;/h2&gt;
&lt;p&gt;5 SELECT [컬럼명]&lt;br&gt;1 FROM [테이블명]&lt;br&gt;2 WHERE [조건절]&lt;br&gt;3 GROUP BY [컬럼명] // 같은 그룹의 총 주문수&lt;br&gt;4 HAVING [그룹BY를 사용할 때의 조건절]&lt;br&gt;6 ORDER BY [컬럼명] [정렬방식]&lt;br&gt;7 LIMIT [오프셋]&lt;/p&gt;
&lt;p&gt;FROM (1): 데이터가 어디서부터 가져올지 지정. 즉, 쿼리에서 사용할 테이블을 선택하는 단계.&lt;br&gt;WHERE (2): 데이터를 필터링하는 단계로, 테이블에서 조건에 맞는 행만 추출.&lt;br&gt;GROUP BY (3): 데이터를 그룹화하는 단계. 같은 값을 가진 컬럼을 기준으로 그룹을 만들어서, 각 그룹에 대한 집계 연산을 수행할 수 있게 한다. 예를 들어 &amp;quot;같은 그룹의 총 주문 수&amp;quot;처럼 그룹별로 데이터를 계산할 때 사용.&lt;br&gt;HAVING (4): GROUP BY로 그룹화된 데이터에 대한 추가 필터링 조건을 설정. WHERE와 달리, 그룹화된 결과에 조건을 적용하는 것이 특징.&lt;br&gt;SELECT (5): 지정한 컬럼들을 조회하고, 이 단계에서만 데이터를 실제로 선택해 보여준다.&lt;br&gt;ORDER BY (6): 조회된 데이터를 정렬한다. 컬럼명과 정렬방식(오름차순 ASC나 내림차순 DESC)을 지정할 수 있다.&lt;br&gt;LIMIT (7): 조회된 결과의 행 수를 제한하고, 오프셋을 설정하여 몇 번째부터 데이터를 가져올지 지정.&lt;/p&gt;
&lt;h2&gt;테이블 조인&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;INNER조인 (교집합)&lt;/strong&gt; : 두 테이블에서 공통된 값을 가진 데이터만 조회&lt;br&gt;예: SELECT * FROM A INNER JOIN B ON A.id = B.a_id;&lt;br&gt;결과: 두 테이블에서 일치하는 행만 반환&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;left join or right join (차집합)&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;왼쪽 테이블의 모든 행과 오른쪽 테이블에서 일치하는 행을 조회하며, 일치하지 않는 경우 NULL로 표시&lt;/li&gt;
&lt;li&gt;오른쪽 테이블의 모든 행과 왼쪽 테이블에서 일치하는 행을 조회하며, 일치하지 않는 경우 NULL로 표시&lt;br&gt;예 :&lt;br&gt;SELECT FROM A LEFT JOIN B ON A.id = B.a_id;&lt;br&gt;SELECT FROM A RIGHT JOIN B ON A.id = B.a_id;&lt;/li&gt;
&lt;li&gt;왼쪽 테이블의 모든 데이터가 반환되며, 오른쪽 테이블에 매칭되지 않는 값은 NULL로 채워진다.&lt;/li&gt;
&lt;li&gt;오른쪽 테이블의 모든 데이터가 반환되며, 왼쪽 테이블에 매칭되지 않는 값은 NULL로 채워진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;FULL JOIN(FULL OUTER JOIN) (합집합)&lt;/strong&gt; :&lt;br&gt;두 테이블의 모든 데이터를 포함하며, 일치하지 않는 데이터는 NULL로 표시.&lt;br&gt;예: SELECT * FROM A FULL OUTER JOIN B ON A.id = B.a_id;&lt;br&gt;두 테이블에 일치하는 데이터와 일치하지 않는 데이터를 모두 조회. 일치하지 않는 부분은 NULL이 채워진다.&lt;/p&gt;
&lt;h1&gt;트랜잭션 (ACID원칙)&lt;/h1&gt;
&lt;p&gt;트랜잭션은 DB의 하나의 논리적 작업 단위를 의미 여러 작업이 묶여 하나의 작업처럼 처리되는 것을 말함.&lt;/p&gt;
&lt;p&gt;원자성 (Atomicity) : 전체다 성공하면 반영 , 실패하면 전체 다 실패 하지만 save포인트를 만들고 그 시점으로 롤백은 가능&lt;br&gt;ex) 여러 작업 중 중간에 오류가 발생하면, 트랜잭션 전체가 롤백되어 아무런 변화도 없는 상태로 복원.&lt;br&gt;&lt;br&gt;&lt;br&gt;일관성 (Consistency) : 트랜잭션이 실행되기 전과 후에 데이터베이스가 일관된 상태를 유지해야 한다.데이터베이스에 미리 정의된 규칙(제약 조건 등)을 항상 만족하도록 보장&lt;br&gt;ex) 송금 트랜잭션의 경우 A 계좌에서 돈이 빠져나가면 B 계좌에는 동일한 금액이 더해져야 한다.&lt;br&gt;&lt;br&gt;&lt;br&gt;고립성 (Isolation) : 트랜잭션 간에 서로의 작업이 독립적으로 실행되어야 하며, 중간 결과가 다른 트랜잭션에 영향을 주어서는 안된다.&lt;br&gt;ex) 두 사용자가 동시에 같은 상품을 구매할 때, 고립성이 보장되면 재고가 음수가 되는 상황이 발생하지 않는다.&lt;br&gt;&lt;br&gt;&lt;br&gt;지속성 (Durability) : 결과를 커밋해서 그 결과가 확정이 되었다면 데이터베이스에 확실하게 반영해서 영구적으로 적용되어야 한다.&lt;br&gt;ex) 회원가입을 했다면 휘발성이 되면 안된다.&lt;/p&gt;
&lt;h2&gt;트랜잭션 예시&lt;/h2&gt;
&lt;p&gt;회원 가입을 위한 트랜잭션에서, 새로운 사용자 정보를 데이터베이스에 삽입한 후 회원에게 환영 메일을 보내는 두 가지 작업이 트랜잭션으로 묶여 있을 수 있다. 두 작업이 모두 성공해야만 트랜잭션이 커밋되며, 하나라도 실패하면 전체 작업이 롤백되어 데이터베이스에 아무 변화도 남기지 않는다.&lt;/p&gt;
&lt;h1&gt;비관적인 락(잠금) vs 낙관적인 락(잠금)&lt;/h1&gt;
&lt;h2&gt;비관적인 락&lt;/h2&gt;
&lt;p&gt;데이터에 대한 잠금을 통해 동시성 문제를 방지하는 접근 방식. 데이터의 수정 작업이 진행될 때.&lt;br&gt;다른 트랜잭션이 동일한 데이터에 접근하거나 수정하는 것을 방지하기 위해 잠금을 설정.&lt;br&gt;비관적 잠금은 트랜잭션이 데이터에 접근하거나 변경할 때 해당 데이터에 대한 잠금을 강제적으로 수행함으로써 데이터의 일관성을 유지하는 방식.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터에 대한 잠금을 트랜잭션이 수행하기 전부터 설정한다.&lt;/li&gt;
&lt;li&gt;트랜잭션이 완료될 때까지 잠금을 유지.&lt;br&gt;단점 : 잠금을 유지하는 동안 다른 트랜잭션이 대기하게 되어 성능 저하가 발생할 수 있음. 교착상태가 발생할 수 있음유지하는 동안 다른 트랜잭션이 대기하게 되어 성능 저하가 발생할 수 있음. 교착상태가 발생할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;낙관적인 락&lt;/h2&gt;
&lt;p&gt;데이터 충돌이 발생하지 않을 것을 가정하에 동작하는 방식.&lt;br&gt;잠금을 설정하지 않고, 트랜잭션이 데이터를 읽고 수정한 후 변경 사항을 데이터베이스에 반영할 때 데이터 충돌여부를 확인.&lt;br&gt;충돌이 발생하지 않은 경우 데이터베이스에 반영.&lt;br&gt;이 방식은 데이터 충돌이 발생할 수 있으며, 충돌 발생 시 트랜잭션을 재시도 해야할 수 있다.&lt;br&gt;트랜잭션의 수가 많거나 데이터 충돌이 빈번하게 발생하는 경우, 성능저하가 발생할 수 있다.&lt;/p&gt;</description>
      <category>DataBase</category>
      <category>dbms</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>myh7754</author>
      <guid isPermaLink="true">https://myh7754.tistory.com/2</guid>
      <comments>https://myh7754.tistory.com/2#entry2comment</comments>
      <pubDate>Mon, 11 Nov 2024 11:23:59 +0900</pubDate>
    </item>
  </channel>
</rss>