<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>천방지축 개발노트</title>
    <link>https://hoon93.tistory.com/</link>
    <description>this.blog = thinkingContext.getBean(정리.class);</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 07:14:27 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>천방지축 개발노트</managingEditor>
    <image>
      <title>천방지축 개발노트</title>
      <url>https://tistory1.daumcdn.net/tistory/3020476/attach/78c369d9731d4b1ca2c94c5539ff5c1e</url>
      <link>https://hoon93.tistory.com</link>
    </image>
    <item>
      <title>Merge 동작 방식(3-way와 Snapshot)</title>
      <link>https://hoon93.tistory.com/87</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Merge를 하고 Commit 히스토리를 훑어보던 중, 대상 브랜치에만 존재하는 이력들을 발견해서,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;히스토리가 다르면 Merge가 실패했어야 했던 것 아닌가?&quot;라는 의문이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Merge의 동작 방식&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Merge는 '두 브랜치의 작업을 합친다는 커밋(merge commit)'을 만들어 히스토리를 통합하는 기능&lt;/span&gt;이다. 그래서 &quot;아 이런 방식으로 두 갈래로 갈라진 커밋 이력을 하나의 흐름으로 정리하는구나&quot; 라고 이해했었다. 근데 대상 브랜치에만 이력들이 +a 로 존재하는데도 Merge가 정상적으로 되는 경우가 있어 무언가 놓치고 있는 것 같아 확인해봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일단 Merge를 '갈라진 브랜치들의 Commit들을 합치는 행위'라고만 하는 것은 단순히 결과를 설명하는 말이었다. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;하지만 정확히는 Git Merge가 실제로 하는 일은 &lt;span style=&quot;color: #ee2323;&quot;&gt;현재(대상) 브랜치에 다른 브랜치의 스냅샷(Snapshot)을 비교하여, (두 개발 라인을) 하나의 결과 스냅샷으로 통합하는 작업&lt;/span&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Git에서는 &lt;b&gt;'프로젝트 전체 상태'&lt;/b&gt;를 &lt;b&gt;'스냅샷(Snapshot)'&lt;/b&gt;이라 표현한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;커밋(Commit)&lt;/span&gt; 또한 변경된 내용에 대한 목록?이라기보다,&lt;span style=&quot;color: #ee2323;&quot;&gt; '그 시점의 Snapshot'&lt;/span&gt;를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 찾아보면 Merge는 '3-way'를 기준으로 통합/결과 스냅샷을 만든다고 정의하고 있다. 3-way라는 건 쉽게 말해 3개의 Snapshot을 의미하는데, 이들을 비교해 최종 결과 스냅샷을 만들어 낸다는 것이다. 각 요소가 무엇인지 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1396%; text-align: center;&quot;&gt;&lt;b&gt;3-way&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 58.2558%; text-align: center;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1396%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Base&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 58.2558%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 브랜치의 공통 조상(merge-base) 스냅샷&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1396%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ours&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 58.2558%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 브랜치(merge 받는 쪽)의 스냅샷&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.1396%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Theirs&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 58.2558%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;합치려는 대상 브랜치의 스냅샷&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Merge가 확인하는 것은 '커밋 히스토리의 동일 여부'가 아니라 base 대비 ours가 어떻게 바뀌었는지, base 대비 theirs가 어떻게 바뀌었는지의 관계다. 나는 왜인지 변경 이력들을 통합하는 것이라 오해하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 브랜치의 공통 조상(merge-base) 를 찾는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공통 조상(base) &amp;harr; 내 브랜치(ours) &amp;harr; 상대 브랜치(theirs) 이 3개의 스냅샷을 통합&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Merge Commit 생성 및 반영&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Merge의 본질은 스냅샷을 통합해 '최종 결과 스냅샷'을 만드는 것이다. 결론은 커밋 히스토리를 맞추는 게 아니라, &lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;각 커밋이 가리키는 스냅샷(파일 내용과 구조)을 비교해 결과를 만드는 것&lt;/span&gt;이다. 그래서 최종 결과 파일 내용이 동일해서 추가로 바뀔 게 없다면 Merge는 충돌 없이 정상 종료된다.&lt;/u&gt; 이것만 기억하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예시 (히스토리가 달라도 Merge가 되는 경우)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cq2XfQ/dJMb996ss0C/GH7k9Cctygtl5tjNVnWR9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cq2XfQ/dJMb996ss0C/GH7k9Cctygtl5tjNVnWR9k/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;864&quot; data-origin-height=&quot;465&quot; data-filename=&quot;commit history.png&quot; style=&quot;width: 46.411%; margin-right: 10px;&quot; data-widthpercent=&quot;46.96&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cq2XfQ/dJMb996ss0C/GH7k9Cctygtl5tjNVnWR9k/img.png&quot; alt=&quot;commit 히스토리&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcq2XfQ%2FdJMb996ss0C%2FGH7k9Cctygtl5tjNVnWR9k%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;864&quot; height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BRMRw/dJMcabDc46Y/U56rcTRBWd3RvjiuXeLJTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BRMRw/dJMcabDc46Y/U56rcTRBWd3RvjiuXeLJTk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;627&quot; data-filename=&quot;cherryPick과 Merge.png&quot; style=&quot;width: 52.4262%;&quot; data-widthpercent=&quot;53.04&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BRMRw/dJMcabDc46Y/U56rcTRBWd3RvjiuXeLJTk/img.png&quot; alt=&quot;cherry-pick 히스토리&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBRMRw%2FdJMcabDc46Y%2FU56rcTRBWd3RvjiuXeLJTk%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;1316&quot; height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 기능을 긴급 반영해야 해서 &lt;b&gt;cherry-pick&lt;/b&gt;을 했었다. 이때 stage 브랜치에 dev 브랜치보다 커밋 이력이 더 있었는데도, dev &amp;rarr; stage Merge가 정상적으로 되는 이유는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;stage 브랜치가 dev에서 갈라져 나온 적이 있다면 공통 조상(base)은 존재한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;stage 브랜치에 cherry-pick 커밋이 추가되면 히스토리가 dev와 달라지지만, 공통 조상은 여전히 존재하므로 Merge 자체는 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;stage에 cherry-pick으로 같은 수정이 이미 반영돼 있으면, 나중에 dev를 merge할 때 그 수정이 다시 들어오더라도 stage의 파일 내용은 &lt;b&gt;'이미 같은 상태'&lt;/b&gt; or &lt;b&gt;'이미 반영된 상태'&lt;/b&gt;다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우 실제로 바뀔 내용이 없어서 충돌 없이 Merge가 끝날 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Merge는 히스토리 개수나 모양을 비교하는 게 아니라, &lt;b&gt;공통 조상(base) 기준으로 스냅샷 변경 내용&lt;/b&gt;을 비교한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;당연한 말이지만, 반대로 공통 조상(base) 기준으로 같은 부분을 서로 다르게 수정했다면 충돌이 날 수 있다. 이건 Merge가 불가능한 게 아니라, 충돌을 어떻게 정리할지의 문제이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <category>Diff</category>
      <category>Git</category>
      <category>merge</category>
      <category>PATCH</category>
      <category>snapshot</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/87</guid>
      <comments>https://hoon93.tistory.com/87#entry87comment</comments>
      <pubDate>Fri, 20 Feb 2026 23:17:23 +0900</pubDate>
    </item>
    <item>
      <title>Kubernetes(k8s)에서 Ingress와 Gateway 이해하기</title>
      <link>https://hoon93.tistory.com/86</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a title=&quot;프록시와 로드밸런서 비교 정리글&quot; href=&quot;https://hoon93.tistory.com/85&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;L4/L7 그리고 프록시와 로드밸런서&lt;/a&gt;가 어떤 역할을 하는지 알아봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 이게 내가 개발하는 서비스에서 어떻게 이용되고 있었던 걸까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개념은 새로 생긴 게 아니라 &quot;역할이 분리/계층화&quot;된 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시, 로드밸런서로 관점에서 보는 Web Server와 WAS&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전통적인 웹 아키텍처에서 '웹서버(Web Server)'의 기능이라고 하면 보통 클라이언트 요청을 가장 앞에서 받아 '정적 자원'의 서빙과 동적 자원의 필요시 WAS로 요청을 넘기는 역할을 한다고 알고 있다. 근데 이 백엔드(WAS/앱)로 요청을 넘긴다는 건 사실 Reverse Proxy의 기능과 뒤에 있는 여러 백엔드 인스턴스로의 분산까지 수행함을 의미하고 있다. 즉, Nginx/Apache 같은 웹서버들은 정적 자원 제공만 하는 것은 아니고, 일반적으로 '정적 서빙/프록시/로드밸런싱'를 모두 하고 있었던 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정적 리소스 서빙: HTML/CSS/JS, 이미지 등 파일을 그대로 응답&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reverse Proxy(프록시): 클라이언트 요청을 받아 백엔드(WAS/앱) 로 전달&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로드밸런싱: 여러 백엔드 인스턴스 중 하나로 분산&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 웹서버와 함께 비교되어 나오는 개념인 WAS(Tomcat 등)도 사실 HTTP를 받고 정적 파일을 내려주는 이런 업무들을 단독으로 충분히 할 수 있다. 하지만 서비스 규모가 커지고 운영 요구가 복잡해지면서, 여러 업무를 모두 책임지는 방식은 결국 한계에 부딪혔다. 마치 객체지향 프로그래밍에서 모듈이 커짐에 따라 '단일 책임의 원칙'을 지키면서 확장하듯이, 성능 자체라기보다 &lt;span style=&quot;color: #ee2323;&quot;&gt;운영/보안/정책/확장 때문에 웹서버가 기존에 하던 일을 하나하나 구분해서 전용 담당자를 두게 만든 것이 필요해진 것이다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 그래서 정적 서빙은 CDN/오브젝트 스토리지가 처리하고 기타 역할들도 로드밸런서, 쿠버네티스(Kubernetes), Gateway 등의 구성요소로 분리되어 운영되는 형태가 흔해졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kubernetes ? Gateway ? 확장된 여러 개념들&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심은 &quot;기존에 누가 하던 일을, 왜, 누가 가져갔는가&quot;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일단 게이트웨이(Gateway)가 어디에 배치되고 무엇을 하는가를 이해하기 위해선 Kubernetes(k8s)에 대해 조금 이해할 필요가 있다. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;k8s는 여러 개의 서버(노드)를 하나의 클러스터로 묶어서, 그 위에 컨테이너 애플리케이션(Pod)을 자동으로 배포&amp;middot;확장(Scaling)&amp;middot;복구 해주는 운영/관리 기술 또는 소프트웨어&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;k8s의 구성하는 대표적인 용어/개념을 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 125px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;한 줄 정의&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;핵심 역할&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;기억 포인트&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Cluster&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;K8s가 관리하는 전체 운영 환경(노드들의 묶음).&lt;/span&gt; 풀네임은 Kubernetes Cluster.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;애플리케이션이 &quot;한 서버&quot;가 아니라 여러 노드/파드에 분산되어 돌아가는 운영 단위.&lt;br /&gt;&lt;br /&gt;클러스터가 다르면, 기본적으로 Node/Pod는 공유되지 않는다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;k8s가 관리하는 범위&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ingress&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 HTTP(S) 라우팅 규칙(리소스)이 적힌 노트&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Host/Path 기반으로 &quot;어느 Service로 보낼지&quot; 선언&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ingress = 규칙&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Ingress Controller&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 HTTP(S)를 어떤 Service로 보낼지 정하는 L7 관문&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 외부 요청을 받고 Ingress 규칙대로 Service로 프록시(라우팅). 실제 트래픽을 받는 건 Controller지만 실무에선 보통 통째로 &quot;Ingress&quot;라고 부름&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Controller가 실제로 받는다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Node&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Pod가 올라가는 실제 서버(VM/물리)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Pod는 항상 어떤 Node 위에서 실행됨.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Node = Pod의 집&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Pod&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;컨테이너가 실행되는 배포 최소 단위&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앱이 실제로 실행되는 곳. &lt;br /&gt;중요한 특징은 Pod IP는 바뀔 수 있음(휘발성)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Pod = 앱 실행된 형태&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 18.8371%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Service&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.3027%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Pod들을 묶는 클러스터 내부 가상 주소(가상 IP/DNS)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 28.8371%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부/내부에서 Pod에 직접 붙지 않고 Service로 접근. 트래픽을 Pod들로 분산&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.0232%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바뀌는 Pod들의 논리 주소를 Service를 통해 접근&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 정의하면 K8s는 &quot;클러스터&quot;라는 운영 환경 위에서 리소스를 관리하는 시스템이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 '클러스터에서 실제로 실행되는 인스턴스'를 '워크로드(Workload)'라고 말하는데, 그 워크로드는 결국 Pod 형태로 실행되기에 문맥에 따라 '실행 중인 Pod들' 혹은 '그 Pod들을 운영하는 단위' 정도로 이해하면 될 것 같다. 또 Node는 클러스터 내에서 실제로 일을(워크로드를) 수행하는 Node이기도 하므로 Worker Node(워커 노드)라고도 부른다. 실무에서 '워크로드가 돈다'고 하면 Pod들이 실행 중인 상태를 말한다고 이해하면 되겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Gateway 역할 그리고 어디에 위치하는가?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;k8s Ingress Controller와 API Gateway.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;975&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mefP5/dJMcahwvr67/dv8yCgdaJorxqVH9cw1OTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mefP5/dJMcahwvr67/dv8yCgdaJorxqVH9cw1OTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mefP5/dJMcahwvr67/dv8yCgdaJorxqVH9cw1OTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmefP5%2FdJMcahwvr67%2Fdv8yCgdaJorxqVH9cw1OTk%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; alt=&quot;k8s Ingress Controller와 API Gateway&quot; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;413&quot; data-filename=&quot;k8s Ingress Controller와 API Gateway.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;975&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Gateway는 Ingress Controller와 함께 비교된다. 위에서 Ingress Controller는 L7 Reverse Proxy 계열로써 Host/Path/Header 등을 보고 트래픽을 어느 Service로 보낼지 결정한다고 했다. 즉, Ingress(Controller)는 출발점이 &quot;클러스터 진입&quot;인 &quot;L7 라우팅&quot;을 수행하는 것이다. 이때 시스템과 여러 요구가 커지면서 클러스터 진입이라는 '라우팅 규칙'만큼 클라이언트 요청을 통제하거나 어떤 정책(인증/인가/로깅 등)을 일관되게 적용하고자 하는 니즈가 생긴 것이다. 그래서 Gateway라는 개념으로 역할이 분화됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 개념들과 마찬가지로 Gateway라는 새로운 네트워크 계층이 생겨난 것이 아니다. Gateway는 본질적으로 Ingress Controller와 마찬가지로 실제로 트래픽을 받아서 라우팅하는 L7 Reverse Proxy/Load Balancer이다. 다만 Gateway가 Proxy/Ingress와 다른 점은 라우팅 자체보다 &quot;정책&quot;을 중심 기능으로 운영한다는 점에서 다르다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그니까 Ingress도 정책을 다룰 수 있는데, 정책을 운영 가능한 방식으로 하기 위해 업무 담당자를 나눈 것일 뿐이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. Ingress만으로 충분한 구조&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Client &amp;rarr; External LB &amp;rarr; Ingress Controller &amp;rarr; Service &amp;rarr; Pod&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클러스터 진입 라우팅 중심, 인증/정책은 애플리케이션(또는 플러그인)으로 처리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. Gateway + Ingress를 함께 쓰는 구조&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Client &amp;rarr; WAF &amp;rarr; External LB &amp;rarr; API Gateway &amp;rarr; Ingress Controller &amp;rarr; Service &amp;rarr; Pod&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전사 정책/관리/관측 등은 Gateway, 클러스터 내부 라우팅은 Ingress&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;3. Ingress Controller가 Gateway 역할까지 흡수한 구조&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Client &amp;rarr; External LB &amp;rarr; Ingress Controller(플러그인/정책 포함) &amp;rarr; Service &amp;rarr; Pod&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정책까지 Ingress에 몰아넣어 단순하지만, 관리 범위는 클러스터 내부.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>API Gateway</category>
      <category>ingress</category>
      <category>Ingress Controller</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>load balancer</category>
      <category>POD</category>
      <category>reverse proxy</category>
      <category>Service</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/86</guid>
      <comments>https://hoon93.tistory.com/86#entry86comment</comments>
      <pubDate>Sat, 31 Jan 2026 08:33:19 +0900</pubDate>
    </item>
    <item>
      <title>프록시(Proxy)와 로드밸런서(Load Balancer),  L4/L7 한 번에 비교 정리</title>
      <link>https://hoon93.tistory.com/85</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업무하다 보면 인프라와 관련한 여러 가지 용어가 들린다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시, 로드밸런서, L4/L7, 게이트웨이 등등...&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무시하기 힘든 위 용어들이 더 어렵게 느껴지는 이유는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서로 다른 축(관점)의 용어가 한 문장에 섞여 쓰이기 때문이었다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;차례대로 정리해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 개념들의 분류&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일단 자주 들리는 각 개념들을 분류해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 항목이 무엇을 의미하고 어떤 역할을 하는지는 순서대로 풀어 보겠다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 38.3722%; text-align: center;&quot;&gt;&lt;b&gt;객체&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%; text-align: center;&quot;&gt;&lt;b&gt;관점&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.0232%; text-align: center;&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 38.3722%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;L4/L7&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;능력/계층&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.0232%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;판단 기준이 무엇인가?&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 38.3722%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;LB/Proxy/Gateway/Ingress&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 23.6047%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역할/기능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 38.0232%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무슨 역할을 하는가?&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Load Balancer, Proxy 에서의 L4 vs L7&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로드밸런서(Load Balancer, LB)나 프록시(Proxy)가 무엇인지를 얘기하기 전에 함께 자주 사용되는 용어를 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 L4나 L7 앞에 붙은 'L'은 'Layer'를 의미한다. 학교 다닐 때 봤었던 OSI 7계층에서 말하는 그 계층이 맞다. 쉽게 말해, L4/L7은 &quot;OSI에서의 4층/7층 관점에서의 동작&quot;에 해당하는 개념이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(TCP/IP 모델을 쓴다고 해도 &quot;L4/L7&quot; 같은 표현은 계속 OSI 용어로 살아있는 것 같다)&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.6745%; text-align: center;&quot;&gt;&lt;b&gt;객체&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%; text-align: center;&quot;&gt;&lt;b&gt;어떤 내용을 보고 판단하는가?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.6745%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;L4 기반(전송)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. IP + TCP/UDP + 포트(Port) 정도까지만 보고 분산/중계&lt;br /&gt;&lt;br /&gt;2. HTTP 내용(URI, 헤더 등)은 보통 해석하지 않음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.6745%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;L7 기반(응용)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 49.3023%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;L4 기능 + HTTP 요청 정보(Host/Path/Header/Cookie 등)까지 확인 및 조작 가능&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;예) /api는 서비스1, /auth는 서비스2 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;또는 api.example.com 이면 A로, web.example.com 이면 B로&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 헷갈렸던 점은 LB(Load Balancer)랑 L4가 혼용되어서 언급되다 보니까, 자연스럽게 L4 = LB 인걸로 넘어갔었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;L4는 LB에 딱 맞는 최소 기능이었기에 전통적으로 L4 장비를 쓰는 게 매우 일반적이었어서 그런 거라고는 하지만, 어쨌든 공부하는 입장에서 위 표처럼 굳이 따지고 보면 로드밸런서는 기능(부하 분산)이고, L4/L7은 판단 기준(어디까지 해석하느냐) 또는 방식에 가깝다. &quot;무엇을 보고 라우팅/제어하느냐&quot;가 핵심인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고로 L7은 HTTP(URI, 헤더, 쿠키 등)를 이해(해석)하기 때문에 정책/라우팅이 풍부하다는 장점이 있지만 더 강력한 만큼 비용/복잡도가 크다는 특징이 있다. 그래서 인증/인가 연계/캐시 등의 애플리케이션 레벨인 Ingress나 게이트웨이(Gateway)로써의 기능은 L7이 더 쓰이게 된다고 한다. 포인트는 위와 같은 이유로 LB 얘기하면 L4가 더 자주 나오긴 하지만, 로드밸런서든 프록시든 기능(역할)은 일의 범위가 달라질 뿐 'L4 방식'으로도, 'L7 방식'으로도 구현될 수 있다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시(Proxy) 와 로드밸런서(Load Balancer, LB)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;프록시와 로드밸런서.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1bUMS/dJMcadm9UT2/ZcbdTqxobO9uYzcewB2ks1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1bUMS/dJMcadm9UT2/ZcbdTqxobO9uYzcewB2ks1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1bUMS/dJMcadm9UT2/ZcbdTqxobO9uYzcewB2ks1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1bUMS%2FdJMcadm9UT2%2FZcbdTqxobO9uYzcewB2ks1%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; alt=&quot;Proxy 와 Load Balancer&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;333&quot; data-filename=&quot;프록시와 로드밸런서.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;프록시(Proxy)의 뜻&lt;/span&gt;을 영어사전에 찾아보면 &quot;대리인&quot;이다. 무엇을 대신할까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;네트워크 적으로 이 단어는 &lt;span style=&quot;background-color: #f3c000; color: #ee2323;&quot;&gt;'클라이언트와 서버 사이에서 요청/응답을 대신 받아서! 대신 통신!'한다는 의미이자 역할&lt;/span&gt;을 나타낸다. 즉, 요청이 처음부터 끝까지 클라이언트와 서버가 &quot;직접 붙어 통신&quot;을 완료하는 것 대신 중간에 &quot;Proxy를 거쳐 통신&quot;하게 만드는 구성이라는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 구성의 필요 이유이자 주된 목적 중 하나는 바로 프록시를 앞세워서 요청 출발지로부터 목적지의 IP 등을 숨기기/추상화하는 것이고, 여기에 라우팅&amp;middot;보안같은 정책을 얹기 좋기 때문이다. API 요청을 예시로 보면 Proxy가 중간에서 내부 서버로 포워딩해주기 때문에 클라이언트는 실제 백엔드 서버 자원의 실제 IP나 어떤 구성으로 되어있는지를 알 수 없게 되고 또 몰라도 되게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 다음으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;로드밸런서(Load Balancer)&lt;/span&gt;는 사전에 찾아보면.. 없지만 어쨌든 &lt;span style=&quot;background-color: #f3c000; color: #ee2323;&quot;&gt;들어오는 트래픽을 여러 엔드포인트(서버)로 분산해 처리량/가용성을 높이는 역할&lt;/span&gt;을 한다고 정의된다. 정리하면 둘 모두 &quot;앞에서 요청을 대신 받아서 뒤로 보낸다&quot;는 형태는 비슷하지만 의도가 다른 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시= 대리인(정책/통제 중심)이자 문지기/통제자&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로드밸런싱 = 분배기(성능/가용성 중심)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 현실적으로 한 제품이 두 기능을 다 하는 경우가 많아 그게 그거인 상황이라 더 헷갈리는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;표로 정리해봤다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 104px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;Proxy(프록시)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;Load Balancer(로드밸런서)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심 목적&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청을 '거쳐가게' 만들어서 정책을 걸기 좋다.&lt;br /&gt;클라이언트와 서버 사이에서 대신 통신하며 통제/보안/변환한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 서버로 트래픽을 분산해 처리량/가용성 확보.&lt;br /&gt;즉, 부하를 분산해서 서비스가 버티게 한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중심 질문&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청/응답에서 무엇을 숨기고(노출을 줄이고)/어떤 정책을 적용할까?&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어느 서버에 얼마나 나눠 보낼까&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대표 기능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;헤더/경로 변환, 인증 연계, 캐시, (L7에서) WAF&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;분산, 장애 서버 제외, 정책 기반 라우팅(IP/Port/경로/호스트 등)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;관계&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Proxy는 LB 기능(분산)을 포함할 수 있음&lt;/span&gt; (L4/L7 모두 가능)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;LB는 Proxy 형태로 동작할 수 있음&lt;/span&gt; (L4/L7 모두 가능)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이처럼 LB가 Proxy처럼 동작하거나 프록시가 LB 기능(분산)을 포함하는 경우가 일반적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 아키텍처를 이해하고자 할 때 &quot;Proxy냐 LB냐?&quot;보다 이 컴포넌트는 L4에서 분산하는지, L7에서 라우팅/정책까지 하는지를 봐보려고 하면 조금 더 도움이 될 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reverse Proxy와 Forward Proxy의 차이&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시의 기능을 '직접 통신' 대신 '프록시를 거쳐 통신'하게 만드는 구성이라고 했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 Proxy만큼 Reverse Proxy라는 표현도 함께 자주 들리곤 한다. 이유는 운영에서 Proxy라고 말할 때 이는 곧 리버스 프록시(Reverse Proxy)를 지칭하는 일이 많기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Reverse Proxy와 Forward Proxy의 구분 핵심은 &lt;b&gt;&quot;클라이언트 &amp;harr; 서버 사이 경로에서 누굴 대신하느냐(누굴 숨기느냐)&quot;&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 104px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;Reverse Proxy(리버스 프록시)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;Forward Proxy(포워드 프록시)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구분 방법&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(서비스) 측 네트워크 앞단에 붙어서&lt;/span&gt; 외부 클라이언트 요청이 내 서비스로 들어올 때 먼저 프록시가 받고 내부 서버로 전달합니다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;클라이언트(사용자) 측 네트워크에 붙어서&lt;/span&gt; 클라이언트가 외부 서버로 나갈 때 프록시가 대신 요청을 보냅니다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 12.2093%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예시&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 41.7444%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트 입장에서는 '뒤에 실제 서버들이 여러 대/여러 개'인 것을 몰라도 됨 (서버가 숨겨짐)&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;예) 사용자는 www.example.com만 알면 되고, 뒤에는 프록시 역할을 하는 Nginx/Ingress가 여러 서비스로 나눠준다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.0464%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자별 인증/감사 로그, 사내망에서 인터넷 접근 통제 등&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;예) 회사 PC로는 직접 google.com 시도하려고 하면 회사의 포워드 프록시 정책에 의해 접속 불가.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쉽게 말해서, Reverse Proxy는 밖(클라이언트)이 내 서비스로 들어올 때의 문지기/분배기이고, Forward Proxy는 '내(클라이언트)가 밖으로 나갈 때 대리인'으로 이해하면 쉽다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하면, LB/Proxy 같은 용어는 &quot;무슨 역할을 하느냐&quot; 이고 L4/L7은 &quot;무엇을 보고 판단하느냐&quot;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 글에서는 같은 관점으로 클라우드 환경에서 자주 쓰이는 Ingress/Gateway/WAF 등의 개념들을 이어서 정리해 보겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>L4</category>
      <category>L7</category>
      <category>load balancer</category>
      <category>network</category>
      <category>Proxy</category>
      <category>로드밸런서</category>
      <category>리버스프록시</category>
      <category>포워드프록시</category>
      <category>프록시</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/85</guid>
      <comments>https://hoon93.tistory.com/85#entry85comment</comments>
      <pubDate>Thu, 15 Jan 2026 01:48:34 +0900</pubDate>
    </item>
    <item>
      <title>동일 계정의 타 환경 중복 로그인 제한하기(Jwt + Redis 그리고 다중 탭 고려)</title>
      <link>https://hoon93.tistory.com/84</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동일한 계정이 여러 환경에서 중복 로그인이 되는 상황을 제어해야 하는 요구사항이 있어 적용해봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 로그인 성공/실패 처리가 아니라, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이미 로그인된 계정이 다시 로그인할 경우 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 상황을 '중복'으로 볼지(탭/브라우저/기기), 무엇을 기준으로 판단할지, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 어떤 방식으로 이전 로그인 상태를 정리할지까지 정책과 구현을 함께 결정해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동시 로그인 제어 기능을 붙이면서 마주한 JWT의 한계&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심은 단순 인증이 아니라 &quot;현재 이 계정이 로그인 중인가?&quot;를 서버가 판단할 수 있어야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JWT 기반 인증은 일반적으로 AccessToken으로 매 API요청을 인증하고, 토큰의 서명과 만료 시간을 검증하는 구조이다. 그니까 원래 토큰 인증이라고 하면 Stateless라고 해서 &quot;서버가 세션 상태를 안 들고도 인증을 할 수 있다&quot;는 것이 장점이자 전제인 인증 방법인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 동시 로그인을 금지해야 한다면, 특정 아이디에 대해 지금 허용되는 세션은 딱 하나뿐이어야 한다는 것이니까 Stateless한 JWT 만으로는 &quot;이 사용자가 이미 로그인 중인지&quot;를 알 수 없음이 분명했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;본질적으로 기존 로그인 상태를 비교해야 했기에, 계정의 현재 로그인 상태를 알고 있어야 한다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버가 jwt의 로그인 상태를 기억하게 만들기위한 첫번째 고민&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;redis.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buHzit/dJMcajnhYL9/kCBNtAJRb455Qc4hHXWEmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buHzit/dJMcajnhYL9/kCBNtAJRb455Qc4hHXWEmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buHzit/dJMcajnhYL9/kCBNtAJRb455Qc4hHXWEmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuHzit%2FdJMcajnhYL9%2FkCBNtAJRb455Qc4hHXWEmk%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; alt=&quot;Redis&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;267&quot; data-filename=&quot;redis.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Redis를 통해 상태(state) 를 기억하기로 했다. 근데 뭘 어떻게 저장해두면 좋을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음엔 jwt만들때 넣었던 jti값을 sessionId로 두고 Redis에 저장해야겠다고 생각했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ jti: Jwt의 표준 클레임. 토큰을 식별하는 고유ID&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;API요청 시, jwt내 jti값과 Redis에 저장한 jti를 비교하여 동일하면 &amp;rarr; 현재 유효한 사용자로 판단&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Redis와 값이 다르면 &quot;다른 곳에서 재로그인 됐네?&quot; &amp;rarr; 동시 로그인 차단&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 좀 생각해보니 이 토큰 단위 설계는 AccessToken이 갱신(재발급)되는 경우에 잠재적 문제가 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매번 새로운 토큰과 내부 값을 세션에 셋팅하기 때문에, 가장 마지막에 발급된 토큰만 유효하고 이전에 발급된 Access/Refresh Token 은 전부 죽는 구조라는 점에서였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단일 브라우저/탭 사용자에 한해서는 문제가 없을 수도 있겠지만, A탭과 B탭 등 &lt;span style=&quot;color: #ee2323;&quot;&gt;여러 탭으로 동시에 작업하는 사용자 입장에서 같은 브라우저에서 탭 두 개만 띄웠을 뿐인데, 갑자기 한쪽에서 로그인이 튕겨 나가거나 갱신요청으로 인한 탭 간 토큰 핑퐁 현상이 있을 수도 있었다. &lt;/span&gt;(AccessToken은 body로 응답하기에 변경 건에 대해서 탭간 공유가 안된다)&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;A 탭에서 로그인 &amp;rarr; Token1&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B 탭에서 같은 브라우저 세션으로 페이지 열림 (Token1 공유하거나, 다시 로그인해서 Token2 발급)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 사이에 어느 탭에서 토큰 재발급을 했는지에 따라, 다른 탭이 &amp;ldquo;이전 토큰을 들고 API 요청&amp;rdquo;을 날리면 401/SESSION_CONFLICT으로 강제 로그아웃&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 건 몰라도, 토큰을 갱신(refresh) 할 때마다 Redis에 값을 무조건 덮어쓰는 구조로 만들면 성능적으로 안 좋을 게 뻔했다. &lt;/span&gt;&lt;span style=&quot;color: #000000; background-color: #f6e199;&quot;&gt;로그인 상태 비교를 위해 읽으면 읽었지(read) 재로그인이 아닌 단순 갱신 상황에서도 redis에 매번 write 하는 구조를 피하고 싶었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재발급 상황 및 다중 탭 사용자를 위한 설계 변경&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 Jwt의 jti와는 별도로 계정의 로그인 상태 자체를 대표할 수 있는 식별자가 필요하다고 판단했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;jti는 &quot;토큰 고유ID&quot;라는 원래 의미 그대로 두고, 한 번의 로그인으로 형성된 상태를 대표하는 값으로 &quot;sessionId&quot; 를 새로 정의했다. 이를 로그인 시점에 한 번만 생성해서, 해당 로그인 세션이 유지되는 동안 발급되는 모든 토큰은 같은 &quot;sessionId&quot;를 공유하도록 설계했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 구조는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AccessToken/RefreshToken 둘 다에 같은 &quot;sessionId&quot;를 가진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;sessionId는 로그인 시점에 1회 생성된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰 재발급 시에도 sessionId는 변경되지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String createAccessToken(AdnpUserPrincipalInfo principalInfo, String sessionId) {
  Instant now = Instant.now();
  Instant accessTokenExpiration = now.plus(jwtProperties.getAccessTokenValidity());
  String jti = UUID.randomUUID().toString();

  return Jwts.builder()
    .subject(principalInfo.getUserUnqNo())
    .claim(SESSION_ID_KEY, sessionId)
    .claim(JTI_KEY, jti)
    ....
    .expiration(Date.from(accessTokenExpiration))
    .signWith(key)
    .compact();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;포인트는 sessionId는 로그인할 때에만 값이 생성된다는 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 하면 하나의 로그인 세션 안에서 AccessToken은 여러 번 재발급될 수 있지만, 그 모든 토큰은 같은 세션(sessionId)에 속하게 된다. 재발급 과정에서 구토큰/신토큰이 잠시 혼재하는 상황이 발생하더라도, 동일한 로그인 세션이기 때문에 문제로 취급하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 토큰 발급/응답 과정에서의 코드 일부이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 세션ID 생성
String sessionId = UUID.randomUUID().toString();

// 토큰 생성 (둘 다 동일 sessionId)
long refreshTtlSec = TimeUnit.MILLISECONDS.toSeconds(jwtProvider.getRefreshTokenTtlMillis());
String accessToken = jwtProvider.createAccessToken(userInfo, sessionId);
String refreshToken = jwtProvider.createRefreshToken(userInfo, sessionId);

// Redis 저장
redisService.saveSessionId(userInfo.getUserId(), sessionId, refreshTtlSec);
redisService.saveRefreshToken(userInfo.getUserId(), refreshToken, refreshTtlSec);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;①토큰 갱신(재발급)을 위한 인증수단인 RefreshToken&lt;/span&gt;과 &lt;span style=&quot;color: #ee2323;&quot;&gt;②현재 로그인 세션의 비교를 위한 SessionId를 Redis&lt;/span&gt;에 저장한다. 재발급 시에는 redis에서 userId에 매핑된 sessionId를 조회한 뒤, 해당 값을 새로 발급한 토큰의 claim으로 설정하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;API 요청시 Redis에 로그인 유효성 체크하기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void verifyCurrentSessionId(Authentication auth) {
  ..
  ....
  String currentSid = redisService.getSessionId(userUnqNo);
  currentSid = redisService.getSessionId(userUnqNo);
    if (!currentSid.equals(tokenSid)) {
      // 다른 환경에서 로그인되어 기존 세션 무효
      throw new SessionInvalidatedException(...);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 요청 처리 시에는, JWT에 담긴 sessionId와 Redis에 저장된 현재 sessionId를 비교해 로그인 상태의 유효성을 판단한다. 이 검증 로직은 인증 필터 단계에서 수행하도록 했다. 이로써, &quot;다른 환경에서 로그인했다&quot;는 판단을 서버가 일관되게 내릴 수 있게 됐다. 즉, 토큰이 유효하더라도 &lt;b&gt;현재 로그인 세션에 속하지 않으면&lt;/b&gt; 요청은 거부된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Web Security</category>
      <category>access</category>
      <category>JWT</category>
      <category>REDIS</category>
      <category>refresh</category>
      <category>session</category>
      <category>Token</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/84</guid>
      <comments>https://hoon93.tistory.com/84#entry84comment</comments>
      <pubDate>Fri, 2 Jan 2026 19:21:37 +0900</pubDate>
    </item>
    <item>
      <title>SecurityFilterChain에서 permitAll() 오해와 Authorization 헤더/JWT 검증</title>
      <link>https://hoon93.tistory.com/83</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내가 열심히 만든 코드도 1달만 지나도 기억이 나지 않음을 자책하면서 정리해놔야겠다는 생각이 드는 요즘.. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인증 토큰 없이도 실행(로그인 이전에서의 접근 허용)되어야 하는 일명 '공개 API'가&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 상황에서 실행되지 않는 문제가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Security 설정을 &lt;b&gt;두 개의 SecurityFilterChain&lt;/b&gt;으로 나눠 사용하다가,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제 해결을 위해 &lt;b&gt;단일 체인 구조&lt;/b&gt;로 변경했고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 과정에서 예상하지 못했던 또 다른 문제를 겪게 되어, 그 원인과 해결 과정을 정리해 보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SecurityFilterChain을 두 개로 나누어 사용했을 때의 문제점&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서비스는 인증 수단으로 JWT를 사용하고 있었고, &lt;b&gt;토큰 없이도 호출되어야 하는 &quot;공개 API&quot;&lt;/b&gt;와 &lt;b&gt;인증을 거쳐야만 실행 가능한 &quot;보호 API&quot;&lt;/b&gt;가 공존하는 구조였다. 그래서 처음에는 보안 설정 자체를 구조적으로 분리하는 방향을 선택했다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;공개 API&lt;/span&gt; &amp;rarr; publicSecurity() 체인으로 매칭 &amp;rarr; JWT 필터 없음 &amp;rarr; 항상 통과&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;나머지 API&lt;/span&gt; &amp;rarr; protectedSecurity() 체인으로 매칭 &amp;rarr; JWT 필터 있음 &amp;rarr; 인증/인가 수행&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의도 자체는 공개 API가 토큰 문제로 호출이 실패하는 상황을 원천 차단하고 싶었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(JwtProperties.class)
public class SecurityConfig {
  ...
  ......
  /** Public API 전용 SecurityFilterChain. */
  @Bean
  @Order(1)
  SecurityFilterChain publicSecurity(HttpSecurity http) throws Exception {
    
    String[] publicPaths = publicPathMatcher.getPublicPathPatterns().toArray(String[]::new);
    RequestMatcher publicMatcher = buildPublicRequestMatcher(publicPaths);
    
    http
      .securityMatcher(publicMatcher)
      .csrf(csrf -&amp;gt; csrf.disable())
      .formLogin(form -&amp;gt; form.disable())
      .httpBasic(basic -&amp;gt; basic.disable())
      .sessionManagement(s -&amp;gt; s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
      .authorizeHttpRequests(auth -&amp;gt; auth.anyRequest().permitAll());
    return http.build();
  }
...&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.5117%; text-align: center;&quot;&gt;&lt;b&gt;객체&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.814%; text-align: center;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.6744%; text-align: center;&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JwtAuthenticationFilter&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.814%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인증&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청 헤더의 JWT를 검증&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JwtAuthenticationEntryPoint&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.814%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인증 실패 처리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보호된 API에 인증되지 않은 요청이 들어왔을 때, 401 에러 응답처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AuthorizationManager&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.814%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인가&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 권한&amp;middot;시스템 구분값 등을 기준으로 요청 허용 여부를 판단&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AuthorizationAccessDeniedHandler&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.814%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인가 실패 처리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인증은 되었지만 권한이 부족한 경우에 권한 실패 처리(403)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 31.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PublicPathMatcher&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 15.814%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공개 API&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;YML 에서 정의한 공개 API 경로 패턴을 담은 컴포넌트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 인증/인가 역할을 하는 필터에서 보이는 주요 객체들을 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예시로는 아래 protectedSecurity 메서드를 보면 되겠다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/** Protected(Security) 전용 SecurityFilterChain.*/
@Bean
@Order(2)
SecurityFilterChain protectedSecurity(HttpSecurity http) throws Exception {

  http
    .csrf(csrf -&amp;gt; csrf.disable())
    .formLogin(form -&amp;gt; form.disable())
    .httpBasic(basic -&amp;gt; basic.disable())
    .sessionManagement(s -&amp;gt; s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
    .exceptionHandling(ex -&amp;gt; ex .authenticationEntryPoint(jwtAuthenticationEntryPoint)
    .accessDeniedHandler(authorizationAccessDeniedHandler))
    .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
    .authorizeHttpRequests(auth -&amp;gt; auth.anyRequest().access(authorizationManager));
    
  return http.build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 여러 API를 호출해 보며 테스트해 보던 중에 문제를 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배너처럼 로그인 전에도 누구나 볼 수 있어야 되는 것들은 공개 API들로써 호출돼야 하면서도, 로그인을 했다면 그 토큰 정보에 따라 권한 등의 조회 조건이 추가돼야 했다. 그니까 공개 API면서도 토큰이 있으면 활용해야 했는데, 이게 되지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공개 API가 대략 아래와 같은 형태로 작성되어 있다고 봐보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750750309437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(&quot;/main/contents&quot;)
public MainPortalContentsResponse getMainPortalContents(
    @UserPrincipal UserPrincipalInfo loginUser,
    @RequestBody List picks) {

    final String sysSeCd = (loginUser != null) ? loginUser.getSysSeCd() : null;

    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 코드에서 @UserPrincipal는 &lt;span style=&quot;color: #ee2323;&quot;&gt;@AuthenticationPrincipal&lt;/span&gt;을 감싼 애노테이션으로써 토큰 정보를 Controller에서 받을 수 있게 해둔 것으로, 'loginUser'는 API 요청 시의 AccessToken 정보이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중요한 점은 'loginUser'가 채워지려면, 요청이 인증 필터를 거쳐야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 2체인 분리 구조에서는 위 요청이 '인증 필터(JwtAuthenticationFilter)'가 없는 publicSecurity 체인으로 매칭되었기 때문에, 아무리 토큰을 실어 API 요청을 보내도 필터는 아예 인식하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 로그인 후 요청임에도 불구하고 컨트롤러에게는 항상 '익명 사용자'처럼 보이는 문제가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;공개 경로에서는 인증 정보를 사용하지 않는다&quot;라는 보안 설정과 &quot;로그인 후에는 토큰 기반 처리가 필요하다&quot;라는 요구가 충돌했던 것이었다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 정리하면 처음 설계한 2체인 방식은 &quot;공개 API는 절대 인증을 보지 않는다&quot;일 경우에만 적합했던 것으로 수정이 필요했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음엔 위 요구를 만족시키기 위한 방법으로 비로그인&amp;middot;로그인용 API를 분리하는 방법이 있을 것 같다는 생각을 했다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 인증 전 요청이 필요한 API가 많이 있지 않기도했고... 필요하면 비로그인용 API를 만드는 것으로, 용도에 따라 API를 나누는 명확한 정책이 있으면 장애 분석이 쉬울 수도 있는 장점이 있어서였다. 근데 좀 고민해 보니 같은 역할을 하는 API가 두 벌이 될 수 있다는 전제 자체가 별로여서 다른 방법으로 해결했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나누어져 있던 인증/인가 filterChain을 하나의 체인으로 합쳐서 문제 해결하기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 같은 엔드포인트 API를 쓰되, 토큰이 있으면 읽고 없으면 무시하게 만들고 싶었다. 그래서 단일 SecurityFilterChain으로 합치는 것으로 결정했다. 다음은 필터의 개선 방향이다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JWT 인증필터는 항상 태운다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰이 있으면 인증 컨텍스트를 채운다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰이 없으면 익명으로 진행한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 외는 커스텀 인가 로직 적용.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 하면 같은 공개 API라도 로그인 전 &amp;rarr; 익명 처리, 로그인 후 &amp;rarr; 인증 정보 기반 분기 처리가 자연스럽게 가능해졌다. 수정한 코드를 보면 대략 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750750309437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
@Order(1)
SecurityFilterChain security(HttpSecurity http) throws Exception {

  String[] publicPaths = publicPathMatcher.getPublicPathPatterns().toArray(String[]::new);

  http
    .csrf(csrf -&amp;gt; csrf.disable())
    .sessionManagement(s -&amp;gt; s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
    .exceptionHandling(ex -&amp;gt; ex
    .authenticationEntryPoint(jwtAuthenticationEntryPoint)
    .accessDeniedHandler(authorizationAccessDeniedHandler))
    .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
    .authorizeHttpRequests(auth -&amp;gt; auth
    .requestMatchers(publicPaths).permitAll()
    .anyRequest().access(authorizationManager));

  return http.build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1750750309437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
..
  @Override
  protected void doFilterInternal(HttpServletRequest request, ...) {
    String jwtToken = resolveToken(request);
    if (!StringUtils.hasText(jwtToken)) {
      // 토큰 없으면 그냥 익명으로 진행(공개 자원 가정)
      filterChain.doFilter(request, response);
      return;
    }
    try {
      Authentication auth = jwtProvider.getAuthentication(jwtToken);
      ..
      ..
    }
    ..
  }
..&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떠한 경로로 요청이 들어와도 위 체인이 선택되도록 했다. 그리고 체인에 포함된 인증 필터가 토큰이 있으면 loginUser를 채우고 토큰이 없으면 null로 리턴하여 익명 처리가 가능하도록 했다. 이로써 하나의 API가 인증으로부터 의도한 분기 처리가 가능해졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단일 체인으로 합쳐서 생겼던 부작용 발생&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;401 인증 실패.png&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gds7T/dJMcaaYcsCi/lKqWF8VyELcWjJk4oU7Sv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gds7T/dJMcaaYcsCi/lKqWF8VyELcWjJk4oU7Sv0/img.png&quot; data-alt=&quot;401 인증 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gds7T/dJMcaaYcsCi/lKqWF8VyELcWjJk4oU7Sv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGds7T%2FdJMcaaYcsCi%2FlKqWF8VyELcWjJk4oU7Sv0%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; alt=&quot;401 Unauthorized&quot; loading=&quot;lazy&quot; width=&quot;992&quot; height=&quot;294&quot; data-filename=&quot;401 인증 실패.png&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;401 인증 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 또 다른 문제가 발생했다. 단일 체인으로 합친 이후, 잘 되는 것 처럼 보였던 API가 갑자기 특정 상황에서는 제대로 동작하지 않았다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공개 API 요청임에도 불구하고 아예 호출이 안 되는 것을 확인했었는데.. 원인을 추적해 보니, 공개 API인데 Authorization 헤더를 &quot;잘못된 값&quot;으로 보내면 인증 필터가 검증을 시도하다가 401로 차단할 수 있다는 사실을 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리를 해보면, 문제 원인의 핵심은 공개 API임에도 불구하고 토큰을 헤더에 포함해 요청을 보낸 경우였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 중요한 개념이 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;'permitAll'&lt;/b&gt;&lt;/span&gt;에 대한 오해였다. `permitAll()`은 인가 단계에서 접근을 허용한다는 옵션이었는데, 인증을 건너뛰는 옵션으로 잘못 이해하고 있었다. 내가 놓치고 있었던 부분을 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인증 필터는 요청 경로와 관계없이 항상 실행되고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Authorization 헤더가 존재하는 순간 토큰 검증을 시도하며&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰이 없거나, 만료되었거나, 잘못된 값이면 &lt;b&gt;공개 API라도 필터 단계에서 요청이 차단&lt;/b&gt;될 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Authorization 헤더에 값이 없으면 인증 필터는 인증 시도 자체를 하지 않고 그대로 다음 단계로 넘기게 되지만,&lt;span style=&quot;color: #ee2323;&quot;&gt; 헤더가 존재하는 순간부터는 공개 API 여부와 무관하게 토큰 검증을 시도하게 되고,&lt;/span&gt; 이 과정에서 값이 없거나 토큰 만료 등 유효하지 않은 형태일 경우 공개 API라도 필터 단계에서 요청이 차단됐던 것이었다. 실제로 확인해 보니 업무단에서 API 호출 시 헤더에 빈값? 등을 담아 API 요청을 하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결국 백엔드에서 어떤 방식으로 손을 보더라도, 프론트에서는 인증이 필요한 요청과 아닌 요청을 구분해 API를 호출해야 했다. 그래서 프론트와 규칙을 다시 합의했다. 공개 API 요청에는 Authorization 헤더를 아예 포함하지 않고, 인증이 필요한 요청에서만 AccessToken을 전송하도록 통일했다(빈 값/임의 값 금지). 적용 이후 공개 API가 간헐적으로 401로 막히던 현상은 재현되지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Authorization 헤더 처리 기준을 더 명확히 이해하고 있었다면 삽질을 덜 했을 텐데ㅜㅠ&lt;/span&gt;&lt;/p&gt;</description>
      <category>Web Security</category>
      <category>@AuthenticationPrincipal</category>
      <category>JWT</category>
      <category>Security</category>
      <category>spring</category>
      <category>Token</category>
      <category>인가</category>
      <category>인증</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/83</guid>
      <comments>https://hoon93.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 29 Dec 2025 00:56:39 +0900</pubDate>
    </item>
    <item>
      <title>세션, 토큰, 쿠키, 캐시 비교 정리</title>
      <link>https://hoon93.tistory.com/82</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;세션(Session), 토큰(Token), 쿠키(Cookie), 캐시(Cache)에 대해 비교 정리해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;세션(Session)과 토큰(Token) 정리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 두 용어는 &lt;span style=&quot;color: #ee2323; background-color: #f3c000;&quot;&gt;인증정보가 저장되는 위치에 따라 구분&lt;/span&gt;되는 용어이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해, &lt;u&gt;인증 상태를 어디에 저장하느냐&lt;/u&gt;를 기준으로 '세션' 또는 '토큰' 기반의 인증 방식으로 나뉜다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. 세션(Session)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;인증 정보를 서버에 저장하는 방식&lt;/span&gt;이다. 로그인 등의 경우 서버는 세션이 없으면 자동으로 세션을 새로 생성하는데, 이와 함께 고유한 키 역할을 하는 Session ID도 생성한다. 그리고 Set-Cookie 헤더를 통해 JSESSIONID를 클라이언트에게 응답하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750739627324&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Set-Cookie: JSESSIONID=Session ID값&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SetCookie값 확인.png&quot; data-origin-width=&quot;4019&quot; data-origin-height=&quot;2186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csNqrx/btsOPKat5ZF/IBv2WJlT0j0n8aQJEs0ep1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csNqrx/btsOPKat5ZF/IBv2WJlT0j0n8aQJEs0ep1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csNqrx/btsOPKat5ZF/IBv2WJlT0j0n8aQJEs0ep1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsNqrx%2FbtsOPKat5ZF%2FIBv2WJlT0j0n8aQJEs0ep1%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; alt=&quot;Set-Cookie값 확인하기&quot; loading=&quot;lazy&quot; width=&quot;4019&quot; height=&quot;2186&quot; data-filename=&quot;SetCookie값 확인.png&quot; data-origin-width=&quot;4019&quot; data-origin-height=&quot;2186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ Set-Cookie : 브라우저에게 &quot;이 값을 쿠키에 저장해&quot;라고 명령하는 HTTP 응답 헤더.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 이후 클라이언트(브라우저)는 서버로부터 받은 Session ID를 쿠키에 저장하여 매 요청마다 전송하게 되고, 서버는 이 Session ID를 기반으로 서버 내 세션 저장소에서 인증 정보를 조회하는 구조인 것이다. 세션 저장소로는 설정에 따라 JVM 메모리나 별도의 DB 또는 Redis 등이 올 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 클라이언트는 단지 Session ID만 가지면서, 실질적인 사용자 정보/인증 상태는 서버 자원이 기억하므로 Stateful 구조라고 표현한다. 인증 상태는 서버 자원에 의존하기에 확장성이나 부하 분산에 불리할 수 있는 특징이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 토큰(Token)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;인증 정보를 담고 있는 토큰이라는 것을 클라이언트가 저장/유지하는 방식&lt;/span&gt;이다. 대표적으로 'JWT 토큰(JSON Web Token)'이 있다. 클라이언트(브라우저)는 식별 수단 그 자체인 토큰을 서버로부터 받은 후, 매 요청마다 Authorization 헤더 혹은 쿠키(Cookie)에 토큰을 함께 전송한다. 그리고 서버는 이 토큰 내에 담긴 사용자 정보, 만료 시간 등을 확인하는 식으로 매번 검증을 진행하는 구조인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;토큰은 서버가 클라이언트에게 부여하는 &quot;자격증&quot; 같은 것이고, 클라이언트는 서버 자원에 접근하기 위해 서버에게 이 자격증을 보여주는 것이라고 생각하면 쉽다. 서버는 토큰을 검증만 하고 별도로 인증 상태를 저장하지 않기에 Stateless 구조라고 표현한다. 토큰은 서버 자원을 적게 쓰기 때문에 수평 확장이 쉬운 반면, 토큰 탈취 시 토큰 유효시간 만료 이전까지는 서버가 강제로 무효화시키는데 어려움이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래는 로그인 후 토큰을 생성하고 헤더에 정보를 담는 코드 예시이다. 뭔가 복잡해 보이지만 대강의 흐름을 파악해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1750750309437&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class TokenProvider {
  ...
  ...
  public void createTokenAndAddHeader(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
    // 로그인 성공 후 토큰 처리
    String email = authResult.getName();
    String authorities = authResult.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(&quot;,&quot;));

    // userid 가져오기
    UserResponseDto userResponseDto = userService.findByEmail(email);
    String userId = userResponseDto.getUserId();

    // Access 토큰(JWT) 생성
    String accessToken = createAccessToken(authorities, userId);

    // Refresh 토큰(JWT) 생성 후 사용자 도메인에 저장하여 토큰 재생성 요청시 활용한다.
    String refreshToken = createRefreshToken();
    userService.updateRefreshToken(userId, refreshToken);
    
    // 응답 Header에 토큰 세팅
    response.addHeader(TOKEN_ACCESS_KEY, accessToken);
    response.addHeader(TOKEN_REFRESH_KEY, refreshToken);
    response.addHeader(TOKEN_USER_ID, userId);
  }
  
  ...
}&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쿠키(Cookie)란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 세션&amp;amp;토큰과 함께 나오는 용어라 헷갈리기 쉽지만, 쿠키란 정확히는 &lt;span style=&quot;color: #ee2323;&quot;&gt;문자열(String) 기반 key-value 형태의 데이터&lt;/span&gt;이다. 그리고 브라우저는 각 도메인(Domain) 별로 쿠키를 구분하여 저장하는 쿠키 저장소(cookie storage)를 내부적으로 관리하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;누군가 &quot;쿠키에 저장한다&quot;라고 표현한다면 이때 쿠키 저장소를 말하는 거구나라고 알아들으면 되겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 쿠키는 주로 인증 정보를 저장하고 전달하는 수단으로 많이 사용될 뿐, 사실 Session ID든, JWT Token이든, 사용자가 원하는 문자열 무엇이든 담을 수 있는 것이다. 다시 말해, 쿠키(Cookie)는 인증 방식이 아니라 인증 정보를 저장하고 전달하는 데 사용되는 데이터 전송 또는 저장 도구이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;개발자모드로 확인하는 쿠키 저장소.png&quot; data-origin-width=&quot;3290&quot; data-origin-height=&quot;1713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/514nS/btsOPvYVBxU/BR4oyeeZ25tj2v7KY8cHPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/514nS/btsOPvYVBxU/BR4oyeeZ25tj2v7KY8cHPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/514nS/btsOPvYVBxU/BR4oyeeZ25tj2v7KY8cHPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F514nS%2FbtsOPvYVBxU%2FBR4oyeeZ25tj2v7KY8cHPk%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; alt=&quot;개발자모드로 확인하는 쿠키 저장소&quot; loading=&quot;lazy&quot; width=&quot;3290&quot; height=&quot;1713&quot; data-filename=&quot;개발자모드로 확인하는 쿠키 저장소.png&quot; data-origin-width=&quot;3290&quot; data-origin-height=&quot;1713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;개발자모드(F12) &amp;gt; Application &amp;gt; Cookies 항목에서 확인 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쿠키 저장소에 쿠키가 저장되어 있다면, 매 서버 요청마다 브라우저는 해당 쿠키를 자동으로 서버에 함께 전송한다는 특징이 있다(서버 요청 시 브라우저는 쿠키 저장소에 있는 데이터를 꺼내서 Cookie Header에 넣고 서버로 전송).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;서버요청시 Cookie값 확인.png&quot; data-origin-width=&quot;3768&quot; data-origin-height=&quot;2179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lue6y/btsOQJbeyK6/KcwcKb4B6pw9LT15Wdbg0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lue6y/btsOQJbeyK6/KcwcKb4B6pw9LT15Wdbg0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lue6y/btsOQJbeyK6/KcwcKb4B6pw9LT15Wdbg0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flue6y%2FbtsOQJbeyK6%2FKcwcKb4B6pw9LT15Wdbg0K%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; alt=&quot;서버요청 시 담기는 쿠키값 확인&quot; loading=&quot;lazy&quot; width=&quot;3768&quot; height=&quot;2179&quot; data-filename=&quot;서버요청시 Cookie값 확인.png&quot; data-origin-width=&quot;3768&quot; data-origin-height=&quot;2179&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;개발자모드(F12) &amp;gt; Network 탭에서 HTTP 요청에 포함된 쿠키 확인 가능&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;캐시(Cache)란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;자주 사용할 데이터를 빠르게 제공하기 위해 임시로 저장해두는 공간&lt;/span&gt;으로써, 인증과는 별개의 개념이다. 인증이 아닌 &quot;성능 최적화 도구&quot;인 것이다. 캐시는 브라우저, 서버, CDN, 프록시 등 다양한 위치에 존재할 수 있다. 예를 들어, 사용자가 이미지 파일을 요청할 때, 서버가 매번 응답하지 않고 브라우저 캐시나 CDN 캐시를 통해 기존 응답을 제공해 속도를 높일 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web Security</category>
      <category>cache</category>
      <category>cookie</category>
      <category>jsessionid</category>
      <category>JWT</category>
      <category>session</category>
      <category>Token</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/82</guid>
      <comments>https://hoon93.tistory.com/82#entry82comment</comments>
      <pubDate>Wed, 25 Jun 2025 22:37:16 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] 카카오 OAuth 로그인 구현하기</title>
      <link>https://hoon93.tistory.com/81</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카카오 OAuth 간편 인증을 구현하는 과정을 차례대로 기록해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth2.0 기본 흐름&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth란 &quot;다른 서비스에 비밀번호를 알려주지 않고 내 정보를 제공하거나 인증을 맡기는 방법&quot;이다. 이번 예제인 카카오 로그인 외에도 일반적인 OAuth2.0의 기본 흐름은 아래와 같다(네이버, 구글 등).&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;사용자가 &quot;카카오 로그인&quot; 버튼 클릭 및 인증 진행&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;카카오가 사용자에게 &quot;동의 요청&quot; 확인(이 사이트에 생년월일 정보를 제공해도 돼?)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;사용자 &quot;동의&quot;클릭시, 카카오는 사이트에게 인가 코드(Authorization Code) 전달&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;사이트는 Authorization Code를 들고 카카오에 Access Token 요청&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;카카오는 사이트에게 Access Token 전달&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;사이트는 Access Token을 들고 카카오에 &quot;사용자 정보 요청&quot; API 호출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;카카오가 사용자 정보 (이메일, 이름 등)를 사이트에 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카카오 개발자 센터 가입 및 내 애플리케이션 등록&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카카오 인증과 관련된 설정들은 &lt;a title=&quot;Kakao develops 사이트&quot; href=&quot;https://developers.kakao.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;카카오 개발자 센터&lt;/a&gt;에서 관리할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1747025531487&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Kakao Developers&quot; data-og-description=&quot;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&quot; data-og-host=&quot;developers.kakao.com&quot; data-og-source-url=&quot;https://developers.kakao.com/&quot; data-og-url=&quot;https://developers.kakao.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tdMKr/hyYRoBblg7/K2IvEzJBaKWopgOJ4ErKwK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/btTPNf/hyYRm4qbMO/z5dcHrsxQdkWB4KyP6BLn0/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/bjLZGo/hyYRtoWFns/fKGqmVphtD1yJSyEzrXq8k/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.kakao.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tdMKr/hyYRoBblg7/K2IvEzJBaKWopgOJ4ErKwK/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/btTPNf/hyYRm4qbMO/z5dcHrsxQdkWB4KyP6BLn0/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/bjLZGo/hyYRtoWFns/fKGqmVphtD1yJSyEzrXq8k/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000');&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;Kakao Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;kakao 로그인 내 앱 등록.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bibTcx/btsNTBSmEJj/ze1ANt6HNzKwZIiNmywMCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bibTcx/btsNTBSmEJj/ze1ANt6HNzKwZIiNmywMCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bibTcx/btsNTBSmEJj/ze1ANt6HNzKwZIiNmywMCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbibTcx%2FbtsNTBSmEJj%2Fze1ANt6HNzKwZIiNmywMCK%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;760&quot; height=&quot;439&quot; data-filename=&quot;kakao 로그인 내 앱 등록.png&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동의 항목 설정&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;kakao 로그인 동의항목.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;2185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blXd8z/btsNUBX56qR/NqHVtQebteiEz1JKOJcWMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blXd8z/btsNUBX56qR/NqHVtQebteiEz1JKOJcWMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blXd8z/btsNUBX56qR/NqHVtQebteiEz1JKOJcWMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblXd8z%2FbtsNUBX56qR%2FNqHVtQebteiEz1JKOJcWMk%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; alt=&quot;개인정보 동의항목&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;377&quot; data-filename=&quot;kakao 로그인 동의항목.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;2185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 쇼핑몰 사이트를 가입하려고 했을 때, 여기서 선택한 항목들이 홈페이지 첫 로그인 시 확인창으로 나타나게 된다. 만약 위 사진처럼 닉네임과 프로필 사진만 허용되어 있다면, 개인정보 동의 시 쇼핑몰은 설정해놨던 닉네임과 프로필 사진 정보만 kakao로부터 받아 회원가입 처리를 진행한다는 것을 의미한다. 아래에 설명할 application.yml 설정 내 scope 옵션에는 동의 설정한 항목 이외에 다른 ID값을 적혀있으면 에러가 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kakao 로그인 호출 화면 만들기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클릭하면 &quot;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;/oauth2/authorization/kakao&lt;/span&gt;&quot; 를 호출하는 버튼을 간단히 만들었다. 이 URI은 Spring Security가 자동으로 만들어주는 로그인 트리거용 주소이다. 해당 주소를 호출하면 Spring Security가 자동으로 yml에 설정한 authorization-uri (https://kauth.kakao.com/oauth/authorize)로 redirect 시켜준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;div class=&quot;login-container&quot;&amp;gt;
    &amp;lt;a href=&quot;/oauth2/authorization/kakao&quot;&amp;gt;
      &amp;lt;img src=&quot;/kakao_login_medium_narrow.png&quot; alt=&quot;카카오 로그인&quot;&amp;gt;
    &amp;lt;/a&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;카카오 로그인 버튼 만들기.png&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNsw3/btsNVux97st/zZ1lqqtdcP15EYqAERX5Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNsw3/btsNVux97st/zZ1lqqtdcP15EYqAERX5Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNsw3/btsNVux97st/zZ1lqqtdcP15EYqAERX5Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNsw3%2FbtsNVux97st%2FzZ1lqqtdcP15EYqAERX5Sk%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; alt=&quot;카카오 로그인 버튼&quot; loading=&quot;lazy&quot; width=&quot;623&quot; height=&quot;373&quot; data-filename=&quot;카카오 로그인 버튼 만들기.png&quot; data-origin-width=&quot;623&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;버튼으로 사용한 이미지는 &lt;a title=&quot;개발자센터 디자인 가이드&quot; href=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/design-guide&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 제공받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot 프로젝트 설정&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kakao 간편 인증 처리를 위한 application.yml 설정은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  security:
    oauth2:
      client:
        registration:
          kakao:
            client-id: [카카오 개발자 센터에서 발급받은 앱 키 (REST API 키)]
            client-secret: [비워도 됨]
            redirect-uri: &quot;{baseUrl}/login/oauth2/code/kakao&quot;
            authorization-grant-type: authorization_code
            scope: profile_nickname
            client-name: Kakao
        provider:
          kakao:
            authorization-uri: https://kauth.kakao.com/oauth/authorize
            token-uri: https://kauth.kakao.com/oauth/token
            user-info-uri: https://kapi.kakao.com/v2/user/me
            user-name-attribute: id&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6048%; text-align: center;&quot;&gt;&lt;b&gt;옵션&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.3952%; text-align: center;&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6048%;&quot;&gt;redirect-uri&lt;/td&gt;
&lt;td style=&quot;width: 76.3952%;&quot;&gt;사용자가 인증을 완료하고 나서 카카오로부터 Authorization Code를 수신받을 주소이다.&lt;br /&gt;&lt;br /&gt;기본적으로 Spring Security가 OAuth 처리를 위해 인가 코드를 수신받는 URI 형식은 &quot;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;/login/oauth2/code/{registrationId}&lt;/span&gt;&quot;으로 약속돼있다! 여기서 registrationId는 &quot;kakao&quot;이기 때문에 위와 같이 설정. redirect-uri를 커스터마이징해서 다른 경로로 바꾸면 Spring Security는 그 경로를 인식/처리하지 못한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6048%;&quot;&gt;authorization-uri&lt;/td&gt;
&lt;td style=&quot;width: 76.3952%;&quot;&gt;카카오에 인가 요청을 보낼 실제 주소이다. 회신될 &lt;span style=&quot;color: #ee2323;&quot;&gt;Authorization Code는 인증 과정이 성공했다는 &quot;확인용 1회용 코드&quot;&lt;/span&gt;라고 생각하자.&lt;br /&gt;&lt;br /&gt;Access Token을 브라우저에 직접 노출시키지 않기 위해 단계를 분리시켜놓은 것.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6048%;&quot;&gt;token-uri&lt;/td&gt;
&lt;td style=&quot;width: 76.3952%;&quot;&gt;인증이 완료된 후 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;사용자 정보에 실제 요청/접근할 수 있는 열쇠&quot;인 Access Token&lt;/span&gt;을 요청할 주소이다. 쇼핑몰 입장에서는 이 Access Token이 사용자가 인증됐다는 증거가 된다.&lt;br /&gt;&lt;br /&gt;로그인할 때 토큰만 먼저 발급하는 이유는 사용자 정보의 노출을 줄이기 위함이며, 사이트가 정말 필요한 데이터만 API를 통해 명시적으로 요청하게끔 강제하기 위해서이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 23.6048%;&quot;&gt;user-info-uri&lt;/td&gt;
&lt;td style=&quot;width: 76.3952%;&quot;&gt;사용자 정보를 요청할 주소이다. Spring Security는 access token을 발급받은 직후에, 사용자 정보를 가져오기 위해 자동으로 user-info-uri에 HTTP 요청을 보낸다. 즉, 쇼핑몰은 Access Token을 들고 카카오에 사용자 정보를 요청한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사이트 도메인 및 Redirect URI 설정하기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카카오 개발자 센터에 내 애플리케이션의 도메인과 인가 코드(Authorization Code)를 받을 Redirect URI를 등록해야 한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;특히 Redirect URI는 yml에 설정했었던 redirect-uri 옵션과 반드시 동일하게 작성해야만 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;사이트 도메인 등록.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;1493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2RYxr/btsNTBLWff2/17vaK0edwiESeyatKHwK2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2RYxr/btsNTBLWff2/17vaK0edwiESeyatKHwK2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2RYxr/btsNTBLWff2/17vaK0edwiESeyatKHwK2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2RYxr%2FbtsNTBLWff2%2F17vaK0edwiESeyatKHwK2K%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;4400&quot; height=&quot;1493&quot; data-filename=&quot;사이트 도메인 등록.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;1493&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;redirectURI 등록.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;1687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl3Omb/btsNULzXQvM/Kd4TD3cTEjKcKHdj22XZ01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl3Omb/btsNULzXQvM/Kd4TD3cTEjKcKHdj22XZ01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl3Omb/btsNULzXQvM/Kd4TD3cTEjKcKHdj22XZ01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl3Omb%2FbtsNULzXQvM%2FKd4TD3cTEjKcKHdj22XZ01%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;4400&quot; height=&quot;1687&quot; data-filename=&quot;redirectURI 등록.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;1687&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Security(SecurityFilterChain) 추가 설정하기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {

  @Autowired
  CustomOAuth2UserService customOAuth2UserService;

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    HttpSecurity httpSecurity = http
        .authorizeHttpRequests(auth -&amp;gt; auth
          // 로그인 페이지 접근 허용(이외 경로들은 접근시 인증 필요)
          .requestMatchers(&quot;/&quot;, &quot;/login&quot;, &quot;/css/**&quot;, &quot;/js/**&quot;, &quot;/*.png&quot;).permitAll()
          .anyRequest().authenticated()
        )
        .oauth2Login(oauth2 -&amp;gt; oauth2
          .loginPage(&quot;/&quot;) // 로그인 페이지 설정
          .defaultSuccessUrl(&quot;/home&quot;, true) //로그인 성공시 보낼 주소 작성
          .failureUrl(&quot;/&quot;)
          // 사용자 정보 요청이 완료되면, customOAuth2UserService를 호출
          .userInfoEndpoint(userInfo -&amp;gt; userInfo.userService(customOAuth2UserService))
        );

    return http.build();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정적 리소스 및 /, /login 등의 로그인 관련 페이지들은 인증 없이 접근을 허용하도록 Security 설정을 수정해야 한다. 특정 주소 접근에 인증이 필요한데, 로그인이 안 되어 있는 경우에 redirect할 페이지는 loginPage()에 지정하면 된다. user-info-uri로 요청했던 사용자 정보를 수신하면 userInfoEndpoint()에 이 정보를 처리하기 위한 메서드를 설정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth2 인증 후 '사용자 정보'요청 처리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class CustomOAuth2UserService implements OAuth2UserService&amp;lt;OAuth2UserRequest, OAuth2User&amp;gt; {

  @Override
  public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    // 카카오로부터 사용자 정보 받아오기
    OAuth2User oauth2User = new DefaultOAuth2UserService().loadUser(userRequest);
    System.out.println(&quot;카카오 사용자 정보: &quot; + oauth2User.getAttributes());

    // OAuth2User 객체를 리턴해야 세션에 인증정보 저장됨
    return new DefaultOAuth2User(
      Collections.singleton(new SimpleGrantedAuthority(&quot;ROLE_USER&quot;)), // 권한
      oauth2User.getAttributes(), // 사용자 정보 전체
      &quot;id&quot; // 사용자 식별자 키 (카카오 기본값: &quot;id&quot;)
      );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OAuth2UserService는 '사용자 정보'요청 이후, 그 응답을 처리하기 위한 인터페이스이다. OAuth2User 객체를 만들어주는 역할을 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;카카오 간편인증 성공.png&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDk8Rc/btsNTyPCjRQ/nXHNKkIc57MBnULR8IBOdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDk8Rc/btsNTyPCjRQ/nXHNKkIc57MBnULR8IBOdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDk8Rc/btsNTyPCjRQ/nXHNKkIc57MBnULR8IBOdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDk8Rc%2FbtsNTyPCjRQ%2FnXHNKkIc57MBnULR8IBOdK%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;620&quot; height=&quot;385&quot; data-filename=&quot;카카오 간편인증 성공.png&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Spring</category>
      <category>kakao</category>
      <category>oauth</category>
      <category>Spring Security</category>
      <category>간편인증</category>
      <category>로그인</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/81</guid>
      <comments>https://hoon93.tistory.com/81#entry81comment</comments>
      <pubDate>Fri, 16 May 2025 20:59:56 +0900</pubDate>
    </item>
    <item>
      <title>Springboot를 Docker Image로 생성 및 실행(Dockerfile)</title>
      <link>https://hoon93.tistory.com/80</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot 애플리케이션을 Docker Container로 실행하기 위해선&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 Docker Image로 만들어야 한다. 차례대로 기록해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;springboot 프로젝트 받기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;springInitializr.png&quot; data-origin-width=&quot;3122&quot; data-origin-height=&quot;2231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfevKu/btsNNieT1aS/Grr43YlrFxZ7VbgtEpc830/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfevKu/btsNNieT1aS/Grr43YlrFxZ7VbgtEpc830/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfevKu/btsNNieT1aS/Grr43YlrFxZ7VbgtEpc830/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfevKu%2FbtsNNieT1aS%2FGrr43YlrFxZ7VbgtEpc830%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; alt=&quot;Spring initializr&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;472&quot; data-filename=&quot;springInitializr.png&quot; data-origin-width=&quot;3122&quot; data-origin-height=&quot;2231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 스프링부트 프로젝트를 &lt;a title=&quot;start.spring.io&quot; href=&quot;https://start.spring.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;initializr&lt;/a&gt;를 통해 생성하자. 아래 GENERATE 버튼을 클릭하면 간단하게 생성 완료. 의존성으로 Spring Web과 DevTools를 추가했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TDxD8/btsNNHEDXjo/Ej90qlyHEkKu4LfMebBRNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TDxD8/btsNNHEDXjo/Ej90qlyHEkKu4LfMebBRNk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;360&quot; data-filename=&quot;controller 생성.png&quot; width=&quot;500&quot; height=&quot;270&quot; style=&quot;width: 56.2321%; margin-right: 10px;&quot; data-widthpercent=&quot;56.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TDxD8/btsNNHEDXjo/Ej90qlyHEkKu4LfMebBRNk/img.png&quot; alt=&quot;RestController 생성&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTDxD8%2FbtsNNHEDXjo%2FEj90qlyHEkKu4LfMebBRNk%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;667&quot; height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KD9Fm/btsNLSnmTeU/r8IMgmu0UNbHieMo1z3FvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KD9Fm/btsNLSnmTeU/r8IMgmu0UNbHieMo1z3FvK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;317&quot; data-filename=&quot;localhost 확인.png&quot; style=&quot;width: 42.6051%;&quot; data-widthpercent=&quot;43.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KD9Fm/btsNLSnmTeU/r8IMgmu0UNbHieMo1z3FvK/img.png&quot; alt=&quot;localhost 확인&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKD9Fm%2FbtsNLSnmTeU%2Fr8IMgmu0UNbHieMo1z3FvK%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;445&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;controller 하나를 간단히 만들어서 서버가 정상적으로 동작하는지 확인해 봤습니다. 문제가 없다면 해당 부트 프로젝트를 docker image로 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Dockerfile 만들기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dockerfile 만들기.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p7mck/btsNP9uv3c9/dyGBbbwELs0Czh6WFtkKHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p7mck/btsNP9uv3c9/dyGBbbwELs0Czh6WFtkKHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p7mck/btsNP9uv3c9/dyGBbbwELs0Czh6WFtkKHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp7mck%2FbtsNP9uv3c9%2FdyGBbbwELs0Czh6WFtkKHK%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; alt=&quot;dockerfile 생성&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;350&quot; data-filename=&quot;dockerfile 만들기.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Dockerfile은 말 그대로 파일인데, Docker Image를 만들게 해주는 파일이다. 중요한 점으로 파일명은 꼭 'Dockerfile'이라고 만들도록 하자. 윈도우에선 상관없더라도 리눅스 환경에서는 대소문자를 구분하기에 dockerfile(소문자)로 하면 추후에 문제가 될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk

COPY build/libs/*SNAPSHOT.jar app.jar

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;부트 프로젝트를 만들 때 jdk를 17로 선택했었기 때문에 컨테이너의 '베이스 이미지'로 'openjdk:17-jdk'를 작성했다. COPY는 호스트 컴퓨터에 있는 파일을 복사해서 컨테이너로 전달하는 역할하는 명령어다. 사용법은 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;COPY [호스트 컴퓨터에 있는 복사할 파일의 경로] [컨테이너에서 파일이 위치할 경로 겸 이름]&lt;/span&gt;.&lt;span style=&quot;color: #ee2323;&quot;&gt; 컨테이너는 이미지를 기반으로 만들어&lt;/span&gt;지는 것이기에 우리는 빌드한 파일을 COPY할 예정입니다. ENTRYPOINT는 컨테이너가 시작할 때 실행되는 명령어로 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;ENTRYPOINT [&quot;명령어&quot;, &quot;옵션&quot;, &quot;실행할 내용&quot;]&lt;/span&gt; 각각 분리해서 작성.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot 빌드&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너로 옮길 jar파일을 만들자. 인텔리제이에서 터미널 창을 열고(Alt + F12) 아래 명령어를 실행하면 빌드가 진행된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew clean build&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jar gradle build.png&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;1613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l8qYI/btsNOSUkp9w/Q0R1l3Ee2Ul1DLrxUHoDTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8qYI/btsNOSUkp9w/Q0R1l3Ee2Ul1DLrxUHoDTk/img.png&quot; data-alt=&quot;gradle build 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8qYI/btsNOSUkp9w/Q0R1l3Ee2Ul1DLrxUHoDTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8qYI%2FbtsNOSUkp9w%2FQ0R1l3Ee2Ul1DLrxUHoDTk%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; alt=&quot;gradle build&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;452&quot; data-filename=&quot;jar gradle build.png&quot; data-origin-width=&quot;1426&quot; data-origin-height=&quot;1613&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;gradle build 성공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker Image 생성&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker build -t demo-server .&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker image build.png&quot; data-origin-width=&quot;3094&quot; data-origin-height=&quot;1079&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkcW0s/btsNOKoQW0Q/lmRcKCLnciDbrNOb7oCzxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkcW0s/btsNOKoQW0Q/lmRcKCLnciDbrNOb7oCzxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkcW0s/btsNOKoQW0Q/lmRcKCLnciDbrNOb7oCzxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkcW0s%2FbtsNOKoQW0Q%2FlmRcKCLnciDbrNOb7oCzxK%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; alt=&quot;docker image 생성하기&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;265&quot; data-filename=&quot;docker image build.png&quot; data-origin-width=&quot;3094&quot; data-origin-height=&quot;1079&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Dockerfile을 재료로 하여 Image를 만든다고 했었다. &lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker build [dockerfile이 위치한 경로] -t [이미지명]&lt;/span&gt; 명령어로 이미지를 생성하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;이미지 생성 확인.png&quot; data-origin-width=&quot;2345&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0Af78/btsNQhMthdB/yiVfTJ1Ckx59jiP6CRhS6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0Af78/btsNQhMthdB/yiVfTJ1Ckx59jiP6CRhS6k/img.png&quot; data-alt=&quot;빌드된 이미지 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0Af78/btsNQhMthdB/yiVfTJ1Ckx59jiP6CRhS6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0Af78%2FbtsNQhMthdB%2FyiVfTJ1Ckx59jiP6CRhS6k%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; alt=&quot;docker image 리스트 확인&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;88&quot; data-filename=&quot;이미지 생성 확인.png&quot; data-origin-width=&quot;2345&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빌드된 이미지 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot Image로 컨테이너 실행&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker run image.png&quot; data-origin-width=&quot;3076&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2vniw/btsNPv5OTvO/qwtfbecmg0x2FJe7jqnB7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2vniw/btsNPv5OTvO/qwtfbecmg0x2FJe7jqnB7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2vniw/btsNPv5OTvO/qwtfbecmg0x2FJe7jqnB7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2vniw%2FbtsNPv5OTvO%2Fqwtfbecmg0x2FJe7jqnB7k%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; alt=&quot;springboot 컨테이너 실행&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;107&quot; data-filename=&quot;docker run image.png&quot; data-origin-width=&quot;3076&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링부트는 기본적으로 8080포트에서 실행된다. 따라서 아래 명령어를 통해 이미지를 컨테이너로 실행(위 명령어에 대한 내용은 &lt;a title=&quot;컨테이너 실행 명령어 상세 내용&quot; href=&quot;https://hoon93.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 확인). 컨테이너의 log를 확인해 보니 정상적으로 스프링부트가 실행된 것이 확인되며, 브라우저에 localhost:8080 로도 응답이 정상 출력됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하자면, 하나의 애플리케이션을 이미지로 만들었고, 이를 컨테이너로 실행시켰다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>container</category>
      <category>Docker</category>
      <category>Docker image</category>
      <category>Dockerfile</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/80</guid>
      <comments>https://hoon93.tistory.com/80#entry80comment</comments>
      <pubDate>Sun, 11 May 2025 18:46:04 +0900</pubDate>
    </item>
    <item>
      <title>Docker Volume 개념 및 활용 예시</title>
      <link>https://hoon93.tistory.com/79</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커에서 컨테이너가 제거되면 내부에 저장되어 있던 데이터까지 함께 삭제되어 없어지는 문제가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 방지하기 위해 개념인 볼륨(Volume)을 활용하여 MySQL 컨테이너를 실행해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;볼륨(Volume)을 활용해 MySQL 컨테이너 실행&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1744879848222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run -v C:\docker-workspace\mysql/mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;볼륨은 -v 옵션을 통해 설정이 가능하며, 사용법은 '&lt;span style=&quot;background-color: #f3c000;&quot;&gt;-v [호스트의 폴더 절대경로]:[컨테이너의 폴더 절대경로]&lt;/span&gt;'이다(다른 옵션들의 의미는 &lt;a title=&quot;Docker 명령어 사용법&quot; href=&quot;https://hoon93.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 확인). 포인트는 아래와 같다. 별5개로 중요!!! &lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;[호스트의 폴더 절대 경로]에 디렉토리가 이미 존재할 경우&lt;/span&gt;, 호스트의 디렉터리가 컨테이너의 디렉터리를 덮어씌운다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;[호스트의 폴더 절대 경로]에 디렉토리가 존재하지 않을 경우&lt;/span&gt;, 호스트의 디렉터리 절대 경로에 디렉터리를 새로 만들고 컨테이너의 디렉터리에 있는 파일들을 호스트의 디렉터리로 복사해온다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mysql 컨테이너 data 경로.png&quot; data-origin-width=&quot;2954&quot; data-origin-height=&quot;1771&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Vrx2o/btsNAKuYNdw/6zwE3UTbPzMpbyUQEToI7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Vrx2o/btsNAKuYNdw/6zwE3UTbPzMpbyUQEToI7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Vrx2o/btsNAKuYNdw/6zwE3UTbPzMpbyUQEToI7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVrx2o%2FbtsNAKuYNdw%2F6zwE3UTbPzMpbyUQEToI7k%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; alt=&quot;mysql data 절대경로&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;396&quot; data-filename=&quot;mysql 컨테이너 data 경로.png&quot; data-origin-width=&quot;2954&quot; data-origin-height=&quot;1771&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 이미지(image) 사용법을 보면 뒤에 '{폴더 절대경로}/datadir&lt;span style=&quot;background-color: #f3c000;&quot;&gt;:/var/lib/mysql&lt;/span&gt;' 식으로 작성하면 된다고 설명돼있다(저장 경로는 DB 종류마다 다름). 저는 호스트 볼륨으로 사용할 공간으로 C:\docker-workspace\mysql 라는 폴더를 만들었습니다. 주의할 점으로는 명령문 콜론(:) 바로 앞에 있는 맨 마지막 폴더(위 사진 예시에서는 datadir)까지 전부 미리 만들어 놓으면 처음 실행 시 컨테이너의 폴더를 덮어씌우므로 미리 만들어놓지 않고 명령문에만 붙여놓으면 되겠다. 저는 datadir 대신 'mysql_data'를 폴더 이름으로 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;호스트 볼륨 절대경로 생성.png&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;1523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEiKn1/btsNzfJLNdW/injl6rIxKHDJpXCwuRKRP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEiKn1/btsNzfJLNdW/injl6rIxKHDJpXCwuRKRP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEiKn1/btsNzfJLNdW/injl6rIxKHDJpXCwuRKRP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEiKn1%2FbtsNzfJLNdW%2Finjl6rIxKHDJpXCwuRKRP0%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; alt=&quot;호스트 볼륨 절대경로 생성&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;386&quot; data-filename=&quot;호스트 볼륨 절대경로 생성.png&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;1523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명령문을 실행하니 mysql_data 폴더가 만들어지면서 여러 잡다한 파일들이 생겨난 것을 확인할 수 있는데, 실제 컨테이너 내부 공간의 데이터 파일들과 동일함을 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;컨테이너 내부 저장 데이터 확인.png&quot; data-origin-width=&quot;4260&quot; data-origin-height=&quot;633&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUD7ne/btsNBDDvy54/C4pkbcJOF33YIbu9B4Kea1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUD7ne/btsNBDDvy54/C4pkbcJOF33YIbu9B4Kea1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUD7ne/btsNBDDvy54/C4pkbcJOF33YIbu9B4Kea1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUD7ne%2FbtsNBDDvy54%2FC4pkbcJOF33YIbu9B4Kea1%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; alt=&quot;컨테이너 저장 데이터 확인&quot; loading=&quot;lazy&quot; width=&quot;4260&quot; height=&quot;633&quot; data-filename=&quot;컨테이너 내부 저장 데이터 확인.png&quot; data-origin-width=&quot;4260&quot; data-origin-height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 컨테이너에 접속하여 DB에 새로운 데이터 추가하기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1745571208169&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker exec -it [컨테이너ID] bash
$ mysql -u root -p&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 명령어로 컨테이너 내부로 이동하고 MySQL에 접속해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데이터베이스 생성.png&quot; data-origin-width=&quot;2465&quot; data-origin-height=&quot;2117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9wFlB/btsNzOdX65N/x2kSuFKBiYNaBhkYlHZGqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9wFlB/btsNzOdX65N/x2kSuFKBiYNaBhkYlHZGqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9wFlB/btsNzOdX65N/x2kSuFKBiYNaBhkYlHZGqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9wFlB%2FbtsNzOdX65N%2Fx2kSuFKBiYNaBhkYlHZGqK%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; alt=&quot;create database&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;515&quot; data-filename=&quot;데이터베이스 생성.png&quot; data-origin-width=&quot;2465&quot; data-origin-height=&quot;2117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;mydb라는 데이터베이스를 생성한 후 exit.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너 삭제&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1744879848222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker ps
$ docker rm -f [컨테이너ID]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적이라면 컨테이너를 삭제(remove)했으므로, 컨테이너 내부 디렉토리에 저장되어 있을 mydb라는 데이터베이스가 없어져야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 컨테이너 재실행&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1744879848222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run -v C:\docker-workspace\mysql/mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql
$ docker exec -it [컨테이너ID] bash
$ mysql -u root -p
$ show databases;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너 내부가 아닌 호스트의 저장 공간에 DB 데이터를 뒀기 때문에, 컨테이너를 재실행해도 새로 생성했던 남아있음을 확인할 수 있다. &lt;span style=&quot;color: #ee2323;&quot;&gt;한 가지 중요한 점은 볼륨을 이용하여 MySQL 컨테이너를 처음 띄울 때 설정했던 초기 비밀번호가 호스트 저장 공간에 셋팅이 돼버린다는 점이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744879848222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run -v C:\docker-workspace\mysql/mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pwd1234 -d -p 3306:3306 mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 컨테이너를 다시 띄울 때 위와 같이 다른 비밀번호를 이용하여 셋팅한다고 하더라도, 호스트의 디렉터리가 컨테이너의 디렉터리를 덮어씌우기 때문에 초기에 볼륨에 설정했던 비밀번호가 적용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker Volume 저장 공간 이해하기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker volume 저장 공간 이해하기.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;2391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YOIBE/btsNDIQS94K/tiEBmXtfUsLjKbxpMmSxuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YOIBE/btsNDIQS94K/tiEBmXtfUsLjKbxpMmSxuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YOIBE/btsNDIQS94K/tiEBmXtfUsLjKbxpMmSxuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYOIBE%2FbtsNDIQS94K%2FtiEBmXtfUsLjKbxpMmSxuk%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; alt=&quot;docker volume 저장 공간 이해하기&quot; loading=&quot;lazy&quot; width=&quot;4400&quot; height=&quot;2391&quot; data-filename=&quot;docker volume 저장 공간 이해하기.png&quot; data-origin-width=&quot;4400&quot; data-origin-height=&quot;2391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 컨테이너가 실행된 상태에서 호스트 저장 공간에 파일을 추가하면, 컨테이너 저장 공간에 해당 파일이 복사돼있는 것을 알 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>Docker</category>
      <category>image</category>
      <category>mysql</category>
      <category>Volume</category>
      <category>도커</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/79</guid>
      <comments>https://hoon93.tistory.com/79#entry79comment</comments>
      <pubDate>Mon, 28 Apr 2025 21:09:08 +0900</pubDate>
    </item>
    <item>
      <title>Docker 명령어 사용 및 예시(nginx 설치&amp;amp;실행)</title>
      <link>https://hoon93.tistory.com/78</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker를 이용하여 nginx를 실행해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;진행하면서 쓰이는 각각의 명령어에 대해서도 순서대로 정리해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명령어를 통해 nginx 이미지 다운받기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커(Docker)가 실행되어 있는 상태에서 CMD 또는 PowerShell에 '&lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker pull nginx&lt;/span&gt;' 명령어만 실행하면 nginx를 설치할 수 있다. 이때 이미지를 다운로드받는다고 표현하는데, 여기서 우리는 nginx 이미지를 설치(pull)한 것이다. 이미지(Image)란 특정 프로그램을 실행하는 데 필요한 모든 정보(버전, 설정 등)를 포함한 것이라고 이해하면 된다. 추가적인 개념 내용은 &lt;a title=&quot;컨테이너와 이미지란?&quot; href=&quot;https://hoon93.tistory.com/48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 확인.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744879481167&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
e6557c42ebea: Pull complete
b0c073cda91f: Pull complete
8a628cdd7ccc: Pull complete
32ef64864ec3: Pull complete
ec74683520b9: Pull complete
6c95adab80c5: Pull complete
ad8a0171f43e: Pull complete
Digest: sha256:5ed8fcc66f4ed123c1b2560ed708dc148755b6e4cbd8b943fab094f2c6bfa91e
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고로 이 이미지(Image)라는 것은 &lt;a title=&quot;Dockerhub&quot; href=&quot;https://hub.docker.com/_/nginx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;dockerhub&lt;/a&gt;에서 내려받아진다. &lt;span style=&quot;color: #ee2323;&quot;&gt;dockerhub는 '컨테이너 이미지 저장소'&lt;/span&gt;로 소스코드 저장소인 Github와 유사한 플랫폼이다. 결국 우리는 Github와 같이 사람들이 올려둔 이미지를 pull 명령어를 통해 다운받아 사용하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dockerhub 사용.png&quot; data-origin-width=&quot;3280&quot; data-origin-height=&quot;2389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uE1da/btsNwNLRzlc/bKxEoyOSLZ5Km86fBWqvdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uE1da/btsNwNLRzlc/bKxEoyOSLZ5Km86fBWqvdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uE1da/btsNwNLRzlc/bKxEoyOSLZ5Km86fBWqvdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuE1da%2FbtsNwNLRzlc%2FbKxEoyOSLZ5Km86fBWqvdk%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; alt=&quot;dockerhub 홈페이지&quot; loading=&quot;lazy&quot; width=&quot;3280&quot; height=&quot;2389&quot; data-filename=&quot;dockerhub 사용.png&quot; data-origin-width=&quot;3280&quot; data-origin-height=&quot;2389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 당연하게도 각각의 이미지마다 여러 버전들이 있을 수 있다. dockerhub에서는 이러한 버전들을 tag라는 이름으로 구분하고 있다. 이미지를 pull받을때 콜론 뒤에 특정 tag를 작성하면 원하는 버전의 이미지를 다운받을 수 있다. 예를 들어 '&lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker pull nginx:stable-alpine3.20-perl&lt;/span&gt;' 식이 있겠다. 처음 방식처럼 tag 명을 작성하지 않으면, tag가 없는 이미지가 다운받아지는 것이 아니라 도커에서 자동으로 '&lt;span style=&quot;background-color: #f3c000;&quot;&gt;:latest&lt;/span&gt;'를 붙여준다. 'latest'는 의미 그대로 최신 버전의 nginx 이미지를 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치된 이미지(Image) 확인&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'&lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker image ls&lt;/span&gt;' 명령어로 설치된 이미지들을 조회할 수 있다. 'REPOSITORY'는 이미지의 이름으로 직전에 다운받은 nginx 가 있는 것이 확인된다. 버전인 'TAG'를 보면 'latest' 인 것도 확인할 수 있다. 'CREATE'는 다운받은 날짜가 아니라 이미지 자체가 만들어진 날짜임을 인지하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ls = list&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744879848222&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
nginx        latest    5ed8fcc66f4e   18 hours ago   279MB&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다운받은 이미지를 통해 컨테이너 실행&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너라는 환경에 이미지를 꽂아 실행한다고 생각하면 쉽다. 저는 nginx 컨테이너 이름을 webserver라고 지정해 봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker run --name webserver -d -p 80:80 nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 명령어의 기본 골격은 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker run 이미지명[:태그명]&lt;/span&gt; 인데, 옵션을 풀어보면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 84px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; text-align: center; height: 21px;&quot;&gt;옵션&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; text-align: center; height: 21px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-d&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;백그라운드에서 컨테이너를 실행시키는 옵션&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;--name [컨테이너 이름]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너의 이름을 지정하는 옵션&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 36.2791%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;-p [호스트 포트]:[컨테이너 포트]&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 63.7209%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;호스트 컴퓨터와 컨테이너의 포트를 매핑하는 옵션&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Nginx 서버 실행 확인&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브라우저에 localhost:80 을 치니 nginx가 제공하는 웹 화면이 떴음을 확인했다. 몇 줄의 명령어만으로 nginx의 설치 및 실행을 할 수 있게 된 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_nginx 실행확인.png&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKuyVi/btsNz048Nws/sIxpayPK6nfi5lkLeLNLw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKuyVi/btsNz048Nws/sIxpayPK6nfi5lkLeLNLw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKuyVi/btsNz048Nws/sIxpayPK6nfi5lkLeLNLw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKuyVi%2FbtsNz048Nws%2FsIxpayPK6nfi5lkLeLNLw1%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; alt=&quot;nginx 컨테이너 기동 확인&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;345&quot; data-filename=&quot;edited_nginx 실행확인.png&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행되고 있는 모든 컨테이너 상태 확인&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'&lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker ps&lt;/span&gt;'명령어로 내 '호스트(host) 컴퓨터'에서 실행 중인 컨테이너를 조회할 수 있다. nginx라는 프로그램을 동작시킬 수 있는 Image를 '독립적인 컴퓨터 환경'인 컨테이너에 꽂아 'nginx 컨테이너'를 구성한 것으로 이해하면 쉽다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ps = process status&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker ps명령.png&quot; data-origin-width=&quot;3582&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6SaaK/btsNwnzTqTG/KDUpztrKTQpSpqVLerSW20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6SaaK/btsNwnzTqTG/KDUpztrKTQpSpqVLerSW20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6SaaK/btsNwnzTqTG/KDUpztrKTQpSpqVLerSW20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6SaaK%2FbtsNwnzTqTG%2FKDUpztrKTQpSpqVLerSW20%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;3582&quot; height=&quot;255&quot; data-filename=&quot;docker ps명령.png&quot; data-origin-width=&quot;3582&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행중인 컨테이너 중지하기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'&lt;span style=&quot;background-color: #f3c000;&quot;&gt;docker stop 컨테이너명(or 컨테이너ID)&lt;/span&gt;' 명령어로 컨테이너를 종료해 보자. 이것으로 nginx가 실행 중인 컴퓨터인 컨테이너를 종료시킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker stop webserver
webserver&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_nginx 종료확인.png&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nwhPs/btsNzcLUfIj/obiPAVzKPQGslmkpjQiXxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nwhPs/btsNzcLUfIj/obiPAVzKPQGslmkpjQiXxk/img.png&quot; data-alt=&quot;nginx 컨테이너 종료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nwhPs/btsNzcLUfIj/obiPAVzKPQGslmkpjQiXxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnwhPs%2FbtsNzcLUfIj%2FobiPAVzKPQGslmkpjQiXxk%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; alt=&quot;nginx 컨테이너 종료 확인&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;376&quot; data-filename=&quot;edited_nginx 종료확인.png&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nginx 컨테이너 종료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너 로그(log)보기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너가 정상적으로 잘 종료됐는지 로그로 확인해보자. '&lt;span style=&quot;background-color: #f3c000;&quot;&gt;--tail&lt;/span&gt;' 옵션을 이용해 '로그 끝부터 표시할 줄 수'를 지정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745383323184&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker logs --tail 10 webserver&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>container</category>
      <category>Docker</category>
      <category>image</category>
      <category>nginx</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/78</guid>
      <comments>https://hoon93.tistory.com/78#entry78comment</comments>
      <pubDate>Fri, 25 Apr 2025 23:43:36 +0900</pubDate>
    </item>
    <item>
      <title>Docker Desktop 설치 및 확인</title>
      <link>https://hoon93.tistory.com/77</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커(Docker)를 Windows나 macOS 같은 데스크탑 환경에서 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쉽게 사용할 수 있도록 도와주는 Docker Desktop 설치 및 실행 과정을 포스팅해 보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치 파일 다운로드&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Install Docker Desktop.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6VsNT/btsNpyHSCsD/1m3gls8Yon7cF0xjnoivkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6VsNT/btsNpyHSCsD/1m3gls8Yon7cF0xjnoivkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6VsNT/btsNpyHSCsD/1m3gls8Yon7cF0xjnoivkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6VsNT%2FbtsNpyHSCsD%2F1m3gls8Yon7cF0xjnoivkk%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;600&quot; height=&quot;236&quot; data-filename=&quot;Install Docker Desktop.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a title=&quot;Docker 공식 문서 사이트&quot; href=&quot;https://docs.docker.com/desktop/setup/install/windows-install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;도커 공식 문서 사이트&lt;/a&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서 설치 파일을 받도록 하겠습니다. 화면을 조금 스크롤해서 내용을 읽어보면 WSL2 또는 Hyper-V 기반으로 Docker Desktop을 설치할 수 있다고 설명돼있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;WSL2 및 HyperV.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgIvP4/btsNob8G900/T5Ojxe7T2kNTi6nKKh1qhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgIvP4/btsNob8G900/T5Ojxe7T2kNTi6nKKh1qhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgIvP4/btsNob8G900/T5Ojxe7T2kNTi6nKKh1qhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgIvP4%2FbtsNob8G900%2FT5Ojxe7T2kNTi6nKKh1qhK%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;600&quot; height=&quot;313&quot; data-filename=&quot;WSL2 및 HyperV.png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너 기술은 리눅스 기능을 기반으로 만들어졌기 때문에 반드시 리눅스 커널이 필요하다. 하지만 윈도우는 당연하게도 자체적으로는 리눅스 커널을 제공하지 않는다. 따라서 Docker를 실행하기 위해선 리눅스 환경을 가상화해야 한다. 이를 위해 두 가지 방식이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫째, &lt;span style=&quot;color: #ee2323;&quot;&gt;'리눅스 커널을 포함한 경량 VM'인 WSL2&lt;/span&gt;를 활용하는 방식이 있다. 둘째, Hyper-V 기반 전통적인 가상 머신 방식으로, 윈도우에 Hyper-V를 사용해 전체 리눅스 OS를 VM으로 올린 뒤 그 안에 Docker를 설치하여 실행한다. 이 방식은 안정적이지만 상대적으로 무겁고 Windows Pro 이상 버전에서만 가능하다는 특징도 있다. &lt;/span&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ WSL = Windows Subsystem for Linux&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 방식 모두 리눅스 커널 환경을 제공하므로 Docker 사용에는 차이가 없으니 저는 그냥 더 가볍다고 하는 WSL2 기반으로 설치하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WSL2 기반으로 설치 진행&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;install docker config.png&quot; data-origin-width=&quot;2344&quot; data-origin-height=&quot;1053&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oSkfX/btsNmejxdIN/dZ2kSxdOnJ3zAI421AuEeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oSkfX/btsNmejxdIN/dZ2kSxdOnJ3zAI421AuEeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oSkfX/btsNmejxdIN/dZ2kSxdOnJ3zAI421AuEeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoSkfX%2FbtsNmejxdIN%2FdZ2kSxdOnJ3zAI421AuEeK%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;600&quot; height=&quot;270&quot; data-filename=&quot;install docker config.png&quot; data-origin-width=&quot;2344&quot; data-origin-height=&quot;1053&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;'Use WSL 2 instead of Hyper-V'&lt;/span&gt;&lt;/b&gt; 항목을 선택한 채로 설치 진행. 만약 이걸 선택하지 않으면 Docker가 리눅스 컨테이너를 실행할 때, Hyper-V 가상 머신 방식을 사용함을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;'Allow Windows containers to be used with this installation'&lt;/span&gt;&lt;/b&gt; 항목은 Docker 컨테이너가 리눅스말고 Windows 환경의 컨테이너도 쓸 수 있게 허용할지에 대한 항목이다. .NET Framework 기반 앱이나 IIS를 이용할 필요가 있다면 해당 항목을 체크하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;재부팅 후 서비스 약관 동의&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치가 완료되면 컴퓨터 재부팅을 하게 되는데, Docker Desktop이 자동 실행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker service accept.png&quot; data-origin-width=&quot;2190&quot; data-origin-height=&quot;1329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HutEo/btsNmaIg8q2/GRo949QIUujc55DqWBVdjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HutEo/btsNmaIg8q2/GRo949QIUujc55DqWBVdjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HutEo/btsNmaIg8q2/GRo949QIUujc55DqWBVdjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHutEo%2FbtsNmaIg8q2%2FGRo949QIUujc55DqWBVdjK%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;600&quot; height=&quot;364&quot; data-filename=&quot;docker service accept.png&quot; data-origin-width=&quot;2190&quot; data-origin-height=&quot;1329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker setting.png&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lq354/btsNlCkIffy/hvdSLpt8kStuxbQ0t1OJYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lq354/btsNlCkIffy/hvdSLpt8kStuxbQ0t1OJYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lq354/btsNlCkIffy/hvdSLpt8kStuxbQ0t1OJYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flq354%2FbtsNlCkIffy%2FhvdSLpt8kStuxbQ0t1OJYK%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;716&quot; height=&quot;294&quot; data-filename=&quot;docker setting.png&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WSL2 업데이트&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;WSL update 실패알림.png&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d14f0o/btsNleYGURb/z2XvhpcKCu6JkIChbXhvDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d14f0o/btsNleYGURb/z2XvhpcKCu6JkIChbXhvDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d14f0o/btsNleYGURb/z2XvhpcKCu6JkIChbXhvDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd14f0o%2FbtsNleYGURb%2Fz2XvhpcKCu6JkIChbXhvDK%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;600&quot; height=&quot;412&quot; data-filename=&quot;WSL update 실패알림.png&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아무것도 안 했는데 WSL 업데이트에 실패했다는 경고 창이 뜬다(심장덜컥). 이 경우에는 침착하게 CMD창을 관리자 권한으로 열고 수동으로 WSL을 업데이트해 주면 된다. 명령어는 wsl --update.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;wsl update명령.png&quot; data-origin-width=&quot;2627&quot; data-origin-height=&quot;1039&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0mesc/btsNl8wXEPv/LN7alpD2vttFCdQRHtaPv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0mesc/btsNl8wXEPv/LN7alpD2vttFCdQRHtaPv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0mesc/btsNl8wXEPv/LN7alpD2vttFCdQRHtaPv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0mesc%2FbtsNl8wXEPv%2FLN7alpD2vttFCdQRHtaPv0%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;700&quot; height=&quot;277&quot; data-filename=&quot;wsl update명령.png&quot; data-origin-width=&quot;2627&quot; data-origin-height=&quot;1039&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker Desktop 재실행&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;업데이트가 완료되면 docker desktop을 재실행하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;install docker survey.png&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;1313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo0lor/btsNpZ568Ov/pmhi1h6KnlOnufiR6IaA10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo0lor/btsNpZ568Ov/pmhi1h6KnlOnufiR6IaA10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo0lor/btsNpZ568Ov/pmhi1h6KnlOnufiR6IaA10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo0lor%2FbtsNpZ568Ov%2Fpmhi1h6KnlOnufiR6IaA10%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;700&quot; height=&quot;383&quot; data-filename=&quot;install docker survey.png&quot; data-origin-width=&quot;2398&quot; data-origin-height=&quot;1313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;docker desktop 메인화면.png&quot; data-origin-width=&quot;2645&quot; data-origin-height=&quot;1453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/shNkK/btsNpDvMdvO/vZpIhrT1KdpRTjk4UF9DTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/shNkK/btsNpDvMdvO/vZpIhrT1KdpRTjk4UF9DTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/shNkK/btsNpDvMdvO/vZpIhrT1KdpRTjk4UF9DTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FshNkK%2FbtsNpDvMdvO%2FvZpIhrT1KdpRTjk4UF9DTK%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;750&quot; height=&quot;412&quot; data-filename=&quot;docker desktop 메인화면.png&quot; data-origin-width=&quot;2645&quot; data-origin-height=&quot;1453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정상적으로 Docker Desktop이 실행됐다. 위 사진과 같이 메인 화면 왼쪽 하단에 'Engine running' 표시를 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;docker CLI 명령어 실행 확인&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CMD 또는 PowerShell을 통해 Docker CLI 명령이 잘 수행되는지도 확인.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744876761263&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\user&amp;gt;docker version
Client:
 Version:           28.0.4
 API version:       1.48
 Go version:        go1.23.7
 Git commit:        b8034c0
 Built:             Tue Mar 25 15:07:48 2025
 OS/Arch:           windows/amd64
 Context:           desktop-linux

Server: Docker Desktop 4.40.0 (187762)
 Engine:
  Version:          28.0.4
  API version:      1.48 (minimum version 1.24)
  Go version:       go1.23.7
  Git commit:       6430e49
  Built:            Tue Mar 25 15:07:22 2025
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.26
  GitCommit:        753481ec61c7c8955a23d6ff7bc8e4daed455734
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>Docker</category>
      <category>docker desktop</category>
      <category>WSL2</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/77</guid>
      <comments>https://hoon93.tistory.com/77#entry77comment</comments>
      <pubDate>Thu, 17 Apr 2025 22:25:16 +0900</pubDate>
    </item>
    <item>
      <title>@Bean, @Configuration, @ComponentScan, @Import 정리</title>
      <link>https://hoon93.tistory.com/76</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리가 애플리케이션을 만들기 위해서는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;어떤 클래스의 오브젝트를 만들고 그들을 어떻게 연결할 것인가?&quot;에 대한 정보가 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 구성 정보를 스프링에게 전달하는 여러 가지 방법이 있는데,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너 초기화 과정에서 빈을 동적으로 생성하도록 약속된 애노테이션들에 대해 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Bean을 등록시키는 중요 애노테이션&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. @Bean&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1738648326658&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class PaymentConfig {

    @Bean
    public PaymentService paymentService() {
        return new PaymentService();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Bean 애노테이션이 붙어있는 메소드는 'Bean을 만드는 메소드'를 의미한다. 쉽게 return 되는 객체가 빈으로 등록된다고 이해하면 된다. 이 등록 방식은 애플리케이션의 구성을 위해 직접적으로 객체를 추가하기 위한 용도이기 때문에, 일반적으로 @Configuration클래스 내에 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;참고로 컨테이너 내에서 Bean으로 등록되는 오브젝트들은 아이디(Bean Nama)가 하나씩 부여되는데, 팩토리 메소드로 만드는 경우 디폴트 값으로는 메소드 이름을 따라간다. 별도의 이름을 지정하려면 메소드명을 변경하거나 아니면 @Bean(&quot;이름&quot;)으로 변경이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. @Configuration&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'Config 정보를 가지고 있는 클래스'를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너는 전체 애플리케이션의 '구성 정보'를 필요로 한다고 얘기했었다. 여기서 구성이라는 단어는 '어떻게 형성되는가?'라는 건데, 이게 곧 @Configuration 애노테이션의 의미(풀 명칭으로는 Configuration Metadata)이기도 하다. @Configuration이 붙은 클래스는 전체 애플리케이션 및 Container를 구성하는데 필요한 정보(클래스들 간의 어떤 의존 정보 등)를 담고 있다고 약속되어 있기 때문에 애플리케이션 컨텍스트(Spring Container)에 가장 처음 등록된다는 특징이 있다. 그래서 이와 같은 이유로 필요한 오브젝트들을 전체적으로 등록시키는 @ComponentScan이 메타 애노테이션으로 함께 붙어있는 모습을 자주 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링에서 제공하는 구성 정보 클래스들 외에도 해당 애노테이션을 사용하여 우리가 직접 구성 정보를 작성할 수 있지만, 생각해 볼 만한 부분이 있다. 사실 @Configuration는 &lt;span style=&quot;color: #ee2323;&quot;&gt;ProxyBeanMethods&lt;/span&gt;라는 Attribute를 가지고 있다. 그리고 이 속성의 디폴트 값은 true이다. &lt;span style=&quot;color: #ee2323;&quot;&gt;true의 의미는 오브젝트를 생성하는 코드가 여러 번 실행된다고 하더라도 딱 한 개의 오브젝트를 재사용&lt;/span&gt;하도록 한다는 특징이 있다. 다시 말해, @Configuration라는 특별한 애노테이션을 달아놓은 클래스에는 별도로 false 옵션을 주지 않는 한 스프링은 생성자가 여러 번 호출이 되어도 딱 하나의 오브젝트만 생성/관리된다는 것이다. 다시 말해, 여러 번 생성하거나 또는 사용하는 오브젝트가 여러 개가 된다고 할지라도 동일한 오브젝트가 사용됨을 스프링이 보장한다. @Bean을 이용해 여러 번 생성해도 동일한 인스턴스를 반환하지만, 반대로 속성값이 false라면 새로운 객체를 반환한다. 추가적으로 '@Configuration이 붙어있는 클래스는 구성 정보 클래스다'라는 것을 클래스 명으로도 표현하기 위해 보통 postfix로 &quot;... Config&quot; 식으로 이름이 붙어있는 것이 일반적인 특징이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;3. @Component&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Component는 스프링에서 빈을 등록하는 가장 기본적인 애노테이션으로 이 애노테이션이 붙은 클래스는 'Bean Object가 될 대상'이라는 것을 의미한다. Spring Container의 &quot;컴포넌트 스캐너&quot;는 해당 애노테이션이 붙은 클래스를 모두 찾아서 빈으로 등록하기 때문에 개발자가 new 키워드로 객체를 생성하지 않아도 Spring이 알아서 생성하고 관리해 준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리가 많이 사용하는 @Controller, @Service, @Repository 또한 @Component의 확장형으로 가독성 및 역할을 명확히 하기 위해 추가된 애노테이션이다. 그리고 @Component가 붙은 클래스들은 자동으로 빈으로 등록하는 용도이긴 하지만, 이를 스캔하는 역할은 @ComponentScan이 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;4. @ComponentScan&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@ComponentScan은 @Component가 붙은 클래스를 찾아서 오브젝트를 생성하고 Bean으로 등록하는 역할을 담당한다. 알고 있어야 할 점은 @ComponentScan도 basePackages라는 속성이 존재하며, 이 옵션을 별도로 지정하지 않는 한 선언된 클래스가 포함돼있는 패키지를 기준으로 내부에 존재하는 클래스들을 스캔한다는 것이다. Spring Boot 프로젝트에서는 @SpringBootApplication 내부에 메타 애노테이션으로 @ComponentScan이 포함되어 있기 때문에, 별도로 우리가 선언하지 않아도 자동으로 동작하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;springboot_basePackage.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQZey/btsMNrDWcwy/3PoKl4CHQ30DzeSFQ9eukk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQZey/btsMNrDWcwy/3PoKl4CHQ30DzeSFQ9eukk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQZey/btsMNrDWcwy/3PoKl4CHQ30DzeSFQ9eukk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQZey%2FbtsMNrDWcwy%2F3PoKl4CHQ30DzeSFQ9eukk%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;726&quot; height=&quot;294&quot; data-filename=&quot;springboot_basePackage.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 위 사진과 같이 @ComponentScan(여기선 @SpringBootApplication)이 붙은 클래스가 특정 패키지 내에 있다면, 기본적으로 해당 패키지와 그 하위 패키지 내의 컴포넌트들만 스캔 대상이 된다. 즉, main이라는 패키지 밖에 존재하는 HelloController 등의 클래스는 스캔 대상에서 제외된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;5. @Import&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 클래스에 @Import 애노테이션이 붙어있다면, 해당 클래스가 빈으로 등록이 될 때! @Import의 attribute에 명시된 클래스들까지 빈으로 함께 등록함을 의미한다. 즉, 스캔 대상은 아니었지만 attribute로 클래스 정보를 넘겨줌으로써 Bean을 명시적으로 추가하는데 사용한다. 해당 애노테이션은 일반적으로 @Configuration 클래스에서 작성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 @Import를 이용하면 @Configuration 애노테이션이 붙어 설정 전용 빈이라고 명시된 클래스 외에 일반 빈 클래스를 빈으로 등록할 수 있긴 하다. 하지만 해당 클래스가 나중에라도 스캔 대상으로 사용될 수도 있고 클래스가 빈으로 등록된다는 의미로 @Component 애노테이션을 붙여놓는 것이 바람직한 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;EnableAutoConfiguration.png&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgLQ32/btsMMceWDvo/JTlockxIS894AAWoZXol1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgLQ32/btsMMceWDvo/JTlockxIS894AAWoZXol1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgLQ32/btsMMceWDvo/JTlockxIS894AAWoZXol1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgLQ32%2FbtsMMceWDvo%2FJTlockxIS894AAWoZXol1k%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;498&quot; height=&quot;114&quot; data-filename=&quot;EnableAutoConfiguration.png&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 @Import는 어떤 목적으로 import를 했을까? 라는 의문을 갖게 만든다. 그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;Spring Boot에서는 @EnableXXX 형식의 애노테이션을 정의하고 @Import를 내부적으로 사용&lt;/span&gt;하는 방식을 활용하고 있다. 한 가지 예로는 @SpringBootApplication 내에 존재하는 @EnableAutoConfiguration가 있다. 찾아보면 그냥 @Import(AutoConfigurationImportSelector.class)자체를 SpringBootApplication의 메타 애노테이션으로 붙여 넣을 수도 있지만, 이를 한번 감싸서 어떠한 기능을 위해 명시적으로 등록하고 있는지를 표현하고 있음을 알 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>@import</category>
      <category>bean</category>
      <category>component</category>
      <category>ComponentScan</category>
      <category>Configuration</category>
      <category>proxybeanmethods</category>
      <category>SpringBootApplication</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/76</guid>
      <comments>https://hoon93.tistory.com/76#entry76comment</comments>
      <pubDate>Wed, 19 Mar 2025 23:11:18 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch와 직렬화 엑세스(ORA-08177) 오류 원인 및 해결</title>
      <link>https://hoon93.tistory.com/75</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQLException: ORA-08177: can't serialize access for this transaction&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Batch 설정 중 '이 트랜잭션에 대한 직렬화 액세스를 할 수 없습니다.'는 에러가 발생했다. 결론부터 말하자면, 현재 애플리케이션의 트랜잭션 격리 수준이 직렬화(Serializable)로 설정되어 있는데, 이 규칙을 지키지 못해 발생한 에러이다. 여기서 '격리 수준'이란 트랜잭션 간의 동시성 문제를 해결하기 위한 규칙을 의미한다. 그중 Serializable 옵션은 한 트랜잭션이 특정 데이터를 읽는 동안, 다른 트랜잭션이 동일한 데이터에 접근/수정하는 것을 막기 위해 그 데이터에 잠금을 걸어 동시 접근을 방지한다. 따라서, 모든 트랜잭션이 마치 직렬로 실행되는 것처럼 보장하는 옵션이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 Serializable 방식은 직관적이면서 &quot;안전하다&quot;라고 느껴지지만, 현실적으로 '성능'과 '읽기 일관성'때문에 보편적인 DB작업에서는 사용되지 않는다. 실제로 A라는 트랜잭션이 조작 중인 데이터를 다른 B 트랜잭션이 조작 이전의 상태로 읽는 것은 현실적으로 대부분의 애플리케이션에서 허용되며, 이는 사용자 경험과 시스템 성능을 개선하는데 기여하는 부분이기도 하다. 특히 Batch 작업처럼 대량의 데이터를 처리하거나 여러 트랜잭션이 병렬로 실행되는 경우, 충돌과 교착 상태가 빈번하게 발생할 수 있기 때문에 Serializable은 ORA-08177 오류를 초래할 가능성이 높다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 사실 난 격리 수준을 Serializable로 설정한 적이 없다. 근데 왜 이런 오류가 발생할까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;찾아보니 Spring Boot는 기본적으로 애플리케이션이 이용 중인 데이터베이스 벤더별 기본 격리 수준을 따른다고 한다. @Transactional 애노테이션의 내부를 보면 isolation()이라는 추상 메소드의 기본 리턴 값이 Isolation.DEFAULT로 설정돼있는 점을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Transactional.png&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFCnZw/btsLcHO5mtu/rBAA7gPmK083F4JHQbfEYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFCnZw/btsLcHO5mtu/rBAA7gPmK083F4JHQbfEYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFCnZw/btsLcHO5mtu/rBAA7gPmK083F4JHQbfEYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFCnZw%2FbtsLcHO5mtu%2FrBAA7gPmK083F4JHQbfEYK%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; alt=&quot;Transactional 애노테이션 소스 코드&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;376&quot; data-filename=&quot;Transactional.png&quot; data-origin-width=&quot;857&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 이 Isolation.DEFAULT가 데이터베이스 벤더의 기본값을 따른다는 의미인데, Oracle의 기본 격리 수준은 'READ_COMMITTED'이다(참고로 MySQL은 'REPEATABLE_READ'). 그니까 직접적으로 옵션을 건들지 않는 한 'READ_COMMITTED'로 자동 설정돼야 하는데, 대체 왜 Serializable로 설정되는 건데... &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 뭔가 이상하지만 직접적으로 'Spring Boot의 전체 트랜잭션 격리 수준을 설정하는 옵션'을 주면 될 것 같아 아래 설정을 추가해 보기도 했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732024594877&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.transaction.default-isolation: READ_COMMITTED&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 위 방법으로도 에러가 해결되지 않았는데.. 열심히 삽질하여 드디어 이유를 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;Spring Batch의 필수 구성 요소인 JobRepository가 트랜잭션 관리에 있어 독립적인 설정을 가지&lt;/span&gt;기 때문이었다. 내부적으로 Spring Batch는 자동 구성에 의한 JobRepositoryFactoryBean 클래스를 통해서 JobRepository를 생성하는데, 이때 AbstractJobRepositoryFactoryBean의 기본 설정이 Serializable로 되어 있기 때문에, spring.transaction.default-isolation 설정이 적용되지 않았던 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;AbstractJobRepositoryFactoryBean.png&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgxJpB/btsLjo8Urw0/NqLr12xJkJpH5zecliM4xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgxJpB/btsLjo8Urw0/NqLr12xJkJpH5zecliM4xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgxJpB/btsLjo8Urw0/NqLr12xJkJpH5zecliM4xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgxJpB%2FbtsLjo8Urw0%2FNqLr12xJkJpH5zecliM4xK%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; alt=&quot;ISOLATION_SERIALIZABLE&quot; loading=&quot;lazy&quot; width=&quot;678&quot; height=&quot;196&quot; data-filename=&quot;AbstractJobRepositoryFactoryBean.png&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;196&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 해결하려면 JobRepository 설정을 명시적으로 변경해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JobRepository 의 격리 수준 변경 방법 2가지&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) JobRepository 를 직접 Bean으로 재정의/등록하며 속성 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732024893261&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public JobRepositoryFactoryBean jobRepository(DataSource dataSource, PlatformTransactionManager transactionManager) {
	JobRepositoryFactoryBean jobRepository = new JobRepositoryFactoryBean();
	jobRepository.setIsolationLevelForCreate(&quot;ISOLATION_READ_COMMITTED&quot;);
	jobRepository.setDataSource(dataSource);
	jobRepository.setTransactionManager(transactionManager);
	return jobRepository;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 설정 파일을 통한 속성 변경&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 배치 작업 테이블을 생성/조작할 때의 트랜잭션 격리 수준을 제어하는 옵션인데, 스프링부트를 이용할 경우 프로퍼티 파일을 이용하여 더 간편하게 설정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732329981472&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.batch.jdbc.isolation-level-for-create: READ_COMMITTED&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <category>batch</category>
      <category>jobrepository</category>
      <category>ora-08177</category>
      <category>serialize</category>
      <category>spring</category>
      <category>transaction</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/75</guid>
      <comments>https://hoon93.tistory.com/75#entry75comment</comments>
      <pubDate>Sat, 14 Dec 2024 22:43:27 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Batch] ORA-00955 에러 해결하기</title>
      <link>https://hoon93.tistory.com/74</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Batch의 초기 구성을 설정하는 과정에서 여러 오류가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://hoon93.tistory.com/73&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 글&lt;/a&gt;에 이어 발생할 수 있는 ORA-0085 에러에 대해 알아보자.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQLSyntaxErrorException: ORA-00955: 기존의 객체가 이름을 사용하고 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ORA-00955 Error.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eGX4Yz/btsLm6ftRVz/sSG3K9b2lkV5G1tdHd7KGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eGX4Yz/btsLm6ftRVz/sSG3K9b2lkV5G1tdHd7KGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eGX4Yz/btsLm6ftRVz/sSG3K9b2lkV5G1tdHd7KGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeGX4Yz%2FbtsLm6ftRVz%2FsSG3K9b2lkV5G1tdHd7KGK%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; alt=&quot;table already exists&quot; loading=&quot;lazy&quot; width=&quot;893&quot; height=&quot;189&quot; data-filename=&quot;ORA-00955 Error.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1732024144596&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException: 
Failed to execute SQL script statement #1 of class path resource [org/springframework/batch/core/schema-oracle10g.sql]: 
CREATE TABLE BATCH_JOB_INSTANCE ( JOB_INSTANCE_ID NUMBER(19,0) NOT NULL PRIMARY KEY , VERSION NUMBER(19,0) , JOB_NAME VARCHAR2(100 char) NOT NULL, JOB_KEY VARCHAR2(32 char) NOT NULL, constraint JOB_INST_UN unique (JOB_NAME, JOB_KEY) ) ; 
nested exception is java.sql.SQLSyntaxErrorException: ORA-00955: 기존의 객체가 이름을 사용하고 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Batch 애플리케이션을 재기동하니 위 에러가 발생했다. 찾아보니 이전에 메타 테이블을 초기화시키기 위해 설정했었던 속성이 문제의 원인이었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733985765914&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.batch.job.initialize-schema=always&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1733985747448&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.sql.init.mode=always&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유사한 성격의 위 두 속성들의 'ALWAYS' 값은 애플리케이션이 시작될 때마다 초기화 스크립트의 실행을 강제하는데, 이 경우 이미 존재하는 테이블을 다시 생성(CREATE TABLE) 하려고 시도하면서 충돌이 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테이블 생성시 발생하는 충돌 문제 해결 방법&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) DDL 스크립트 실행과 관련한 속성(&lt;span style=&quot;text-align: start;&quot;&gt;spring.sql.init.mode 등&lt;/span&gt;) 삭제.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 이미 생성된 테이블을 덮어쓰려는 시도가 없도록 'always' 대신 'never'로 옵션 값 재설정. 추가적으로 배포 환경(개발, 테스트, 운영) 별로 서로 다른 설정을 사용하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733820792483&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  profiles: dev
  sql:
    init:
      mode: always
---
spring:
  profiles: prod
  sql:
    init:
      mode: never&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 사용하는 DB가 오라클(Oracle)이 아니라면 'CREATE TABLE IF NOT EXISTS' 구문으로 메타 테이블 초기화를 위한 스크립트를 수정.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 배치와 관련한 메타 테이블을 전부 삭제 후 재기동. 메타 테이블 삭제 시에는 DROP TABLE ... CASCADE CONSTRAINTS 명령어를 사용하여 참조 무결성 제약 조건도 함께 제거해야 한다는 점을 주의해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733820669076&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DROP TABLE BATCH_JOB_EXECUTION CASCADE CONSTRAINTS;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>batch</category>
      <category>ora-00955</category>
      <category>spring</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/74</guid>
      <comments>https://hoon93.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 12 Dec 2024 19:37:08 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Batch] Table &amp;quot;BATCH_JOB_INSTANCE&amp;quot; not found</title>
      <link>https://hoon93.tistory.com/73</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Batch 프로젝트의 초기 Config를 구성하는 과정에서 로컬과 개발 서버에서는 정상적으로 동작하는데,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이상하게 운영 서버에서 실행하면 에러가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해결하는 과정에서 여러 에러를 마주쳤는데, 최대한 상기하며 정리해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;BadSqlGrammarException &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;[SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;BatchJobInstance_Notfound.png&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE2UUb/btsKPQRHAv3/dCuiPthM7cmovlMm1MpOLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE2UUb/btsKPQRHAv3/dCuiPthM7cmovlMm1MpOLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE2UUb/btsKPQRHAv3/dCuiPthM7cmovlMm1MpOLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE2UUb%2FbtsKPQRHAv3%2FdCuiPthM7cmovlMm1MpOLK%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;1202&quot; height=&quot;591&quot; data-filename=&quot;BatchJobInstance_Notfound.png&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;운영에 배포하기 전에 개발서버에서 정상 동작하는 것을 확인했었다. 그리고 에러 로그도 SQL Exception 관련 문제라고 말하지만 문제가 되는 테이블은 Spring Batch에서 관리하는 메타 테이블이기 때문에 mapper 쪽을 잘못 작성해서 발생한 문제는 아니다. 어쨌든 해당 에러에 대해 구글링을 해봐도 'BATCH_JOB_INSTANCE' 테이블이 존재하지 않아서 발생하는 문제라는 것을 알 수 있고, 대부분 아래 설정으로 해결하라고 알려주고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732023466336&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.batch.jdbc.initialize-schema = always&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 설정 파일(yaml)에 위 속성을 추가해서 로컬과 개발서버에서의 에러를 해결할 수 있기는 했다. 하지만 말도 안 되게도 저의 경우엔 이게 운영서버에선 적용되지 않는 듯 같은 에러가 계속됐다&lt;s&gt;(왜 내게만 이런 시련을..)&lt;/s&gt; application-prod.yaml 파일에 DB 설정이나 오타가 있던 것은 당연히 아니었고 계정에 대한 DB권한 문제도 아니였다. &lt;a title=&quot;Spring Boot 가이드&quot; href=&quot;https://docs.spring.io/spring-boot/how-to/data-initialization.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;에서도 해당 설정에 대한 내용이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;InitializeSpringBatchDatabase.PNG&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/75MfH/btsKOzcyuKB/WMakbilst9HMHrzd9chPSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/75MfH/btsKOzcyuKB/WMakbilst9HMHrzd9chPSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/75MfH/btsKOzcyuKB/WMakbilst9HMHrzd9chPSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F75MfH%2FbtsKOzcyuKB%2FWMakbilst9HMHrzd9chPSk%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;1226&quot; height=&quot;547&quot; data-filename=&quot;InitializeSpringBatchDatabase.PNG&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용을 보면, &lt;span style=&quot;color: #409d00;&quot;&gt;&quot;스프링 배치를 사용하면 대부분의 데이터베이스에 대한 SQL 초기화 Script를 함께 제공하는데, 스프링부트가 데이터베이스 유형을 감지해서 시작 시 해당 스크립트를 실행한다&quot;&lt;/span&gt; 라고 설명돼있다. 이게 무슨 말이냐면, 스프링 배치를 사용하기 위한 관련 Dependency를 추가하면 org.springframework.batch:spring-batch-core 라이브러리가 받아지는데, 이 라이브러리 패키지 내에 스프링 배치가 관리하는 메타 테이블에 대한 초기화용 DDL이 내장돼있다는 말이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spring_batch_core_sqlFile.png&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FYNhI/btsKN3kUKFQ/iY9oV3nPeYezRygXRMmdT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FYNhI/btsKN3kUKFQ/iY9oV3nPeYezRygXRMmdT1/img.png&quot; data-alt=&quot;메타 테이블의 초기화를 위한 DB별 SQL파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FYNhI/btsKN3kUKFQ/iY9oV3nPeYezRygXRMmdT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFYNhI%2FbtsKN3kUKFQ%2FiY9oV3nPeYezRygXRMmdT1%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;468&quot; height=&quot;404&quot; data-filename=&quot;spring_batch_core_sqlFile.png&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메타 테이블의 초기화를 위한 DB별 SQL파일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기본적으로 제공되는 메타 테이블의 초기화 방법에 대해서 커스터마이징할 수 있고, 속성 값으로 '애플리케이션 시작 시 스키마를 항상 초기화' 하도록 'always'로 지정하라는 것이다(참고로 한 번만 실행하거나 이미 있다면 생성하지 않는 속성 값은 없다). 어쨌든 제 경우에는 같은 소스가 개발 환경에만 적용되는 슬픈 현상이 있었지만, 애초에 운영의 배치 실행 이력까지 매번 초기화시키면 데이터 손실이 발생하니 권장되지 않는다. 그래서 한 번만 실행하고 'never'로 수정하여 재배포하거나 애플리케이션 기동 이전에 org/springframework/batch/core 라이브러리 내의 SQL을 직접 실행시키는 방법이 있겠다. 간단한 방법이 있지만 springboot의 스타일로 적용시켜보고 싶어 다른 방법을 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1732028304113&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Boot Reference Documentation&quot; data-og-description=&quot;This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/3.0.0/reference/htmlsingle/#howto.data-initialization.batch&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/3.0.0/reference/htmlsingle/#howto.data-initialization.batch&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/3.0.0/reference/htmlsingle/#howto.data-initialization.batch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/3.0.0/reference/htmlsingle/#howto.data-initialization.batch&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot Reference Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;InitializeDB_SqlScripts.png&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clR8e6/btsKOMCPpBK/MRwYXaxtKz8M86dHgJMOwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clR8e6/btsKOMCPpBK/MRwYXaxtKz8M86dHgJMOwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clR8e6/btsKOMCPpBK/MRwYXaxtKz8M86dHgJMOwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclR8e6%2FbtsKOMCPpBK%2FMRwYXaxtKz8M86dHgJMOwk%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;1065&quot; height=&quot;334&quot; data-filename=&quot;InitializeDB_SqlScripts.png&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 보면 spring.sql.init.mode 설정으로 스프링 부트가 schema-${platform}.sql 및 data-${platform}.sql 형식의 데이터베이스 벤더별 스크립트를 처리할 수 있다고 한다. 그니까 애플리케이션 기동 시에 내가 정의해놓은 SQL 스크립트를 자동으로 실행시켜준다는 건데, 이걸 활용하면 꽤 유용한 것을 할 수 있겠다는 생각도 든다. 어쨌든 해당 설정으로 Batch 테이블을 초기화하는데 이용해 봤고 DB에 테이블이 정상적으로 생성된 것을 확인했다.&lt;/p&gt;
&lt;pre id=&quot;code_1732023557178&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  sql:
    init:
      mode: always
      schema-locations: classpath:org/springframework/batch/core/schema-oracle.sql&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;비교 정리&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 80px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;설정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;대상&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;초기화 범위&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px; text-align: center;&quot;&gt;&lt;b&gt;사용 형태&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 40px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;spring.batch.jdbc.initialize-schema &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Spring Batch 전용 테이블 관리 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Spring Batch 메타 테이블(BATCH_*) &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 배치 작업을 위한 테이블 자동 생성 및 관리 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; spring.sql.init.mode &lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 일반 SQL 스크립트 실행 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 데이터베이스 전체 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 테이블 생성, 기본 데이터 삽입 등 일반 작업 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이후 또 다른 에러가 발생하는데.. &lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;다음 글에서 정리해보겠습니다.&lt;/p&gt;</description>
      <category>Spring</category>
      <category>batch</category>
      <category>batch_job_instance</category>
      <category>spring</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/73</guid>
      <comments>https://hoon93.tistory.com/73#entry73comment</comments>
      <pubDate>Wed, 20 Nov 2024 00:05:10 +0900</pubDate>
    </item>
    <item>
      <title>의존성 역전 원칙(DIP)과 인터페이스 소유권의 역전</title>
      <link>https://hoon93.tistory.com/72</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'의존성 역전 원칙(DIP)'은 '의존성 주입'의 약자인 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DI(Dependency Injection)와 비슷하게 생겼기에 유사한 개념이라고 혼동할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;연관이 아예 없는 것은 아니지만 어쨌든 ' SOLID'라고 객체지향 설계 원칙에서 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;제일 마지막에 등장하는 원칙인 'Dependency Inversion Principle' 과 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이로 인해 생각해 볼 Spring Web MVC 구조에 대해서도 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 역전 원칙(Dependency Inversion Principle) 이란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DIP의 정의는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&quot;상위 수준 모듈은 하위 수준 모듈에 의존해서는 안 된다. 둘 모두 추상화에 의존해야 한다. 또한, 추상화는 구체적인 사항에 의존해서는 안 되며, 구체적인 사항은 추상화에 의존해야 한다.&quot;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히나 이런 개념들은.. 솔직히 정의만 봐서는 무슨 말인지 와닿지가 않는다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이해하기 힘든 문장들을 코드와 함께 하나하나 파헤쳐 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &quot;상위 수준 모듈은 하위 수준 모듈에 의존해서는 안 된다.&quot;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731840045796&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PaymentService {
    private KakaoPayment kakaoPayment;
    
    public PaymentService() {
        this.kakaoPayment = new KakaoPayment(); // 클래스에 직접적으로 의존하고 있다.
    }
    
    public void processPayment() {
        kakaoPayment.pay();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 226px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 94px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; text-align: center; height: 90px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;상위 모듈&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 90px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;애플리케이션의 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;비즈니스 로직이나 핵심 기능&lt;/span&gt;을 담당하는 모듈을 의미한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;흔히 '의존하는 객체'를 담당하며, 위 코드에서는 PaymentService와 같은 클래스가 결제 처리라는 핵심 기능을 담당하므로 상위 수준 모듈이라고 할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 112px;&quot;&gt;
&lt;td style=&quot;width: 13.9535%; text-align: center; height: 100px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;하위 모듈&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 86.0465%; height: 100px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;실제 구체적인 구현을 담당하는 모듈을 의미한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;흔히 '의존되는 객체'를 담당하며, 위 코드에서는 KakaoPayment와, NaverPayment와 같은 결제 방식의 구체적인 구현이 하위 수준 모듈이라고 할 수 있다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하위 수준 모듈의 가장 큰 특징은 기술적인 세부 사항을 다루며, 자주 변경될 가능성이 크다는 것.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;상위 모듈(비즈니스 로직)은 하위 모듈(구체적인 결제 방식)에 의존하면 안 된다는 의미&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어, PaymentService가 KakaoPayment라는 구체적인 결제 방식을 직접적으로 사용한다면, 추후에 결제 방식을 NaverPayment로 변경 시 상위 모듈인 PaymentService를 수정해야 한다. 따라서 Class를 직접적으로 의존하게 하면 시스템의 결합도를 높이고, 유지 보수를 어렵게 만들 수 있다. 참고로 여기서 모듈이라는 건 애플리케이션 내에서 응집도가 높고 결합도가 낮은 그런 기능들을 쪼개놓은 것들을 나타내며, Java에서는 패키지 단위일 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731841074959&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Payment {
    void pay();
}

class KakaoPayment implements Payment {
    public void pay() {
        System.out.println(&quot;Kakao payment processing...&quot;);
    }
}

class NaverPayment implements Payment {
    public void pay() {
        System.out.println(&quot;Naver payment processing...&quot;);
    }
}

class PaymentService {
    private Payment Payment;

    public PaymentService(Payment Payment) {
        this.Payment = Payment; // 추상화에 의존
    }

    public void processPayment() {
        paymentMethod.pay();
    }
}&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;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &quot;둘 모두 추상화에 의존해야 한다.&quot;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'둘 모두'라는 단어를 구체적으로 풀이해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;상위 모듈 입장에서 보면 &quot;상위 모듈은 하위 모듈의 추상화(인터페이스 또는 추상 클래스)에 의존해야 한다&quot;라는 것이고, 두 번째로 하위 모듈 입장에서는 &quot;하위 모듈은 추상화를 구현해야 한다&quot;는 것. 즉, 추상화된 인터페이스나 추상 클래스를 구현하는 역할로 하위 모듈을 개발해야 함을 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 줄 정리하자면 상위 수준 모듈은 추상화에 의존해야 하고, 하위 수준 모듈은 추상화를 구현해야 함을 뜻한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. &quot;추상화는 구체적인 사항에 의존해서는 안 되며, 구체적인 사항은 추상화에 의존해야 한다.&quot;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쉽게 말해, 추상화(인터페이스, 추상 클래스)는 구체적인 구현인 클래스를 의존하지 않으며, 클래스는 추상화에 의존해야 한다는 것을 의미한다. 위 코드로 설명하자면 추상화(Payment 인터페이스)는 클래스(KakaoPayment, NaverPayment)에 의존하지 않고 있다(당연하게도..)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반대로 클래스는 인터페이스(Payment)를 구현하고 있기에, 추상화에 의존하는 형태라고 할 수 있다. 이로써 결제 방식이 무엇이든 상관없이 PaymentService는 바뀌지 않게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어렵게 풀이했지만 결국 2번과 3번의 문장은 같은 의미이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과적으로 이런 원칙을 지킴으로써, 구체적인 구현을 변경하더라도 시스템의 다른 부분, 특히 상위 모듈에는 영향을 미치지 않게 된다. 상위 모듈은 여전히 추상화에 의존하고, 구체적인 세부 사항은 하위 모듈에서 변경될 수 있기 때문에 시스템의 유연성과 확장성이 높아진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 DIP를 적용하려면 스프링 컨테이너라는 제3의 존재에 의한 '의존성 주입(Dependency Injection, DI)'이 필요하며, 이과 함께 사용되어 더욱 강력한 설계 패턴이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 역전 원칙(DIP)과 인터페이스 소유권의 역전(Interface Ownership Inversion)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위의 DIP 설명에서 A가 B를 의존하여 사용한다면 A가 상위 모듈 B가 하위 모듈이고, 이때 B가 바뀌면 A도 수정해야 하는 결과를 낳기 때문에 결국 '추상화'. 즉, Interface를 이용하여 의존하라는 것이라고 했다. 근데 사실 이런 코드 레벨에서의 의존은 당연하고 자연스러운 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 Interface를 만들어서 추상화에 의존하도록 하는 건 맞다고 하더라도, 결국 모듈인 패키지 수준으로 보자면 그 Interface 자체를 의존하고 있는 것이기 때문에, 여전히 상위 모듈이 Interface인 하위 모듈을 의존하고 있다고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 &lt;span style=&quot;color: #ee2323;&quot;&gt;'인터페이스 소유권의 역전'&lt;/span&gt;과 '분리 인터페이스 패턴(Separated Interface Pattern)'이 필요하다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 개념은, 인터페이스는 어느 패키지에 들어 있어야 되는가?에 대한 개념이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;인터페이스 소유권의 역전.png&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LwJ2N/btsKQaPXMQ9/3GOtRHcC6ZoUh2feX18Yj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LwJ2N/btsKQaPXMQ9/3GOtRHcC6ZoUh2feX18Yj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LwJ2N/btsKQaPXMQ9/3GOtRHcC6ZoUh2feX18Yj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLwJ2N%2FbtsKQaPXMQ9%2F3GOtRHcC6ZoUh2feX18Yj0%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; alt=&quot;인터페이스 소유권의 역전&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;405&quot; data-filename=&quot;인터페이스 소유권의 역전.png&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 인터페이스를 만들면, 이것을 구현한 클래스와 같은 패키지에 있는 것이 맞아 보일 수 있지만, 많은 경우에 Interface는 자신을 구현한 클래스 쪽이 아니라 자신을 사용하는 쪽에 있는 게 더 자연스러운 경우가 많다고 한다. 그래서 '인터페이스의 소유권을 역전'시켜야 한다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같이 작업함으로써 소프트웨어 설계에서 &quot;상위 계층이 하위 계층에 의존한다&quot;라는 전통적인 의존성 방향이 역전(Invert)됐다. 따라서 하위 모듈(구현체)이 변경되더라도 상위 모듈은 영향을 받지 않는 구조가 됨을 위 그림으로 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 역전 원칙을 잘 따르는 코드를 만들 때의 작업은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 첫 번째 작업: Interface를 만들어내고 추상화를 한 다음에 모든 코드가 추상화에만 의존하도록 만듦.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 두 번째 작업: &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Interface&lt;/span&gt;를 구현한 클래스, 이런 클래스가 있는 모듈에 두는 게 아니라 이를 사용하는 클라이언트(코드)가 있는 모듈(패키지)로 이전시키는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 Web Controller와 Service 계층 간에는 DIP를 적용하지 않는가?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 Spring Web MVC의 구조는 아래와 같다. 웹 계층(Web Controller)과 서비스 계층(Service Layer) 간의 의존성으로 보아 DIP를 적용한다면,&amp;nbsp; 서비스 계층의 interface도 Controller 패키지에 위치해야 하지 않을까 싶다. 하지만 왜 그렇게 설계하지 않는 걸까?&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731842467518&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;com.sample.app
    ├── controller
    │   └── PaymentController.java
    ├── service
    │   ├── PaymentService.java (인터페이스)
    │   └── impl
    │       └── PaymentServiceImpl.java
    └── mapper
        └── PaymentMapper.java&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;의존성 역전을 하는 이유는 애플리케이션의 중심이 되는 도메인/비즈니스 로직을 가진 상위 모듈이 기술적인 메커니즘을 다루는 변경 가능성이 높은 하위 모듈에 의존하지 않게 만드는 것이 목적&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 Web MVC 구조에서는 변경 가능성이 높은 Web과 UI, Data 그리고 각종 Infra 기술들이 하위 모듈로 취급되며, 비즈니스 로직을 다루는 서비스 계층과 이 안에서 다루는 도메인 오브젝트가 가장 중심이 되는 상위 모듈이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생각해보면 Controller는 단순히 Web 요청을 받아서 서비스 계층에 전달하고, 그 결과를 UI에 반환하는 역할에 초점이 맞춰져 있기도 하다. 따라서, 상위 모듈인 서비스 계층을 중심으로 모든 의존성이 형성되는 것이다. 이렇게 되면 중요한 애플리케이션 로직이 웹 요청을 어떤 식으로 받아서 처리하는지? 혹은 뒤에서 데이터를 어떻게 읽어오는지? 혹은 어떠한 인프라 기술을 사용하는지에 영향을 받지 않는 안정적인 구조가 된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Dependency Inversion Principle</category>
      <category>DIP</category>
      <category>interface ownership inversion</category>
      <category>MVC</category>
      <category>SOLID</category>
      <category>spring</category>
      <category>web mvc</category>
      <category>오블완</category>
      <category>의존성 역전 원칙</category>
      <category>인터페이스 소유권의 역전</category>
      <category>티스토리챌린지</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/72</guid>
      <comments>https://hoon93.tistory.com/72#entry72comment</comments>
      <pubDate>Sun, 17 Nov 2024 22:52:30 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot(스프링부트) 핵심 특징과 Spring과의 관계</title>
      <link>https://hoon93.tistory.com/71</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot와 Spring의 관계&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot(스프링부트)라는 단어를 처음 마주하면 그래서 Spring Framework와 다른 거야? 다르다면 어떻게 다른 건데?라는 궁금증이 생긴다. 굳이 구분을 짓자면 이름에서도 알 수 있듯이 Springboot는 Spring과 별개이다. 그래서 Spring의 버전과 Springboot의 버전을 말할 일이 있다면 잘 주의해서 말해야 한다. 하지만 그렇다고 Springboot가 Spring을 대체하거나 승계하여 나온 새로운 기술도 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론부터 말하자면, Springboot는 Spring Framework를 더 쉽게 사용하도록 돕는 도구들의 모음이다. 즉, Springboot는 Spring을 효과적으로 사용하는 방법에 대한 강한 의견이 반영된 프레임워크로써 우리는 Springboot로 Spring 프로젝트를 만든다. 결과적으로 Springboot로 Spring Framework를 사용하는 애플리케이션을 만드는 것이기 때문에, 'Spring의 동작 원리를 이해하지 못한 채로 Springboot를 잘 활용할 수는 없다' 라는 말이 그래서 나오는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot의 두 가지 핵심 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1) Springboot는 Containerless 하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot의 역사는 Containerless 웹 개발 아키텍처의 지원 요청으로부터 논의 및 개발이 시작됐다. 아래는 실제 Spring 프로젝트의 issues tracker(이슈 관리)에 한 개발자가 개선 요청을 했던 &lt;a title=&quot;spring-projects 이슈 트래킹&quot; href=&quot;https://github.com/spring-projects/spring-framework/issues/14521&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;문의글&lt;/a&gt;인데, 지금도 글을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730295375290&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Improved support for 'containerless' web application architectures [SPR-9888] &amp;middot; Issue #14521 &amp;middot; spring-projects/spring-framewor&quot; data-og-description=&quot;Mike Youngstrom opened SPR-9888 and commented As the enterprise development landscape grows more diverse the simpler the application framework the more likely developers are to adopt the framework....&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-framework/issues/14521&quot; data-og-url=&quot;https://github.com/spring-projects/spring-framework/issues/14521&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/csrmdY/hyXpCOpbty/8DQYElBrnWHYHka50jAhe0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bVeqxY/hyXpySK3fC/kdkg2BrPKtPnhg41CYGrWK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/14521&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-framework/issues/14521&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/csrmdY/hyXpCOpbty/8DQYElBrnWHYHka50jAhe0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bVeqxY/hyXpySK3fC/kdkg2BrPKtPnhg41CYGrWK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Improved support for 'containerless' web application architectures [SPR-9888] &amp;middot; Issue #14521 &amp;middot; spring-projects/spring-framewor&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Mike Youngstrom opened SPR-9888 and commented As the enterprise development landscape grows more diverse the simpler the application framework the more likely developers are to adopt the framework....&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내용을 보면, &lt;a title=&quot;Servlet Container란&quot; href=&quot;https://hoon93.tistory.com/66&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;서블릿 컨테이너&lt;/a&gt;에 대한 스프링의 의존도가 스프링 웹 애플리케이션을 만들려는 개발자의 학습 난이도를 높인다는 문제를 제시하고 있다. 실제로 Spring을 사용하더라도 Java의 웹 개발 환경에 적용하려면 spring 자체에 대한 지식 이외의 알아야 할 것들이 많고 생각보다 진입 장벽이 높다. 그래서 몇 가지 예를 나열하며 기능 업그레이드를 해달라는 요청이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;issusForStartSpringboot.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d8khuR/btsKqnCZgRL/KRtez7Ozjtfwfk5IkVRSjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d8khuR/btsKqnCZgRL/KRtez7Ozjtfwfk5IkVRSjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d8khuR/btsKqnCZgRL/KRtez7Ozjtfwfk5IkVRSjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd8khuR%2FbtsKqnCZgRL%2FKRtez7Ozjtfwfk5IkVRSjK%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; alt=&quot;Spring project issue tracker&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;264&quot; data-filename=&quot;issusForStartSpringboot.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;최종적으로 스프링의 개발자들은 위 요구사항을 수용하기는 하되, Spring을 고치기보다 Spring Boot라고 이름 붙인 새로운 프로젝트를 시작하기로 결정했고. 그러한 댓글을 남기며 정식으로 Boot 프로젝트를 공개했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 위 이슈의 포인트인 Containerless라는 단어의 의미는 Container가 필요 없다는 것이 아니고 Serverless와 유사하다. 개발자들은 Spring 개발에 있어 서버(Servlet Container)와 관련된 모든 번거로운 작업들(설치/관리 등)을 신경 쓰지 않고 개발해서 배포하고 운영하는 방법을 원했고, Springboot는 마치 이 Container가 없는 것처럼 감춰줘서 즉, Servlet Containerless한 개발을 할 수 있게 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730295626153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MySpringBootApplication
public class HellobootApplication {
	
    public static void main(String[] args) {
        SpringApplication.run(HellobootApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 위와 같이 Springboot가 처음 만들어준 main 메소드를 딸깍 실행하기만 하면 전혀 셋팅하지 않은 Tomcat이 동작하면서 Spring 애플리케이션이 실행되는 것이다. Springboot가 &lt;span style=&quot;color: #ee2323;&quot;&gt;자동 구성(auto-configuration)&lt;/span&gt;으로 복잡한 설정을 대신해 주고 있는 것인데, 이것이 &quot;Spring 개발(Spring을 기반으로 하는 독립 실행형 애플리케이션을 잘 만들 수 있도록)을 도와주는. Spring 자체를 확장하고 있는 프레임워크&quot;라고 불리는 이유이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2) Springboot는 Opinionated 하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Opinionated의 사전적 정의를 찾아보면 &quot;자기 의견을 강하게 고집하는, 주장이 강한, 독선적인&quot; 이런 류의 뜻인데, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공식 레퍼런스에 소개되어 있는 전체 문장의 의미는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;'어떤 기술을 써야 할지 많은 고민을 하고 환경 구성하는 것으로 프로젝트를 시작하는 것이 일반적일 텐데, 일단 내가 정해줄게. 고객에게 가치를 주기 위해 필요한 비즈니스 로직을 개발하는 데에만 집중하고 추가적인 건 나중에 고민하자'&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SpringbootIsOpinionated.png&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CeycT/btsKqzXDF47/XlKrRlUttnORLtMDI3l4FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CeycT/btsKqzXDF47/XlKrRlUttnORLtMDI3l4FK/img.png&quot; data-alt=&quot;SpringBoot is opinionated&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CeycT/btsKqzXDF47/XlKrRlUttnORLtMDI3l4FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCeycT%2FbtsKqzXDF47%2FXlKrRlUttnORLtMDI3l4FK%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;1002&quot; height=&quot;373&quot; data-filename=&quot;SpringbootIsOpinionated.png&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SpringBoot is opinionated&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Framework와 Java 표준 그리고 오픈소스가 제공하는 각종 기술을 사용하기 위해 직접 등록해야 하는 객체들을 자동 구성을 통해 Springboot만의 스프링 사용방법을 제시하고 있다. 심지어 다양한 기술을 사용할 때의 최적의 구성으로 default 값들을 다 세팅하여 빈으로 등록해 준다.(유연하게 원하는 부분을 커스터마이징 가능) 그래서 Springboot가 빠르고 광범위한 영역의 Spring 개발 경험 제공한다는 말을 하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 필요시 직접 Bean을 생성할 수도 있지만, boot가 특정 기술을 어떻게 사용하는 게 좋겠구나 하고 자동 구성으로 만들어준 객체가 있다면, 그 의견(방법)을 따라가는 게 좋겠다. 다시 말해, 무작정 Spring 객체를 생성해서 만들지 말고 Boot가 제공하는 자동구성 빈을 우선적으로 사용하는 것이 좋다. 그래서 특정 오브젝트의 사용 방법을 넘어 Springboot가 Spring을 어떤 식으로 제공하는가? 를 파악해 보는 것도 중요한 것 같다&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot(스프링부트)의 장단점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 88px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.5891%; height: 17px; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 36.2403%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.1705%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;추가 설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 11.5891%; height: 51px; text-align: center;&quot; rowspan=&quot;3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 36.2403%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빠르고 광범위한 영역의 Spring 개발 경험 제공&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.1705%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존에는 Spring이 제공하는 많은 선택지 중에서 기술들을 결정하는데 고민하는 시간이 길었음. but 빠르게 Spring 기반의 프로젝트를 시작할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 36.2403%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;운영환경에서 사용할만한 수준으로의 발전도 어느정도 손쉽게 된다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.1705%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;외부 파일인 XML을 사용하지 않는 것이 Springboot의 지향점으로 XML작성을 필요로 하지 않는다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 36.2403%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필요할 때 부트의 구성을 수정하거나 확장이 쉽다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.1705%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;boot에 내장된 디폴트 구성을 커스터마이징하는데 매우 유연하다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.5891%; height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 36.2403%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;많은 부분을 고민하지않고 시작할 수 있기 때문에, Spring의 기본원리에 대한 이해가 없으면 결국 한계가 온다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 52.1705%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Springboot가 Spring 기술을 어떻게 활용하는지를 이해해야 하며, 자동으로 만들어주는 구성과 디폴트 설정이 어떤 것인지 확인할 수 있어야만 기본 구성을 수정하거나 확장할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot 3.0 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;22년도 말에 나온 Springboot 3.0의 가장 큰 특징은 Spring 6 버전을 기반으로 하고 있다는 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 2.x 등의 이전 버전을 Springboot 3.0 이상으로 버전 업을 하려면 확인해야 하는 사항은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;build.gradle에서 Springboot 버전 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JDK버전 확인(Spring6에 맞춰 따라오는 조건들 중 이전까지는 jdk8도 가능했으나 이제는 최소 JDK 17이상을 지원하도록 설계됨)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;gradle의 자체 버전 확인(gradle/wrapper/gradle-wrapper.properties파일). Eclipse와 IntelliJ IDEA는 Gradle과 같이 동작하는 것처럼 보이지만 사실 맞물려서 동작할 뿐 별개의 독립적인 툴이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java 패키지명 확인. Java의 서버와 관련한 표준을 모아둔 Java EE(Java Enterprice Edition)의 새로운 이름인 jakarta EE 버전으로 패키지명을 변경해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Spring</category>
      <category>container</category>
      <category>Containerless</category>
      <category>opinionated</category>
      <category>spring</category>
      <category>springboot</category>
      <category>스프링부트</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/71</guid>
      <comments>https://hoon93.tistory.com/71#entry71comment</comments>
      <pubDate>Wed, 30 Oct 2024 23:18:11 +0900</pubDate>
    </item>
    <item>
      <title>Method invocation may produce NullPointerException 과 Optional 활용</title>
      <link>https://hoon93.tistory.com/70</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IDE에서 &quot;Method invocation may produce NullPointerException&quot; 경고 메시지를 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;왜 이런 Warning이 표시되는가?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Method invocation '~~~' may produce NullPointerException 경고를 해석해 보면 &lt;span style=&quot;color: #ee2323;&quot;&gt;메서드 호출 시 NullPointerException이 발생할 수 있다는 것을 의미&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Method invocation Warning.png&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NBK9f/btsJZ6VJHUR/ZVbD0J90K9JBaJa31ysXB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NBK9f/btsJZ6VJHUR/ZVbD0J90K9JBaJa31ysXB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NBK9f/btsJZ6VJHUR/ZVbD0J90K9JBaJa31ysXB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNBK9f%2FbtsJZ6VJHUR%2FZVbD0J90K9JBaJa31ysXB1%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; alt=&quot;Method invocation may produce NullPointerException 경고 메시지&quot; loading=&quot;lazy&quot; width=&quot;873&quot; height=&quot;414&quot; data-filename=&quot;Method invocation Warning.png&quot; data-origin-width=&quot;873&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저의 경우엔 getString() 메서드를 호출할 때 paramters 객체가 null일 수도 있다는 말인데.. 사실 위 코드는 Spring에서 제공하는 인터페이스를 활용하는 거라 JobParameters가 null일 수가 없는 코드이기는 했다. IDE가 이런 메시지를 나타내는게 이해가 안 되긴 하지만, 좀 생각해 보니 getString()를 호출했을때 특정 키에 해당하는 값이 없다면 null일 수도 있다는 것을 덕분에 인지하게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론적으로 해당 경고 메시지는 이번 경우처럼 객체 or 리턴 값이 null 상태일 가능성이 있기 때문인 것으로, 이 경고를 없앨 가장 기본적인 방법은 아래와 같이 명시적으로 체크하는 방법이 있겠다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728485728974&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (parameters != null)
    String fileName = parameters.getString(&quot;fileName&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이처럼 null을 체크하는 다양한 방법이 있지만, 이 기회에 Java의 Optional을 사용하여 null 을 처리하는 방법을 정리해 봤다. 아래는 Optional 객체를 사용하는 코드로 수정한 건데, 주요 함수의 활용 방법을 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728469408572&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public void validate(JobParameters parameters) throws JobParametersInvalidException {

  String fileName =
    Optional.ofNullable(parameters.getString(&quot;fileName&quot;))
    .orElseThrow(() -&amp;gt; new JobParametersInvalidException(&quot;fileName is missing or null&quot;));

  if ( !StringUtils.endsWithIgnoreCase(fileName, &quot;xlsx&quot;) ) {
    throw new JobParametersInvalidException(&quot;This is not Excel File&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Optional 객체의 메소드 활용법&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Optional은 Java 8부터 추가된 유틸리티 클래스&lt;span style=&quot;color: #409d00;&quot;&gt;(java.util.Optional)&lt;/span&gt;로, null을 안전하게 처리하기 위한 객체이다. 다양한 메서드를 제공하기 때문에 이를 활용하면 실수를 방지하고 null 처리를 보다 유연하게 할 수 있다. 또한 메서드 체이닝이 가능하기에 잘 활용하면 가독성도 높일 수 있다. 다음은 Optional이 제공하는 주요 메서드들의 특징 및 예제이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Optional.of(T value)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파라미터인 value의 값이 존재하면 해당 값을 가진 Optional 객체를 반환한다. 만약 값이 null 이라면 NullPointerException이 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728488186473&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String str = &quot;Hello&quot;;
Optional&amp;lt;String&amp;gt; optionalStr = Optional.of(str); // 정상 동작&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. Optional.ofNullable(T value)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;of()메소드와 비슷하지만, value 값이 null일 경우 예의를 발생시키는 것 아닌 Optional.empty()를 반환한다. &lt;span style=&quot;color: #ee2323;&quot;&gt;Optional.empty()는 '비어 있는 Optional 객체'를 의미하는데, 이는 null을 대신하여 '값이 없음'을 표현하는 Optional의 방식이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728488546461&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; firstOpt = Optional.ofNullable(&quot;Hello&quot;);
Optional&amp;lt;String&amp;gt; secondOpt = Optional.ofNullable(null);

System.out.println(firstOpt); // output: Optional[Hello]
System.out.println(secondOpt); // output: Optional.empty&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 변수 firstOpt은 값이 존재하기 때문에 해당 값을 포함하는 Optional 객체를 반환하며, 변수 secondOpt은 null이기 때문에 빈 Optional 객체인 Optional.empty를 반환한다. 다음은 Optional 객체의 실제 값을 확인하는 방법들 중 일부이다.&lt;/p&gt;
&lt;pre id=&quot;code_1728559969151&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 방법 1: get()을 사용하여 값 가져오기 (값이 없으면 예외 발생)
String value = firstOpt.get();
System.out.println(&quot;Value는 &quot; + value); // output: Value는 Hello

// 방법 2: ifPresent()를 사용하여 값이 있을 때만 출력
firstOpt.ifPresent(value -&amp;gt; System.out.println(&quot;Value는&quot; + value));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;3. orElse(T other)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 값이 존재하면 그 값을 반환하고, 값이 없으면 설정한 값을 반환한다. 즉, 초기화할 디폴트 값을 지정할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1728488417618&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optionalStr = Optional.ofNullable(null);
String result = optionalStr.orElse(&quot;Default Value&quot;);
System.out.println(result); // Output: Default Value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. orElseThrow(Supplier&amp;lt;? extends X&amp;gt; exceptionSupplier)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;orElse()와&lt;span&gt; &lt;/span&gt;&lt;/span&gt;마찬가지로 파라미터 값이 존재하면 그 값을 반환하지만, 값이 없을 경우 변수를 할당하지 않고 그 즉시 예외를 발생한다. 예외는 설정한 예외로 throw 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728488571585&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optionalStr = Optional.ofNullable(null);
String result = optionalStr.orElseThrow(() -&amp;gt; new IllegalStateException(&quot;No value present&quot;));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. isPresent()&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체가 값이 존재하면 true 반환하고, null이면 false를 반환한다. 비슷한 기능의 함수로 isEmpty()도 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728488304927&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optionalStr = Optional.ofNullable(null);

// isPresent()를 사용하여 값이 존재하지 않는지 확인 (모든 Java 버전에서 가능)
if (optionalStr.isPresent()) {
    System.out.println(&quot;Value exists: &quot; + optionalStr.get());
} else {
    // 해당 코드가 실행된다.
    System.out.println(&quot;Value is null&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1728563259650&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// isEmpty()를 사용하여 Optional.empty()인지 확인 (Java 11 이상)
if ( optionalStr.isEmpty() ) {
    System.out.println(&quot;The value is Optional.empty()&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. ifPresent(Consumer&amp;lt;? super T&amp;gt; action)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파라미터 값이 있을 때 특정 동작(&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Consumer&lt;/span&gt;)을 수행시킬 수 있다. ifPresent()의 파라미터 값이 없다면 예외가 발생하기 때문에, null이 전달되지 않도록 주의해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728488367982&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optionalStr = Optional.of(&quot;Hello&quot;);
optionalStr.ifPresent(value -&amp;gt; System.out.println(&quot;Value는 &quot; + value));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ Consumer: Java의 함수형 인터페이스 중 하나로, 하나의 인자를 받아서 아무것도 반환하지 않는 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;7. ifPresentOrElse(Consumer&amp;lt;? super T&amp;gt; action, Runnable emptyAction)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫 번째 파라미터 자리인 action의 값으로는 Optional 객체 내의 값이 존재할 때 실행할 동작을 함수형 인터페이스를 정의하고, 두 번째 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;emptyAction에는 값이 없을 때 실행할 동작을 정의한 Runnable 함수형 인터페이스를 정의하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728487887034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; optionalValue = Optional.ofNullable(&quot;Hello&quot;);

optionalValue.ifPresentOrElse(
    value -&amp;gt; System.out.println(&quot;Value는 &quot; + value),  // 값이 있을 때 실행
    () -&amp;gt; System.out.println(&quot;No value present&quot;)     // 값이 없을 때 실행
);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java</category>
      <category>Java</category>
      <category>null</category>
      <category>NullPointerException</category>
      <category>optional</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/70</guid>
      <comments>https://hoon93.tistory.com/70#entry70comment</comments>
      <pubDate>Fri, 11 Oct 2024 01:26:22 +0900</pubDate>
    </item>
    <item>
      <title>코드로 살펴보는 DispatcherServlet과 Servlet 인터페이스 (2)</title>
      <link>https://hoon93.tistory.com/69</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글에서 DispatcherServlet이 Aware 인터페이스를 활용해 어떤 역할을 하는지 알아봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번에는 Servlet 인터페이스로부터 파헤쳐 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;DispatcherServlet 계층 구조.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;869&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mECAL/btsJV8tbhst/gHDqGck6raKlk9OkK4s1K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mECAL/btsJV8tbhst/gHDqGck6raKlk9OkK4s1K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mECAL/btsJV8tbhst/gHDqGck6raKlk9OkK4s1K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmECAL%2FbtsJV8tbhst%2FgHDqGck6raKlk9OkK4s1K1%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;1700&quot; height=&quot;869&quot; data-filename=&quot;DispatcherServlet 계층 구조.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;869&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Servlet 인터페이스로부터 시작하는 DispatcherServlet&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 서블릿(Servlet)이라는 개념을 찾아보면 '자바 웹 애플리케이션에서 HTTP 요청을 처리하기 위한 서버 측 컴포넌트'라고 정의되어 있다. 그러니까 Java에서 웹 요청을 처리하는 기술이자 구성 요소(오브젝트)라는 말인데.. 주로 Spring으로 웹 개발을 했었던 나에겐 당연한듯 익숙했던 Servlet이라는 대명사의 의미에 왜 Java라는 특정 언어에 종속된 단어가 포함되어 있는 거지? 싶어서 더 찾아봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론적으로 Servlet이라는 개념은 Java에만 해당하는 개념으로, 다른 프로그래밍 언어나 각 언어의 웹 프레임워크에서는 비슷한 역할을 수행하는 별도의 구조 or 패턴이 구현돼있다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;예) ASP.NET MVC에서는 라우팅(routing) 시스템이 Client Request을 받고 해당 로직을 수행할 컨트롤러를 결정하는 방식으로 웹 요청을 처리한다고 함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 이 Servlet은 Client 요청/응답을 처리하는 역할을 수행하도록 약속된 Interface로써, Java의 모든 종류의 Servlet 오브젝트들은 이 Interface를 구현하는 것이 기본 규약이다. 그리고 Spring에서는 이 Servlet 기술을 구현한 DispatcherServlet&lt;span style=&quot;color: #409d00;&quot;&gt;(org.springframework.web.servlet)&lt;/span&gt;이라는 오브젝트를 제공하고 있다. 즉, DispatcherServlet은 Servlet의 일종으로 Spring MVC에서 HTTP 요청을 처리하는 중요한 객체이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Servlet Interface.png&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BFDCI/btsJW77O89b/Sn7a00CJaFJed91NDKmuQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BFDCI/btsJW77O89b/Sn7a00CJaFJed91NDKmuQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BFDCI/btsJW77O89b/Sn7a00CJaFJed91NDKmuQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBFDCI%2FbtsJW77O89b%2FSn7a00CJaFJed91NDKmuQk%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;1113&quot; height=&quot;481&quot; data-filename=&quot;Servlet Interface.png&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Client 요청을 처리하는 Servlet의 생명주기는 크게 생성, 요청 처리, 소멸이라고 할 수 있는데, 이에 해당하는 각각의 기본 메서드가 존재한다. 주요 메서드들의 상세 내용을 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 120px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 24.6511%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;Method&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.3489%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;기능&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 24.6511%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;init()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.3489%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Servlet Container에 의해 Servlet 인스턴스가 생성될 때 최초에 한번 init()이 호출되며, 서블릿 동작에 필요한 설정 등의 리소스 초기화 작업 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 24.6511%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;service()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.3489%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HTTP 요청을 처리하는 메서드로. 매 요청이 들어올 때마다 service() 메서드가 호출된다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 24.6511%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;destroy()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.3489%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Servlet Container&lt;/span&gt;에 의해 Servlet이 종료될 때 호출되며, Servlet이 사용한 리소스를 해제하는 등의 정리 작업을 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.6511%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; getServletConfig() &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.3489%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Servlet 설정 정보를 반환하는 기능을 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 24.6511%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; getServletInfo() &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 75.3489%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;버전, 저작권 등의 Servlet 정보를 반환. 기본적으로 빈 문자열을 반환하도록 되어있으며, 의미 있는 값을 반환하려면 해당 메서드를 오버라이드하여 사용하면 된다. &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HttpServlet의 구조와 역할&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HttpServlet은 Servlet을 구현한 자바 EE 표준 추상 클래스로, 이름에서도 알 수 있듯이 HTTP 프로토콜 기반의 웹 요청을 처리하기 위한 서블릿 오브젝트이다. 정확히는 Servlet 인터페이스를 바로 구현하는 것은 아니고 Servlet을 구현한 추상 클래스인 GenericServlet을 상속받고 있는 구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GenericServlet의 내부 소스를 간략히 말하자면 환경 설정(ServletConfig) 등의 아주 기본적인 공통 기능을 담고 있는데, 프로토콜에 독립적인(HTTP 프로토콜에 종속되지 않는) Servlet을 개발할 때 사용할 수 있도록 설계된 것으로 확인된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;HttpServlet의 service메서드.png&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1tlGa/btsJV1ubN0X/KwZHZZHU4amMw8Yr3Tisvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1tlGa/btsJV1ubN0X/KwZHZZHU4amMw8Yr3Tisvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1tlGa/btsJV1ubN0X/KwZHZZHU4amMw8Yr3Tisvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1tlGa%2FbtsJV1ubN0X%2FKwZHZZHU4amMw8Yr3Tisvk%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;933&quot; height=&quot;770&quot; data-filename=&quot;HttpServlet의 service메서드.png&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 핵심인 HttpServlet은 service()를 오버라이드하여 HTTP 요청을 처리하기 위한 doGet(), doPost(), doPut(), doDelete() 와 같은 메서드를 추가적으로 제공하고 있다. service()는 웹 요청이 들어올 때마다 호출되기 때문에, 내부적으로 각 HTTP Method 별로 유연하게 처리할 수 있도록 구현하고 있는 것이다. 만약 웹 애플리케이션 개발을 위해 어떤 Servlet을 개발해야 한다면, 이 HttpServlet을 상속하여 구현하면 되겠다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트 요청을 처리하는 핵심 메서드인 service()는 아래 두 파라미터를 받고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 70px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 27.907%; text-align: center; height: 20px;&quot;&gt;종류&lt;/td&gt;
&lt;td style=&quot;width: 72.093%; text-align: center; height: 20px;&quot;&gt;용도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 27.907%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HttpServletRequest&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 72.093%; height: 40px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 클라이언트가 서버로 보낸 요청 정보를 담고 있는 객체.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) URL, 쿠키, 세션 등의 클라이언트가 보낸 다양한 정보를 얻을 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;width: 27.907%; height: 10px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HttpServletResponse&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 72.093%; height: 10px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 서버가 클라이언트에게 전송할 응답 정보를 다루는 객체.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 해당 객체를 통해 응답 코드, HTML, JSON, 파일 등을 클라이언트에게 전송할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DispatcherServlet은 어디에서 관리되는가?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전통적으로 boot가 아닌 Spring에서는 web.xml이라는 설정 파일에 애플리케이션이 시작될 때 어떤 서블릿이 요청을 처리할지를 정의한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728224337136&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;dispatcher&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;org.springframework.web.servlet.DispatcherServlet&amp;lt;/servlet-class&amp;gt;
    &amp;lt;init-param&amp;gt;
        &amp;lt;param-name&amp;gt;contextConfigLocation&amp;lt;/param-name&amp;gt;
        &amp;lt;param-value&amp;gt;/WEB-INF/config/dispatcher-servlet.xml&amp;lt;/param-value&amp;gt;
    &amp;lt;/init-param&amp;gt;
&amp;lt;/servlet&amp;gt;

&amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;dispatcher&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/&amp;lt;/url-pattern&amp;gt;
&amp;lt;/servlet-mapping&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 오해하면 안 되는 것은 DispatcherServlet이 Spring MVC에서 가장 중요한 오브젝트인 것은 맞지만 Spring이 아닌 Servlet Container에서 관리되며 구동된다는 점이다. 즉, spring에서 web.xml에 위와 같은 코드를 작성하는 것은 DispatcherServlet을 Bean으로 등록하는 행위가 아니라 서블릿 컨테이너에서 요청을 처리할 Servlet을 지정해 주기 위함이다. 다시 말해, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;web.xml은 WAS가 참고하는 설정 파일인 것이다. Spring은 애플리케이션 내의 객체들의 생명주기를 관리하며, HTTP 요청 처리와는 직접적인 관련은 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하자면 DispatcherServlet은 웹 애플리케이션이 시작될 때 Servlet Container(Tomcat 등)에 의해 생성되고, 웹 요청이 들어오면 Spring과 상호작용하여 이를 처리하는 역할을 담당한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>dispatcherservlet</category>
      <category>genericservlet</category>
      <category>httpserlvetresponse</category>
      <category>HttpServlet</category>
      <category>HttpServletRequest</category>
      <category>servlet</category>
      <category>spring</category>
      <category>was</category>
      <category>web.xml</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/69</guid>
      <comments>https://hoon93.tistory.com/69#entry69comment</comments>
      <pubDate>Sun, 6 Oct 2024 23:44:11 +0900</pubDate>
    </item>
    <item>
      <title>코드로 살펴보는 DispatcherServlet과 Aware (1)</title>
      <link>https://hoon93.tistory.com/68</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DispatcherServlet의 역할과 계층 구조&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트로부터 웹 요청이 들어왔을 때 WAS는 Spring Container가 가진 Object한테 요청에 대한 처리를 위임한다. 그리고 Spring에서 이 역할을 하는 것이 정확히는 DispatcherServlet인데, 위 행동을 하기 위해 어떤 Object가 어떤 요청 처리를 담당하고 있는지 알 수 있는 '매핑 정보'를 필요로 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉 Spring 애플리케이션이 실행되면 DispatcherServlet이 빈으로 등록될 때 매핑 정보를 주입해 줘야 하는데, 어떻게 이게 가능한 걸까? 상속과 구현 계층 구조를 확인해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;DispatcherServlet 계층 구조.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;869&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebrI1K/btsJDu41tWb/qF5S8U2qg5eMCRNtwm7Nd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebrI1K/btsJDu41tWb/qF5S8U2qg5eMCRNtwm7Nd1/img.png&quot; data-alt=&quot;펼쳐보는 DispatcherServlet의 상속/구현 계층 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebrI1K/btsJDu41tWb/qF5S8U2qg5eMCRNtwm7Nd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebrI1K%2FbtsJDu41tWb%2FqF5S8U2qg5eMCRNtwm7Nd1%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; alt=&quot;DispatcherServlet 계층 구조&quot; loading=&quot;lazy&quot; width=&quot;1700&quot; height=&quot;869&quot; data-filename=&quot;DispatcherServlet 계층 구조.png&quot; data-origin-width=&quot;1700&quot; data-origin-height=&quot;869&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;펼쳐보는 DispatcherServlet의 상속/구현 계층 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Aware로 시작하는 DispatcherServlet&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부 소스를 보면 가장 안쪽에 Aware이라는 마커 인터페이스(Marker Interface)가 있다. 일반적으로 마커 인터페이스 자체는 비어있기에 아무런 기능도 하지 않는 껍데기이지만, 이를 확장하여 특정 기능을 수행하는 다양한 하위 인터페이스가 존재하고 이를 사용하도록 설계되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Aware.PNG&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x2R6o/btsJDslC0HH/rPpDxYDqKUfSxTy15rDjyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x2R6o/btsJDslC0HH/rPpDxYDqKUfSxTy15rDjyK/img.png&quot; data-alt=&quot;Aware를 구현하는 다양한 인터페이스들&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x2R6o/btsJDslC0HH/rPpDxYDqKUfSxTy15rDjyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx2R6o%2FbtsJDslC0HH%2FrPpDxYDqKUfSxTy15rDjyK%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; alt=&quot;Aware를 구현하는 다양한 인터페이스들&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;411&quot; data-filename=&quot;Aware.PNG&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Aware를 구현하는 다양한 인터페이스들&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 Spring에서 Aware라는 이름이 postfix하게 붙은 인터페이스들은 컨테이너가 자동으로 특정한 정보를 주입하도록 약속되어 있다. 다시 말해, Aware 인터페이스 자체와 그와 함께 제공되는 다른 인터페이스들은 &quot;이 객체가 특정 객체를 필요로 한다&quot;라는 일종의 신호인 것이다. 이 중에서 DispatcherServlet은 ApplicationContextAware 인터페이스를 구현하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;setApplicationContext.PNG&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOTbzK/btsJEnwPuHG/RThtnuDhqRdfu01hYFkgFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOTbzK/btsJEnwPuHG/RThtnuDhqRdfu01hYFkgFK/img.png&quot; data-alt=&quot;ApplicationContextAware를 구현하여 사용하는 DispatcherServlet&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOTbzK/btsJEnwPuHG/RThtnuDhqRdfu01hYFkgFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOTbzK%2FbtsJEnwPuHG%2FRThtnuDhqRdfu01hYFkgFK%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; alt=&quot;ApplicationContextAware를 구현하는 DispatcherServlet&quot; loading=&quot;lazy&quot; width=&quot;985&quot; height=&quot;402&quot; data-filename=&quot;setApplicationContext.PNG&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ApplicationContextAware를 구현하여 사용하는 DispatcherServlet&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ApplicationContextAware를 구현한 클래스는 setApplicationContext라는 라이프사이클 메소드를 구현하도록 되어있는데, 클래스가 Spring에 빈으로 등록될 때 Container가 해당 메소드를 호출하면서 자기 자신인 현재의 ApplicationContext를 주입한다는 것을 코드와 주석으로 확인할 수 있다. 그리고 궁극적으로 DispatcherServlet이 애플리케이션 컨텍스트를 주입받는 이유는 Spring MVC의 핵심 구성요소인 &lt;span style=&quot;color: #ee2323;&quot;&gt;HandlerMapping&lt;/span&gt;과 &lt;span style=&quot;color: #ee2323;&quot;&gt;HandlerAdapter&lt;/span&gt;라는 빈 등을 사용하기 위해서이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring MVC에서 HTTP 요청을 처리하는 핵심 메소드인 doDispatch()를 뜯어보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726472965991&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Process the actual dispatching to the handler.
 * &amp;lt;p&amp;gt;The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * &amp;lt;p&amp;gt;All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 */
@SuppressWarnings(&quot;deprecation&quot;)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request; // 실제로 처리할 HTTP 요청을 저장
    HandlerExecutionChain mappedHandler = null; // 요청을 처리할 handler를 저장하는 변수
    boolean multipartRequestParsed = false; // multipart 요청 여부 확인
    // 비동기 처리를 위한 객체
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // 현재 request를 처리할 수 있는 적합한 Controller를 검색. 없으면 404.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 현재 request를 처리할 수 있는 handlerAdapter를 검색
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            ...
            // handler 호출 전 필요한 작업 수행
            if (!mappedHandler.applyPreHandle(processedRequest, response))
                return;
            // 실제로 핸들러를 호출(실행)
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            // 비동기 처리가 시작됐는지 확인
            if (asyncManager.isConcurrentHandlingStarted())
                return;
            ...
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        ...
        // 결과를 클라이언트에게 전달
        processDispatchResult(processedRequest, response, ....);
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DispatcherServlet은 HandlerMapping을 통해 HTTP 요청에 맞는 Controller(Handler)를 찾고, HandlerAdapter를 사용해 해당 Handler를 실행한다. 즉, 웹 요청 처리를 위해 필요한 빈들을 애플리케이션 컨텍스트를 통해 전달받아 이를 참고해서 웹 요청과 응답 처리의 흐름을 제어하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 (특정 애노테이션을 추가하지 않은 일반적인 경우에) Controller에서 String을 반환하면, Spring MVC는 해당 값은 View 이름으로 간주한다. 따라서 마지막 processDispatchResult() 메소드 동작시 내부적으로 뷰 리졸버(ViewResolver)에 의해 JSP와 같은 View 파일을 찾아 결과를 렌더링한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 글에서는 Aware와 함께 상위에 위치하고 있는 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 다른 인터페이스인 Servlet에 대해 얘기해보겠습니다. &lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>AWARE</category>
      <category>dispatcherservlet</category>
      <category>HandlerAdapter</category>
      <category>HandlerMapping</category>
      <category>spring</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/68</guid>
      <comments>https://hoon93.tistory.com/68#entry68comment</comments>
      <pubDate>Thu, 26 Sep 2024 00:52:05 +0900</pubDate>
    </item>
    <item>
      <title>IoC/DI란? 그리고 Spring이 적용하는 OCP(개방 폐쇄 원칙)</title>
      <link>https://hoon93.tistory.com/67</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Container는 흔히 Spring IoC/DI Container 라고 불릴 정도로&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IoC와 DI는 Spring에서 빠질 수 없는 가장 대표적인 특징이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 DI(의존성 주입) 그리고 IoC(제어의 역전)라는 단어만 따로 보면 직관적으로 이해하기 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;분리되어 있는 두 개념을 한 번에 정리하면서,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체 지향 설계 원칙 중 하나인 OCP(개방 폐쇄 원칙)를 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Spring IoC/DI Container의 의미&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 주입(Dependency Injection)이라는 용어는 의존관계를 주입해 준다는 것을 말하는데, 여기서 '의존 관계'라는 건 Spring에서만의 특별한 개념이 아니라 어떤 A라는 객체가 B라는 클래스 or 오브젝트를 사용하는 것을 뜻한다. 이때 'A는 B에 의존하고 있다'라고 표현하며 다이어그램으로는 'A---&amp;gt;B' 라고 나타낸다. 그리고 '주입'을 해준다는 말은 레퍼런스를 넘겨준다는 것을 의미한다. 결론적으로 서로가 존재해야지만 제대로 동작하는 코드로 완성될 수 있는 두 개의 의존 관계에 있는 오브젝트를 &lt;span style=&quot;color: #ee2323;&quot;&gt;Spring이 런타임 시에 그 관계를 주입&lt;/span&gt;을 통해서 맺어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, Spring Container가 DI(객체 생성, 의존관계를 통한 주입 작업)를 수행하며 이렇게 객체의 생성과 관리를 개발자가 아닌 다른 주체(Spring)가 맡게 되어 제어권이 역전되었다고 하는 것이 Inversion of Control(IoC)이다. 추가적으로 일반적인 소프트웨어 설계 개념에서 시스템 내 서로 의존성을 가지는 모듈이나 클래스들을 묶어 주는 역할을 하는 것을 &quot;Assembler&quot;라고 하는데 그래서 Spring Container를 Assembler라고도 표현하는 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DI와 OCP(Open-Closed Principle, 개방 폐쇄 원칙)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;의존 주입과 개방 폐쇄 원칙.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2K2rR/dJMcajubJLY/TCA1ED2f8SU47oJXmK27JK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2K2rR/dJMcajubJLY/TCA1ED2f8SU47oJXmK27JK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2K2rR/dJMcajubJLY/TCA1ED2f8SU47oJXmK27JK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2K2rR%2FdJMcajubJLY%2FTCA1ED2f8SU47oJXmK27JK%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; alt=&quot;DI 와 OCP(개방 폐쇄 원칙)&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;333&quot; data-filename=&quot;의존 주입과 개방 폐쇄 원칙.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'개방 폐쇄 원칙'이란 용어를 보면 Open과 Closed 사이에 하이픈(hyphen, -)이 들어가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;열려있으면서(open) 동시에 닫혀있다(close)라는 말이 무엇일까? 이 표현만으로는 의미를 이해하기 어렵다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론부터 말하자면 &lt;span style=&quot;color: #ee2323;&quot;&gt;OCP란&lt;/span&gt; 클래스(모듈)는 확장에는 열려있어야 하고 변경에는 닫혀 있어야 한다는 것을 의미한다. 즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;클래스는 해당 클래스의 기능을 확장할 때 그 클래스의 코드는 변경되지 않아야 된다는 원칙&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 A와 B 클래스가 서로 의존관계에 있을 때, B의 변경(클래스명 등등)이 일어날 때마다 A의 코드도 변경해야 하는 부담이 생길 수 있다. 이러한 변경 가능성이 높은 코드의 결합도를 낮추기 위해 OCP를 적용하는 가장 대표적인 방법으로 B 클래스를 추상화한 Interface를 A가 의존하게 만드는 방식이 있다. 그리고 이 Interface를 구현한 클래스들을 만들도록 설계하면 A가 특정 클래스(B)에 의존하고 있지 않게 만들 수 있고 따라서, Interface를 구현한 클래스를 아무리 많이 만들어도 A의 코드를 수정할 필요가 없어지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725891739497&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HelloController {
    // 생성자를 통해 주입받으면서 의존관계가 더 명확하게 드러남
    private final HelloServiceInf helloServiceInf;
    
    public HelloController(HelloServiceInf helloServiceInf) {
    	this.helloServiceInf = helloServiceInf;
    }
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Controller는 Service클래스를 추상화한 Interface인 helloServiceInf를 의존 및 주입받고 있다. 하지만 소스 코드 레벨에서는 의존하지 않더라도 실제 런타임 시에는 Interface를 구현한 클래스 중 어떤 것을 사용할지 결정되어야 한다. 소스 코드에서는 Interface만 이용한다고 돼있으니까 연관관계(주입)를 만들어줘야 하는데, 이 작업을 하는 과정이 DI이며 DI를 해주는 존재가 Spring Container이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 Servlet Container는 Servlet Object를 직접 만들어서 사용하지만, 이와 다르게 Spring Container는 메타정보를 통해 싱글톤 Object를 만든다. 그리고 이때, Spring은 싱글톤 Object만 생성하는 것이 아닌 우리가 전달한 메타정보(= 구성 정보)를 이용해 Object가 사용할 다른 Object를 주입하는 작업까지 수행한다. 다시 말해, A객체가 B를 사용한다면 B 또한 Spring Container가 관리하는 Bean으로 등록한 후에 A가 사용할 수 있도록 주입을 하는 순서를 거친다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 Spring은 특정 Interface 타입을 DI할때 해당 Interface를 구현한 클래스가 있는지 찾아보고 만약 단일 주입 후보(단 한 개의 Bean Object)만 있다면, 해당 Bean을 자동으로 주입해 준다. 이러한 방식을 'Autowiring'이라고 부른다. 그래서 Spring에서 @Autowired라는 애노테이션을 통해 주입받을 수 있는 것이다. 만약 단일 주입 후보가 아니라면 어떤 방식으로든 객체 간의 의존 관계를 어떻게 매핑하고 주입하느냐를 Spring Container에게 알려줘야만 하며 이 과정이 없다면 에러가 발생한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>Autowiring</category>
      <category>dependency injection</category>
      <category>inversion of control</category>
      <category>OCP</category>
      <category>spring</category>
      <category>spring container</category>
      <category>개방 폐쇄 원칙</category>
      <category>스프링</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/67</guid>
      <comments>https://hoon93.tistory.com/67#entry67comment</comments>
      <pubDate>Sat, 21 Sep 2024 09:35:10 +0900</pubDate>
    </item>
    <item>
      <title>Servlet Container란? 그리고 Spring과의 관계</title>
      <link>https://hoon93.tistory.com/66</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Servlet과 Servlet Container&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Servlet은 URI정보 및 클라이언트의 요청 정보를 분석하는 기능을 담당하는 Java 표준 기술이다. 그리고 이 표준 기술을 이용한 Servlet Container 중 가장 대표적인 예로 Tomcat이 있다. 다시 말해, 우리가 많이 사용하는 Tomcat이라는 WAS는 (Servlet) Container 기술을 구현한 라이브러리 중의 하나이며, Tomcat 자체가 Java의 표준이 아니다. 이외에도 종류가 많은데 특히 Springboot에서는 Tomcat 과 Jetty, undertow(언더토우) 이 3가지의 Container를 Standalone하게 동작할 수 있도록 지원하고 있다(쉽게 변경 및 선택이 가능하다)&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725772424657&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Boot Reference Documentation&quot; data-og-description=&quot;This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/3.0.0-M1/reference/htmlsingle/#using.build-systems.starters&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/3.0.0-M1/reference/htmlsingle/#using.build-systems.starters&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/3.0.0-M1/reference/htmlsingle/#using.build-systems.starters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/3.0.0-M1/reference/htmlsingle/#using.build-systems.starters&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot Reference Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00; text-align: start;&quot;&gt; ※ Standalone : '독립적이다'란 단어로 '혼자서 동작할 수 있다'를 의미하는데, 다른 소프트웨어나 외부 서비스의 지원 없이 독립적으로 실행할 수 있는 애플리케이션을 말한다(Springboot의 톰캣을 예로 들자면 애플리케이션 자체에 내장돼있어 별도의 설치 없이 실행할 수 있다는 의미) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Servlet Container과 Spring Container&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 글에서 컨테이너란 무언가를 담아 관리하는 공간이라고 했었다. 마찬가지로 Servlet Container란 특정 기능(회원가입 등)의 처리를 담당하는 Web Component(Java에서는 Servlet)들의 라이프사이클을 관리한다. Client Request(웹 요청)가 왔을 때, 이 Servlet Container가 어떤 Servlet(Web Component)에게 처리를 위임할지 결정하는 Routing 역할까지 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725776423630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
// ServletWebServerFactory: 서블릿 컨테이너의 종류에 종속되지 않도록 추상화한 인터페이스
ServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
WebServer webServer = serverFactory.getWebServer(servletContext -&amp;gt; {
    // 생성한 서블릿 컨테이너에 서블릿을 추가
    servletContext.addServlet(&quot;Front Controller&quot;, new HttpServlet() {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ... {
            if (req.getRequestURI().equals(&quot;/hello&quot;) {
                System.out.println(&quot;hello URI로 매핑&quot;);
                if(req.getMethod().equals(HttpMethod.GET.name())) {
                    ...
                }
                ...
            }
            else if (req.getRequestURI().equals(&quot;/user&quot;)) {
                System.out.println(&quot;user URI로 매핑&quot;);
                ...
                resp.setStatus(HttpStatus.OK.value());
                resp.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
            }
            else {
                resp.setStatus(HttpStatus.NOT_FOUND.value()); //404
            }
        }
    }).addMapping(&quot;/hello&quot;); // URL 매핑
});
webServer.start();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Servlet을 계속 추가하는 방식을 이용하면 애플리케이션 입장에선 각각의 요청마다 웹 요청/응답을 위해 Request/Response Object를 매번 다뤄야 하는 방식이기 때문에 한계가 있다. 그래서 위 예제 코드와 같이 Servlet에 공통적으로 있는 코드를 중앙화하여 전, 후처리를 하는 개념인 'Front Controller'를 만드는 방식이 등장했다. 궁극적으로 Spring에서는 이 Front Controller와 같은 기능을 수행하는 'DispatcherServlet'이라는 서블릿 클래스가 존재한다. 이 오브젝트 덕분에 우리는 일일이 Request 정보를 분석하고 바인딩하는 코드를 작성하지 않아도 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Front Controller.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bATK26/btsJvYwTjm0/s7cmqVGjCNpRvc1AdyzpGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bATK26/btsJvYwTjm0/s7cmqVGjCNpRvc1AdyzpGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bATK26/btsJvYwTjm0/s7cmqVGjCNpRvc1AdyzpGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbATK26%2FbtsJvYwTjm0%2Fs7cmqVGjCNpRvc1AdyzpGK%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; alt=&quot;Servlet Container와 Spring Container&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;Front Controller.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 그림과 같이 Servlet Container를 통해서 Spring Container가 호출되며, 구체적으로 DispatcherServlet은 Spring Container에서 Controller에 값을 바인딩하거나 결과를 받아서 웹 응답 Object를 생성한다. 웹 요청을 처리하기 위해서는 Servlet Container가 반드시 필요하며, 이름이 비슷하다고 해서 뭔가 Servlet Container를 아예 대체하는 기술로 Spring Container라는 게 있는 것이 아니다. 즉, 기본적으로 자바의 웹 표준 기술을 사용하려면 Servlet Container가 필요하다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>dispatcherservlet</category>
      <category>Front Controller</category>
      <category>servlet</category>
      <category>Servlet Container</category>
      <category>spring</category>
      <category>spring container</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/66</guid>
      <comments>https://hoon93.tistory.com/66#entry66comment</comments>
      <pubDate>Tue, 17 Sep 2024 02:14:08 +0900</pubDate>
    </item>
    <item>
      <title>maven-default-http-blocker 에러 발생 원인 및 해결 방법(feat. IntelliJ)</title>
      <link>https://hoon93.tistory.com/65</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인텔리제이(IntelliJ)에 프로젝트 세팅을 하기 전에, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이클립스(Eclipse)에서 먼저 정상적으로 실행이 되는지 전부 확인하고 진행을 하는 편이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(아직 이클립스가 더 편함ㅠㅜ)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 Eclipse에서는 잘 동작하는 소스더라도 새롭게 IntelliJ에 첫 세팅을 하려고 하면 항상 문제가 발생한다. 이때 확인해 볼 부분이 몇 가지가 있는데, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 글에서는 Maven update 및 build 시 빌생하는 http blocker 에 대해서 얘기해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Blocked mirror for repositories&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Intellij에서 Maven update를 하려고보니 아래 에러 메시지를 뱉으면서 진행이 안된다.(당연한 말이지만 eclipse에서도 발생할 수 있다)&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Could not transfer artifact org.springframework.boot:... from/to maven-default-http-blocker (http://0.0.0.0/): Blocked mirror for repositories:&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 이런 문제가 발생하는걸까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 모든 것의 원흉(?)이자 근본적인 원인은 IntelliJ IDEA는 설치 시 자체 내장된 Maven과 함께 settings.xml 파일을 제공하기 때문이다. 따라서 직접 외부 Maven을 명시적으로 지정하지 않으면, 자동으로 디폴트 내장 Maven과 그 설정 파일을 사용하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bundled Maven.png&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7AUcP/btsJrZRzZXd/C82xdGeuFHFPU1UvabQr4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7AUcP/btsJrZRzZXd/C82xdGeuFHFPU1UvabQr4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7AUcP/btsJrZRzZXd/C82xdGeuFHFPU1UvabQr4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7AUcP%2FbtsJrZRzZXd%2FC82xdGeuFHFPU1UvabQr4k%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; alt=&quot;Build, Execution, Deployment&quot; loading=&quot;lazy&quot; width=&quot;693&quot; height=&quot;402&quot; data-filename=&quot;bundled Maven.png&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 비교적 최근에 설치한 IntelliJ에 내장된 Maven의 버전은 3.9.6&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Maven은 3.8.1 버전부터 기본적으로 HTTP로 연결된 Repository로부터의 다운로드를 차단하는 'HTTP Blocker' 기능을 포함하고 있다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보안 강화를 위한 것으로, 이 'HTTP Blocker'가 http에 대한 외부 연결을 막아 https의 사용을 강제하고 있던 것이였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 Maven을 사용하려면 위 사진과 동일한 경로인 [Settings -&amp;gt; Build, Execution, Deployment -&amp;gt; Build Tools-&amp;gt; Maven]에서 Maven home path의 경로가 올바르게 설정되어있는지 확인.(설정바꿨는데도 여전히 에러가 난다고요? 알고 있습니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨋든 이게 문제가 안되다면 다행이겠지만 보통 회사에서는 Nexus 등으로 라이브러리를 별도로 관리하는데, 이 내부망에 존재하는 Repository의 주소는 보통 https가 아닌 http로 관리되는 것 같다. Nexus에 SSL 인증서를 등록하여 HTTPS로 변경하면 모두가 편하겠지만, 이건 현실적으로 당장은 불가능하다. 그래서 관련 에러를 구글링해보면 해결책으로 아래 내용과 같이 설정파일(settings.xml)내 id가 'maven-default-http-blocker'인 부분을 주석 처리하는 방법들이 많이 있는 듯하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ settings.xml 위치: C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2024.2.0.2\plugins\maven\lib\maven3\conf (버전 등의 이유로 중간 파일명 확인)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725549753222&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;mirror&amp;gt;
  &amp;lt;id&amp;gt;maven-default-http-blocker&amp;lt;/id&amp;gt;
  &amp;lt;mirrorOf&amp;gt;external:http:*&amp;lt;/mirrorOf&amp;gt;
  &amp;lt;name&amp;gt;Pseudo repository to mirror external repositories initially using HTTP.&amp;lt;/name&amp;gt;
  &amp;lt;url&amp;gt;http://0.0.0.0/&amp;lt;/url&amp;gt;
  &amp;lt;blocked&amp;gt;true&amp;lt;/blocked&amp;gt; 
&amp;lt;/mirror&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;분명 이 방법으로 해결이 되는 것은 맞다. 근데 사실 내장 Maven 안 쓰려고 Maven home path 수정했던 것이고 이 또한 몇 번이나 제대로 설정했는지 확인했다... 심지어 ~/.m2/repository 파일 날리고 다시 강제 Maven update까지 했는데도 안된다. 왜 계속 build에 실패하는 거며 왜 위 파일까지 수정해야 하는 거지 싶어서 삽질한 결과, 그것은 바로 IntelliJ IDEA 자체의 캐시(Cache) 때문이라고 한다 .&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IntelliJ는 프로젝트를 빠르게 열고 코드를 탐색할 수 있도록 프로젝트 파일과 의존성, 빌드 정보를 인덱싱하고 이를 IntelliJ 설정 디렉토리 내에 캐시로 저장한다. 따라서 IntelliJ에서 라이브러리가 제대로 import 되지 않거나 변경사항이 적용되지 않을 때 이 캐시를 지워주면 해결되는 경우가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IntelliJ IDEA의 캐시를 정리하는 방법은 [File &amp;rarr; invalidate Caches...] 경로에서 'Invalidate and Restart' 버튼을 클릭하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Invalidate Caches.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5rzbV/btsJrJVJSDV/h4dFMDGdYd5BmUMgbRKeY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5rzbV/btsJrJVJSDV/h4dFMDGdYd5BmUMgbRKeY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5rzbV/btsJrJVJSDV/h4dFMDGdYd5BmUMgbRKeY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5rzbV%2FbtsJrJVJSDV%2Fh4dFMDGdYd5BmUMgbRKeY1%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;516&quot; height=&quot;354&quot; data-filename=&quot;Invalidate Caches.png&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러면 어렵게 설정 파일 건드리지 않고 기분 좋지 않게 문제 해결 완료.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;별것도 아닌 게 시간을 잡아먹는다 .&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발도구</category>
      <category>http blocker</category>
      <category>IntelliJ</category>
      <category>Maven</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/65</guid>
      <comments>https://hoon93.tistory.com/65#entry65comment</comments>
      <pubDate>Tue, 10 Sep 2024 22:57:47 +0900</pubDate>
    </item>
    <item>
      <title>Spring Container란 무엇인가?</title>
      <link>https://hoon93.tistory.com/64</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너(Container)란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;꼭 스프링(Spring)뿐만이 아니더라도 소프트웨어 분야에서는 컨테이너(Container)라는 용어가 자주 등장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물건을 해외로 보낼 때 항구에서 화물을 적재하기 위한 공간을 컨테이너라고 하듯이, 컨테이너라는 것은 결국 '무언가를 담는 공간'을 의미한다.(매번 그렇지만 다양한 맥락에서 사용되는 단어들은 무언가 되게 추상적으로 느껴진다 )&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 서버에서 동작하는 거의 대부분의 기술은 컨테이너 위에서 오브젝트를 만들어 놓고 필요에 따라 그 오브젝트를 제공받아 사용하는 방식으로 유지된다. Spring에서 몇 가지 예를 들자면, DispatcherServlet 그리고 DB와의 Connection을 연결해 주는 DataSource 등이 있겠다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725198140619&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;servlet&amp;gt;
    &amp;lt;servlet-name&amp;gt;dispatcher&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;org.springframework.web.servlet.DispatcherServlet&amp;lt;/servlet-class&amp;gt;
&amp;lt;/servlet&amp;gt;
&amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;dispatcher&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/&amp;lt;/url-pattern&amp;gt;
&amp;lt;/servlet-mapping&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 코드는 스프링 개발자라면 한 번씩은 들어봤을 DispatcherServlet이라는 오브젝트를 XML Config로 등록하는 부분이다. Web과 관련된 기술을 사용할 때 톰캣(Tomcat)과 같은 서블릿(Servlet) 기술을 이용하는데, 외부에서 요청이 들어오면 서블릿 컨테이너(Servlet Container)는 내부적으로 클라이언트 요청을 처리하기 위해 미리 등록해둔 이 Servlet 오브젝트를 꺼내와서 사용하게 하는 방식으로 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너(Spring Container)란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서블릿(Servlet)이라는 이름의 오브젝트가 서블릿 컨테이너(Servlet Container)에서 관리되듯이, 빈(Bean)이라는 이름으로 불리는 오브젝트의 생명주기를 관리하는 것이 스프링 컨테이너(Spring Container)이다. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Container&lt;/span&gt; 란 스프링이라는 기술 자체와 스프링과 연관된 수많은 기술을 통틀어 가장 핵심에 있으면서 모든 것의 기반이 되는 스프링의 기능이다. 그래서 Spring Container를 Spring이라고도 부르는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;springContainer.png&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Sz1bC/btsJnjBT340/PtqJxT8wT9JxhK8BhV2av0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Sz1bC/btsJnjBT340/PtqJxT8wT9JxhK8BhV2av0/img.png&quot; data-alt=&quot;The Spring IoC Container&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Sz1bC/btsJnjBT340/PtqJxT8wT9JxhK8BhV2av0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSz1bC%2FbtsJnjBT340%2FPtqJxT8wT9JxhK8BhV2av0%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; alt=&quot;Spring IoC Container&quot; loading=&quot;lazy&quot; width=&quot;474&quot; height=&quot;316&quot; data-filename=&quot;springContainer.png&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;The Spring IoC Container&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해, 이 컨테이너라는 것은 내부에서 오브젝트를 관리하기 위한 것이며, 따라서 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Spring Container&lt;/span&gt; 또한 '구성 정보'를 필요로 한다. 이때 구성 정보라는 것은 &quot;어떤 클래스의 오브젝트를 만들고 어떻게 연결할 것인가?&quot;에 대한 정보를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 우리는 크게 아래 2가지 정보를 Spring에게 알려줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 어떤 클래스를 런타임에 동작하는 오브젝트로 등록할 것인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 런타임에 각 빈들의 의존관계를 어떻게 맺을 것인가?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;코드로 보는 스프링 컨테이너&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;beanFactory.PNG&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FUH8N/btsJnaSxqBy/aCtT2btxV8wBZPTXkpJNxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FUH8N/btsJnaSxqBy/aCtT2btxV8wBZPTXkpJNxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FUH8N/btsJnaSxqBy/aCtT2btxV8wBZPTXkpJNxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFUH8N%2FbtsJnaSxqBy%2FaCtT2btxV8wBZPTXkpJNxk%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; alt=&quot;Spring Reference beans Introduction&quot; loading=&quot;lazy&quot; width=&quot;613&quot; height=&quot;120&quot; data-filename=&quot;beanFactory.PNG&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a title=&quot;SpringFramework Document&quot; href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/introduction.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 보면 BeanFactory 인터페이스가 모든 유형의 객체를 관리할 수 있는 Spring Framework의 IoC Container의 기초라고 설명돼있다. (ApplicationContext는 BeanFactory의 기능을 확장한 인터페이스로 더 많은 기능을 제공)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1725198351789&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    // 구성 정보를 담은 Config.class 내부에 Something 팩토리 매소드 존재
    BeanFactory beanFactory = new AnnotationConfigApplicationContext(Config.class);
    // 컨테이너로부터 Something 오브젝트를 제공받음
    Something service = (Something) beanFactory.getBean(Something.class);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 위 코드는 실제 컨테이너의 단순한 사용 예제이다. 즉, Spring Framework의 핵심인 Spring Container 또한 하나의 오브젝트로 존재한다는 것을 알 수 있다. 이 팩토리는 한번 오브젝트를 생성해서 리턴해주고 끝나는 게 아니라 계속 담아두는 공간이면서, 만들어진 오브젝트 사이에 관계 설정까지 해주는 Dependency Injection이 일어나는 컨테이너이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링에는 Web도 있고 Security도 있고 여러 가지 기술(최근에는 Spring AI까지 나옴.. )이 많지만, 극단적으로 말하면 아주 많이 활용되는 대표적인 케이스들을 모아 스프링이 우리가 개발해야 될 클래스들을 미리 만들어서 제공해 준 것에 불과한 것이다. 이게 없다고 하더라도 스프링이 기본적으로 제공해 주는 이 빈팩토리(BeanFactory)를 이용하면 객체지향 설계 원칙이 잘 적용된 애플리케이션을 만들어낼 수 있게 되는 것이다. 스프링의 가장 핵심적인 정체성은 Spring Container이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>ApplicationContext</category>
      <category>BeanFactory</category>
      <category>container</category>
      <category>spring</category>
      <category>SpringContainer</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/64</guid>
      <comments>https://hoon93.tistory.com/64#entry64comment</comments>
      <pubDate>Fri, 6 Sep 2024 01:01:14 +0900</pubDate>
    </item>
    <item>
      <title>Bean이란? 그리고 Spring의 싱글톤 활용(singleton registry)</title>
      <link>https://hoon93.tistory.com/63</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빈(Bean) 이란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Bean이란 컴포넌트 모델에서 유래한 JavaBeans에서 따온 단어이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컴포넌트 모델(Component Model)은 모듈화, 재사용성, 유지 보수성을 위해 발전해 온 소프트웨어 개발의 범용적인 개념인데, 자바(Java)는 이 개념을 기반으로 JavaBeans라는 개발 표준을 도입했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 Bean이라는 용어는 Java 초기부터 &lt;span style=&quot;color: #ee2323;&quot;&gt;'재사용 가능한 객체(Object)'&lt;/span&gt;를 지칭하는 용어로 자리잡았고, 이 개념이 스프링 프레임워크(Spring Framework)까지 이어지게 된 것이다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이로 인해, 스프링 컨테이너에 의해 관리되는 객체를 Bean이라는 용어로 부르게 된 것이며 자바에서 컴포넌트(Component)란 사실상 Bean과 동일한 의미로 사용된다. 결론적으로 애플리케이션의 기능을 담당하고 있는 클래스의 오브젝트를 Bean이라고 부르면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 빈 클래스(Bean Class): 실제로 애플리케이션이 시작될 때 오브젝트를 만드는 데 사용되는 클래스.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 빈 오브젝트(Bean Object) : 실제로 애플리케이션이 시작될 때 빈 클래스에 의해 만들어져 사용되는 오브젝트.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링의 싱글톤 레지스트리(Singleton Registry)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spring singleton.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tCThT/btsJiIOqUVZ/VSBdKQUbmf36Inpm8PsR60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tCThT/btsJiIOqUVZ/VSBdKQUbmf36Inpm8PsR60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tCThT/btsJiIOqUVZ/VSBdKQUbmf36Inpm8PsR60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtCThT%2FbtsJiIOqUVZ%2FVSBdKQUbmf36Inpm8PsR60%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; alt=&quot;Spring Framework Singleton VS Singleton Design Pattern&quot; loading=&quot;lazy&quot; width=&quot;855&quot; height=&quot;171&quot; data-filename=&quot;spring singleton.png&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Bean이란 곧 오브젝트이고 스프링(Spring)에 의해 관리된다는 것을 알아봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 우리 모두가 알다시피 스프링의 주요 특징으로 스프링은 수많은 Bean들을 싱글톤하게. 즉, 인스턴스가 오직 하나만 존재하도록 보장한다고 익히 들어왔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링이 아닌 자바로 구현한 싱글톤(Singleton Pattern)을 코드로 구현해 보면 일반적으로 아래의 모습과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1724737991480&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class A {
    // 외부에서 객체 생성을 막기 위한 private 생성자
    private A() {
    }

    public static A getInstance() {
        A instance = (A) SingletonRegistry.getInstance(&quot;A&quot;);
        if (instance == null) {
            instance = new A();
            SingletonRegistry.register(&quot;A&quot;, instance);
        }
        return instance;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1724736968335&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SingletonRegistry {

    // 생성된 인스턴스를 보관하기 위한 Map형태의 static 변수
    private static Map&amp;lt;String, Object&amp;gt; registry = new HashMap&amp;lt;&amp;gt;();

    private SingletonRegistry() {
    }

    // 인스턴스를 등록하는 static 메서드
    public static void register(String key, Object instance) {
    	// 인스턴스가 아직 생성되지 않았다면 생성
        if (!registry.containsKey(key)) {
            registry.put(key, instance);
        }
    }
    
    // 인스턴스를 반환하는 static 메서드
    public static Object getInstance(String key) {
        return registry.get(key);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;디자인 패턴에서 나오는 싱글톤 패턴은 static(정적) 메소드를 이용해 단 하나의 객체만 생성하고, 최초 생성 이후에는 동일한 객체를 반환하는 방식으로 설계된다. 이렇게 보면 스프링을 사용하지 않고도 위 코드만 활용하면 문제없는거 아닌가? 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 static메소드는 인스턴스를 생성하지 않고도 어디서든 쉽게 접근할 수 있다는 장점이 있는 반면, 전역적으로 사용되기 때문에 (애플리케이션 내 객체 간의 의존관계가 명확하지 않으면) 다양한 곳에서 무분별하게 사용될 수 있다는 잠재적 문제가 있다. 또한 정적 필드는 클래스 로딩 시점에 메모리에 할당되어, 외부에서 의존성 주입을 받을 수 없다는 가장 큰 문제가 있고, 이 때문에 테스트 환경을 구성하는 데 어려움이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;장점만 있을 것 같은 싱글톤 패턴의 단점에 대해서는 구글링 조금만해도 수많은 자료에서 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;심지어 디자인 패턴의 저자 중 한 명인 에릭 감마(Erich Gamma)는 책을 개정할 때, 싱글톤 패턴(Singleton Pattern)을 책에서 제외하고 싶었다는 &lt;a title=&quot;informIT Interview&quot; href=&quot;https://www.informit.com/articles/article.aspx?p=1404056&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인터뷰&lt;/a&gt;를 하기도 했는데, 이런 히스토리를 보면 참 재밌는 것 같다.(다른 공동 저자들이 의견에 찬성하지 않아 그냥 남겨두게 됐다고..ㅋㅋ)&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724744933087&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson | Design Patterns 15 Years Later:&quot; data-og-description=&quot;Home &amp;gt; Articles &amp;gt; Software Development &amp;amp; Management Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson Erich Gamma, Richard Helm, and Ralph Johnson talk to Larry O'Brien about Design Patterns, 15 years later. Lik&quot; data-og-host=&quot;www.informit.com&quot; data-og-source-url=&quot;https://www.informit.com/articles/article.aspx?p=1404056&quot; data-og-url=&quot;https://www.informit.com/articles/article.aspx?p=1404056&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.informit.com/articles/article.aspx?p=1404056&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.informit.com/articles/article.aspx?p=1404056&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson | Design Patterns 15 Years Later:&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Home &amp;gt; Articles &amp;gt; Software Development &amp;amp; Management Design Patterns 15 Years Later: An Interview with Erich Gamma, Richard Helm, and Ralph Johnson Erich Gamma, Richard Helm, and Ralph Johnson talk to Larry O'Brien about Design Patterns, 15 years later. Lik&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.informit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 서버 애플리케이션 입장에서는 클라이언트 요청마다 매번 새로운 인스턴스를 만들면 메모리 비용이 낭비될 수 있기 때문에 딱 하나의 오브젝트만 만들고 공유해서 사용하는 경우가 분명히 필요하다. 이때 Spring의 싱글톤을 사용하면 위와 같은 문제를 해결할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring을 사용하는 많은 이유가 있지만 기본적으로 Spring 자체가 일종의 Singleton Registry(싱글톤 레지스트리)로 기능을 하는데, 이는 위 코드와 같이 private 생성자를 사용해야 하는 방법이 아닌 평범한 자바 클래스를 싱글톤으로 활용할 수 있게 해준다. 결론적으로 Singleton Registry 인 스프링 컨테이너(Spring Container)는 빈(Bean) 생성뿐만 아니라 관계 설정 등에 대한 제어권까지 가지게 되면서 우리가 객체들을 손쉽게 싱글톤 방식으로 관리할 수 있게 해준다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>bean</category>
      <category>javaBeans</category>
      <category>Singleton</category>
      <category>singleton registry</category>
      <category>spring</category>
      <category>spring container</category>
      <category>싱글톤</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/63</guid>
      <comments>https://hoon93.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 1 Sep 2024 13:09:11 +0900</pubDate>
    </item>
    <item>
      <title>[OOP] 왜 Class가 아니라 Object Oriented Programming 일까?</title>
      <link>https://hoon93.tistory.com/62</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'객체지향 프로그래밍'의 정의를 찾아보면 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'소프트웨어 개발에서 객체를 중심으로 문제를 해결하는 방법론' 혹은 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'프로그램을 객체라는 단위로 구성하여 개발하는 프로그래밍 기법'이라고 많은 곳에서 알려준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;너무나 당연한 말처럼 알고있던 이 단어가 어느날 문득 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 Class Oriented 가 아니라 Object Oriented 라는 명칭으로 정해졌을까?라는 궁금점이 생겨 다시 정리해봤다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;객체지향 프로그래밍(OOP, Object-Oriented Programming)이란?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OOP 라는 용어를 모른다고 할지라도 특히나 Java로 개발을 하면 반드시 객체를 만들게 되어있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 우리가 만드는 건 오브젝트(Object)가 맞다. 하지만 사실 코딩을 할 땐 Object라는 단어를 한 글자씩 타이핑하진 않고 실제로 public class { ... } 이런 식으로 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리는 클래스를 만드는데 왜 '클래스 오리엔티드 프로그래밍'이라고 하지 않고 OOP라고 부를까??&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론부터 말하면, 우리는 클래스를 코딩하지만 그 클래스를 이용하여 실제로 메모리 위에서 기능을 수행하는 오브젝트를 만들기 때문이다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;결국 프로그래밍에서&lt;/b&gt;. 그리고 Java 개발에서. 더 나아가 스프링(Spring)에서조차 &lt;b&gt;가장 관심이 있는 것은&lt;/b&gt; '&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;메모리 위에서 실제로 동작하는 오브젝트&lt;/span&gt;&lt;/b&gt;라고 불리는 무언가'인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;클래스(Class)와 객체(Object), 인스턴스(Instance) 총정리&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;클래스와 인스턴스.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMrqOl/dJMcagqJSeH/OSuK0VXIJcTBuFKZ15Kzk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMrqOl/dJMcagqJSeH/OSuK0VXIJcTBuFKZ15Kzk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMrqOl/dJMcagqJSeH/OSuK0VXIJcTBuFKZ15Kzk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMrqOl%2FdJMcagqJSeH%2FOSuK0VXIJcTBuFKZ15Kzk1%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; alt=&quot;Class 와 instance&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;321&quot; data-filename=&quot;클래스와 인스턴스.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1) 클래스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a title=&quot;Oracle Java Document&quot; href=&quot;https://docs.oracle.com/javase/tutorial/java/concepts/class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JAVA 공식 문서&lt;/a&gt;에서는 클래스를 '객체를 정의하는 청사진(blueprint)'이라고 표현하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문장의 느낌을 보면 클래스는 Object를 만들기 위해 필요한 설계도라는 걸 알 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해, 우리는 클래스를 코딩하고 그 클래스를 이용해서 오브젝트를 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2) 객체(Object), 인스턴스(Instance)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설계도라고 표현하는 클래스로 무언가를 만들어내야 하는데, 그렇게 클래스의 내용을 실체화한 것이 오브젝트라고 설명했다. 그리고 클래스의 실체인 오브젝트를 Java에서는 '인스턴스'라고 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로그램을 실행했을 때 (그 클래스를 기반으로) 실제로 동작하는 무언가. 이 오브젝트가 기능을 수행하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해, 설계도인 클래스는 말 그대로 객체를 생성하는 템플릿 역할을 할 뿐, 실제로 메모리에 존재하면서 동작하는 것은 객체이다. 객체가 생성되고, 이 객체들이 서로 메시지를 주고받으며 프로그램이 실행된다. 따라서 런타임에서의 주체인 객체의 생성과 상호작용에 중점을 두는 것이 OOP인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클래스의 인스턴스가 곧 오브젝트&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이는 Java Document에 나오는 표현으로써, 자바에서 오브젝트가 뭔가요? 하면 &quot;클래스의 인스턴스입니다&quot; 라고 하는 것이 기술적으로 완벽한 답변이라고 한다. 즉, 이 객체라는 것의 다른 이름은 '클래스의 인스턴스'이다. 참고로 인스턴스란 '앞에 나온 추상적인 것에 대한 실체'라는 것을 뜻한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;3) 정리&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체지향 프로그래밍(Object Oriented Programming)은 특정 기능을 수행하는 로직이 담겨있는 설계도인 클래스를 만들고, 이 클래스들을 실체화한 객체들 간의 상호 작용으로 애플리케이션을 구성하는 방법을 의미한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>Class</category>
      <category>Object Oriented Programming</category>
      <category>OOP</category>
      <category>객체지향 프로그래밍</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/62</guid>
      <comments>https://hoon93.tistory.com/62#entry62comment</comments>
      <pubDate>Mon, 26 Aug 2024 20:28:40 +0900</pubDate>
    </item>
    <item>
      <title>HTTPie 설치 및 Terminal &amp;amp; IntelliJ에서의 사용법</title>
      <link>https://hoon93.tistory.com/61</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HTTPie라고 터미널에서 HTTP 요청을 간편하게 수행하고 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;응답까지 가독성좋게 출력해주는 오픈소스 도구가 있는데, &lt;br /&gt;이 HTTPie를 설치하는 방법과 IntelliJ에서 테스트하는 과정을 포스팅해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저처럼 Postman이나 웹브라우저로 매번 화면을 번갈아가며 사용하는 것도 귀찮아져&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;intelliJ IDE내 터미널에서 Rest API 테스트를 하고 싶은 분들한테 유용할 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Chocolatey를 통한 HTTPie 설치 및 테스트&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;HTTPie.png&quot; data-origin-width=&quot;1387&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qm4EH/btsIWbQ3nOh/49ShATo1Zp0cuGyxFVwLZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qm4EH/btsIWbQ3nOh/49ShATo1Zp0cuGyxFVwLZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qm4EH/btsIWbQ3nOh/49ShATo1Zp0cuGyxFVwLZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqm4EH%2FbtsIWbQ3nOh%2F49ShATo1Zp0cuGyxFVwLZK%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; alt=&quot;HTTPie 공식 홈페이지&quot; loading=&quot;lazy&quot; width=&quot;1387&quot; height=&quot;734&quot; data-filename=&quot;HTTPie.png&quot; data-origin-width=&quot;1387&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우리가 원하는 건 터미널에서 사용하는 방법이니 &lt;a title=&quot;Chocolatey 공식홈페이지&quot; href=&quot;https://httpie.io/desktop&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 사이트&lt;/a&gt;에서 HTTPie App을 다운받으면 안되겠죠.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;터미널에서 HTTPie를 이용하기 위해선 'Chocolatey'라는 Windows OS의 소프트웨어 패키지 관리 도구가 필요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(아래에서 나오지만 Python 패키지 관리 도구인 'pip'로도 설치 가능)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패키지 관리 도구라고 말했는데, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그냥 쉽게 말해 다양한 Windows 프로그램을 간편하게 설치하고 관리할 수 있는 툴이라고 이해하시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chocolatey Website.png&quot; data-origin-width=&quot;1605&quot; data-origin-height=&quot;767&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RQcNi/btsIVGw0hBI/Rm6a2hcNn46nd0z5HSvQBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RQcNi/btsIVGw0hBI/Rm6a2hcNn46nd0z5HSvQBK/img.png&quot; data-alt=&quot;Chocolatey Installing&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RQcNi/btsIVGw0hBI/Rm6a2hcNn46nd0z5HSvQBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRQcNi%2FbtsIVGw0hBI%2FRm6a2hcNn46nd0z5HSvQBK%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; alt=&quot;Chocolatey 공식 홈페이지&quot; loading=&quot;lazy&quot; width=&quot;1605&quot; height=&quot;767&quot; data-filename=&quot;chocolatey Website.png&quot; data-origin-width=&quot;1605&quot; data-origin-height=&quot;767&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Chocolatey Installing&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a title=&quot;Chocolatey Installing&quot; href=&quot;https://chocolatey.org/install#individual&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Chocolatey Installing&lt;/a&gt; 창에 들어가서 명령어를 복사합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722850407427&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;product&quot; data-og-title=&quot;Installing Chocolatey&quot; data-og-description=&quot;Chocolatey is software management automation for Windows that wraps installers, executables, zips, and scripts into compiled packages. Chocolatey integrates w/SCCM, Puppet, Chef, etc. Chocolatey is trusted by businesses to manage software deployments.&quot; data-og-host=&quot;chocolatey.org&quot; data-og-source-url=&quot;https://chocolatey.org/install#individual&quot; data-og-url=&quot;https://chocolatey.org/install&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ecJf77/hyWKCOgiX9/uUiPAje2kVNpxbZQe0Ibu1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eQ41d/hyWKFEebH5/cALcVyfe25kkG953bRLXlK/img.png?width=150&amp;amp;height=150&amp;amp;face=0_0_150_150,https://scrap.kakaocdn.net/dn/ztofd/hyWKuJsT1N/k4EMVIkNEIWAdgfhiRgnQk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://chocolatey.org/install#individual&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chocolatey.org/install#individual&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ecJf77/hyWKCOgiX9/uUiPAje2kVNpxbZQe0Ibu1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/eQ41d/hyWKFEebH5/cALcVyfe25kkG953bRLXlK/img.png?width=150&amp;amp;height=150&amp;amp;face=0_0_150_150,https://scrap.kakaocdn.net/dn/ztofd/hyWKuJsT1N/k4EMVIkNEIWAdgfhiRgnQk/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;Installing Chocolatey&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Chocolatey is software management automation for Windows that wraps installers, executables, zips, and scripts into compiled packages. Chocolatey integrates w/SCCM, Puppet, Chef, etc. Chocolatey is trusted by businesses to manage software deployments.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chocolatey.org&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;PowerShell Administrator running.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mw077/btsIWh4uL5l/KKEKLPgvR6FsKBvq3wtUT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mw077/btsIWh4uL5l/KKEKLPgvR6FsKBvq3wtUT1/img.png&quot; data-alt=&quot;powershell 관리자권한 실행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mw077/btsIWh4uL5l/KKEKLPgvR6FsKBvq3wtUT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMw077%2FbtsIWh4uL5l%2FKKEKLPgvR6FsKBvq3wtUT1%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;478&quot; height=&quot;264&quot; data-filename=&quot;PowerShell Administrator running.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;powershell 관리자권한 실행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;window 시작 검색하는곳에서 'Windows PowerShell'을 권리자 권한으로 실행하여 복사한 명령어를 실행.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;chocolatey Install Command.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0HA2R/btsIUTXOAz3/8r9xuuvTPZdkG9jbQCjzt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0HA2R/btsIUTXOAz3/8r9xuuvTPZdkG9jbQCjzt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0HA2R/btsIUTXOAz3/8r9xuuvTPZdkG9jbQCjzt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0HA2R%2FbtsIUTXOAz3%2F8r9xuuvTPZdkG9jbQCjzt0%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; alt=&quot;Set-ExecutionPolicy Chocolatey&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;463&quot; data-filename=&quot;chocolatey Install Command.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치가 완료되면 아래 명령를 통해 HTTPie 설치 진행.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722849388680&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;choco install httpie&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;choco install httpie.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8SWwh/btsIUwofEfk/1edudfPL9XY0EoKIurNPe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8SWwh/btsIUwofEfk/1edudfPL9XY0EoKIurNPe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8SWwh/btsIUwofEfk/1edudfPL9XY0EoKIurNPe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8SWwh%2FbtsIUwofEfk%2F1edudfPL9XY0EoKIurNPe1%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; alt=&quot;choco install httpie&quot; loading=&quot;lazy&quot; width=&quot;902&quot; height=&quot;473&quot; data-filename=&quot;choco install httpie.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;473&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마지막에 '스크립트를 실행할꺼야?'라는거에 저는 A(yes to all) 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 설치가 완료되면 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;버전 확인(PowerShell 재시작)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722844937182&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http --version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제없다면 http 및 https 요청 테스트를 해봅니다&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(광범위하게 사용되고 있는 GitHub의 공용 API로 진행)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722845009892&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http -v http://api.github.com
http -v https://api.github.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자 여기까지 문제없이 잘 진행됐다면.. 졸업을 축하드립니다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저의 경우 아래와 같이 SSL 에러가 발생&lt;s&gt;(쉽게되는게 없네...대체 왜 나만ㅠㅜ)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;http: error: SSLError: HTTPSConnectionPool(host='api.github.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1000)'))) while doing a GET request to URL: https://api.github.com/&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;http 요청은 잘 됐지만, https 요청에서 에러가 발생했습니다. 만약 저와 같은 문제가 발생한다면 아래 포스팅 확인.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것 때문에 한 3~4시간넘게 고생했는데, 결론부터 말하자면 pip를 통해 재설치하는 것으로 해결했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;pip을 통한 HTTPie 설치 및 테스트&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ 찾아보니 &quot;피아이피&quot;가 아닌, &quot;핍&quot;이라고 불림.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 기존 설치했던 HTTPie 관련 패키지 제거&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722847721263&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip uninstall httpie requests certifi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) HTTPie를 재설치하기 전에 Python과 pip를 최신 버전으로 설치&lt;/span&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;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Python 최신 버전 설치&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;python downloads windows.png&quot; data-origin-width=&quot;1335&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FaVyL/btsIUSdzAfN/I2sGDOquEG6p4gXTluQ6kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FaVyL/btsIUSdzAfN/I2sGDOquEG6p4gXTluQ6kk/img.png&quot; data-alt=&quot;python downloads windows&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FaVyL/btsIUSdzAfN/I2sGDOquEG6p4gXTluQ6kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFaVyL%2FbtsIUSdzAfN%2FI2sGDOquEG6p4gXTluQ6kk%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; alt=&quot;Python 공식 홈페이지&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;396&quot; data-filename=&quot;python downloads windows.png&quot; data-origin-width=&quot;1335&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;python downloads windows&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;Python 공식 웹사이트&quot; href=&quot;https://www.python.org/downloads/windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Python 공식 웹사이트&lt;/a&gt;에서 최신 버전을 다운로드하고 설치 진행.&lt;/p&gt;
&lt;figure id=&quot;og_1722850483135&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Python Releases for Windows&quot; data-og-description=&quot;The official home of the Python Programming Language&quot; data-og-host=&quot;www.python.org&quot; data-og-source-url=&quot;https://www.python.org/downloads/windows&quot; data-og-url=&quot;https://www.python.org/downloads/windows/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dldeJb/hyWKGwmEHI/18E7zmqcjJWeux7QqBxyR1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://www.python.org/downloads/windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.python.org/downloads/windows&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dldeJb/hyWKGwmEHI/18E7zmqcjJWeux7QqBxyR1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&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;Python Releases for Windows&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The official home of the Python Programming Language&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.python.org&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;pip 최신 버전 설치&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1722850931588&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python -m pip install --upgrade pip&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 Python 설치 시 pip도 함께 설치된다고 하지만, 눈으로 확인하기 전엔 믿을 수 없으니.. 확실하게 명령어 실행!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) HTTPie 재설치&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722848639291&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install httpie&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;pip를 통해 httpie재설치.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CvLz0/btsIWgLpeeh/S4ccWg1KD5i3U2Bj1n4kOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CvLz0/btsIWgLpeeh/S4ccWg1KD5i3U2Bj1n4kOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CvLz0/btsIWgLpeeh/S4ccWg1KD5i3U2Bj1n4kOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCvLz0%2FbtsIWgLpeeh%2FS4ccWg1KD5i3U2Bj1n4kOk%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; alt=&quot;pip install httpie&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;740&quot; data-filename=&quot;pip를 통해 httpie재설치.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) HTTPie 설치 확인 및 IntelliJ에서 요청 테스트&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1722848744598&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http --version&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;설치가 완료되면, HTTPie와 관련 패키지가 올바르게 설치되었는지 확인! 또 확인!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;https Test success.png&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK8WRu/btsIVJgbBRe/umcCxZZ4k5N46rVX7nnkv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK8WRu/btsIVJgbBRe/umcCxZZ4k5N46rVX7nnkv0/img.png&quot; data-alt=&quot;IntelliJ IDE Terminal&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK8WRu/btsIVJgbBRe/umcCxZZ4k5N46rVX7nnkv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK8WRu%2FbtsIVJgbBRe%2FumcCxZZ4k5N46rVX7nnkv0%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; alt=&quot;IntelliJ IDE Terminal&quot; loading=&quot;lazy&quot; width=&quot;515&quot; height=&quot;407&quot; data-filename=&quot;https Test success.png&quot; data-origin-width=&quot;715&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IntelliJ IDE Terminal&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PowerShell 안에서도 가능하지만 원래의 목적대로 IntelliJ에서 확인하고 싶은 Api로 최종 테스트 진행.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;IntelliJ에서 터미널을 여는 단축키는 ALT+F12 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 삽질을 해봤으나 결국 재설치하여 해결 완료 &lt;/span&gt;&lt;/p&gt;</description>
      <category>개발도구</category>
      <category>CHOCO</category>
      <category>Chocolatey</category>
      <category>HTTP</category>
      <category>httpie</category>
      <category>https</category>
      <category>httpsconnectionpool</category>
      <category>IntelliJ</category>
      <category>PIP</category>
      <category>python</category>
      <category>SSLError</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/61</guid>
      <comments>https://hoon93.tistory.com/61#entry61comment</comments>
      <pubDate>Mon, 5 Aug 2024 20:16:45 +0900</pubDate>
    </item>
    <item>
      <title>이클립스 Java Build Path와 Deployment Assembly 그리고 target</title>
      <link>https://hoon93.tistory.com/60</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트의 Properties 창을 보면 여러 설정들이 있는데,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;target이란&amp;nbsp;디렉토리를 알아보던 중 Java Build Path와 Deployment Assembly 에 대해 정리해 봤다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java Build Path&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_java Build Path 설정탭.PNG&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1xZXL/btshaTykNSp/445hQ6Z4kqRwUfieXpUhk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1xZXL/btshaTykNSp/445hQ6Z4kqRwUfieXpUhk1/img.png&quot; data-alt=&quot;Java Build Path항목의 Source탭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1xZXL/btshaTykNSp/445hQ6Z4kqRwUfieXpUhk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1xZXL%2FbtshaTykNSp%2F445hQ6Z4kqRwUfieXpUhk1%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; alt=&quot;Properties의 Java Build Path&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;358&quot; data-filename=&quot;edited_java Build Path 설정탭.PNG&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Java Build Path항목의 Source탭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) Source 탭은 컴파일 및 빌드 과정에서 소스 파일들을 이용할 수 있도록 프로젝트 내의 Java 파일 등의 경로를 지정하는 곳이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 이클립스에서 'Build'는 주로 컴파일을 의미하기에, Java Compile Path라고 생각하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 'Output folder'의 경로는 java가 소스파일들을 컴파일하여 실행 가능한 .class 파일을 생성하는 경로를 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제로 '프로젝트명/target/classes' 폴더를 열어보면 각종 설정 파일 및 xml 파일 또한 위치해있는데, 그 이유는 Java코드가 런타임에 이러한 리소스들을 의존하고 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Deployment Assembly&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Web Deployment Assembly.PNG&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chYVMk/btshbtMCSmk/1hAExoMAxZEYn4kMp4ovG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chYVMk/btshbtMCSmk/1hAExoMAxZEYn4kMp4ovG1/img.png&quot; data-alt=&quot;Web Deployment Assembly 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chYVMk/btshbtMCSmk/1hAExoMAxZEYn4kMp4ovG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchYVMk%2FbtshbtMCSmk%2F1hAExoMAxZEYn4kMp4ovG1%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; alt=&quot;Properties의 Deployment Assembly&quot; loading=&quot;lazy&quot; width=&quot;665&quot; height=&quot;326&quot; data-filename=&quot;Web Deployment Assembly.PNG&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Web Deployment Assembly 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Properties의 &quot;(Web) Deployment Assembly&quot;는 Spring 프로젝트에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;웹 애플리케이션의 소스들이 WAR파일 내에서 어떻게 배치되는지를 정의&lt;/span&gt;하는 항목이다. Maven은 해당 설정 및 pom.xml의 내용을 참고하여 빌드를 수행한다.&lt;/span&gt;&lt;/p&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: 34px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 6.25%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;Source&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 6.25%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;Deploy Path&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 6.25%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Source Path들은 프로젝트 내의 소스 경로를 나타냄.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 6.25%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring 프로젝트의 소스들이 &lt;br /&gt;WAR파일 내에서 실제로 배치될 경로를 나타냄.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(위 사진과 같이 WAR파일 내의 폴더 구조를 기준으로 지정된다)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 일반적으로 웹 애플리케이션의 root 경로 '/' 가 &lt;br /&gt;프로젝트 소스의 '/src/main/webapp'으로 알고 있는데, &lt;br /&gt;이 또한 Deploy Path에서 확인이 가능하다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 때문에 WAR파일 폴더구조(웹 애플리케이션의 구조)가 webapp 하위 경로에서부터 시작하는 것이며, 그래서 URL에 &lt;br /&gt;'/index.jsp'를 치면 webapp/index.jsp파일이 실행되는 것.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 웹 애플리케이션은 정적인 리소스와 동적인 리소스를 구분하여 관리되며, Maven도 이 구조와 마찬가지로 리소스들을 관리한다. 다음은 이클립스에서 Maven Build 진행 시의 핵심 과정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byJzA3/btshb7v5IHT/g2QAoiDzTPbBqIc6HkqzY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byJzA3/btshb7v5IHT/g2QAoiDzTPbBqIc6HkqzY1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;224&quot; data-filename=&quot;pomXml.PNG&quot; style=&quot;width: 50.7849%; margin-right: 10px;&quot; data-widthpercent=&quot;51.38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byJzA3/btshb7v5IHT/g2QAoiDzTPbBqIc6HkqzY1/img.png&quot; alt=&quot;pom.xml의 build태그&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyJzA3%2Fbtshb7v5IHT%2Fg2QAoiDzTPbBqIc6HkqzY1%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;457&quot; height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFsZuq/btshiD1bBGs/y3Sk5jr9p1D019eyZb79p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFsZuq/btshiD1bBGs/y3Sk5jr9p1D019eyZb79p0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;273&quot; data-filename=&quot;edited_target폴더.PNG&quot; width=&quot;400&quot; height=&quot;207&quot; style=&quot;width: 48.0523%;&quot; data-widthpercent=&quot;48.62&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFsZuq/btshiD1bBGs/y3Sk5jr9p1D019eyZb79p0/img.png&quot; alt=&quot;target 폴더 하위의 각종 파일들&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFsZuq%2FbtshiD1bBGs%2Fy3Sk5jr9p1D019eyZb79p0%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;527&quot; height=&quot;273&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;pom.xml과 target 폴더&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 컴파일 진행하여 'target/classes' 폴더에 클래스 파일들을 위치시킴.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 정적인 리소스 파일들(HTML, CSS, JavaScript, 이미지 파일 등)을 관리하는 폴더도 생성함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;rarr; pom.xml에 적혀있는 위치(target/${project.name})에 정적 리소스들을 복사.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;rarr; 'target/${project.name}' 폴더로 자동 생성되는 것은 Maven의 기본 동작.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 궁극적으로 Maven은 1번 및 2번 동작으로 미리 만들어둔 파일들과 이외 각종 결과물들을 활용해 WAR파일을 생성함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) '프로젝트명/target' 폴더는 컴파일 및 빌드 시 생성되는 파일들이 위치하는 곳이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) Maven은 빌드 시에 리소스를 컴파일하고 정적인 파일들을 위치시키며, 이를 패키징해서 실행 가능한 아티팩트인 WAR파일 생성하는데, 이때 프로젝트의 구조와 설정을 기반으로 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, Maven은 &quot;Web Deployment Assembly&quot;탭의 설정과 pom.xml의 정의된 내용을 참고하여 빌드를 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 WAR 뿐만 아니라 다른 리소스들 또한 pom.xml에 작성된 경로(일반적으로 ${project.basedir}/target)에 모두 위치한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>deployment assembly</category>
      <category>Eclipse</category>
      <category>java build path</category>
      <category>Maven</category>
      <category>Properties</category>
      <category>target</category>
      <category>War</category>
      <category>이클립스</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/60</guid>
      <comments>https://hoon93.tistory.com/60#entry60comment</comments>
      <pubDate>Wed, 24 May 2023 23:28:49 +0900</pubDate>
    </item>
    <item>
      <title>경로(Path) 설정시 *, ** 차이</title>
      <link>https://hoon93.tistory.com/59</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;별표 == 애스터리스크(Asterisk) == 와일드카드(Wildcard)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;xml_Asterisk.PNG&quot; data-origin-width=&quot;861&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgYqGj/btr9Yl7Ey5n/98fL9vQIsUFhLar5LXIV1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgYqGj/btr9Yl7Ey5n/98fL9vQIsUFhLar5LXIV1K/img.png&quot; data-alt=&quot;XML 설정파일에서의 경로 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgYqGj/btr9Yl7Ey5n/98fL9vQIsUFhLar5LXIV1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgYqGj%2Fbtr9Yl7Ey5n%2F98fL9vQIsUFhLar5LXIV1K%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;861&quot; height=&quot;429&quot; data-filename=&quot;xml_Asterisk.PNG&quot; data-origin-width=&quot;861&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;XML 설정파일에서의 경로 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한국어로는 별표인 *는 애스터리스크 또는 와일드카드라고 불리는데, 개발 소스에서 각종 path 설정을 하는데 있어 자주 사용되기에 정확하게 어떤 의미인지 궁금했다. 찾아보면 Spring설정 파일을 포함하여 많은 프로그래밍 언어에서 패턴 또는 Select쿼리에서 다른 문자를 일치시키거나 대체하는데 사용된다고 설명돼있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;쉽게 정리하자면, * 와 ** 는 여러 파일 및 디렉토리와 일치하는 경로 또는 특정 패턴을 지정하는데 사용하는 문자다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로 동일한 의미인 애스터리스크와 와일드카드가 마치 서로 다른 용어처럼 불리는 이유는 사용되는 분야나 문맥에 따라 다르기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;애스터리스크(Asterisk)&lt;/b&gt; : 일반적으로 프로그래밍 언어(특히 C, C++, JAVA 등)에서 사용되는 용어.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;와일드카드(Wildcard)&lt;/b&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;* 와 ** 의 차이점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000; color: #ee2323;&quot;&gt;&lt;b&gt;1. ** : 0개 이상의 디렉토리 및 모든 하위 디렉토리를 의미.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1681352181044&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;path=&quot;/path/**/*file.txt&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 경로 예시에서의 의미는 &quot;/path&quot; 디렉토리 아래의 모-든 디렉토리 및 그 하위에 있는 모든 디렉토리 내에서 &quot;file.txt&quot;로 끝나는 모든 파일을 포함한다는 것을 뜻한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ 현재 디렉토리인 &quot;/path&quot; 아래에 바로있는 file.txt파일도 포함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000; color: #ee2323;&quot;&gt;&lt;b&gt;2. * : 단일 디렉토리 or 파일 이름에서 0개 이상의 문자와 일치해야함을 의미.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1681352206853&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;path=&quot;/path/*/*file.txt&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 예제의 의미는 &quot;/path&quot; 디렉토리 밑에 있는 단일 디렉토리 내에서 &quot;file.txt&quot;로 끝나는 모든 파일을 뜻한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫번째 예시에서의 ** 패턴과 다르게 * 패턴을 사용했기 때문에, &quot;/path&quot;디렉토리에 바로 존재하는 &quot;file.txt&quot;라고 끝나는 이름의 파일은 무시하고, 하위의 단일 디렉토리에서만 일치시킨다는 점에 유의해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;※ 추가적으로 * 는 하위 디렉토리의 하위 디렉토리같은 더 깊은 수준의 경로에서는 일치시키지 않는다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>spring #스프링 #java #xml설정 #별표 #애스터리스크 #와일드카드</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/59</guid>
      <comments>https://hoon93.tistory.com/59#entry59comment</comments>
      <pubDate>Thu, 13 Apr 2023 21:43:58 +0900</pubDate>
    </item>
    <item>
      <title>Vue.js 정의 및 핵심 기능</title>
      <link>https://hoon93.tistory.com/58</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Vue.js 란 무엇인지 그리고 핵심 컨셉인&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 바인딩(Data Binding)과 컴포넌트(Component)에 대해 간략히 정리해보자.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Vue.js란??&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;vuejs.png&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNmH3E/btq3SY7gnGU/KimsFoDkC1BK7KXRZtrDpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNmH3E/btq3SY7gnGU/KimsFoDkC1BK7KXRZtrDpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNmH3E/btq3SY7gnGU/KimsFoDkC1BK7KXRZtrDpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNmH3E%2Fbtq3SY7gnGU%2FKimsFoDkC1BK7KXRZtrDpK%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; alt=&quot;vue logo&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;417&quot; data-filename=&quot;vuejs.png&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;vue.js 자체는 프론트엔드. 즉, View와 관련된 처리들을 개발자 대신 관리해주는 라이브러리이다. 그래서 마치 jQuery를 이용하듯이 Script코드 몇 줄만 작성하면 곧바로 VUE를 적용할 수 있다. 공식 문서에서는 Progressive Framework라고 소개되는데, 이것은 처음 말한 것과 같은 간단한 방법으로 기존 프로젝트를 vue.js로 부분부분 점진적으로 또 쉽게 업데이트해나가며 전환할 수 있음을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 여기서 프레임워크라는 단어가 나온다. VUE가 라이브러리이면서 프론트엔드 자바스크립트 프레임워크인 이유는 많은 확장 기능과 개발 지원 도구가 제공되어, 큰 규모의 SPA(Single Page Application, 단일 페이지 애플리케이션)도 만들 수 있도록 설계를 도와주기 때문이다. 따라서 VueCLI, VueRouter, Vuex등의 라이브러리와 연계하여 Vue.js를 프레임워크로써 사용하면 대규모 애플리케이션의 기본 규칙과 뼈대를 빠르게 결정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 바인딩(Data Binding)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 개념에 대한 정의 이전에 아래 간단한 코드를 먼저 봐보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ {{ message }}와 같이 이중 중괄호로 감싸는 것은 데이터를 렌더링하는 방식인 Mustache라고 부르는 기법으로, 텍스트 콘텐츠의 해당 위치에 message속성을 bind함을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619854737181&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;div id=&quot;app&quot;&amp;gt;
    &amp;lt;h1&amp;gt;{{ message }}&amp;lt;/h1&amp;gt;
  &amp;lt;/div&amp;gt;
  
  &amp;lt;script src=&quot;https://cdn.jsdelivr.net/npm/vue@2.6.0&quot;&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;script&amp;gt;
    var app = new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue.js'
      }
    })
  &amp;lt;/script&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;&lt;span style=&quot;color: #000000;&quot;&gt;Vue.js는 html과 같이 태그를 읽어 들이는 형태이기에 빠르게 학습할 수 있다는 장점이 있다. 아마 이 부분이 인기있는 이유 중 하나일 듯 싶다. 위 코드를 실행시키면, 다음과 같은 문자가 출력된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;hello Vue.png&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;85&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Kj8b/btq3SS7dfEb/57jQT6z5iqDDtenjA4R3a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Kj8b/btq3SS7dfEb/57jQT6z5iqDDtenjA4R3a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Kj8b/btq3SS7dfEb/57jQT6z5iqDDtenjA4R3a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Kj8b%2Fbtq3SS7dfEb%2F57jQT6z5iqDDtenjA4R3a1%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; alt=&quot;vue 실행&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;85&quot; data-filename=&quot;hello Vue.png&quot; data-origin-width=&quot;304&quot; data-origin-height=&quot;85&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 '개발자 도구(F12)'에서 app.message = 'Hi Hoon';로 문자열 변경.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;데이터바인딩.png&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxnHhc/btq3TxBj65u/qxNfNizRn4sDhsB8VFN6R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxnHhc/btq3TxBj65u/qxNfNizRn4sDhsB8VFN6R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxnHhc/btq3TxBj65u/qxNfNizRn4sDhsB8VFN6R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxnHhc%2Fbtq3TxBj65u%2FqxNfNizRn4sDhsB8VFN6R1%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; alt=&quot;vue data binding&quot; loading=&quot;lazy&quot; width=&quot;205&quot; height=&quot;82&quot; data-filename=&quot;데이터바인딩.png&quot; data-origin-width=&quot;205&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그저 자바스크립트의 데이터 값을 변경했을 뿐인데, 렌더링 내용이 곧바로 변경됐다. 이를 JSP에서 JavaScript로 구현했다면 기본적으로 Element를 찾은 다음에 상태 값을 변경해야 했을텐데, 이 번거로운 작업이 간단하게 처리됐다. 이것이 Vue의 강력한 핵심 기능인 데이터 바인딩이다. Vue.js는 데이터가 먼저 존재하고 이 데이터를 기반으로 DOM을 구축한다. 즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;데이터의 상태에 따라 DOM의 렌더링을 동기화&lt;/span&gt;하는 데이터 지향(Data Driven)적인 애플리케이션을 설계한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;컴포넌트(Component)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사이트의 규모가 커지면 HTML 파일뿐만 아니라 Javascript, CSS의 소스코드도 많아져 각각의 기능들이 어떤 HTML과 연결되어 있는지 파악하기 힘들어진다. 예를 들어 하나의 Javascript 파일은 다양한 기능들로 구성&amp;amp;수정될 수 있고, 또 이것을 여러 HTML 파일들에서 사용하고 있을 수 있기 때문에 유리 관리가 힘들어지고 불필요한 소스코드를 포함하게 될 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;vue component.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwpq5V/btq3UsthtLF/ZyCp60Kj6OFNu4i7YocAPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwpq5V/btq3UsthtLF/ZyCp60Kj6OFNu4i7YocAPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwpq5V/btq3UsthtLF/ZyCp60Kj6OFNu4i7YocAPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwpq5V%2Fbtq3UsthtLF%2FZyCp60Kj6OFNu4i7YocAPk%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; alt=&quot;컴포넌트&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;720&quot; data-filename=&quot;vue component.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이에 Vue.js는 한 UI에 해당하는 필요한 소스코드는 하나의 파일에 묶어서 관리할 수 있도록 컴포넌트(Component)라는 기능을 제공한다. 즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;컴포넌트란 HTML과 Javascript, CSS를 한 세트로 하여 기능별로 묶어서 사용하는 UI 부품&lt;/span&gt;이라고 할 수 있다. 따라서 화면의 Header 또는 Footer 등의 특정 기능을 컴포넌트로 만들면, 하나의 파일에 외관(디자인 요소)뿐만 아니라 어떤 속성을 가지고 있으며, 어떤 액션을 하는지를 모두 포함시킬 수 있다. 정리하자면, 컴포넌트는 서로 관련있는 소스코드를 묶어서 재사용 및 유지관리를 수월하게 만든다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>javascript</category>
      <category>component</category>
      <category>framework</category>
      <category>Reactive</category>
      <category>vue.js</category>
      <category>데이터바인딩</category>
      <category>컴포넌트</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/58</guid>
      <comments>https://hoon93.tistory.com/58#entry58comment</comments>
      <pubDate>Sat, 1 May 2021 17:15:29 +0900</pubDate>
    </item>
    <item>
      <title>2개 이상의 다중 컬럼으로 기본키 지정하기</title>
      <link>https://hoon93.tistory.com/57</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개발 업무를 하려면 필수적으로 ERD를 보게 되는데, 그냥 어렴풋이 넘겨짚었던 개념을 이제서야 정리한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB테이블의 Properties 또는 ERD를 보면 아래 예제처럼 2개 이상의 칼럼이 Primary Key로 지정된 테이블을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pJ3Cf/btq1D0FAgOz/kNXvf0DQWlAPWeVfTbyFm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pJ3Cf/btq1D0FAgOz/kNXvf0DQWlAPWeVfTbyFm1/img.png&quot; data-filename=&quot;compositeKey.png&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;191&quot; style=&quot;width: 74.0691%; margin-right: 10px;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pJ3Cf/btq1D0FAgOz/kNXvf0DQWlAPWeVfTbyFm1/img.png&quot; alt=&quot;복합키&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpJ3Cf%2Fbtq1D0FAgOz%2FkNXvf0DQWlAPWeVfTbyFm1%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;785&quot; height=&quot;191&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cb5lWH/btq1C0Z6s18/iBQZa3bmgrnZF53kZtH4r0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cb5lWH/btq1C0Z6s18/iBQZa3bmgrnZF53kZtH4r0/img.png&quot; data-filename=&quot;Entity IE notation.png&quot; data-origin-width=&quot;290&quot; data-origin-height=&quot;211&quot; style=&quot;width: 24.7695%;&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cb5lWH/btq1C0Z6s18/iBQZa3bmgrnZF53kZtH4r0/img.png&quot; alt=&quot;정보공학 표기법 ERD&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcb5lWH%2Fbtq1C0Z6s18%2FiBQZa3bmgrnZF53kZtH4r0%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;290&quot; height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지 대강 느낌적으로 잘 사용해서 다행이었지만, 하나의 테이블에 어떻게 기본키가 여러 개 존재할 수 있지?라는 의문을 마음 한구석에 쌓아 두고 있었다. 그래서 확인해 본 결과 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;테이블은 오직 하나의 기본키(PK)를 가질 수 있다&quot;라는 것은 정확한 정의&lt;/span&gt;라는 것을 확인했다. 근데 여기서 &lt;u&gt;포인트는 PK를 오직 하나의 컬럼으로만 설정할 수 있다는 것으로 잘못 해석하면 안 된다&lt;/u&gt;라는 점이다. 나는 이 부분을 알고 있다고 착각하고 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해, 위 테이블에서 PK로 설정된 &quot;PRODUCT_GROUP_CD&quot;, &quot;PRODUCT_CATEGORY_CD&quot; 두 칼럼은 각각의 칼럼이 Unique하게 중복되면 안 되는 것이 아니라, 두 칼럼을 합쳐서 봤을 때 중복이 아니라면 무결성의 원칙을 지키는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;예) PK의 속성으로 A와 B라는 두 칼럼이 있다면 [ A = 1, B = 1 ] , [ A = 1, B = 2 ] 는 중복된 것이 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 정확히는 복합키(Composite Key)를 만드는 것이지 기본키(PK)가 복수라는 것은 잘못된 표현이다. 따라서 다음 SQL문에서도 에러가 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;primaryKey Error.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yNnos/btq140j4qef/Ck2I9VcqFScDw03hYkE6KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yNnos/btq140j4qef/Ck2I9VcqFScDw03hYkE6KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yNnos/btq140j4qef/Ck2I9VcqFScDw03hYkE6KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyNnos%2Fbtq140j4qef%2FCk2I9VcqFScDw03hYkE6KK%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; alt=&quot;기본키 중복 에러&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;378&quot; data-filename=&quot;primaryKey Error.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위에서 말했다시피, 테이블에 PK를 복수로 생성할 수 없다. 그렇다면 기본키를 구성하는 칼럼을 복수로 설정하기 위해서는 어떻게 해야 할까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;primaryKey insert.png&quot; data-origin-width=&quot;1109&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4wdYk/btq11Mz0juv/GJO9DuH3Pi426WrrkvISM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4wdYk/btq11Mz0juv/GJO9DuH3Pi426WrrkvISM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4wdYk/btq11Mz0juv/GJO9DuH3Pi426WrrkvISM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4wdYk%2Fbtq11Mz0juv%2FGJO9DuH3Pi426WrrkvISM1%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; alt=&quot;기본키 제약조건&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;643&quot; data-filename=&quot;primaryKey insert.png&quot; data-origin-width=&quot;1109&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 SQL문과 같이 Primary Key 제약조건으로 속성들을 괄호 안에 설정해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리하자면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;기본키를 구성하는 칼럼은 복수일 수 있지만, 기본키가 복수일 수는 없다&quot;&lt;/span&gt; 이것만 생각하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 부분은 두 테이블 간의 '&lt;a href=&quot;https://hoon93.tistory.com/22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;식별자와 비식별자 관계&lt;/a&gt;' 이론과도 연결되는 개념인데, 실무에서 당황하지 않고 적용시킬 수 있도록 다시 제대로 볼 필요가 있음을 느낀다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Database</category>
      <category>composite key</category>
      <category>constraint</category>
      <category>creat table</category>
      <category>PK</category>
      <category>PRIMARY KEY</category>
      <category>기본키</category>
      <category>복합키</category>
      <category>비식별자</category>
      <category>식별자</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/57</guid>
      <comments>https://hoon93.tistory.com/57#entry57comment</comments>
      <pubDate>Wed, 7 Apr 2021 20:37:10 +0900</pubDate>
    </item>
    <item>
      <title>EJB와 스프링(Spring) 개론</title>
      <link>https://hoon93.tistory.com/56</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;EJB(Enterprise Java Bean)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java bean이란 자바 객체를 재사용 가능하도록 즉, 컴포넌트화시킬 수 있는 코딩 방침을 정의한 것을 의미한다(bean은 쉽게 component 또는 객체라고 이해하면 좋다). 그리고 EJB란 엔터프라이즈급 어플리케이션 개발을 단순화하기 위해 발표한 스펙이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;당연한 말이지만 서비스가 커지면서 많은 객체들을 필요하게 되었고, 이러한 &lt;span style=&quot;color: #ee2323;&quot;&gt;비즈니스 객체들을 관리하는 컨테이너를 만들어서 필요할 때마다 컨테이너로부터 객체를 받는 식으로 관리&lt;/span&gt;하면 효율적이겠다. 라는 것에서 탄생한 개념이다. 이러한 취지는 좋았으나, 서비스가 구현해야 하는 실제 비즈니스 로직보다 EJB 컨테이너를 사용하기 위한 상투적인 코드(상속 and 구현해야 하는 클래스)들이 많다는 불편함이 있었다. (작성된 코드 또한 EJB컨테이너가 없다면 사용할 수 없었음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;심각한 것은 벤더 사마다 EJB 컨테이너를 구현한 내용이 다르기 때문에 다른 벤더 사의 컨테이너로의 변경에 어려움이 있었고, 이것은 설정이 너무 복잡하다는 문제로 부각되기 시작합니다. 따라서 프로젝트 자체가 특정 기술(개발 툴 등)에 종속. 즉, 기술 침투되는 문제가 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너의 탄생&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EJB는 컨테이너로부터 필요한 객체를 꺼내 사용하는 방식으로 객체들 간의 의존성을 해결하려는 것이었지만 비즈니스 로직에 특정 기술이 종속되는 것이 가장 큰 문제점이었습니다. 이에 2002년 '로드 존슨'은 특정 클래스를 상속하거나 인터페이스를 구현하지 않는 평범한 자바 클래스(&lt;b&gt;POJO(Plain Old Java Object)&lt;/b&gt;, 느슨한 Java Bean, Spring Bean)을 이용하며 단순하지만 EJB에서 제공하는 고급 기술을 제공하는 Spring을 창시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;springFramework.png&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E5nZx/btqYkaRCBKb/vHgV5N6XpLaebGyoCd7DTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E5nZx/btqYkaRCBKb/vHgV5N6XpLaebGyoCd7DTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E5nZx/btqYkaRCBKb/vHgV5N6XpLaebGyoCd7DTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE5nZx%2FbtqYkaRCBKb%2FvHgV5N6XpLaebGyoCd7DTK%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; alt=&quot;스프링 프레임워크&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;356&quot; data-filename=&quot;springFramework.png&quot; data-origin-width=&quot;727&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링은 &lt;b&gt;「자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량급 애플리케이션 프레임워크」&lt;/b&gt;라고 정의된다. 평범한 자바 클래스를 이용하여 &lt;span style=&quot;color: #ee2323;&quot;&gt;EJB의 기능을 유지&lt;/span&gt;하면서 복잡성(기존 EJB컨테이너에서 제공하는 API를 상속받거나 구현하여 코드를 작성하는 부분들)을 제거해 코드가 단순&amp;amp;가벼워졌고(&lt;span style=&quot;color: #ee2323;&quot;&gt;경량&lt;/span&gt;급), 스프링에 특화된 인터페이스 구현을 요구하지 않는 등 자바 개발의 폭넓은 간소화를 실현하게 됩니다. 또한 스프링은 위 그림과 같은 &lt;b&gt;여러 객체들을 의존성 해결(DI) 및 제어(IOC)&lt;/b&gt;하며 객체들의 &lt;span style=&quot;color: #ee2323;&quot;&gt;라이프 사이클을 관리&lt;/span&gt;해 줍니다.&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;스프링은 특정 기술에 종속되지 않고(기술 비침투적)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;객체를 관리할 수 있는 컨테이너를 제공하는 것이 스프링의 기본 철학인 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;DI(Dependency Injection, 의존성 주입)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;EJB컨테이너와 스프링 컨테이너는 모두 객체의 의존성 해결 및 관리를 목적으로 한다고 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성? 객체 간에 의존하다는 것은 무엇을 의미할까?&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614068792647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class A(){
	public static void main(String[] args){
		B b = new B(); //A클래스는 B클래스를 사용한다. 즉, A는 B에 의존한다(의존적이다).
		b.hello();
	}
}

class B(){
	public void hello(){
		system.out.print(&quot;hello&quot;);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 위와 같은 객체 간의 의존성을 스프링 컨테이너에서는 의존성 주입(DI, Dependency Injection)을 통해 해결합니다. 의존성 주입이란 사용자가 직접 new 키워드를 사용하여 객체를 생성하지 않고, 외부(컨테이너)에서 생성된 객체를 주입받는 방식을 말합니다. 즉, 의존하는 객체를 직접 생성하는 대신 스프링 컨테이너로부터 의존 객체를 주입(전달)받는 방식이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단어별로 정리해보자.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;의존&lt;/b&gt; : A라는 클래스가 어떤 일을 하기 위해 B에 있는 기능을 사용하는 것으로 다른 객체에 의존하여 처리한다는 개념.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;주입&lt;/b&gt; : 생성돼 있는 객체를 주입받는 방식으로 사용하게 됨을 말한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;빈 설정 정보를 바탕으로 컨테이너가!! 자동으로 주입해 줌. &amp;larr; 제어하는 주체가 뒤바뀜.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;IOC(Inversion of Control, 제어의 역전)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체(인스턴스)의 생성과 소멸 등 개발자가 직접 제어해야 하는 부분들을 프레임워크(정확하게는 컨테이너)가 대신 처리하는 것을 의미한다. 제어의 주도권이 개발자에서 스프링 프레임워크로 넘어가짐. 그래서 제어의 역전이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>DI</category>
      <category>ejb</category>
      <category>IOC</category>
      <category>spring</category>
      <category>스프링</category>
      <category>컨테이너</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/56</guid>
      <comments>https://hoon93.tistory.com/56#entry56comment</comments>
      <pubDate>Tue, 23 Feb 2021 18:14:36 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] Variable, Attribute, Property 차이</title>
      <link>https://hoon93.tistory.com/55</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바스크립트를 공부하다 보면 프로퍼티(Property)라는 개념이 등장하는데,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대충 넘겨짚고만 있었다가 상황마다 불리는 용어가 구분되는 등&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;미묘한 차이가 있음을 깨닫고 이번에 정리해봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변수와 속성 두 개념과 각각 비교되는데, 어떠한 차이가 있는지 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;실행 컨텍스트와 변수객체(VariableObject)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;variable과 attribute.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y0mMG/dJMcaaYm5lU/x7aaolEGkbQFJHbkaPo8cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y0mMG/dJMcaaYm5lU/x7aaolEGkbQFJHbkaPo8cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y0mMG/dJMcaaYm5lU/x7aaolEGkbQFJHbkaPo8cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy0mMG%2FdJMcaaYm5lU%2Fx7aaolEGkbQFJHbkaPo8cK%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;600&quot; height=&quot;305&quot; data-filename=&quot;variable과 attribute.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 자바스크립트에서 코드는 크게 Global Context(전역)와 Function Context(함수 영역)에서 실행된다. Global Context란 브라우저의 window 객체와 같이 전체 코드에서 1개만 생성되며, Function Context는 각각의 함수 호출마다 생성된다. 이렇게 실행된 컨텍스트들은 각각 변수객체(VariableObject, VO)를 가지는데, 여기에 우리가 선언한 변수나 함수에 대한 정보가 담기게 된다. 다시 말해, 변수 or 함수를 선언하는 것은 VO에 프로퍼티를 만드는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전역 컨텍스트와 함수 컨텍스트는 참조하는 변수객체가 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AbstractVO &lt;br /&gt;|&lt;br /&gt;|&lt;span style=&quot;color: #333333;&quot;&gt;&amp;rarr;&lt;/span&gt; GlobalContextVO // &lt;span style=&quot;color: #1b711d;&quot;&gt;Global Context의 변수객체(VO)는 전역객체이다.&lt;/span&gt;&lt;br /&gt;|&amp;nbsp; &amp;nbsp; ( VO === this === global )&lt;br /&gt;|&lt;br /&gt;|&lt;span style=&quot;color: #333333;&quot;&gt;&amp;rarr;&lt;/span&gt; FunctionContextVO // &lt;span style=&quot;color: #1b711d;&quot;&gt;Function Context의 변수객체(VO)는 Activation Object(AO, 활성화 객체)이다.&lt;/span&gt;&lt;br /&gt;|&amp;nbsp; &amp;nbsp; ( VO === AO )&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;VO 자체는 코드상에서 접근이 불가능하지만, 전역 컨텍스트의 VO에 있는 변수는 참조가 가능하다(아래 예제의 a와 b).&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1613720703555&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var a = &quot;Hi&quot;;

function b(){
	var c = &quot;Bye&quot;;
}

console.log(a); // VO(globalContext)의 변수에 직접 접근.
console.log(window.a); //전역 객체 === VO(globalContext)인 점을 이용해 간접적으로 접근.
console.log(a === window.a); //true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;변수(Variable)와 프로퍼티(Property) 정리&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. 변수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1613638185635&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var a = &quot;Global Context&quot;;

function func(){
	var a = &quot;Function Context&quot;;
	console.log(window.a); //Global Context
	console.log(a); //Function Context
}
func();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변수란 실행 컨텍스트에 포함되어 있는 이름과 값 사이의 연결을 의미한다. 다시 말해, &lt;span style=&quot;color: #ee2323;&quot;&gt;변수가 선언돼있는 Context 또는 Scope 관점&lt;/span&gt;에서의 이름과 값 사이의 연결을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 프로퍼티&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1613713680460&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var a = &quot;Hi&quot;;
window.a; //Hi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로퍼티란 &lt;span style=&quot;color: #ee2323;&quot;&gt;객체 내에 포함(선언)&lt;/span&gt;되어있는 변수(멤버변수 라고도 함)를 의미한다. 그리고 위 예제의 변수 a는 전역(Global Context) 변수이면서, 동시에 전역객체의 프로퍼티다. 왜냐하면 위 코드의 실행 컨텍스트는 브라우저이기에 변수 a는 window의 프로퍼티라고도 할 수 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;3. 변수와 프로퍼티의 상호작용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1613723508959&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//define as property, access as a variable
window.a = &quot;1&quot;;
a; //1

//define as variable, access as a property
var b = &quot;2&quot;;
window.b; //2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;VO가 전역 객체일 경우에는 위와 같은 상호작용이 가능하지만, 함수 컨텍스트와의 상호작용은 에러가 날것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;속성(Attribute)과 프로퍼티(Property)&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) &lt;span style=&quot;color: #ee2323;&quot;&gt;브라우저는 웹페이지(즉, Html)을 읽어(파싱(parsing)) DOM객체를 생성&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1b711d;&quot;&gt;※DOM(document Object Model) : 말그대로 DOM 또한 자바스크립트 객체이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 이때 브라우저는 HTML 표준 속성(attribute)을 인식하고, 이 표준 속성을 사용해 DOM 프로퍼티(property)를 만든다. 따라서 요소가 id, class 같은 표준 속성으로만 구성되어 있다면, 이에 해당하는 DOM객체의 프로퍼티(property)가 자연스레 만들어진다. 하지만 HTML에서 태그는 복수의 속성(attribute)을 가질 수 있으며, 사용자가 만든 속성도 있을 수 있는데, 이처럼 표준이 아닌 속성일 때는 상황이 달라진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좀 더 설명하자면, 태그 &amp;lt;body id=&quot;page&quot;&amp;gt;가 있을 때, DOM 객체에서 body.id=&quot;page&quot;를 사용할 수 있다. 그런데 속성-프로퍼티가 항상 일대일로 매핑되지는 않는다. 따라서 속성과 프로퍼티는 다를 수 있다. 또한 한 요소에선 표준인 속성이 다른 요소에선 표준이 아닐 수 있다는 점에도 주의해야 한다. &lt;span style=&quot;color: #1b711d;&quot;&gt;예) &quot;type&quot;은 &amp;lt;input&amp;gt; 요소(HTMLInputElement)에선 표준이지만, &amp;lt;body&amp;gt;(HTMLBodyElement)에선 아님.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 34.0699%; text-align: center;&quot;&gt;&lt;b&gt;속성(Attribute)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.279%; text-align: center;&quot;&gt;&lt;b&gt;프로퍼티(Property)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;용도&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.0699%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HTML 내에 쓰인다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.279%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DOM 객체 내에 쓰인다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;타입(key)&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.0699%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대/소문자를 구분하지 않는다&lt;br /&gt;(HTML내에서는 모두 소문자).&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.279%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바스크립트 영역으로, 대/소문자를 가린다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 19.6512%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이름(value, 값)&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 34.0699%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문자열&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.279%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DOM 프로퍼티의 값은 항상문자열이 아니다.&lt;br /&gt;예) checkbox에 사용되는 input checked 프로퍼티의 경우 boolean값을 가짐. 모든 타입이 가능하며, 각 표준 프로퍼티의 타입은 명세서에 설명되어 있음.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>javascript</category>
      <category>context</category>
      <category>javascript</category>
      <category>VariableObject</category>
      <category>변수</category>
      <category>속성</category>
      <category>실행 컨텍스트</category>
      <category>프로퍼티</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/55</guid>
      <comments>https://hoon93.tistory.com/55#entry55comment</comments>
      <pubDate>Fri, 19 Feb 2021 17:56:30 +0900</pubDate>
    </item>
    <item>
      <title>[jQuery] serialize()사용 시 checkbox값 전송 안되는 문제 해결방법</title>
      <link>https://hoon93.tistory.com/54</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보통 form내의 input태그 값들을 Ajax를 이용해 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버로 보내기 위한 과정에서 serialize() 함수를 이용해왔다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 이번에 checkbox가 무수히 많이 필요한 화면 개발을 하면서 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;serialize()는 checkbox의 체크된 값만 적용되며, 체크되지 않은 값은 무시한다는 것을 알게 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래는 serialize() 함수에 대한 &lt;b&gt;&lt;a title=&quot;serialize설명&quot; href=&quot;https://api.jquery.com/serialize/#serialize&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jQuery API 공식문서&lt;/a&gt;&lt;/b&gt;의 일부이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-filename=&quot;jQuery의 serialize.png&quot; data-origin-width=&quot;835&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ta0OI/btqVnYGvz87/vXMXBAlXGiyG2XuKKirLeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ta0OI/btqVnYGvz87/vXMXBAlXGiyG2XuKKirLeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ta0OI/btqVnYGvz87/vXMXBAlXGiyG2XuKKirLeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTa0OI%2FbtqVnYGvz87%2FvXMXBAlXGiyG2XuKKirLeK%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; alt=&quot;serialize 내용&quot; loading=&quot;lazy&quot; width=&quot;835&quot; height=&quot;186&quot; data-filename=&quot;jQuery의 serialize.png&quot; data-origin-width=&quot;835&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 우리의 문제를 해결해 줄 포인트로, &lt;span style=&quot;color: #ee2323;&quot;&gt;jQuery serialize는&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 오직 form태그에 포함되어 있는 요소들만을 취급한다는 것과&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② &lt;span style=&quot;color: #ee2323;&quot;&gt;input태그의 &quot;radio&quot;와 &quot;checkbox&quot;타입에서 체크 표시가 된 값만 포함&lt;/span&gt;한다고 나와있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, serialize()는 checkbox에서 체크되지 않은 값은 제외한다는 뜻인데, 저는 체크가 되지 않았을 때의 값도 반드시 필요했습니다. 아래 JSP코드를 보면, 서버(DB)로부터 받은 addressCheck에 'Y'값이 있다면 checkbox에 체크가 되어있어야 하고, 되어있지 않더라도 언제든지 수정&amp;amp;조회가 가능해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1612162290378&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;c:choose&amp;gt;
  &amp;lt;c:when test=&quot;vo.addressCheck eq 'Y'}&quot;&amp;gt;
     &amp;lt;input type=&quot;checkbox&quot; name=&quot;addressCheck&quot; id=&quot;addressCheck&quot; value=&quot;Y&quot; checked&amp;gt; &amp;lt;label for=&quot;addressCheck&quot;&amp;gt;주소등록 여부&amp;lt;/label&amp;gt;
  &amp;lt;/c:when&amp;gt;
  &amp;lt;c:otherwise&amp;gt;
     &amp;lt;input type=&quot;checkbox&quot; name=&quot;addressCheck&quot; id=&quot;addressCheck&quot; value=&quot;Y&quot;&amp;gt; &amp;lt;label for=&quot;addressCheck&quot;&amp;gt;주소등록 여부&amp;lt;/label&amp;gt;
  &amp;lt;/c:otherwise&amp;gt;
&amp;lt;/c:choose&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 문제를 해결하기 위해 찾아보니 javaScript부분에서 serialize로 input들 값을 모두 가져온 후, 체크가 안된 checkbox를 별도로 셋팅해 줄 수밖에 없다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1612159693358&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const dataForm = $(document.forms[&quot;basicForm&quot;]).serializeObject();

dataForm[&quot;addressCheck&quot;] = $(&quot;input#addressCheck&quot;).is(&quot;:checked&quot;) ? &quot;Y&quot; : &quot;N&quot; ;

console.log( dataForm.addressCheck ); // &quot;Y&quot; or &quot;N&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① form태그 안의 값들을 serializeObject() 함수를 이용해 객체로 합칩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※ &lt;span style=&quot;background-color: #f3c000;&quot;&gt;serialize()&lt;/span&gt;는 form에 있는 element들을 문자열 하나로 합치는 형태(예: a=1&amp;amp;b=2&amp;amp;a=1&amp;amp;b=2&amp;amp;c=3&amp;amp;d=4)이며, &lt;span style=&quot;background-color: #f3c000;&quot;&gt;serializeObject()&lt;/span&gt;는 객체 형태(예: { a=&quot;1&quot;, b=&quot;2&quot;, c=&quot;3&quot;, d=&quot;4&quot; } )로 만들기 때문에 키/값 추가를 쉽게 하기 위해 사용했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② 삼항연산자와 jQuery의 is()함수를 이용해 체크가 되어있으면 &quot;Y&quot;, 아니라면 &quot;N&quot;값을 객체에 넣어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;③ 콘솔에서 객체 내 값을 직접 확인.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;form내의 input값들을 서버로 보내기위해 묶어준 후, 추가 및 수정하는 방식에 따라 serialize() 또는 serializeArray(), serializeObject()를 이용하면 된다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>javascript</category>
      <category>CheckBox</category>
      <category>javascript</category>
      <category>jQuery</category>
      <category>jsp</category>
      <category>serializeObject</category>
      <category>springboot</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/54</guid>
      <comments>https://hoon93.tistory.com/54#entry54comment</comments>
      <pubDate>Mon, 1 Feb 2021 16:12:23 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] 인덱스 스캔(Index Scan)과 Full Table Scan</title>
      <link>https://hoon93.tistory.com/53</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 테이블 스캔(Full Table Scan)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/999D97455FD318D628?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/999D97455FD318D628?original&quot; data-alt=&quot;Full Table Scan과 high water mark&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/999D97455FD318D628&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F999D97455FD318D628&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; alt=&quot;Full Table Scan과 high water mark&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;213&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Full Table Scan과 high water mark&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 테이블에 존재하는 모든 데이터를 읽어 가면서 조건에 맞으면 결과로서 추출하고 조건에 맞지 않으면 버리는 방식이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) Oracle의 경우, 테이블의 고수위 마크(HWM, High Water Mark) 아래의 모든 블록을 읽는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;※고수위 마크(HWM): 테이블에 데이터가 쓰여졌던 블록 상의 최상위 위치(현재는 지워져서 데이터가 존재하지 않을 수도 있음)를 의미.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 위 그림과 같이 일반적으로 블록들은 서로 인접되어 있기 때문에, Full Table Scan는 한 번의 I/O에 여러 블록을 옮겨온다. 즉, 한 번의 I/O에 데이터를 다중 블록 단위로 메모리에 가져오기 때문에, Row 당 소요되는 입출력 비용이 인덱스 스캔에 비해 적다. 메모리에 옮겨진 블록들은 순차적으로 읽힌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 결과를 찾기 위해 꼭 필요해서 모든 블록을 읽은 것이다. 따라서 이렇게 읽은 블록들은 재사용성이 떨어진다. 그래서 전체 테이블 스캔 방식으로 읽은 블록들은 메모리에서 곧 제거될 수 있도록 관리된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ff0000;&quot;&gt;그렇다면 옵티마이저가 '전체 테이블 스캔'을 선택하는 경우는 언제일까??&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0931%; text-align: center;&quot;&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.8837%; text-align: center;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0931%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적용 가능한 인덱스가 없는 경우&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.8837%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 결합 인덱스의 선두 칼럼이 존재하지 않을 때.&lt;br /&gt;&lt;br /&gt;2. 적용할 인덱스가 있지만 칼럼을 가공 or 연산하여 그 인덱스를 사용할 수 없을 때.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0931%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;넓은 범위의 데이터 액세스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.8837%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;적용 가능한 인덱스가 존재하더라도 처리 범위가 넓어서 전체 테이블 스캔이 보다 적은 비용이 든다면 Full Table Scan을 적용할 수 있다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0931%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소량의 테이블 액세스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.8837%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고수위 마크(HWM) 내에 있는 블록이 &lt;span style=&quot;color: #409d00;&quot;&gt;DB_FILE_MULTIBLOCK_READ_COUNT&lt;/span&gt;(한번에&amp;nbsp;I/O 작업으로 읽어들이는 최대 블럭 수) 이내에 있다면 Full Tables Scan이 일어날 수 있다. (항상 그런것은 아님)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0931%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;병렬처리 액세스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.8837%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;병렬처리는 Full Table Scan을 더욱 효과적으로 수행하게 되므로 병렬처리로 수행되는 실행계획을 수립할 때는 항상 Full Table Scan을 선택한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 17.0931%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;'FULL' 힌트를 적용한 경우&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 44.8837%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Full 힌트를 사용했을 때. 단, Full힌트가 적절하지 않다면 옵티마이저는 이를 무시할 수 있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※&lt;a class=&quot;tx-link&quot; href=&quot;https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/DB_FILE_MULTIBLOCK_READ_COUNT.html#GUID-69E45087-14DF-492B-B55A-8F2E936947A5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DB_FILE_MULTIBLOCK_READ_COUNT&lt;/a&gt; : 이 값이 크면 한 번의 I/O로 더 많은 수의 블록을 읽어오므로 Full Table Scan시에는 성능이 증가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;하지만 특정 블록만 필요한 경우에 불필요한 블록까지 함께 읽어들이므로 디스크를 읽는 시간이 증가할 뿐만 아니라 크기가 한정된 Buffer Pool을 많이 차지함으로써 자주 쓰이는 데이터를 밀어내어 오히려 Cache의 효율을 떨어뜨리는 결과를 가져올 수도 있다. 따라서 작업 특성에 따라 적절한 값을 세팅해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;오라클의 Optimizer는 이 값이 크면 작업의 효율성을 위해서 Full Table Scan을 선택할 가능성이 커지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스 스캔(Index Scan)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/998CDB4F5FD3196F25?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/998CDB4F5FD3196F25?original&quot; data-alt=&quot;Full Table Scan&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998CDB4F5FD3196F25&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998CDB4F5FD3196F25&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; alt=&quot;전체 테이블 스캔&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;310&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Full Table Scan&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;606&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/998A524F5FD3196F26?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/998A524F5FD3196F26?original&quot; data-alt=&quot;Index Scan&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998A524F5FD3196F26&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998A524F5FD3196F26&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; alt=&quot;인덱스 스캔&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;310&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;606&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Index Scan&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 인덱스(Index)를 구성하는 칼럼의 값을 기반으로 데이터를 추출하는 액세스 기법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;2) 인덱스에 존재하지 않는 칼럼의 값이 필요한 경우에는 현재 읽은 레코드 식별자를 이용하여 테이블을 액세스해야 한다. 그러나 SQL문에서 필요로 하는 모든 칼럼이 인덱스 구성 칼럼에 포함된 경우 테이블에 대한 액세스는 발생하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 인덱스의 구성 칼럼이 A+B라면 먼저 칼럼 A로 정렬되고 칼럼 A의 값이 동일할 경우에는 칼럼 B로 정렬된다. 그리고 칼럼 B까지 모두 동일하면 레코드 식별자로 정렬된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 인덱스가 구성 칼럼으로 정렬되어 있기 때문에 인덱스를 경유하여 데이터를 읽으면 그 결과 또한 정렬되어 반환됨. &amp;rarr; 따라서 인덱스의 순서와 동일한 정렬 순서를 사용자가 원하는 경우에는 정렬 작업을 하지 않을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대용량 데이터 중에서 극히 일부의 데이터를 찾을 때, &lt;b&gt;&lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/52&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인덱스 스캔 방식&lt;/a&gt;&lt;/b&gt;(Index Scan)은 인덱스를 이용해 몇 번의 I/O만으로 원하는 데이터를 쉽게 찾을 수 있다. 그러나 전체 테이블 스캔(Full Table Scan)은 테이블의 모든 데이터를 읽으면서 원하는 데이터를 찾아야 하기 때문에 비효율적인 검색을 하게 된다. 그러나 반대로 테이블의 대부분의 데이터를 찾을 때는 한 블록씩 읽는 인덱스 스캔 방식 보다는 어차피 대부분의 데이터를 읽을 거라면 한번에 여러 블록씩 읽는 전체 테이블 스캔 방식이 유리할 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Database</category>
      <category>full table scan</category>
      <category>high water mark</category>
      <category>HWM</category>
      <category>Index Scan</category>
      <category>Oracle</category>
      <category>오라클</category>
      <category>인덱스 스캔</category>
      <category>전체 테이블 스캔</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/53</guid>
      <comments>https://hoon93.tistory.com/53#entry53comment</comments>
      <pubDate>Mon, 14 Dec 2020 14:27:57 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] 인덱스(Index)와 B트리 인덱스(트리 기반 인덱스)</title>
      <link>https://hoon93.tistory.com/52</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스(Index) 정의 및 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원하는 데이터를 쉽게 찾을 수 있도록 돕는 책의 '찾아보기'와 유사한 개념으로, &lt;span style=&quot;color: #ff0000;&quot;&gt;기본적인 목적은 검색 성능의 최적화&lt;/span&gt;이다. 즉, 조건을 만족하는 데이터를 인덱스를 통해 효과적으로 찾을 수 있도록 돕는다. 추가적으로 이 부분은 DBA분에게 물어보니 &lt;span style=&quot;color: #0900ff;&quot;&gt;인덱스를 활용한 데이터 조회는 전체 데이터의 15% 이내의 데이터를 조회할 때가 인덱스 효율이 가장 좋으며&lt;/span&gt;, 그 이상의 건수에 대한 조회가 필요할 때부터는 효율이 떨어진다고 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테이블에 인덱스를 생성하지 않아도 되고 여러 개를 생성해도 된다. &amp;rarr; 선택적으로 생성할 수 있는 구조.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DML 작업(Insert, Update, Delete 등)은 테이블과 인덱스를 함께 변경해야 하기 때문에 오히려 느려질 수 있다는 단점이 존재하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스를 경유해서 반환된 결과 데이터는 인덱스 데이터와 동일한 순서로 갖는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스를 생성할 때 동일 칼럼으로 구성된 인덱스를 중복해서 생성할 수 없다. &amp;rarr; 인덱스 구성 칼럼은 동일하지만 칼럼의 순서가 다르면 서로 다른 인덱스이기 때문에 생성 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스(Index) 생성과 사용&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1768991238278&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE INDEX 인덱스명 ON 테이블명(칼럼명1, 칼럼명2, &amp;hellip; )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ON 테이블명 뒤 괄호 내에 파라미터로 칼럼명을 1개만 두어 단일 칼럼에 대한 인덱스를 생성할 수도 있고, 위 예시대로 여러 칼럼을 지정하는 인덱스(다중 인덱스 or 복합 인덱스)를 사용할 수도 있다. 중요한 점은 다중 인덱스를 이용하려면 다중 인덱스 생성 시 설정한 칼럼을 Select 조회 조건으로 모두 사용해야 옵티마이저가 해당 다중 인덱스를 활용할 확률이 높다. 또한 인덱스는 NOT NULL 값에 대해서만 생성되기 때문에, NULL 여부를 체크하는 조회 쿼리는 인덱스를 사용하지 못한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스의 활용 방법 &amp;amp; 고려사항&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징에서도 언급했지만 인덱스를 생성한다고 무조건 좋은 것이 아니다. 어느 상황에서 인덱스의 사용 효율이 좋을까? 개발 당시에 성능이 좋던 쿼리들도 운영단계에서 데이터가 쌓이다 보면 조회 속도가 느려질 수 있는데, 보통 이 경우에 인덱스를 생성한다. 또한 테이블 내 전체 데이터 수의 5% 미만일 경우 적은 실행 비용(COST)으로 조회 성능이 가장 좋다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. INDEX 효율이 좋은 경우 또는 칼럼을 정리해보면&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;큰 테이블에서 적은 양의 데이터를 조회할 때(Table Full Scan하면 시간이 오래 걸리기 때문)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Where절이나 Join조건을 자주 사용되는 칼럼&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 Row(데이터)들을 기준으로 봤을 때, Null값을 많이 포함한 칼럼&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 반대로 INDEX 효율이 떨어지는 경우는 언제일까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;전체 데이터 수의 15% 이상 조회가 필요한 경우&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테이블의 데이터가 적은 경우, 인덱스를 설정하지 않는 게 오히려 성능이 좋다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조회(Select)보다 삽입(Insert), 수정(Update), 삭제(Delete) 처리 비중이 많은 테이블&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Where절이나 Join조건을 자주 사용되지 않는 칼럼&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로, 조회 쿼리 실행 시 조건식에서 INDEX를 이용하려면 해당 인덱스 칼럼을 가공 or 연산하지 않은 상태에서 비교하여야만 쿼리가 INDEX를 탄다는 점을 고려해야 한다. &lt;span style=&quot;color: #22741c;&quot;&gt;예) 전화번호 칼럼의 경우(02-111-1111) 맨 앞 지역번호(02)에 해당하는 문자열만을 분리(가공/연산)하여 조건 검색하면 인덱스를 타지 않음.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜냐하면 인덱스 칼럼에 외부적 변형이 일어나면, 말 그대로 상대값과 비교되기 전에 칼럼이 가공이 된 것이기&amp;nbsp;때문에 당연히 인덱스를 사용할 수 없게 되는 것이다. 여기서 칼럼의 외부적 변형이란 쿼리 조건식에 인덱스를 가진 칼럼을&amp;nbsp;연산, 결합, 사용자 지정 함수 등의 방법으로 가공함을 의미하며, 인덱스 사용 시 이러한 변형이 일어나지 않도록 주의해야 한다. 다시 말해, 조건식에서는 테이블 칼럼을 변경하지 말고 비교 값을 변경해서 비교해야 인덱스 스캔이 된다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 인덱스를 타지 않는 쿼리 조건식 예제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768991601093&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM PHONEBOOK WHERE SUBSTR(number, 1, INSTR(number, '-'));
SELECT * FROM PHONEBOOK WHERE regist_date + 1 &amp;gt; 2;
SELECT * FROM PHONEBOOK WHERE SUBSTR(user_name, -8) = 'Peterson';
SELECT * FROM PHONEBOOK WHERE TO_CHAR(join_date, 'yyyymmdd') = '20201231'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② 인덱스를 타는 쿼리 조건식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768991620290&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM PHONEBOOK WHERE number LIKE '02-%';
SELECT * FROM PHONEBOOK WHERE regist_date &amp;gt; 2;
SELECT * FROM PHONEBOOK WHERE user_name = 'Jordan Bernt Peterson';
SELECT * FROM PHONEBOOK WHERE join_date = TO_DATE('20201231', 'yyyymmdd')&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B-트리 인덱스(트리 기반 인덱스) 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B-Tree index는 DBMS에서 가장 일반적인 인덱스로, 트리 구조의 최상위에 하나의 &quot;Root Block(Node)&quot;가 존재하고 그 하위에 자식 Block(Node)이 붙어 있는 형태이다. 트리 구조의 가장 하위에 있는 노드를 &quot;Leaf Block&quot;이라 하며, Root Block도 아니고 Leaf Block도 아닌 중간 Block을 &quot;Branch Block&quot;이라고 한다. 데이터베이스에서 인덱스와 실제 데이터가 저장된 데이터는 따로 관리되는데, 인덱스의 Leaf Block은 항상 실제 데이터 레코드를 찾아가기 위한 주소값을 가지고 있다. 그렇다면 이러한 블록들을 이용해 원하는 데이터를 어떻게 찾아낼까?? 아래&amp;nbsp;그림은 B-트리 인덱스의 구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99428D4A5FCEDB310D?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99428D4A5FCEDB310D?original&quot; data-alt=&quot;B트리 인덱스 구조&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99428D4A5FCEDB310D&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99428D4A5FCEDB310D&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; alt=&quot;B Tree Index structure&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;323&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;B트리 인덱스 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;어떤 데이터의 주소 값을 알기 위해 인덱스 49번 블록의 값을 찾고 싶다면??&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;루트블록에서 60보다 작으므로 왼쪽 포인터로 이동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;49는 브랜치블록의 40보다 크므로 오른쪽 포인터로 이동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이동한 결과 해당 블록이 리프블록이므로 블록 내에 49를 검색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;다음은 B트리 인덱스의 각 블록 별 특징을 정리한 표이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5117%; text-align: center;&quot;&gt;&lt;b&gt;B-트리 인덱스의 구성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.6744%; text-align: center;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 블록 공통&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스 데이터는 순서대로 정렬돼있다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;브랜치 블록(Branch Block)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) Branch Block 중에서 가장 상위에서 있는 블록을 Root Block이라고 한다.&lt;br /&gt;&lt;br /&gt;2) Branch Block은 분기를 목적으로 하는 블록이며, 다음 단계의 Block을 가리키는 포인터를 가진다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 21.5117%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;리프 블록(Leaf Block)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.6744%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 리프 블록은 아래 2개의 데이터로 구성된다.&lt;br /&gt;① (인덱스를 구성하는) 칼럼의 데이터.&lt;br /&gt;② 해당 데이터를 가지고 있는 행의 위치를 가리키는 레코드 식별자(RID, Record Identifier/Rowid)로 구성된다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;※만약, SQL문에서 다른 칼럼이 더 필요하면 리프 블록에 존재하는 레코드 식별자를 이용해서 테이블을 액세스한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;2) 양방향 링크(Double Link)를 가짐.&lt;br /&gt;이것을 통해서 오름 차순(Ascending Order)과 내림 차순(Descending Order) 검색을 쉽게 가능. 따라서 B Tree Index는 '='로 검색하는 일치(Exact Match) 검색과 'BETWEEN', '&amp;gt;' 등과 같은 연산자로 검색하는 범위(Range) 검색 모두에 적합하다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Database</category>
      <category>B tree index</category>
      <category>Block</category>
      <category>B트리 인덱스</category>
      <category>index</category>
      <category>인덱스</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/52</guid>
      <comments>https://hoon93.tistory.com/52#entry52comment</comments>
      <pubDate>Fri, 11 Dec 2020 00:41:55 +0900</pubDate>
    </item>
    <item>
      <title>옵티마이저(Optimizer)와 실행계획(Execution Plan)</title>
      <link>https://hoon93.tistory.com/50</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;옵티마이저(Optimizer)란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/994CF54C5FB66B5419?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/994CF54C5FB66B5419?original&quot; data-alt=&quot;옵티마이저의 종류&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/994CF54C5FB66B5419&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F994CF54C5FB66B5419&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; alt=&quot;옵티마이저의 종류&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;405&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;옵티마이저의 종류&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 옵티마이저(Optimizer)는 &lt;span style=&quot;color: #ee2323;&quot;&gt;SQL문의 요구사항을 처리하기 위한 최적의 실행 방법을 결정하는 역할&lt;/span&gt;을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;rarr; 여기서 최적의 실행방법이란 것은 한 마디로 '실행계획'이라고 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 옵티마이저는 두가지 방식(규칙기반, 비용기반)에 따라 구분된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 현재 대부분의 관계형 데이터베이스는 비용기반 옵티마이저(Cost Based Optimizer)만을 제공한다. 비록 규칙기반 옵티마이저를 제공하더라도 신규 기능들에 대해서는 더 이상 지원하지 않는다. 다만 하위 버전 호환성을 위해서만 규칙기반 옵티마이저가 남아 있을 뿐이다. 하지만 규칙기반 옵티마이저의 규칙은 보편 타당성에 근거한 것들이기 때문에, 이러한 규칙을 알고 있는 것은 옵티마이저의 최적화 작업을 이해하는데 도움이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;규칙기반 옵티마이저(RBO, Rule Based Optimizer) 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;규칙기반 옵티마이저(Rule Based Optimizer)는 말그대로 규칙(우선순위)을 가지고 실행계획을 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;규칙기반 옵티마이저에서는 'rowid를 통한 엑세스 방식'이 우선순위가 가장 높고 '전체 테이블 엑세스 방식'이 우선순위가 가장 낮다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Index를 이용한 액세스 방식이 전체 테이블 엑세스 방식보다 우선순위 높다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;조인 칼럼 인덱스의 존재 유무가 중요한 판단 기준이 된다.&lt;/span&gt; &lt;/b&gt;아래 표는 조인 칼럼 인덱스의 유무에 따라 RBO가 선행 테이블을 선택하는 기준이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 168px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 40.2326%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;인덱스로 인한 실행계획 판단 기준&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9535%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;선행 테이블 결정 방법&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;width: 40.2326%; height: 42px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조인 칼럼에 대한 Index가 양쪽 테이블에 모두 존재하는 경우&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9535%; height: 42px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 순위가 높은 테이블을 선행 테이블(Driving Table 또는 Outer Table)로 선택한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 40.2326%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한쪽 조인 칼럼에만 Index가 존재하는 경우&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9535%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스가 없는 테이블을 선행 테이블로 선택해서 조인을 수행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 63px;&quot;&gt;
&lt;td style=&quot;width: 40.2326%; height: 63px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;양쪽 조인 칼럼에 모두 Index가 존재하지 않는 경우&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9535%; height: 63px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) FROM 절의 뒤에 나열된 테이블을 선행 테이블로 선택&lt;br /&gt;&lt;br /&gt;2) 이때는 보통 Sort Merge Join을 사용( &amp;harr; 일반적으로 둘 중 하나라도 조인 칼럼에 인덱스가 존재한다면 일반적으로 NL Join을 사용한다).&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 40.2326%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조인 테이블의 우선 순위가 동일한 경우&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 43.9535%; height: 21px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;FROM 절에 나열된 테이블의 역순으로 선행 테이블을 선택한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비용기반 옵티마이저(CBO, Cost Based Optimizer) 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비용기반 옵티마이저는 SQL문을 처리하는데 필요한 비용이 가장 적은 실행계획을 선택하는 방식이다. &lt;span style=&quot;color: #ee2323;&quot;&gt;즉, 우선순위와는 상관없다.&lt;/span&gt; &amp;rarr; 예를 들어, 아무리 Index를 이용한다고해도 비용이 전체 테이블 스캔 비용보다 크다고 판단되면 전체 테이블 스캔을 실행계획으로 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서, 규칙기반 옵티마이저가 사용하지 않는 &lt;span style=&quot;color: #ee2323;&quot;&gt;테이블, 인덱스, 칼럼 등의 다양한 객체 통계정보와 시스템 통계정보 등을 이용&lt;/span&gt;한다. (비용기반 옵티마이저는 통계정보가 없는 경우, 정확한 비용 예측이 불가능하기 때문)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동일한 SQL문이라도 DBMS정보, 설정 정보 등의 차이로 서로 다른 실행계획이 생성될 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다양한 한계들로 인해 실행계획의 예측 및 제어가 어렵다는 단점이 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비용기반 옵티마이저의 실행계획에는 반드시 비용사항이 표시된다. 안 돼있다면 그것은 규칙기반 옵티마이저(RBO).&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;옵티마이저가 생성한 실행계획 정보의 구성요소&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행계획을 보고 SQL이 어떻게 실행되는지 정확히 이해할 수 있다면 보다 향상된 SQL의 이해 및 활용이 가능하다. 실행계획을 구성하는 요소에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;조인 순서(Join Order), 조인 기법(Join Method), 액세스 기법(Access Method), 최적화 정보(Optimization Information), 연산(Operation)&lt;/span&gt; 등이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99CF7B345FB672D31A?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99CF7B345FB672D31A?original&quot; data-alt=&quot;실행계획 정보의 구성요소&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CF7B345FB672D31A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99CF7B345FB672D31A&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; alt=&quot;옵티마이저 실행계획&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;151&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행계획 정보의 구성요소&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1) 조인 순서&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조인 작업을 위해 먼저 A 테이블을 읽고 B 테이블을 읽는 작업을 수행한다면 조인 순서는 A &amp;rarr; B.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2) 조인 기법(두 개의 테이블을 조인할 때 사용할 수 있는 방법) 종류&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ① NL Join(NESTED LOOPS) : 프로그래밍에서 사용하는 중첩된 반복문과 유사한 방식으로 조인을 수행하는 기법.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ② Sort Merge Join : 조인 칼럼을 기준으로 데이터를 정렬하여 조인을 수행하는 기법&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ③ Hash Join : 서로 동일한 해쉬 값을 갖는 것들 사이에서 실제 값이 같은지를 비교하면서 조인을 수행하는 기법.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3) 액세스 기법(하나의 테이블을 액세스할 때 사용할 수 있는 방법) 종류&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 인덱스 스캔(Index Scan), 전체 테이블 스캔(Full Table Scan) 등.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4) 최적화 정보&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 최적화 정보는 옵티마이저가 실행계획의 각 단계마다 예상되는 비용 사항을 표시한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 실행계획에 비용 사항이 표시된다는 것은 비용기반 최적화 방식으로 실행계획을 생성했다는 것을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; - 최적화 정보에는 3가지 정보가 존재한다. &amp;rarr; 실제로 SQL을 실행하고 얻은 결과가 아니라 통계 정보를 바탕으로 옵티마이저가 계산한 예상치이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ① Cost: 상대적인 비용 정보.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ② Card: Cardinality의 약자로서, 주어진 조건을 만족한 결과 집합 혹은 조인 조건을 만족한 결과 집합의 건수를 의미&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ③ Bytes: 결과 집합이 차지하는 메모리 양을 바이트로 표시한 것.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Database</category>
      <category>cost based optimizer</category>
      <category>execution plan</category>
      <category>Optimizer</category>
      <category>rule based optimizer</category>
      <category>규칙기반 옵티마이저</category>
      <category>비용기반 옵티마이저</category>
      <category>실행계획</category>
      <category>옵티마이저</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/50</guid>
      <comments>https://hoon93.tistory.com/50#entry50comment</comments>
      <pubDate>Thu, 19 Nov 2020 22:35:54 +0900</pubDate>
    </item>
    <item>
      <title>KREAM(크림) 이용법 및 구매&amp;amp;판매 후기</title>
      <link>https://hoon93.tistory.com/49</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한정판 스니커즈 중개 서비스로 유명한 KREAM에서의 실제 사용 후기에 대해 블로깅해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한정판 신발 거래 플랫폼 KREAM(크림)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;신발 거래 플랫폼이라는 점이 흥미로워 호기심에 KREAM을 다운로드해 직접 사용해봤다. 특히 1:1 거래 과정에서 KREAM의 전문 검수팀(?)이 상품을 검수하고, 합격한 상품만 구매자에게 배송한다는 안내 문구가 가장 인상적이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/995058375FA56B1802?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/995058375FA56B1802?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/995058375FA56B1802&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F995058375FA56B1802&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; alt=&quot;한정판 거래 플랫폼 KREAM&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;312&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 평소 신발을 좋아해 중고거래 사이트나 카페, 블로그 등 여러 커뮤니티를 자주 살펴보는 편입니다. 그런데 중고거래 시장을 보다 보면 가품 판매 사례가 생각보다 흔하고, 설령 평판이 괜찮아 보이는 판매자와 거래하더라도 진품 여부를 최종적으로 판단하고 책임져야 하는 쪽은 결국 구매자라는 점이 늘 부담으로 남았습니다. 이런 상황에서 KREAM이 거래 과정 중간에서 검수라는 안전장치를 제공해 리스크를 크게 줄여준다는 점이 인상적이었고, 사용자 입장에서는 ‘안심하고 거래할 수 있게 해주는 서비스’라는 생각이 들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;KREAM(크림) 이용 방법 &amp;amp; 장점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99EF40465FA68F5716?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99EF40465FA68F5716?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99EF40465FA68F5716&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99EF40465FA68F5716&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; alt=&quot;KREAM 신발 검색1&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;544&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/998DBC465FA68F5715?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/998DBC465FA68F5715?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/998DBC465FA68F5715&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F998DBC465FA68F5715&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; alt=&quot;KREAM 신발 검색2&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;544&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/992EE1465FA68F5714?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/992EE1465FA68F5714?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/992EE1465FA68F5714&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F992EE1465FA68F5714&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; alt=&quot;KREAM 사이즈별 구매가 확인&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;544&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 KREAM 앱을 다운로드한 후, 구매하려는 신발명을 위와 같이 검색하시면 됩니다. 또한 사이즈별 판매금액을 한눈에 볼 수 있는 기능도 있어 참 편리한 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/991BEA385FA693A30C?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/991BEA385FA693A30C?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991BEA385FA693A30C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F991BEA385FA693A30C&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; alt=&quot;KREAM 최근 판매가&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;817&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9905BB385FA693A309?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9905BB385FA693A309?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9905BB385FA693A309&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9905BB385FA693A309&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; alt=&quot;최근 거래 체결가&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;817&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 스크롤을 내리면 마치 주식처럼 모든 거래 체결 내역이 공개되어 거래 전에 사이즈 별 입찰가를 파악할 수도 있습니다. 위 그림 하단의 구매 버튼에 표시된 '즉시 구매가' 라는 건 판매자가 &quot;이 정도 금액에 판매한다면 나는 만족해&quot;하며 작성한 판매 가격입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;반대로 '즉시 판매가' 라는 건 어떠한 구매자가 &quot;이 정도 가격에 해당 신발을 구매하고 싶다&quot;라고 작성한 구매글이라고 할 수 있기 때문에, 판매자 입장에서 즉시 판매가가 괜찮다고 생각이 들면 바로 판매하시면 되는 겁니다. (위 그림은 사이즈별 선택을 한 상황이 아니라 모든 사이즈를 대상으로 통합되어 표시된 즉시 구매&amp;amp;판매가입니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/993B22355FA69C2617?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/993B22355FA69C2617?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/993B22355FA69C2617&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F993B22355FA69C2617&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; alt=&quot;판매 입찰 희망가&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;468&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;779&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;779&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99599E355FA69C2715?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99599E355FA69C2715?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99599E355FA69C2715&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99599E355FA69C2715&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; alt=&quot;구매 입찰 희망가&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;468&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;779&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 다시 스크롤을 내리면 현재 입찰 중인 내역을 확인할 수 있습니다. 255사이즈를 선택한 위 그림의 상황을 보면 현재 2개의 물품이 판매 대기 중인 것을 확인할 수 있고, 구매 입찰칸에는 150,000원의 구매 희망가 내역 1건을 있는 것을 확인할 수 있었습니다. 그래서 저는 1000원을 올려서 구매 희망가로 등록해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9971473C5FA6A3D41A?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9971473C5FA6A3D41A?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9971473C5FA6A3D41A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9971473C5FA6A3D41A&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; alt=&quot;KREAM 구매 등록1&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;544&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/990A533C5FA6A3D61C?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/990A533C5FA6A3D61C?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/990A533C5FA6A3D61C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F990A533C5FA6A3D61C&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; alt=&quot;구매 희망가 기입&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;544&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/990B783C5FA6A3D91C?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/990B783C5FA6A3D91C?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/990B783C5FA6A3D91C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F990B783C5FA6A3D91C&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; alt=&quot;입찰 등록 완료&quot; loading=&quot;lazy&quot; width=&quot;273&quot; height=&quot;544&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;1360&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;KREAM 구매 &amp;amp; 판매 후기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99BAA94A5FA6B12826?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99BAA94A5FA6B12826?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99BAA94A5FA6B12826&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99BAA94A5FA6B12826&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; alt=&quot;KREAM 거래완료&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;500&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99C71C4A5FA6B12826?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99C71C4A5FA6B12826?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C71C4A5FA6B12826&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99C71C4A5FA6B12826&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; alt=&quot;KREAM 택배&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;410&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구매 입찰을 등록해 뒀었는데, 어떤 판매자분이 체결을 해주었고 결론적으로 약 5일 만에 물건을 수령할 수 있었습니다. KREAM 사로 입고가 완료되고 그 후에 검수 종료까지 한 1~2일이 소요되더라고요. 느리긴한데 어쩔 수 없지 뭐 하면서 기다렸습니다. 모든 진행과정은 단계별로 업데이트되며 알람이 온다는 점은 좋았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 조금 의아했던 건 제가 구매와 동시에 신발 하나를 판매 체결도 했었는데요. 분명히 판매자분이 보낸 물건이 KREAM에 입고 완료된 후 약 30분 뒤에 제가 판매하는 물건도 입고 완료 처리됐었는데, 검수 합격 알림까지는 한 8시간 정도 차이가 나더라고요?? ㅋㅋ 이런 걸로 미루어보아, 입고가 완료된 순서대로 검수를 진행하는 것이 아닌가? 싶기도 하면서 그 30분 동안에 많은 물건이 입고되었기에 검수가 지연된 건가 싶기도 하네요. 추가로 거래일자는 저의 판매 체결일자가 구매 체결일보다 하루나 더 빨랐기 때문에, 거래 체결일이 처리 순서가 되지 않음은 확실한 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99406D495FA6C7DC26?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99406D495FA6C7DC26?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99406D495FA6C7DC26&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99406D495FA6C7DC26&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; alt=&quot;KREAM 언박싱1&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;410&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99CD0B495FA6C7DD2A?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99CD0B495FA6C7DD2A?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CD0B495FA6C7DD2A&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99CD0B495FA6C7DD2A&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; alt=&quot;KREAM 언박싱2&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;410&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;크림에서 보내온 배송 상자를 개봉하고 마주친 뾱뾱이와 스니커즈 상자를 둘러싼 비닐 포장지... 검수과정을 마친 후 구매자를 위한 섬세한 재포장과 내부의 신발도 종이헝겊(?)으로 각각 덮어주는 것에서 섬세함이 느껴지네요. &lt;/span&gt;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99CDC0375FA6BDD51B?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99CDC0375FA6BDD51B?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CDC0375FA6BDD51B&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99CDC0375FA6BDD51B&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; alt=&quot;KREAM 인증서&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;410&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99BE04375FA6BDD51D?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99BE04375FA6BDD51D?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99BE04375FA6BDD51D&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99BE04375FA6BDD51D&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; alt=&quot;authenticity guaranteed&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;410&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음으로 신발과 함께 동봉되어 있는 인증서입니다. 인증서에는 어떤 항목을 검사했는지 표시돼있는데, 크림에서 인증(Verified)한 제품이라는 자부심이 느껴지긴 하네요. 그리고 또 보니까 KREAM이라는 단어의 의미가 'Kicks rule everything around me' 였더라고요. 동의하는 바입니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 전반적으로 만족스러운 구매 및 판매 경험이었습니다만, 배송 소요시간이 꽤 걸리는 건 어쩔 수 없는 것 같습니다. 크림이 잘 개선해가며 좋은 서비스 지속됐으면 좋겠네요.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이상 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내돈내산 K.R.E.A.M. 후기&lt;/span&gt;&lt;/p&gt;</description>
      <category>일상</category>
      <category>kicks rule everything around me</category>
      <category>KREAM</category>
      <category>신발 거래</category>
      <category>한정판 신발</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/49</guid>
      <comments>https://hoon93.tistory.com/49#entry49comment</comments>
      <pubDate>Sun, 8 Nov 2020 01:23:20 +0900</pubDate>
    </item>
    <item>
      <title>도커 컨테이너(Container)와 이미지(Image)란?</title>
      <link>https://hoon93.tistory.com/48</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커(Docker)는 Immutable Infrastructure Paradigm 이라는 개념을 기반으로 하기 때문에,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서비스 환경(서비스 인프라) 부분을 이미지화(실행파일화)하여 &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;배포한 뒤 가급적 변경하지 않고 사용한다고 이전 포스팅에서 말했었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번에는 그것을 가능하게 하는 이미지(Image)와 컨테이너에 대해 정리해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Docker Image(이미지)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Docker Image.png&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8b2l/btsJX7oiZSw/zDC2bD0sbHjdVekRH25230/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8b2l/btsJX7oiZSw/zDC2bD0sbHjdVekRH25230/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8b2l/btsJX7oiZSw/zDC2bD0sbHjdVekRH25230/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8b2l%2FbtsJX7oiZSw%2FzDC2bD0sbHjdVekRH25230%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; alt=&quot;Docker Image&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;479&quot; data-filename=&quot;Docker Image.png&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;서비스 운영에 필요한 서버 프로그램, 소스코드 및 라이브러리, 컴파일된 실행 파일을 묶는 형태&lt;/span&gt;를 Docker Image라 한다. 다시 말해, 특정 프로세스를 실행하기 위한(즉, 컨테이너 생성(실행)에 필요한) 모든 파일과 설정값(환경)을 지닌 것으로, 더 이상의 의존성 파일을 컴파일하거나 이것저것 실치 할 필요 없는 상태의 파일을 의미한다.&lt;/span&gt; &lt;span style=&quot;color: #409d00;&quot;&gt;예를 들어 Ubuntu이미지는 Ubuntu를 실행하기 위한 모-든 파일을 가지고 있으며, Oracle이미지는 Oracle을 실행하는데 필요한 파일과 실행명령어, port정보 등을 모-두 가지고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 따라서 도커 이미지의 용량은 보통 수백MB ~ 수GB가 넘는다. 하지만 가상머신의 이미지에 비하면 굉장히 적은 용량이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 이미지는 상태 값을 가지지 않고 변하지 않는다(Immutable).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 하나의 이미지는 여러 컨테이너를 생성할 수 있고, 컨테이너가 삭제되더라도 이미지는 변하지 않고 그대로 남아 있음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 도커 이미지들은 github와 유사한 서비스인 DockerHub를 통해 버전 관리 및 배포(push&amp;amp;pull)가 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5) 다양한 API가 제공되어 원하는 만큼 자동화가 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6) 도커는 Dockerfile이라는 파일로 이미지를 만든다. Dockerfile에는 소스와 함께 의존성 패키지 등 사용했던 설정 파일을 버전 관리하기 쉽도록 명시되어진다.(그래서 누구나 이미지 생성과정을 확인할 수 있으며 수정도 할 수 있다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이미지와 레이어(Layer)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;레이어란 &lt;span style=&quot;color: #ee2323;&quot;&gt;기존 이미지에 추가적인 파일이 필요할 때 다시 다운로드받는 방법이 아닌 해당 파일을 추가하기 위한&amp;nbsp;개념&lt;/span&gt;이다. 이미지는 여러 개의 읽기 전용(read only) layer로 구성되고, 파일이 추가되면 새로운 Layer가 생성됨. 그리고 도커는 여러 개의 Layer를 묶어서 하나의 파일시스템으로 사용할 수 있게 해준다. 그래서 이미지와 레이어는 같은 의미로도 사용된다. 추가적으로 DockerHub 및 개인 저장소에서 이미지를 공유할 때는 바뀐 부분(Layer = image)만 주고받기 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커 컨테이너(Docker Container)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;이미지(Image)를 실행한 상태&lt;/span&gt;로, 응용프로그램의 종속성과 함께 응용프로그램 자체를 패키징 or 캡슐화하여 격리된 공간에서 프로세스를 동작시키는 기술이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 컨테이너는 이미지 Layer에 읽기/쓰기(read-write) Layer를 추가하는 것으로 생성/실행된다. 따라서 여러 개의 컨테이너를 생성해도 최소한의 용량만 사용되며,&amp;nbsp;바뀐 부분을 읽기/쓰기 Layer에 적음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 컨테이너는 종료되었다고 메모리에서 삭제되지않고 남아있다. 삭제하려면 명시적으로 삭제해야 함. 즉, 종료가 되어도 컨테이너 &amp;amp; 읽기/쓰기 Layer 또한 그대로 존재하기 때문에 다시 시작할 수 있음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 컨테이너를 삭제했다는 것은 컨테이너에서 생성한 파일이 사라진다는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt; 예) DB라면 그동안 쌓였던 데이터가 모두 사라진다는 뜻과 동일.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 한 서버는 여러 개의 컨테이너를 가져도 당연히 상관없으며, 컨테이너는 각각 독립적으로 실행된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5) 컨테이너는 커널 공간과 호스트OS 자원(시스템 콜)을 공유한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>container</category>
      <category>Docker</category>
      <category>Dockerhub</category>
      <category>image</category>
      <category>도커</category>
      <category>레이어</category>
      <category>이미지</category>
      <category>컨테이너</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/48</guid>
      <comments>https://hoon93.tistory.com/48#entry48comment</comments>
      <pubDate>Mon, 2 Nov 2020 23:40:55 +0900</pubDate>
    </item>
    <item>
      <title>Adobe(어도비) 프로그램 완전 삭제하기</title>
      <link>https://hoon93.tistory.com/47</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Adobe(어도비) 프로그램 완전 삭제 방법&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어느 날 PC 바탕화면을 딱 보니 예전에 작업할 때 사용했던 어도비 프로그램들과 포토샵, 일러스트 파일들이 너저분하게 널려있더라고요. 앞으로도 사용할 일은 없을 것 같은데.. 그래서 깔끔하게 지우는 방법을 알아봤습니다.&lt;/span&gt;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/994BC9505F6EFDC62E?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/994BC9505F6EFDC62E?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/994BC9505F6EFDC62E&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F994BC9505F6EFDC62E&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; alt=&quot;내PC adobe tools&quot; loading=&quot;lazy&quot; width=&quot;210&quot; height=&quot;346&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9910114F5F6EFDCF30?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9910114F5F6EFDCF30?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9910114F5F6EFDCF30&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9910114F5F6EFDCF30&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; alt=&quot;제어판&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;262&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원래 제가 일반 프로그램을 지울 때는 위 사진과 같이 [제어판] - [프로그램 제거] 로 들어가서 삭제하곤 했는데, 찾아보니 Photoshop과 같은 어도비(Adobe) 프로그램은 프로그램을 삭제했다고 하더라도 라이센스가 PC에 남아 있는다고 하더라고요. 저는 완벽한 삭제를 원했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 Adobe 공식 홈페이지를 찾아보니 Adobe(어도비)에서 관련 프로그램들을 깨끗하게 지울 수 있는 소프트웨어를 제공하고 있더라고요. &lt;b&gt;관련 URL은 여기 &lt;a href=&quot;https://helpx.adobe.com/creative-cloud/kb/cc-cleaner-tool-installation-problems.html?promoid=DNRHP&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;어도비 프로그램 삭제 공식 문서 주소&lt;/a&gt;를 클릭해서 확인!!&lt;/b&gt; 당연한 얘기이지만, Adobe(어도비)에서 정식으로 배포하고 있으니 안심하고 사용하셔도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/991E1D3B5F6F005B30?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/991E1D3B5F6F005B30?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991E1D3B5F6F005B30&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F991E1D3B5F6F005B30&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; alt=&quot;adobe homepage&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;394&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 주소로 이동해서 스크롤을 내리다보면, &quot;How to use ther Creative Cloud Cleaner tool&quot;이라는 항목이 있습니다. 해석해서보면 &quot;Creative Cloud 청소도구 사용방법&quot;이죠? 여기서 자신의 운영체제(OS)를 선택해주시면 됩니다. 저는 윈도우를 사용하기 때문에, Windows를 선택했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99CDE4375F6F01972D?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99CDE4375F6F01972D?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99CDE4375F6F01972D&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99CDE4375F6F01972D&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; alt=&quot;how to use the Adobe Creative Cloud Cleaner tool&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;625&quot; data-origin-width=&quot;620&quot; data-origin-height=&quot;625&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 ①번 오른쪽에 있는 &quot;Download&quot; 버튼을 클릭해주시면 위 사진과 같이 &quot;AdobeCreativeCloudCleanerTool.exe&quot;파일이 다운로드 받아지는데, 그 파일을 우클릭하여 &quot;관리자 권한을 실행&quot;을 클릭해 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/990C27365F6F043832?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/990C27365F6F043832?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/990C27365F6F043832&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F990C27365F6F043832&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; alt=&quot;삭제 프로그램 언어 선택&quot; loading=&quot;lazy&quot; width=&quot;416&quot; height=&quot;180&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 위 사진과 같은 콘솔 창이 뜰텐데, 여기서 원하는 언어를 선택합니다. 저는 영어를 선택하겠습니다. 선택하는 방법은 [e] 누른 후 [Enter] 하시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/996CD2485F6F066335?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/996CD2485F6F066335?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996CD2485F6F066335&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996CD2485F6F066335&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; alt=&quot;삭제 프로그램 약관 동의&quot; loading=&quot;lazy&quot; width=&quot;460&quot; height=&quot;511&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 어도비(Adobe) 프로그램의 라이선스 약관입니다. 해석해보면 &quot;약관에 동의하지 않으면 소프트웨어를 사용하지 말란 말&quot;이니, 동의해주면 됩니다. 방법은 [y] 누른 후 [enter] 키.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99FF544D5F6F06BA34?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99FF544D5F6F06BA34?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99FF544D5F6F06BA34&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99FF544D5F6F06BA34&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; alt=&quot;삭제 프로그램 옵션 선택&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;338&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음은 본인PC에서 정리할 프로그램을 선택하기 위한 옵션을 정하면 됩니다. 저는 모든 Adobe 프로그램을 삭제할 생각이기 때문에 [1]번은 선택했습니다. 일부만 지우고싶다면 다른 옵션을 선택하시면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/991E36475F6F0A0235?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/991E36475F6F0A0235?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991E36475F6F0A0235&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F991E36475F6F0A0235&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; alt=&quot;어도비 프로그램 목록 확인&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;344&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 사진은 현재 PC에 설치된 adobe 프로그램 목록입니다. 마음의 준비가 되셨다면 Clean All인 [11]을 입력 후 [Enter]를 과감하게 내려치시면 됩니다. 그리고 다시 [y] &amp;rarr; [Enter] 입력!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99DDD7445F6F0B4F01?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99DDD7445F6F0B4F01?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99DDD7445F6F0B4F01&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99DDD7445F6F0B4F01&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; alt=&quot;어도비 프로그램 삭제 완료&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;246&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그럼 약 3분뒤에 Creative Cloud 청소도구가 성공적으로 완료됐다는 말과 함께 관련 Log 파일까지 남겼다는 친절한 메세지가 뜹니다. 실제로 어도비(Adobe) 프로그램들이 있던 파일로 가보니 깔끔하게 지워진 것을 확인했습니다. 프로그램의 '바로가기 실행 파일'등의 아이콘들은 별도로 삭제해주시면 됩니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>일상</category>
      <category>Adobe</category>
      <category>Creative Cloud Cleaner tool</category>
      <category>라이센스</category>
      <category>삭제</category>
      <category>어도비</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/47</guid>
      <comments>https://hoon93.tistory.com/47#entry47comment</comments>
      <pubDate>Tue, 20 Oct 2020 19:12:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] NL Join, Sort Merge Join, Hash Join 특징 총정리</title>
      <link>https://hoon93.tistory.com/46</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;NL Join(Nested Loops Join) 수행 원리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 다음은 NL Join의 수행 방식을 단계별로 나타낸 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9969C8415F71359B15?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9969C8415F71359B15?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9969C8415F71359B15&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9969C8415F71359B15&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; alt=&quot;nl join&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;245&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 선행 테이블에서 조건을 만족하는 첫 번째 행을 찾음 &amp;rarr; 이때 선행 테이블에 주어진 조건을 만족하지 않는 경우 해당 데이터는 필터링 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② 선행 테이블의 조인 키를 가지고 후행 테이블에 조인 키가 존재하는지 찾으러 감 &amp;rarr; 조인 시도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;③ 후행 테이블의 인덱스에 선행 테이블의 조인 키가 존재하는지 확인 &amp;rarr; 선행 테이블의 조인 값이 후행 테이블에 존재하지 않으면 선행 테이블 데이터는 필터링 됨 (더 이상 조인 작업을 진행할 필요 없음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;④ 인덱스에서 추출한 레코드 식별자를 이용하여 후행 테이블을 액세스 &amp;rarr;&amp;nbsp;후행 테이블에 주어진 조건까지 모두 만족하면 해당 행을 추출버퍼에 넣음.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⑤ ~ ⑪ 앞의 작업을 반복 수행.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) NL 조인(Join)은 프로그래밍 언어에서 사용하는 중첩된 반복문과 유사한 방식으로 조인을 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) &lt;span style=&quot;color: #ee2323;&quot;&gt;NL Join의 가장 큰 특징은 랜덤 액세스(Random Access) 방식으로 데이터 읽는다&lt;/span&gt;는 것이다. 그리고 랜덤 액세스의 예로는 인덱스 스캔(Index Scan)이 있다. 즉, 인덱스 스캔(Index Scan)은 NL Join 방식으로 조인을 수행한다. 추가적으로 대량의 데이터를 랜덤 액세스로 접근하게 되면 많은 I/O가 발생하여 성능상 좋지 않기 때문에 무조건 랜덤 액세스가 좋은 게 아님을 인지!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) NL Join 기법은 조인이 성공하면 바로 조인 결과를 사용자에게 보여 줌. 따라서 결과를 가능한 한 빨리 화면에 보여줘야 하는 온라인 프로그램에 적당한 조인 기법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5)&amp;nbsp;NL Join 이외의 Sort Merge Join과 Hash Join은 조인 칼럼의 인덱스(Index)가 없어도 사용 가능하며, 메모리에 적재할 수 있는 크기보다 더 커지면 임시 영역(디스크)을 사용한다는 특징이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Sort Merge Join 수행 원리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 다음은 Sort Merge Join의 수행 방식을 단계별로 나타낸 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9975853B5F6D556617?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9975853B5F6D556617?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9975853B5F6D556617&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9975853B5F6D556617&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; alt=&quot;sort merge join&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;292&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 선행 테이블에서 주어진 조건을 만족하는 행을 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② 해당 행들에 대해서, 선행 테이블의 조인 키(칼럼)를 기준으로 데이터를 정렬.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;③ 후행 테이블에서 주어진 조건을 만족하는 행을 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;④ 해당 행들에 대해서, 후행 테이블의 조인 키(칼럼)를 기준으로 데이터를 정렬.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⑤ JOIN을 수행.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⑥ 조인에 성공하면 추출버퍼에 넣는다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2)&amp;nbsp;Sort Merge Join은 NL Join에서의 랜덤 액세스로는 부담이 되던 넓은 범위의 데이터를 처리할 때 이용되던 조인 기법으로, 주로 Full Table Scan 방식으로 데이터를 읽는 기법이다. 정리하자면, Sort Merge Join은 조인 칼럼의 인덱스가 존재하지 않을 경우에도 사용할 수 있는 특징이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3)&amp;nbsp;이처럼 &lt;span style=&quot;color: #ee2323;&quot;&gt;Sort Merge Join은 조인 칼럼을 기준으로 데이터를 정렬한 후 조인을 수행&lt;/span&gt;한다는 특징이 있지만, 조인 작업을 위해 항상 정렬 작업이 발생하는 것은 아니다. 예를 들어, 조인할 테이블 중에서 이미 앞 단계의 작업을 수행하는 도중에 정렬 작업이 미리 수행되었다면 조인을 위한 정렬 작업은 발생하지 않을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) 정렬할 데이터가 많아 메모리에서 모든 정렬 작업을 수행하기 어려운 경우에는 임시 영역(디스크)을 사용하기 때문에 성능이 떨어질 수 있다. 그래서 일반적으로 대량의 조인 작업에서 정렬 작업을 필요로 하는 Sort Merge Join 보다는 CPU 작업 위주로 처리하는 Hash Join이 성능상 유리하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5) Sort Merge Join은 Hash Join과는 달리 동등 조인(Equi&amp;nbsp;Join) 뿐만 아니라 Non-Equi&amp;nbsp;Join에 대해서도 조인 작업이 가능하다.&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #22741c;&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;(&lt;/span&gt;&lt;b&gt;&lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/27&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;동등&amp;amp;비동등 조인&lt;/a&gt;&lt;/b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;은 여기를 클릭하여 확인)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Hash Join 수행 원리&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 다음은 Hash Join의 수행 방식을 단계별로 나타낸 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9975293B5F6D797C25?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9975293B5F6D797C25?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9975293B5F6D797C25&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9975293B5F6D797C25&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; alt=&quot;hash join&quot; loading=&quot;lazy&quot; width=&quot;520&quot; height=&quot;292&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;① 선행 테이블에서 주어진 조건을 만족하는 행을 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;② 해당 행들에 대해서, 선행 테이블의 조인 키(칼럼)를 기준으로 Hash 함수를 적용하여 해쉬 테이블을 생성.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;③ 후행 테이블에서 주어진 조건을 만족하는 행을 찾는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;④ 해당 행들에 대해서, 후행 테이블에 Hash 함수를 적용하여 선행 테이블의 해쉬 테이블에서 맞는 버킷을 찾음.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⑤ JOIN을 수행 &amp;amp; 조인에 성공하면 추출버퍼에 넣는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;⑥ 후행 테이블의 조건을 만족하는 모든 행에 대해서 3~5번 반복.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 해쉬 조인(Hash Join)을 수행할 테이블의 조인 칼럼을 기준으로 Hash 함수를 적용하여 서로 동일한 Hash 값을 갖는 것들 사이에서 실제 값이 같은지를 비교하면서 조인을 수행하는 방식이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해쉬 함수를 적용한 실제 값은 어떤 값으로 해슁(Hashing)될 지 예측할 수 없다. 하지만 해쉬 함수가 적용될 때 동일한 값은 항상 같은 값으로 해슁됨이 보장된다. 하지만 해쉬 함수를 적용할 때 보다 큰 값이 항상 큰 값으로 해슁되고 작은 값이 항상 작은 값으로 해슁된다는 보장은 없다. 그렇기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;Hash Join은 동등 조인(Equi join)에서만 사용할 수 있다&lt;/span&gt;는 특징이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) &lt;span style=&quot;color: #ee2323;&quot;&gt;CPU 작업 위주로 데이터 처리&lt;/span&gt;하는 Hash Join은 NL Join의 랜덤 액세스 문제점과 Sort Merge Join의 문제점인 정렬 작업의 부담을 해결 위한 대안으로 등장하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) Hash Join은 조인 작업을 수행하기 위해 &amp;lsquo;해쉬 테이블&amp;rsquo;을 메모리에 생성한다. &amp;rarr; Hash Table의 크기가 메모리에 적재할 수 있는 크기보다 더 커지면 임시 영역(디스크)에 해쉬 테이블을 저장함. 그렇기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;Hash Join을 할 때는 결과 행의 수가 적은 테이블을 선행 테이블로 사용하는 것이 좋다.&lt;/span&gt; 선행 테이블의 결과를 완전히 메모리에 저장할 수 있다면 임시 영역에 저장하는 작업이 발생하지 않기도 하고 CPU연산을 조금 덜 수행할 수 있기 때문&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5) Hash Join에서는 선행 테이블을 이용하여 먼저 해쉬 테이블을 생성한다고 해서 선행 테이블을 &lt;span style=&quot;color: #ee2323;&quot;&gt;Build Input&lt;/span&gt;이라고도 하며, 후행 테이블은 만들어진 해쉬 테이블에 대해 해쉬 값의 존재여부를 검사한다고 해서 &lt;span style=&quot;color: #ee2323;&quot;&gt;Prove Input&lt;/span&gt;이라고도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;NL Join, Sort Merge Join, Hash Join의 공통점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 3개의 조인은 모두 조인에 성공하면 결과를 추출버퍼에 넣는다는 공통점이 있다. 추출버퍼는 SQL문의 실행결과를 보관하는 버퍼로서 일정 크기를 설정하여 추출버퍼에 결과가 모두 차거나 더 이상 결과가 없어서 추출버퍼를 채울 것이 없으면 결과를 사용자에게 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※추출버퍼: 운반단위, Array Size, Prefetch Size라고도 함.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Database</category>
      <category>equi join</category>
      <category>hash join</category>
      <category>Index Scan</category>
      <category>NL Join</category>
      <category>non equi join</category>
      <category>Oracle</category>
      <category>sort merge join</category>
      <category>SQLD</category>
      <category>오라클</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/46</guid>
      <comments>https://hoon93.tistory.com/46#entry46comment</comments>
      <pubDate>Sun, 18 Oct 2020 14:29:00 +0900</pubDate>
    </item>
    <item>
      <title>SQLD 합격 후기(공부 방법, 참고도서 등)</title>
      <link>https://hoon93.tistory.com/42</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;합격 후기 및 공부 방법, 참고도서, 시험 Tip에 대해 공유해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/993244415F7C6D3C26?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/993244415F7C6D3C26?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/993244415F7C6D3C26&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F993244415F7C6D3C26&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; alt=&quot;sqld 합격&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;277&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-decoration: line-through;&quot;&gt;시험 볼 때는 80~90점 예상했는데, 이게 웬걸..&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQLD 자격 준비 이유 및 소개&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 큰 이유는 웹개발자로써 회사에서 오라클(Oracle)을 사용하기 때문이었습니다. 대학생 시절 개인 공부할때는 MySQL만 사용했었는데...오라클을 사용한다니 DB Query를 짜더라도 뭔가 제대로 알고 만들어야겠다는 생각이 들었어요. 그냥 단순히 쿼리를 찍어내는 것보다 더 재밌게 고민하면서 짤 수 있지 않을까 하는 기대도 했구요ㅎ 종종 IT에서는 자격증이 필요없다? 는 말을 누군가 주위에서 하곤 했는데, 잘모르겠고 SQLD는 개인적으로 정말 많은 도움이 된 것 같아요. SQLP는 얼마나 큰 도움이 될까요. 뭐 어떤 것이든 개인차가 있겠죠? 어쨌든 저는 제대로 알아보자라는 마인드로 학습했었고 그러다보니 오라클(Oracle)에 대한 기본 소양(?)이 생긴 것 같기도 합니다. 여러분도 SQL공부를 할 때 SQLD자격 취득이라는 목표를 두고 하면 좋은 결과가 있을 거라고 생각되네요. 또 SQLD는 정보처리기사와 동일한 국가공인 자격증이라서 민간자격보다 좋으니 1석2조!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;공부 방법 &amp;amp;&amp;nbsp;참고한 책&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1768996235647&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SQL 전문가 가이드 | 한국데이터산업진흥원 - 교보문고&quot; data-og-description=&quot;SQL 전문가 가이드 | SQL 전문가 가이드는 SQL 자격검정 대비 수험서로 데이터베이스와 데이터 모델링에 대한 지식을 바탕으로 최적의 성능을 발휘할 수 있도록 SQL을 작성하고 데이터베이스 프로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000001399869&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000001399869&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cE4gDu/dJMb8PGqbt0/COv5MyxKdLexkP6aDxJKKk/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/bqUMVr/dJMb8YpPqGD/2gKg8FLNoL3YLpN05IDIUk/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/bZOTTg/dJMb8TB3ynm/EbUzryUoHZ7dJq8PVxlEhk/img.jpg?width=599&amp;amp;height=608&amp;amp;face=0_0_599_608&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001399869&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000001399869&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cE4gDu/dJMb8PGqbt0/COv5MyxKdLexkP6aDxJKKk/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/bqUMVr/dJMb8YpPqGD/2gKg8FLNoL3YLpN05IDIUk/img.jpg?width=458&amp;amp;height=593&amp;amp;face=0_0_458_593,https://scrap.kakaocdn.net/dn/bZOTTg/dJMb8TB3ynm/EbUzryUoHZ7dJq8PVxlEhk/img.jpg?width=599&amp;amp;height=608&amp;amp;face=0_0_599_608');&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;SQL 전문가 가이드 | 한국데이터산업진흥원 - 교보문고&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SQL 전문가 가이드 | SQL 전문가 가이드는 SQL 자격검정 대비 수험서로 데이터베이스와 데이터 모델링에 대한 지식을 바탕으로 최적의 성능을 발휘할 수 있도록 SQL을 작성하고 데이터베이스 프로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1768996288712&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;교보문고 | 대한민국 최고의 도서쇼핑몰&quot; data-og-description=&quot;교보문고는 온오프라인을 통틀어 대한민국 최고의 도서쇼핑몰이며 전자책, 음반, 기프트, 문화서비스까지 제공하는 종합문화기업입니다.&quot; data-og-host=&quot;mobile.kyobobook.co.kr&quot; data-og-source-url=&quot;https://search.kyobobook.co.kr/search?keyword=%EC%9D%B4%EA%B8%B0%EC%A0%81+SQL+%EA%B0%9C%EB%B0%9C%EC%9E%90+%EC%9D%B4%EB%A1%A0%EC%84%9C&amp;amp;gbCode=TOT&amp;amp;target=total&quot; data-og-url=&quot;http://mobile.kyobobook.co.kr&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdc7vN/dJMb89x7y2q/WukB6P1Fq3jDDUKYgXMcS1/img.png?width=348&amp;amp;height=184&amp;amp;face=0_0_348_184,https://scrap.kakaocdn.net/dn/bzH80X/dJMb89x7y2p/ytzkUNNpNNOBR7DfKkDaC1/img.png?width=348&amp;amp;height=184&amp;amp;face=0_0_348_184,https://scrap.kakaocdn.net/dn/bDUa4P/dJMb83kmYHJ/eX7tmR7ZtB9H6nMwq1btkk/img.jpg?width=890&amp;amp;height=380&amp;amp;face=0_0_890_380&quot;&gt;&lt;a href=&quot;https://search.kyobobook.co.kr/search?keyword=%EC%9D%B4%EA%B8%B0%EC%A0%81+SQL+%EA%B0%9C%EB%B0%9C%EC%9E%90+%EC%9D%B4%EB%A1%A0%EC%84%9C&amp;amp;gbCode=TOT&amp;amp;target=total&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://search.kyobobook.co.kr/search?keyword=%EC%9D%B4%EA%B8%B0%EC%A0%81+SQL+%EA%B0%9C%EB%B0%9C%EC%9E%90+%EC%9D%B4%EB%A1%A0%EC%84%9C&amp;amp;gbCode=TOT&amp;amp;target=total&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdc7vN/dJMb89x7y2q/WukB6P1Fq3jDDUKYgXMcS1/img.png?width=348&amp;amp;height=184&amp;amp;face=0_0_348_184,https://scrap.kakaocdn.net/dn/bzH80X/dJMb89x7y2p/ytzkUNNpNNOBR7DfKkDaC1/img.png?width=348&amp;amp;height=184&amp;amp;face=0_0_348_184,https://scrap.kakaocdn.net/dn/bDUa4P/dJMb83kmYHJ/eX7tmR7ZtB9H6nMwq1btkk/img.jpg?width=890&amp;amp;height=380&amp;amp;face=0_0_890_380');&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;교보문고 | 대한민국 최고의 도서쇼핑몰&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;교보문고는 온오프라인을 통틀어 대한민국 최고의 도서쇼핑몰이며 전자책, 음반, 기프트, 문화서비스까지 제공하는 종합문화기업입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mobile.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 위 두 책으로 시험 준비를 했습니다. 이론 및 개념은 'SQL 전문가 가이드' 책으로 공부했고, '이기적 SQL 개발자' 도서로는 시험 1주 전에 기출문제만 반정도 풀었어요. 이기적 교재로는&amp;nbsp;이론을 제대로 안 봐서 잘 모르겠지만&amp;nbsp;아무래도 Oracle의 방대한 개념들을&amp;nbsp;축약한&amp;nbsp;것이다 보니 설명이 부족할 것 같다고 생각한&amp;nbsp;것이&amp;nbsp;이유였습니다. 개인적으로 'SQL 전문가 가이드'는 정말 자세하고 친절하게 설명돼있는 느낌이었습니다.(&lt;span style=&quot;text-decoration: line-through;&quot;&gt;그만큼 책 두께가 정말 엄청납니다&lt;/span&gt;)&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어쨌든 저는 'SQL 전문가 가이드'로 공부할 때, 특히 'SQL 기본 및 활용'부터 '최적화 파트'는&amp;nbsp;한 3번은&amp;nbsp;정독한&amp;nbsp;것 같아요. 그러면서 노트에 정리했고요. 확실히 반복해서 읽다 보니 좀 더 이해하게&amp;nbsp;된 느낌이 들더라고요.&amp;nbsp;이게 저의 공부 방법인 것 같아요. 기출문제만 풀기 등으로&amp;nbsp;합격하는 법도 있겠지만 솔직히 자신도 없고ㅋㅋ 시험 응시료인 5만원을 또 버릴까 봐 무서웠습니다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;시험 TIP&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;당연히 시험 내용 대부분 다 중요하지만 그중에서도 특히 '그룹 함수'파트에서 ROLLUP, CUBE 등과 '계층형 질의' 파트는 정말 별 5개로 중요한 것 같아요. 이론만 알면 헷갈릴 수밖에 영역이기도 하고 실제 이번 시험에서 가장 많이 또 어렵게 출제됐었어요. 내용이 애초에 어려운데 피할 수 없으니 즐겨ㄹ.. 는 힘드시겠고 잘 준비하시면 도움이 될 것 같습니다.&lt;/span&gt;&lt;span style=&quot;color: #ff5e00;&quot;&gt;(&lt;b&gt;'&lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/29&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;계층형 Query&lt;/a&gt;'&lt;/b&gt; 파트 등은 제가 정리해둔 포스트가&amp;nbsp;있는데 참고해서 도움이 되셨으면 좋겠습니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 시험 문제는 총 50문제로 객관식 42문제, 주관식 8문제가 나옵니다. 주관식은 단답형으로 정확히 써야 합니다. 합격기준은 100점 만점에 60점 이상으로 한 문제당 2점이니까 30문제 이상 맞춘다고 생각하면 될 것 같네요. 과목별 점수 40% 미만은 과락되니 이점 유의하세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;S&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;QLD 영구 유효 자격증으로 변경하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/996CB3465F6D952103?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/996CB3465F6D952103?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996CB3465F6D952103&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996CB3465F6D952103&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; alt=&quot;보수교육 공지사항&quot; loading=&quot;lazy&quot; width=&quot;820&quot; height=&quot;305&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예전에는 SQLD/SQLP의 유효기간이 없었다고 하는데 17년도 기준으로 유효기간이 생겼다. 하지만 '한국데이터산업진흥원'의 공식 입장과 같이 자격증 취득 후 1년 6개월 이후, '보수교육'이라고 하는 무료 온라인 강의 수강을 하면 '영구' 자격증으로 변경된다고 합니다. (보수교육 수강 완료시 자격증 유효기간이 '취득일로부터 2년'에서 '영구'로 변경)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 자격 유효기간 내 보수교육을 이수하지 못하더라도 보수교육 이수를 통해 자격의 유효기간을 '영구'로 갱신할 수 있다고 합니다. (단, 자격 유효기간 만료 시점부터 보수교육 이수완료 사이 기간은 자격의 효력이 일시 정지)&lt;/span&gt;&lt;/p&gt;</description>
      <category>일상</category>
      <category>SQLD</category>
      <category>공부 방법</category>
      <category>합격</category>
      <category>합격 후기</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/42</guid>
      <comments>https://hoon93.tistory.com/42#entry42comment</comments>
      <pubDate>Tue, 6 Oct 2020 23:18:00 +0900</pubDate>
    </item>
    <item>
      <title>도커(Docker)와 가상머신(Virtual Machine) 비교</title>
      <link>https://hoon93.tistory.com/41</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커(Docker = Docker Engine) 란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99BBBF3D5F6AAFDC20?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99BBBF3D5F6AAFDC20?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99BBBF3D5F6AAFDC20&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99BBBF3D5F6AAFDC20&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; alt=&quot;docker logo&quot; loading=&quot;lazy&quot; width=&quot;312&quot; height=&quot;278&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저는 어떠한 IT용어를 접하면 제일 먼저 사전적 의미를 찾아보는 편인데요, 그래서 찾아보니 도커(Docker)란 '부두(항만) 노동자'를 의미함을 알게 됐었습니다. '부두 노동자'라.. IT용어인데 왠 항구와 관련된 말이 나올까요?? 도커는 2013년에 출시된 기술로 컨테이너 기반의 오픈소스 가상화 플랫폼이라고 정의됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 결론부터 말하자면 '컨테이너(Container)'라는 것은 플랫폼에 상관없이 Application을 실행할 수 있는 기술을 의미합니다. 즉, 우리는 말 그대로 도커를 이용하여 각종 Application들을 어떤 환경에서든 자유롭게 사용할 수 있습니다. 또한 도커의 로고인 '컨테이너를 싣고 운반하는 고래'처럼 Docker(부두 노동자)는 DockerHub(항구)를 통해 여러 Application들을 전 세계에 나르며 버전 관리와 배포를 도와줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커(Docker)와 Immutable Infrastructure Paradigm&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;blockquote class=&quot;txc-textbox&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음에 사람들은 물리서버를 개인이 직접 운영 &amp;rarr; 호스팅 서비스를 이용하기 시작 &amp;rarr; 호스트 서버 구입과 설치에 시간과 돈, 비용이 많이 드는 문제가 발생 &amp;rarr; 가상화가 발전하면서 클라우드 환경으로 변화 &amp;rarr; 가상서버를 임대 &amp;rarr; 서버의 수가 많아짐 &amp;rarr; 개인이 일일이 환경 셋팅하기 힘들어짐 &amp;rarr; Immutable Infrastructure Paradigm 탄생&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 사전적으로 '불변의 구조'라고 해석되는 Immutable Infrastructure는 OS(Ubuntu, CentOS, Windows, MAC 등...)와 서비스 환경(어플리케이션, 소스코드, 시스템 툴, 시스템 라이브러리 등의 서버에 설치하는 것들)을 각각 분리하여 이해하고 분리한 것들 중 서비스 환경(서비스 인프라) 부분을 이미지화(실행파일화)하여 배포한 뒤 가급적 변경하지 않고 사용하는 것을 의미합니다. 서비스 환경의 업데이트는 서비스 환경을 변경하는 것이 아닌 이미지(Image)를 교체하는 식으로 이루어집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 도커(Docker)는 호스트OS와 서비스 운영 환경(서버 프로그램, 소스코드, 컴파일된 binary파일)를 분리한다는 패러다임과 한 번 설정한 운영 환경은 변경하지 않는다(Immutable)는 위 개념을 기반으로 탄생한 도구입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너(Container)와 가상 머신(VM, Virtual Machine) 비교&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨테이너와 가상 머신은 리소스 분리 및 할당 이점이 유사하지만, 컨테이너는 하드웨어 대신 운영 체제를 가상화하기 때문에 다르게 작동합니다.&lt;/span&gt;&lt;/p&gt;
&lt;table border=&quot;0&quot; cellspacing=&quot;5&quot; cellpadding=&quot;0&quot; align=&quot;center&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99AC03415F6AD6E402?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99AC03415F6AD6E402?original&quot; data-alt=&quot;컨테이너(Container)&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99AC03415F6AD6E402&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99AC03415F6AD6E402&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; alt=&quot;docker containerized application&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;327&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;컨테이너(Container)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/td&gt;
&lt;td&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/993E74415F6AD6E503?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/993E74415F6AD6E503?original&quot; data-alt=&quot;가상 머신(Virtual Machine)&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/993E74415F6AD6E503&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F993E74415F6AD6E503&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; alt=&quot;가상 머신&quot; loading=&quot;lazy&quot; width=&quot;410&quot; height=&quot;327&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가상 머신(Virtual Machine)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. 컨테이너(Container)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;도커 공식 문서에 따르면, 컨테이너는 코드 및 종속성을 함께 패키징(Packaging)하는 Application계층의 추상화라고 정의됩니다. 그리고 이 말은 여러 종류의 컨테이너가 동일한 시스템(내PC 등)에서 실행되고 OS커널을 다른 컨테이너와 공유할 수 있으며, 각 컨테이너는 사용자 공간에서 분리된 프로세스로 실행된다는 의미를 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로, 컨테이너는 VM보다 메모리 공간을 적게 차지하고(컨테이너 이미지의 크기는 일반적으로 수십 MB/s), 더 많은 Application을 처리할 수 있으며 더 적은 수의 VM 또는 운영 체제(OS)를 필요로 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 말을 다시 풀어 설명하자면, 위 사진에서 도커(Docker)의 구조를 보면 &lt;span style=&quot;color: #ee2323;&quot;&gt;하나의 HostOS(Host Operating System)위에 Docker를 설치하여 그 위에 각각의 서비스(Application) 환경들을 설치/운영하는 형태&lt;/span&gt;입니다. 따라서 Application들은 각각 OS를 설치하는 것이 아니라 하나의 HostOS위에서 자원만 공유하기 때문에 상대적으로 매우 가벼우며 Docker가 설치된 환경이라면 이미지(Image)가 사용 가능하기 때문에 어디서든 사용 가능하다는 장점이 있습니다. 즉, 컨테이너란 프로세스 단위의 분리 독립된 환경(공간) 안의 가상공간이라고 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 가상 머신(VM, Virtual Machine)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 머신은 하나의 서버를 여러 서버로 전환하는 물리적인 하드웨어의 추상화라고 정의됩니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;하이퍼바이저(Hypervisor) 기법을 통해 단일 시스템에서는 여러 VM을 실행&lt;/span&gt;할 수 있지만, 각 VM에는 운영 체제(OS), 애플리케이션, 필요한 Binary 및 Library의 전체 복사본이 포함되어 있습니다. 따라서 이 복사본은 수십 GB를 차지하기 때문에 VM의 부팅 속도 또한 느려질 수 있다는 특징이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 말해, 가상머신은 Hypervisor를 이용해 하나의 호스트OS에서 여러 개의 OS를 사용하는 방법이며, 위 사진에서의 VM구조와 같이 기반(Infrastructure)인 HostOS 위에 각각의 VM. 즉, GuestOS(Guest Operating System)를 하나씩 설치하는 형태입니다. 이처럼 각각의 가상머신은 하나의 독립된 커널 공간을 가진 완전한 컴퓨터를 생산하는 식의 환경을 구성합니다. 따라서, VM은 각각의 운영체제(OS)를 포함하기 때문에 용량이 굉장히 무겁다는 의미입니다. 하지만 단점만 있는 것은 아닙니다. 다른 GuestOS와 분리 독립된 공간과 자원을 할당받아 사용되기 때문에, 보안성 측면에서 효율적이라고 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr class=&quot;tx-hr-image-4&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 글에서는 Docker의 개념 중 컨테이너(Container)와 이미지(Image)에 대한 개념을 정리해보도록 하겠습니다.&lt;/p&gt;</description>
      <category>Infra &amp;amp; Cloud</category>
      <category>container</category>
      <category>image</category>
      <category>immutable infrastructure</category>
      <category>OS</category>
      <category>virtual machine</category>
      <category>vm</category>
      <category>가상 머신</category>
      <category>도커</category>
      <category>이미지</category>
      <category>컨테이너</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/41</guid>
      <comments>https://hoon93.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 2 Oct 2020 11:51:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] PL/SQL 트리거(Trigger)와 함수(User Defined Function) 특징 및 예제</title>
      <link>https://hoon93.tistory.com/40</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자 정의 함수(User defined Function, Function) 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저(Procedure)처럼 SQL과 로직을 묶은 명령문이다. 다만 다른 점은&amp;nbsp;RETURN을 사용해서 하나의 값을 반드시 되돌려 줘야 한다는 특징이 있다. 보통 값을 계산하고 결과값을 반환하기 위해서 함수를 많이 사용한다. &lt;b&gt;즉, Function은 특정 작업 수행 후 반드시 결과값을 RETURN하는 PL/SQL 블럭(BLOCK).&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성방법은 &lt;a href=&quot;https://hoon93.tistory.com/39&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프로시저&lt;/a&gt;와 동일하다. 단지 Procedure가 아니라 function으로만 바꿔주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728325948139&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE [OR REPLACE] FUNCTION &quot;함수 이름&quot;(){ ... ...&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트리거(Trigger) 특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 트리거(Trigger)란 특정 테이블에 DML이 수행되었을 때, 데이터베이스에서 자동으로 동작하도록 작성된 프로그램. 즉, 사용자가 직접 호출하는 것이 아니라 데이터베이스에서 자동으로 수행하는 프로시저(Procedure)이다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) 지속적으로 조회해야 하는 집계데이터들은 트리거를 통해 미리 계산해서 테이블에 보관하도록 하면 좋음. 또한 SQL의 제약조건 방법을 통해 명시할 수 없는 무결성 제약조건을 구현하고, 관련 테이블의 데이터를 일치시킬 때도 자주 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;예) &amp;lsquo;입고&amp;rsquo; 테이블에 새로운 제품이 들어왔을 때, 그 수량을 &quot;재고&quot; 테이블에 자동으로 반영되게 하는 경우.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) 뷰(View)&amp;nbsp;대해서가 아니라 TABLE에 관해서만 정의될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) &lt;a href=&quot;https://hoon93.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트랜잭션 제어문&lt;/a&gt;(COMMIT, ROLLBACK, SAVEPOINT)을 사용할 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트리거(Trigger&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;) 문법 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre id=&quot;code_1728326171349&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE [OR REPLACE] TRIGGER '트리거 이름'()

  AFTER INSERT
  ON 'trigger를 설정할 테이블'
  FOR EACH ROW

DECLARE &amp;larr; 변수를 선언할 때는 DECLARE문을 사용해야 합니다. 'PL/SQL 블록 기본 문법' 을 확인하려면 여기를 클릭!!
  변수1 데이블명.칼럼명%TYPE;
  변수2 데이블명.칼럼명%TYPE;

BEGIN
  변수1 := :NEW.칼럼명;
  변수2 := :OLD.변수명;

  UPDATE 테이블명
  SET
  Where
  
  If SQL%NOTFOUND then
    &amp;hellip;쿼리문&amp;hellip;
  end if;

END;
/&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) &lt;span style=&quot;color: #ee2323;&quot;&gt;FOR EACH ROW&lt;/span&gt; : 테이블의 각 ROW마다 Trigger를 적용하겠다는 의미.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) :NEW : 신규로 입력된 레코드의 정보를 가지고 있는 구조체를 의미.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) &lt;span style=&quot;color: #ee2323;&quot;&gt;:OLD&lt;/span&gt; : 수정, 삭제되기 전의 레코드를 가지고 있는 구조체를 의미.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4) &lt;span style=&quot;color: #ee2323;&quot;&gt;SQL%NOTFOUND&lt;/span&gt; : 해당 if문에서는 SQL처리 결과가 NULL이 아니면. then 아래 작성한 Query문을 실행.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트리거(Trigger) 실행 예제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre id=&quot;code_1728326048860&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE OR REPLACE TRIGGER sum_trigger
  BEFORE

  INSERT OR UPDATE ON emp
  FOR EACH ROW

  DECLARE
    avg_sal NUMBER;

  BEGIN
    SELECT ROUND(AVG(sal),3)
  INTO avg_sal
  FROM emp;


  DBMS_OUTPUT.PUT_LINE('급여 평균 : ' || avg_sal);

END;
/&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1728326093698&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- DBMS_OUTPUT.PUT_LINE을 출력.
SQL&amp;gt; SET SERVEROUTPUT ON ;

-- INSERT문을 실행. 1 개의 행을 추가.
SQL&amp;gt; INSERT INTO EMP(EMPNO, ENAME, JOB, HIREDATE, SAL) VALUES(1000, 'LION', 'SALES', SYSDATE, 5000);

-- INSERT문을 실행되기 전까지의 급여 평균이 출력된다.
급여 평균 : 2073.214

1 개의 행이 만들어졌습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #666666; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <category>Block</category>
      <category>FOR EACH ROW</category>
      <category>function</category>
      <category>Oracle</category>
      <category>pl/sql</category>
      <category>PLSQL</category>
      <category>procedure</category>
      <category>Trigger</category>
      <category>오라클</category>
      <category>트리거</category>
      <category>프로시저</category>
      <category>함수</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/40</guid>
      <comments>https://hoon93.tistory.com/40#entry40comment</comments>
      <pubDate>Tue, 29 Sep 2020 15:42:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] PL/SQL 프로시저(Procedure) 특징 및 예제</title>
      <link>https://hoon93.tistory.com/39</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저(Procedure)의&amp;nbsp;특징&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자가 자주 실행해야하는 특정 작업을 필요할 때 호출하기위해 절차적인 언어를 이용하여 작성한 이름이 있는 프로그램 모듈(Block)을 의미한다.&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;- 매개 변수를 받을 수 있는 PL/SQL BLOCK 이다. (&lt;b&gt;&lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/38&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PL/SQL 블록이란?&lt;/a&gt;&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 프로시저 내의 변수는 &lt;b&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;Scalar변수&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라고 해서 임시 데이터 1개만 저장할 수 있는 변수&lt;/span&gt;&lt;/b&gt;이며, 모든 형태의 데이터 유형 지정 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PL/SQL의 대입연산자는 &amp;lsquo; := &amp;rsquo;이다. (&amp;harr; T-SQL은 일반적인 &amp;lsquo; = &amp;rsquo;이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PL/SQL에서 사용하는 프로시저 내의 SELECT문장은 반드시 결과 값이 있어야 하며, 그 &lt;span style=&quot;color: #ff0000;&quot;&gt;결과는 반드시 1개&lt;/span&gt;여야 한다. 조회결과가 없거나 2개 이상인 경우에는 에러가 발생(T-SQL은 결과값없어도 상관없음)한다. 그러나 특정한 로직을 처리하면서 결과는 있어야하지만 그 결과 값을 함수(사용자 정의 함수)처럼 반환(return)하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저(Procedure) 문법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;txc-textbox&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;CREATE [OR REPLACE] Procedure&lt;/span&gt; &amp;ldquo;Procedure_name&amp;rdquo;( argument1 &lt;span style=&quot;color: #ff007f;&quot;&gt;[MODE]&lt;/span&gt; data_type1, argument2 &lt;span style=&quot;color: #ff007f;&quot;&gt;[MODE]&lt;/span&gt; data_type2, &amp;hellip; &amp;hellip; )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;IS[AS]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;BEGIN&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;EXCEPTION&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;END;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;/&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;span style=&quot;color: #ff007f;&quot;&gt;CREATE [OR REPLACE]&lt;/span&gt; 구문을 이용하여 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;span style=&quot;color: #ff007f;&quot;&gt;OR REPLACE&lt;/span&gt; : 같은 프로시저가 있을 때, 기존의 프로시저를 무시하고 새로운 내용으로 덮어쓰겠다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) &lt;span style=&quot;color: #ff007f;&quot;&gt;MODE&lt;/span&gt; : mode는 매개변수의 역할을 결정하는 자리이다. mode자리에 들어갈 수 있는 변수는 3가지로 IN, OUT, INOUT이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; ① IN: 운영체제에서 프로시저로 전달될 변수의 모드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; ② OUT: 프로시저에서 처리된 결과라 운영체제로 전달.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; ③ INOUT: IN과 OUT 두 가지 기능 모두 수행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) &lt;span style=&quot;color: #ff007f;&quot;&gt;IS&lt;/span&gt; : PL/SQL의 Block을 시작한다는 의미이며, 프로시저 내(정확히는, Begin문뒤에 나올 SQL문)에서 사용할 변수를 선언하는 곳이다. LOCAL변수는 IS와 Begin사이에 선언해서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) &lt;span style=&quot;color: #ff007f;&quot;&gt;EXCEPTION&lt;/span&gt; : Begin~end사이에서 실행되는 SQL문 실행 도중 발생한 에러를 처리하는 예외 처리부.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) &lt;span style=&quot;color: #ff007f;&quot;&gt;END;&lt;/span&gt; : 실행문의 종료를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7) &lt;span style=&quot;color: #ff007f;&quot;&gt;/&lt;/span&gt; : end; 뒤에 위치하는 슬러시(/)는 데이터베이스에게 프로시저를 컴파일하라는 명령.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저(Procedure) 예제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;txc-textbox&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;CREATE OR REPLACE Procedure&lt;/span&gt; p_DEPT_insert (&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v_DEPTNO in number,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v_dname in varchar2,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v_loc in varchar2,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v_result out varchar2 )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;IS&lt;/span&gt; cnt number := 0;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;BEGIN&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT COUNT(*) INTO CNT FROM DEPT WHERE DEPTNO = v_DEPTNO AND ROWNUM = 1;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;If cnt &amp;gt; 0 then v_result := '이미 등록된 부서번호이다';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;else INSERT INTO DEPT (DEPTNO, DNAME, LOC) VALUES (v_DEPTNO, v_dname, v_loc);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;COMMIT;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v_result := '입력 완료!!';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end if;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;EXCEPTION&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHEN OTHERS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;THEN ROLLBACK; v_result := 'ERROR 발생';&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;END;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff007f;&quot;&gt;/&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저(Procedure) 실행 예제&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote class=&quot;txc-textbox&quot; data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQL&amp;gt; &lt;span style=&quot;color: #ff007f;&quot;&gt;EXECUTE&lt;/span&gt; p_DEPT_insert(10, 'dev', 'seoul', :rslt);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PL/SQL 처리가 정상적으로 완료되었습니다.&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;SQL&amp;gt; print rslt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) EXECUTE구문을 이용하여 정의한 프로시저(Procedure)를 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 프로시저의 OUT변수 rslt를 print할 때, DEPTNO(10)가 이미 존재한다면 '이미 등록된 부서번호'가 출력될 것이다. 반대로 없다면 '입력 완료!!'라고 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <category>Block</category>
      <category>Oracle</category>
      <category>pl/sql</category>
      <category>PLSQL</category>
      <category>procedure</category>
      <category>SQLD</category>
      <category>오라클</category>
      <category>프로시저</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/39</guid>
      <comments>https://hoon93.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 28 Sep 2020 20:44:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] PL/SQL이란? 그리고 블록(Block)</title>
      <link>https://hoon93.tistory.com/38</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Oracle 에서의 블록(Block) 구조 및&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저(Procedure), 사용자 정의 함수(User Defined Function), 트리거(Trigger) 비교 총정리&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PL/SQL(Oracle's Procedural Language extension to SQL, 절차형 SQL)이란??&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) 응용 프로그램에서의 데이터베이스 처리 성능을 향상시키기 위해 SQL문장에서 변수정의, 조건처리(IF), 반복처리(LOOP, WHILE, FOR) 등을 지원하며, &lt;b&gt;오라클 자체에 내장되어 있는&lt;/b&gt;(오라클에서 지원하는) Procedure Language이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;2) PL/SQL을 이용하여 &lt;span style=&quot;color: #ff0000;&quot;&gt;다양한 저장 모듈(Stored Module)&lt;/span&gt;을 개발할 수 있다.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #22741c;&quot;&gt;※저장 모듈: PL/SQL문장을 데이터베이스 서버에 저장하여 사용자와 애플리케이션 사이에서 공유할 수 있도록 만든 일종의 SQL 컴포넌트 프로그램이며, 독립적으로 실행되거나 다른 프로그램으로부터 실행될 수 있는 완전한 실행 프로그램. &lt;b&gt;Oracle의 저장 모듈에는 프로시저(Procedure), 사용자 정의 함수(User Defined Function), 트리거(Trigger)가 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;3) Block구조로 되어있어 다수의 SQL 문을 한 번에 ORACLE DB로 보내서 처리하므로 수행 속도를 향상 시킬 수 있다. 풀어 말하면, 여러 SQL문장을 Block으로 묶고 한 번에 서버로 보내기 때문에 통신량을 줄일 수 있음. 또한 각 기능별로 모듈화 가능하다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PL/SQL 블록(Anonymous Block) 구조 및 특징&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/99855F415F630E1C1D?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/99855F415F630E1C1D?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99855F415F630E1C1D&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F99855F415F630E1C1D&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; alt=&quot;PL/SQL Block Structure&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;394&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 그림과 같이 PL/SQL에서 하나의 블록(Block) 즉, 익명 블록(Anonymous Block)은 Declare, Begin, Exception, End로 구성된다. (Procedure, User Defined Function이나 Trigger의 생성 방법이나 사용 목적은 다르지만 기본적인 문법은 비슷하다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DECLARE문(선언문)을 이용하여 정의되며, DECLARE문의 사용은 선택 사항이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 Block단위란 개념이 중요하다. 이 블록이란 PL/SQL문법으로 따지고 보면 Declare ~ End이다. 그리고 DECLARE은 PL/SQL 블럭의 시작을 의미이며, 변수 생성이 필요 없어 생략 시 Begin이 블럭의 시작을 의미하게 된다. 즉, Begin(Declare) ~ End 는 단순히 PL/SQL 언어에서의 블럭을 의미. 또한 중첩하여 다른 블럭을 포함할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추후에 나올 IS(선언문)는 PL/SQL을 이용하여 작성되는 Procedure, User Defined Function에서 사용되는 것이며, Declare과 마찬가지로 문장의 시작을 의미하면서 동시에 변수가 선언되는 영역이다. IS가 Declare의 역할까지 대신하고 있으므로 이런 상황에서는 Declare가 오면 안 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;변수, 상수 등을 선언하며 SQL문장 간 값을 교환 가능하다는 장점.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PL/SQL에서 변수 정의 시 사용되는 대입연산자는 := , 값 비교연산자는 = (Java에서의 =와 ==와 같다)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;if, loop 등의 절차형 언어사용으로 절차적인 프로그램이 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DBMS 정의 에러 or 사용자 정의 에러를 사용 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PL/SQL은 Oracle에 내장되어있어 PL/SQL과 Oracle을 지원하는 어떤 서버로도 프로그램을 옮길 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PL/SQL &lt;span style=&quot;color: #ff0000;&quot;&gt;함수(Function), 프로시저(Procedure), 트리거(Trigger)&lt;/span&gt; 비교 한눈에 보기&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.5117%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 27.7327%; text-align: center;&quot;&gt;&lt;b&gt;사용자 정의 함수(Function)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.4768%; text-align: center;&quot;&gt;&lt;b&gt;프로시저(Procedure)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.279%; text-align: center;&quot;&gt;&lt;b&gt;트리거(Trigger)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.5117%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성 방법&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7327%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CREATE FUNCTION 문으로 생성.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.4768%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CREATE PROCEDURE 문으로 생성.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.279%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CREATE &lt;b&gt;TRIGGER&lt;/b&gt; 문으로 생성.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.5117%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 방법&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7327%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 프로그래밍 언어처럼 &lt;span style=&quot;color: #409d00;&quot;&gt;'함수 명'()&lt;/span&gt;으로 실행.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.4768%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;EXECUTE '프로시저 명()'&lt;/span&gt; 명령을 통해 실행.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.279%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 쿼리 실행 시 DB에 의해 자동 실행.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.5117%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특징&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 27.7327%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로시저처럼 SQL과 로직을 묶은 명령문. But 반드시 결과값을 return문을 사용하여 내보내줘야 한다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 29.4768%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BEGIN ~ END 절 내에서 COMMIT, ROLLBACK과 같은 &lt;b&gt;&lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/24&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트랜잭션 명령어&lt;/a&gt;(TCL)&lt;/b&gt;사용이 가능하다.&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 31.279%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BEGIN ~ END 절 내에서 트랜잭션 명령어(TCL) 사용 불가능!!&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Database</category>
      <category>BEGIN</category>
      <category>Block</category>
      <category>Declare</category>
      <category>end</category>
      <category>Exception</category>
      <category>Oracle</category>
      <category>pl/sql</category>
      <category>PLSQL</category>
      <category>procedure</category>
      <category>SQLD</category>
      <category>Trigger</category>
      <category>user defined function</category>
      <category>블록</category>
      <category>오라클</category>
      <category>절차형</category>
      <category>절차형 SQL</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/38</guid>
      <comments>https://hoon93.tistory.com/38#entry38comment</comments>
      <pubDate>Sat, 26 Sep 2020 14:21:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] ORA-01950 / ORA-30041 해결: 테이블스페이스 Quota 설정</title>
      <link>https://hoon93.tistory.com/36</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ORA-01950, ORA-30041 에러 발생&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;pre id=&quot;code_1768976878440&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ORA-01950: no privileges on tablespace 'USERS'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해석해보면 &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;ORA-01950&lt;/span&gt;:USERS 테이블스페이스에 대한 권한이 없습니다&quot;라는 건데, &quot;&lt;span style=&quot;color: #ee2323;&quot;&gt;ORA-30041&lt;/span&gt;(테이블스페이스에 할당량을 부여할 수 없습니다)&quot;과 함께 종종 등장하는 &lt;b&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;'테이블 스페이스(Table Space)'&lt;/span&gt;&lt;/b&gt;란 용어는 오라클에서 테이블/인덱스 데이터가 실제로 저장되는 공간이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테이블 스페이스(Table Space)란??&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Oracle_Table Space.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;835&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb9hs4/dJMcaf6rqEG/DDsj21p8KhpxMasVBWrqy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb9hs4/dJMcaf6rqEG/DDsj21p8KhpxMasVBWrqy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb9hs4/dJMcaf6rqEG/DDsj21p8KhpxMasVBWrqy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb9hs4%2FdJMcaf6rqEG%2FDDsj21p8KhpxMasVBWrqy1%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; alt=&quot;오라클 테이블 스페이스(Table Space)&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;272&quot; data-filename=&quot;Oracle_Table Space.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;835&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Oracle에서는 사용자 생성 후 테이블을 생성하고 INSERT 하려는 순간 (혹은 CREATE시에) &lt;span style=&quot;color: #ff0000;&quot;&gt;&quot;ORA-01950: 테이블스페이스 'USERS'에 대한 권한이 없습니다.&quot;&lt;/span&gt; 와 같은 오류가 발생할 수 있다. 왜냐하면 Oracle은 새로운 사용자를 생성한 직후에 기본적으로 할당받는 테이블 스페이스인 'USERS'라는 이름의 ROLE을 부여함. 근데 이 'USERS' Tablespace에는 얼마만큼의 영역을 할당한 것인지 정해지지 않은 상태이다. 즉, users 테이블스페이스에 대해 현재 사용자가 1kb의 데이터도 입력할 수 없는 것임. 따라서 해결법은 해당 테이블스페이스에 대해 공간을 할당해 주면 된다. &lt;b&gt;정리하자면, '테이블스페이스'란 오라클 서버가 테이터를 저장하는 논리적인 데이터 저장 구조를 의미한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테이블스페이스 할당량(Quota) 부여 및 제한 방법&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. unlimited 키워드로 할당에 제한을 두지 않는 방법&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;※quota키워드: 사용하여 USER가 사용할 테이블스페이스의 영역을 할당.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768976083289&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alter user '유저명' default tablespace '테이블스페이스' quota unlimited on '테이블스페이스';
ex) alter user myuser default tablespace users quota unlimited on users;&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;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 특정 용량만큼만 테이블스페이스를 할당&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768976083289&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;alter user '유저명' quota '용량' on '테이블스페이스';
ex) alter user myuser quota 30M on users;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Database</category>
      <category>ORA-01950</category>
      <category>Oracle</category>
      <category>Quota</category>
      <category>talbleSpace</category>
      <category>unlimited on</category>
      <category>오라클</category>
      <category>테이블스페이스</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/36</guid>
      <comments>https://hoon93.tistory.com/36#entry36comment</comments>
      <pubDate>Fri, 25 Sep 2020 19:03:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] User와 Role, 권한</title>
      <link>https://hoon93.tistory.com/35</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DCL(Data Control Language)과 유저(계정)&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DCL이란 한 마디로, 유저의 권한을 제어할 수 있는 명령어이다. 우리는 DB에 접속했다고 해서 바로 테이블, 뷰, 인덱스 등과 같은 오브젝트(Object)를 생성할 수 없다. 왜냐하면 Oracle에서의 사용자(유저, 계정)가 실행하는 모든 &lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DDL&lt;/a&gt;(CREATE, ALTER, DROP, RENAME 등)문장은 그에 해당하는 권한이 있어야만 실행 가능하기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 표는 Oracle 설치 시 기본적으로 제공하는 유저이다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6048%; text-align: center;&quot;&gt;&lt;b&gt;유저&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 48.3722%; text-align: center;&quot;&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6048%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SCOTT&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 48.3722%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Oracle 테스트용 샘플 유저, Default 패스워드: TIGER&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6048%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SYS&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 48.3722%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DBA 역할을 부여받은 유저&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.6048%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SYSTEM&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 48.3722%;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB의 모든 시스템 권한을 부여받은 DBA유저. Oracle설치 시에 패스워드 설정&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Oracle은 유저를 통해 데이터베이스에 접속을 하는 형태이다. 즉, 아이디와 비밀번호 방식으로 인스턴스에 접속을 하고 그에 해당하는 스키마에 오브젝트 생성 등의 권한을 부여받게 된다. 다음은 유저 생성의 권한이 없을때 발생한 에러이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768922341954&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Oracle CONN SCOTT/TIGER &amp;rarr; 연결되었다.
CREATE USER HOON IDENTIFIED BY '패스워드'; &amp;rarr; ERROR: 권한이 불충분하다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;현재 SCOTT 유저는 유저를 생성할 권한을 부여받지 못했기 때문에 권한이 불충분하다는 오류가 발생한다. 이 경우엔 Oracle의 DBA 권한을 가지고 있는 SYSTEM 유저로 접속하면 유저 생성 권한(CREATE USER)을 다른 유저에게 부여할 수 있다. 추가로 유저(계정)를 생성했다고 바로 로그인 할 수도 없다. 로그인을 하려면 CREATE SESSION권한을 부여받아야 한다. 권한 부여 명령 쿼리는&lt;/span&gt; &lt;span style=&quot;color: #409d00;&quot;&gt;GRANT CREATE SESSION TO '계정명';&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 USER(계정)가 생성된 테이블에 대한 접근 시 유의점&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 위 예제에서 생성된 HOON유저가 생성된 MENU 테이블을 SCOTT 유저를 통해서 조회하면 어떻게 될까? &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HOON, SCOTT 뿐만 아니라 모든 유저는 각각 자신이 생성한 테이블 외에 다른 유저의 테이블에 접근하려면 해당 테이블에 대한 오브젝트 권한을 소유자로부터 부여 받아야 한다. 우리가 남의 집에 방문했을 때 집주인의 허락없이는 집에 들어갈 수 없는 것과 같은 이치이다. 집주인은 오브젝트 권한(SELECT, UPDATE, INSERT, DELETE 등)을 각각 따로 관리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HOON이라는 유저가 생성한 MENU라는 테이블은 HOON(집주인)의 소유임. 따라서 SCOTT 등의 다른 유저들은 집주인(소유자)에게 &lt;span style=&quot;color: #ee2323;&quot;&gt;허락(권한)을 받기 전에는 조회 할 수 없음&lt;/span&gt;. 집주인이 &lt;span style=&quot;color: #409d00;&quot;&gt;GRANT SELECT ON MENU TO SCOTT;&lt;/span&gt; 로 허락해줘야 조회(SELECT) 가능.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 SCOTT 유저를 통해서 다른 유저가 소유한 테이블에 접근하려면 객체(테이블)앞에 소유한 유저의 이름을 붙여서 접근해야 함. &lt;span style=&quot;color: #409d00;&quot;&gt;SELECT * FROM HOON.MENU;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서, &lt;span style=&quot;color: #ee2323;&quot;&gt;유저를 생성하면 기본적으로 create session, create table, create procedure 등의 많은 권한을 부여해줘야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유저를 삭제할 때의 명령어는 DROP USER '유저명' 이고, CASCADE명령을 주면 해당 유저가 생성한 객체(테이블)를 먼저 삭제한 후 유저(계정)를 삭제한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;롤(ROLE)이란?&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/9942D34E5F61B0061C?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/9942D34E5F61B0061C?original&quot; data-alt=&quot;롤(Role)은 권한(Privileges)들을 묶어서 관리할 수 있게 도와준다.&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9942D34E5F61B0061C&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F9942D34E5F61B0061C&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; alt=&quot;Privileges Role&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;279&quot; data-origin-width=&quot;778&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;롤(Role)은 권한(Privileges)들을 묶어서 관리할 수 있게 도와준다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위에서 얘기했다시피, 오브젝트 권한(SELECT, UPDATE, INSERT, DELETE 등)은 각각 따른 권한으로써 관리된다. 그리고 이 시스템 권한은 100개 이상이기 때문에 일일이 사용자에게 설정하는 것이 너무 복잡하고, 특히 유저로부터 권한을 관리하기가 어려움. 그래서 롤(ROLE)을 통해 권한을 부여하는 방법을 사용한다. ROLE을 생성하고, 해당 ROLE에 각종 권한들을 부여한 후 ROLE을 다른 ROLE이나 유저에게 부여함으로써 빠르고 정확하게 필요한 권한을 부여할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Role 생성 : &lt;span style=&quot;color: #006dd7;&quot;&gt;CREATE ROLE '롤 이름';&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Role에 권한 부여 : &lt;span style=&quot;color: #006dd7;&quot;&gt;GRANT create session, create table to '롤 이름';&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Role에 부여된 권한을 유저에게 부여 : &lt;span style=&quot;color: #006dd7;&quot;&gt;GRANT '롤 이름' TO '유저 이름';&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가적으로, 현재 사용자(계정)에 대해서 부여된 권한을 조회하기 위해서는 딕셔너리를 통해 검색이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;select * from &lt;span style=&quot;color: #ee2323;&quot;&gt;USER_TAB_PRIVS_MADE&lt;/span&gt;(현재 사용자가 부여한 권한);&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;select * from &lt;span style=&quot;color: #ee2323;&quot;&gt;USER_TAB_PRIVS_RECD&lt;/span&gt;(현재 사용자에게 부여된 권한);&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Database</category>
      <category>DCL</category>
      <category>grant</category>
      <category>Oracle</category>
      <category>Revoke</category>
      <category>Role</category>
      <category>user</category>
      <category>계정</category>
      <category>권한</category>
      <category>롤</category>
      <category>오라클</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/35</guid>
      <comments>https://hoon93.tistory.com/35#entry35comment</comments>
      <pubDate>Thu, 24 Sep 2020 23:56:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] 윈도우 함수(WINDOW FUNCTION)/집계함수(AGGREGATE FUNCTION)</title>
      <link>https://hoon93.tistory.com/34</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우 함수(Window Function) 기본 문법&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Oracle에서의 분석 &amp;amp; 순위 함수인 윈도우 함수는 크게 5가지 그룹으로 분류된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우 함수 사용 시 OVER문구가 키워드로 필수 포함된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WINDOW 함수는 다른 함수와는 달리 중첩(NEST)해서 사용하지는 못하지만, 서브쿼리(Sub Query)에서는 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1728326268135&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT WINDOW_FUNCTION(argument) OVER( [partition by 칼럼] [order by 절] [window 절] )
FROM 테이블명;&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;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;윈도우 함수(Window Function) 종류&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;1. 그룹 내 순위 관련 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;RANK : 동일한 값에 대해서는 동일한 순위를 부여&amp;nbsp;&lt;span style=&quot;color: #409d00;&quot;&gt;예) 1 &amp;rarr; 2 &amp;rarr; 2 &amp;rarr; 4 &amp;rarr; 5 (기준 칼럼값이 동일하다는 전제 하에 공동 2등)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DENSE_RANK : 동일한 순위를 하나의 등수로 취급(누적된 순위를 부여가능)&amp;nbsp;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;예) 1 &amp;rarr; 2 &amp;rarr; 2 &amp;rarr; 3 &amp;rarr; 4&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ROW_NUMBER : 동일한 값이라도 고유한 순위를 부여 &amp;rarr; 동일한 순위를 인정하지 않을 때 사용. Oracle의 경우 rowid가 적은 행이 먼저 나온다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;※ RANK와 DENSE_RANK의 공통점은 둘 다 동일한 값은 동일한 순위를 부여한다는 것.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728326305789&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT JOB, SAL, DENSE_RANK() OVER(order by SAL DESC) DENSE_RANK, ROW_NUMBER() OVER(order by SAL DESC) ROW_NUMBER
FROM 테이블명;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;2. 그룹 내 일반 집계(Aggregate) 관련 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 일반적으로 많이 사용하는 SUM, MAX, AVG, COUNT 함수 등&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 집계함수는 where절에 올 수 없음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;집계함수는 기본적으로 null값을 가진 행을 제외하고 수행한다.&lt;/span&gt; 예들 들어 null을 사칙연산하면 값은 무조건 null이기 때문에 집계함수는 내부적으로 null을 제외하고 계산을 수행함.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;count(column) : null값을 제외한 행의 수를 출력.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;count(*) : null값을 포함한 행의 수 출력. 와일드카드인 애스터리스크(*)는 전체칼럼을 의미하는데, 전체칼럼이 null인 행은 존재할 수 없기 때문에, 결국 전체 행의 수 출력.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;sum(column) : null값을 제외한 합계를 출력.&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;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;3. 그룹내 행 순서 관련 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- FIRST_VALUE : 파티션별 윈도우에서 가장 먼저 나온 값을 구함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- LAST_VALUE : 파티션별 윈도우에서 가장 나중에 나온 값을 구함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- LAG : 이전 원하는 번째 행의 값을 가져옴.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- LEAD : 이후 원하는 번째 행의 값을 가져옴.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;4. 그룹 내 비율 관련 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- RATIO_TO_REPORT : 파티션 내 전체 SUM(칼럼)값에 대한 행별 칼럼 값의 백분율을 소수점으로 구함.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- PERCENT_RANK : 파티션별 윈도우에서 제일 먼저 나오는 것을 0으로, 제일 늦게 나오는 것을 1로 하여, 값이 아닌 행의 순서별 백분율로 표현.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- &lt;span style=&quot;color: #ee2323;&quot;&gt;NTILE&lt;/span&gt; (쿼리결과값을 N등분한 결과를 숫자값으로 얻을 수 있음)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728326380785&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Ntile(4) over (order by &amp;lsquo;SAL&amp;rsquo; DESC) &amp;rarr; &amp;lsquo;급여&amp;rsquo;를 기준으로 4개의 그룹의 분류.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- CUME_DIST : 파티션별 윈도우의 전체건수에서 현재 행보다 작거나 같은 건수에 대한 누적백분율을 구함.&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;size18&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;&lt;b&gt;5. 그룹 내 선형 분석을 포함한 통계 분석 관련 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- CORR 등등&lt;/span&gt;&lt;/p&gt;</description>
      <category>Database</category>
      <category>aggregate</category>
      <category>DENSE_RANK</category>
      <category>null</category>
      <category>Oracle</category>
      <category>over</category>
      <category>RANK</category>
      <category>ROW_NUMBER</category>
      <category>SQLD</category>
      <category>WINDOW FUNCTION</category>
      <category>오라클</category>
      <category>윈도우 함수</category>
      <category>집계 함수</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/34</guid>
      <comments>https://hoon93.tistory.com/34#entry34comment</comments>
      <pubDate>Wed, 23 Sep 2020 19:30:00 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] 그룹 함수(ROLLUP, CUBE, GROUPING SETS, GROUPING) 개념 정리</title>
      <link>https://hoon93.tistory.com/33</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 분석을 위한 3가지 함수 종류 요약&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1) Group Function :&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;① 집계 함수를 제외한 함수를 포함(그룹 간의 소계를 계산하는 &lt;b&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;ROLLUP 함수&lt;/span&gt;&lt;/b&gt;, GROUP BY 항목들을 다차원적 소계로 계산하는 &lt;b&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;CUBE 함수&lt;/span&gt;&lt;/b&gt;, 특정 항목에 대한 소계를 계산하는 &lt;b&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;GROUPING SETS 함수&lt;/span&gt;&lt;/b&gt;)&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;②&lt;span style=&quot;color: #ff0000;&quot;&gt;&amp;nbsp;ROLLUP, CUBE, GROUPING SETS 결과에 대한 정렬이 필요한 경우 ORDER BY절에 정렬 칼럼을 명시.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) &lt;a class=&quot;tx-link&quot; href=&quot;https://hoon93.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Window Function&lt;/a&gt; :&lt;/span&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;① 데이터 웨어하우스에서 발전한 기능으로, &lt;b&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;분석 함수(Analytic Function)&lt;/span&gt;&lt;/b&gt;나 &lt;b&gt;&lt;span style=&quot;color: #0900ff;&quot;&gt;순위 함수(Rank Runction)&lt;/span&gt;&lt;/b&gt;라고 불림.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ff0000;&quot;&gt;&amp;nbsp; &amp;rarr; 중요 포인트는 행과 행간의 관계를 쉽게 정의하기 위해 만든 함수라는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;② &lt;b&gt;group by구문과 병행하여 사용할 수는 없음.&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3) Aggregate Function :&lt;/b&gt;&lt;/span&gt; Window Function내에 분류돼있는 함수이며, COUNT, SUM, MAX, MIN 등 과 같은 각종 집계 함수를 포함.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그룹 함수(Group Function)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1) ROLLUP 함수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;① 조회된 group List에서 소 그룹간의 소계(Subtotal)를 계산.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;②&amp;nbsp;Grouping Columns의 수를 N이라고 했을 때 &lt;span style=&quot;color: #0900ff;&quot;&gt;N+1 Level의 Subtotal이 생성&lt;/span&gt;됨. &lt;span style=&quot;color: #22741c;&quot;&gt;※Level &amp;larr; 계층으로 이해.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;③&amp;nbsp;인수는 계층구조이므로 인수의 순서가 바뀌면 결과도 바뀜. 인수의 순서 주의!! &amp;rarr; 사용법은 group by ROLLUP(칼럼명, 칼럼명, &amp;hellip;&amp;hellip;)&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;④&amp;nbsp;&lt;span style=&quot;color: #ff0000;&quot;&gt;ROLLUP의 경우 계층 간 집계에 대해서는 LEVEL 별 순서를 정렬하지만, 계층 내 GROUP BY 수행 시 생성되는 표준 집계에는 별도의 정렬을 지원하지 않음. 계층 내 정렬을 위해서는 별도의 ORDER BY 절을 사용해야 함(이 부분은 cube, grouping sets도 같음)&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2) CUBE 함수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;① ROLLUP은 단지 가능한 Subtotal만 생성하지만, CUBE는 grouping columns가 결합가능한 모든 값에 대하여 다차원 집계를 생성. &amp;rarr; ROLLUP에 비해 시스템에 부하를 많이 주는 단점있음.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;② CUBE는 내부적으로 Grouping Columns의 순서를 바꾸어 또 한 번의 쿼리를 추가 수행해야 함. 따라서 함수의 인수 순서는 사실상 상관없음.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;③ Grouping Columns의 수를 N이라고 했을 때, &lt;span style=&quot;color: #0900ff;&quot;&gt;2의 N승 Level의 Subtotal이 생성됨.&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3) GROUPING SETS 함수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;grouping sets()에 명시된 인수(칼럼)들에 대한 개별 집계를 구함. 이때 표시된 인수들 간에는 계층 구조인 ROLLUP과 달리 평등한 관계이므로 인수의 순서 달라도 결과는 같음.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ff0000;&quot;&gt;+4) GROUPING() 함수 사용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;① GROUPING 함수는 ROLLUP, CUBE, GROUPING SETS에 모두 사용할 수 있다.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;② ROLLUP, CUBE, GROUPING SETS에 의해 계산된 결과 행에 GROUPING(칼럼명) = 1; 표시되고, 그 외의 일반 결과는 GROUPING(칼럼명) = 0; 나타남.&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;- GROUPING 함수와 CASE문을 응용하기&lt;/p&gt;
&lt;pre id=&quot;code_1728324384970&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
CASE GROUPING(칼럼명) WHEN 1 THEN 'ALL' else 칼럼명 end as 칼럼명
FROM &amp;hellip;&amp;hellip;&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;- 오라클의 경우 DECODE함수로 좀 더 짧게 표현 가능.&lt;/p&gt;
&lt;pre id=&quot;code_1728324394165&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
DECODE( GROUPING(칼럼명), 1, 'ALL', 칼럼명) as 칼럼명
FROM &amp;hellip;&amp;hellip;&lt;/code&gt;&lt;/pre&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <author>천방지축 개발노트</author>
      <guid isPermaLink="true">https://hoon93.tistory.com/33</guid>
      <comments>https://hoon93.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 22 Sep 2020 23:15:00 +0900</pubDate>
    </item>
  </channel>
</rss>