<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>macdduck 님의 블로그</title>
    <link>https://macdduck.tistory.com/</link>
    <description>macdduck 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 22 Sep 2026 04:21:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>macdduck</managingEditor>
    <item>
      <title>GitLab과 CI/CD</title>
      <link>https://macdduck.tistory.com/42</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;GitLab의 장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 오픈소스&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 자체 Git 저장소&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Jenkins는 Git저장소를 제공하고 있지 않기 때문에 GitHub나 GitLab 같은 Git저장소를 써야 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 통합된 환경&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 안전성과 안정성..?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(기업에서 많이 사용하고 있기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 자체 호스팅 가능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(기업에서 많이 사용하고 있는 이유)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 센터의 서버에 직접 설치해서 서비스 가능. 이렇게 되면 기업들의 보안 정책들(소스 코드를 반드시 제어할 수 있는 공간에 둬야 한다 등)을 달성할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, SaaS형으로 인터넷 서비스 형태로 설치 없이 사용 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 이슈 관리 툴&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;- 다양한 플러그인 제공&lt;/h4&gt;</description>
      <category>Cloud</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/42</guid>
      <comments>https://macdduck.tistory.com/42#entry42comment</comments>
      <pubDate>Tue, 21 Jan 2025 20:56:12 +0900</pubDate>
    </item>
    <item>
      <title>vite-react 프로젝트 생성 후 실행 시 error 사항 (작성 중)</title>
      <link>https://macdduck.tistory.com/41</link>
      <description></description>
      <category>연습장</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/41</guid>
      <comments>https://macdduck.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 20 Dec 2024 17:58:56 +0900</pubDate>
    </item>
    <item>
      <title>자료구조 - Array &amp;amp; LinkedList</title>
      <link>https://macdduck.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Array &amp;amp; LinkedList&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 차이점은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 저장되는 방식과 operation의 연산 속도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정된 저장 공간에 연속적이고 순차적으로 저장. compile 시점에 메모리 크기가 결정되며 stack memory영역에 할당.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비연속적으로 데이터 값과 다음 address를 저장. runtime 시점에 동적으로 메모리 크기가 결정되며 heap memory영역에 할당.&lt;/p&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;Array &amp;amp; Dynamic Array&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resize하여 fixed-size를 보완&lt;/p&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;Dynamic Array &amp;amp; LinkedList&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/40</guid>
      <comments>https://macdduck.tistory.com/40#entry40comment</comments>
      <pubDate>Tue, 29 Oct 2024 18:57:02 +0900</pubDate>
    </item>
    <item>
      <title>DB Lock</title>
      <link>https://macdduck.tistory.com/39</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;1. DB Lock&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;1.1 개념&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 데이터베이스에서 여러 트랜잭션이 동시에 같은 데이터에 접근할 때, 데이터의 무결성(일관성)을 보장하기 위해 사용되는 메커니즘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 한 트랜잭션이 특정 데이터에 대해 작업을 하고 있을 때, 다른 트랜잭션이 그 데이터에 접근하지 못하도록 잠그는 것. 데이터의 일관성 유지 및 동시에 발생할 수 있는 충돌을 방지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;1.2 필요성&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; DB 락을 통해 데이터에 대한 접근을 제어하면, 다음과 같은 상황에서 발생할 수 있는 데이터 무결성 문제를 예방할 수 있음.&lt;/p&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: #9feec3;&quot;&gt;Dirty Read&lt;/span&gt;: 한 트랜잭션이 데이터를 수정 중일 때 다른 트랜잭션이 그 데이터를 읽는 상황&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Non-repeatable Read&lt;/span&gt;:&amp;nbsp; 한 트랜잭션이 데이터를 읽은 후, 다른 트랜잭션이 그 데이터를 수정하고 커밋하여 첫 번째 트랜잭션이 동일한 데이터를 다시 읽을 때 값이 달라지는 상황.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Lost Update&lt;/span&gt;: 두 개의 트랜잭션이 동시에 같은 데이터를 수정하려고 할 때, 한 트랜잭션의 수정 내용이 다른 트랜잭션에 의해 덮어쓰여져 사라지는 상황.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;2. 락의 종류&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;span style=&quot;background-color: #9feec3;&quot;&gt;공유 락(Shared Lock, S Lock)&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: #9feec3;&quot;&gt;배타 락(Exclusive Lock, X Lock)&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: #9feec3;&quot;&gt;비관적 락(Pessimistic Locking)&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: #9feec3;&quot;&gt;낙관적 락(Optimistic Locking)&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: #9feec3;&quot;&gt;명명된 락(Named Lock)&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: #9feec3;&quot;&gt;분산 락(Distributed Lock)&lt;/span&gt;: 여러 시스템이나 인스턴스에서 동시에 동일한 자원에 접근할 때, 자원의 일관성을 유지하기 위해 사용되는 락. Redis와 같은 분산 시스템을 사용하여 구현.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;2.1. 비관적 락&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;종류&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Pessimistic Read&lt;/span&gt;: 읽기 락(Shared Lock)을 설정하여, 다른 트랜잭션이 해당 데이터를 읽을 수는 있지만, 수정은 할 수 없도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;span style=&quot;background-color: #9feec3;&quot;&gt;Pessimistic Write&lt;/span&gt;: 쓰기 락(Exclusive Lock)을 설정하여 다른 트랜잭션이 해당 데이터를 읽거나 수정하지 못하도록 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DB 레벨에서의 락 동작&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;span style=&quot;background-color: #9feec3;&quot;&gt;락 설정&lt;/span&gt;: 비관적 락을 사용하면 SQL쿼리나 트랜잭션이 데이터베이스에 접근할 때 락이 설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - &lt;span style=&quot;background-color: #9feec3;&quot;&gt;락 해제&lt;/span&gt;: 일반적으로 트랜잭션이 종료되거나 커밋될 때 해제. 롤백되는 경우에도 락이 해제.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비관적 락은 주로 데이터베이스 레벨에서 동작, 데이터 무결성을 보장하는 데 매우 유용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DB레벨에서 동작하기 때문에 성능에 영향을 미칠 수 있으므로&lt;/span&gt;, 데이터 충돌 가능성이 높은 환경에서 신중하게 사용해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프리부트와 같은 애플리케이션에 서 비관적 락을 설정하면, 데이터베이스가 이 락을 처리하고 관리하게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비관적 락이든, 낙관적 락이든, 이거를 설정했다고 해서 충돌이 완벽하게 해소되었다. 라고 표현할 수 없음. 환경에 따라 달라지기 때문.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 어느 정도의 요청량이 들어올 거라는 것을 파악해야 함. 너무 많은 요청이 온다면, DB Lock보다 메시지 큐(RabbitMQ, Kafka)를 사용하여 쓰기를 대기열에 넣어서 지연시키는 방법으로 하는 것이 더 안전.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;2.2 낙관적 락&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동작방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 낙관적 락(Optimisitc Lock)은 트랜잭션 간의 충돌을 최소화하고 성능을 향상시키기 위해 사용되는 동시성 제어 메커니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 즘.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 비관적 락이 데이터베이스 레벨에서 락을 걸어 다른 트랜잭션의 접근을 차단하는 방식이라면, 낙관적 락은 DB락을&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 사용하지 않고, 대신 데이터가 변경되었는지 확인하여 충돌을 처리하는 방식.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 버전 관리:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - version이라는 필드를 엔티티에 추가하여, 해당 엔티티의 수정 횟수를 추적.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 트랜잭션이 엔티티를 읽을 때, 현재의 버전 번호가 함꼐 읽혀옴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 트랜잭션이 엔티티를 수정하고 저장하려고할 때, 현재 데이터베이스에 저장된 버전 번호와 트랜잭션이 처음 읽어온&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 버전 번호를 비교.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 데이터 충돌 검출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 트랜잭션이 데이터를 저장할 때, 데이터베이스에 저장된 버전 번호가 트랜잭션이 처음 읽어온 버전 번호와 동일하다&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 면, 데이터가 수정되지 않았다고 간주하고 업데이트를 수행. 이때 버전 번호는 증가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 반면, 버전 정보가 다르다면, 다른 트랜잭션이 데이터를 수정한 것으로 간주하고, 현재 트랜잭션을 롤백하거나 재시도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장단점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 장점:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 성능: DB락을 걸지 않으므로 병행 처리 성능 향상.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 유연성: 충돌이 발생했을 때, 비즈니스 로직에 따라 트랜잭션을 재시도하거나 롤백할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 단점:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 충돌 가능성: 데이터가 자주 변경되는 경우, 충돌이 자주 발생할 수 있음. 이로 인해 여러 번의 재시도 필요 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; - 복잡성: 충돌을 처리하기 위한 로직이 추가로 필요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;낙관적 락은 데이터베이스 락을 최소화하면서도 데이터 일관성을 유지할 수 있는 효과적인 방법임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 데이터 충돌이 적고 병행 처리가 많은 시스템에서 유용하게 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 충돌이 발생했을 때의 처리 로직을 잘 설계해야 하며, 특정 상황에서는 비관적 락보다 복잡할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;2.3 데드 락&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 환경에서 데드락은 2개 이상의 트랜잭션이 서로가 점유하고 있는 자원을 기다리면서 영원히 대기 상태에 빠지는 상황을 의미.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상황이 발생하면, 해당 트랜잭션들은 더 이상 진행될 수 없고, 시스템 성능에 큰 영향을 미칠 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 트랜잭션A는 테이블 X의 일부 행을 잠금(Lock)하고 이후 테이블 Y의 행을 잠금하려고 시도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 트랜잭션B는 Y의 일부 행을 잠금하고, 이후 테이블 X의 행을 잠금하려고 시도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 이 경우, 트랜잭션 A와 B는 서로 상대방이 보유한 잠금을 기다리면서 영원히 대기하게 되며, 이로 인해 데드락 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>연습장</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/39</guid>
      <comments>https://macdduck.tistory.com/39#entry39comment</comments>
      <pubDate>Thu, 17 Oct 2024 13:21:05 +0900</pubDate>
    </item>
    <item>
      <title>메일 발송 원리</title>
      <link>https://macdduck.tistory.com/38</link>
      <description>&lt;section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하며&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 플래시 세일(Flash Sale)을 주제로 프로젝트를 진행하고 있다. msa로 진행되고 있는데 그 중 알람 서비스도 맡게 되어, 플래시 세일 1시간 전 사용자가 알림 설정한 상품에 대해 메일을 발송하는 기능을 구현해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 먼저 메일이 어떤 원리로 전송이 되는지 대략적으로 정리하고, 어떤식으로 구현할 건지 간략하게 얘기해보겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;div class=&quot;toc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#a&quot;&gt;1. 이메일 시스템의 주요 구성 요소&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#b&quot;&gt;2. 메일 발송을 위한 프로토콜&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#c&quot;&gt;3. 이메일 발송의 단계별 과정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#d&quot;&gt;4. SMTP의 보안 강화 기술&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;h2 id=&quot;a&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;1. 이메일 시스템의 주요 구성 요소&lt;/span&gt;&lt;/h2&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; 이메일 발송 과정에서 핵심적으로 사용되는 구성 요소는 크게 3가지로 나뉜다. 이메일 클라이언트(MUA), 메일 서버(MTA), 메일 수신 서버(POP3, IMAP)이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;이메일 클라이언트 (Mail User Agent)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이메일 클라이언트는 사용자가 이메일을 작성하고 발송하는 소프트웨어다. 흔히 사용되는 Gmail 등의 애플리케이션이 이에 해당한다. 클라이언트는 작성한 이메일을 메일 서버로 전달하며, 발송 과정에서 SMTP프로토콜을 사용한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;메일 서버 (Mail Transfer Agent)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 메일 서버는 이메일을 실제로 처리하고 전달하는 역할을 한다. 메일 서버는 발신자의 메일 서버와 수진자의 메일 서버로 나뉘며, 이 두 서버는 이메일 전송을 위해 서로 통신한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 예를 들어, A가 Gamil로 B의 Naver mail에 메일을 보낸다고 하면, 구글의 메일 서버와 네이버의 메일 서버가 통신한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;POP3, IMAP &amp;rarr; 수신자의 메일 서버(MTA)에서 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 수신된 이메일은 POP3나 IMAP 프로토콜을 통해 사용자에게 전달된다. 수신자는 이 서버에 접속하여 이메일을 다운로드(POP3)하거나 서버에서 직접 확인(IMAP)할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;POP3&lt;/span&gt;&lt;br /&gt;수신자의 클라이언트에서 이메일을 MTA에서 다운로드한 후, 기본적으로 서버에서 삭제된다.&lt;br /&gt;주로 단일 디바이스에서 이메일을 관리할 때 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;IMAP&lt;/span&gt;&lt;br /&gt;이메일을 서버에 그대로 남겨두고, 여러 디바이스에서 이메일을 동기화할 수 있다.&lt;br /&gt;여러 기기에서 이메일을 확인하는 경우 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;b&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;2. 메일 발송을 위한 프로토콜&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이메일을 발송할 때 사용되는 핵심 프로토콜은 SMTP(Simple Mail Transfer Protocol)이다. 클라이언트가 메일 서버에 이메일을 전송하고, 메일 서버 간 이메일을 전송하는 표준 프로토콜이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;SMTP의 작동 원리&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; SMTP는 클라이언트-서버 모델로 작동하며, 클라이언트가 서버에게 이메일을 보내면 서버는 이를 수신자의 메일 서버로 전송하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 또한, 통신은 명령어와 응답 코드로 이루어지는데, 이 부분은 추후에 다룰 기회가 있으면 자세하게 다뤄보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;c&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;3. 이메일 발송의 단계별 과정&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;[발신자 클라이언트]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 사용자가 이메일 클라이언트에서 이메일을 작성하고, 전송 버튼을 누르면 SMTP프로토콜을 통해 이메일이 발신자의 메일 서버로 전송된다. SMTP는 포트 25에서 작동한다.&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: #99cefa;&quot;&gt;[발신자 메일 서버]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 사용자의 이메일 클라이언트로부터 받은 이메일은 발신자의 메일 서버(MTA)로 전송된다. MTA는 수신자의 이메일 주소를 분석하여 해당 이메일이 어떤 도메인으로 가야 하는지 확인한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; DNS를 사용해 수신자의 도메인에 연결되 MX 레코드(Mail Exchange Record)를 조회한다. 이는 해당 도메인에서 메일을 수신할 수 있는 메일 서버의 정보를 포함하고 있다. 특히 IP 주소가 포함되어 있어, 이를 통해 발신자의 메일 서버는 수신자의 메일 서버로 연결할 수 있다.&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;/p&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: #99cefa;&quot;&gt;[수신자 메일 서버]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이메일이 수신자의 MTA에 도달하면, 수신자 MTA의 MDA(Mail Delivery Agent)는 이메일을 최종적으로 수신자의 메일함으로 전달한다. 이때 이메일은 수신자의 MTA에 저장된다.&lt;/p&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: #99cefa;&quot;&gt;[수신자 클라이언트]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이제 수신자는 자신의 이메일 클라이언트를 통해 메일 서버에 접속하여 이메일을 확인할 수 있다. 이때 이메일을 확인하는 방식에 따라 POP3, IMAP을 선택하게 되는데, 요즘은 거의 IMAP이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;d&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;4. SMTP의 보안 강화 기술&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; SMTP는 인터넷이 비교적 작고 신뢰할 수 있는 환경에서 설계된 메일 전송 프로토콜이었기 때문에 보안이나 인증 측면이 약했다고 한다. 즉 서버 간에 신뢰를 전제로 설계되어 별도의 인증 없이도 메일을 주고받을 수 있었다. 하지만, 인터넷 환경이 복잡해지고 악의적인 사용자가 늘어남에 따라, 현대의 SMTP는 여러 보안 기술을 통해 신뢰 기반의 취약점을 보완하고 있다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 물론 우리가 흔히 쓰는 구글이나 네이버에서 보안관련 설정을 해주기 때문에 우리가 핸들링할 상황은 거의 없을 것 같지만, 그래도 어떤식으로 보안을 강화했는지 간단하게 정리해 보겠다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;SPF (Sender Policy Framework)&lt;/span&gt;&lt;br /&gt;발송자가 인증된 메일 서버를 통해 이메일을 보냈는지 검증한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;DKIM (DomainKeys Identified Mail)&lt;/span&gt;&lt;br /&gt;이메일 본문에 서명하여, 메일이 변경되지 않았음을 보장한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;DMARC (Domain-based Message Authentication, Reporting &amp;amp; Conformance)&lt;/span&gt;&lt;br /&gt;SPF와 DKIM을 함께 사용하여 발송자 신원 확인을 강화하고, 의심스러운 메일에 대해 정책을 설정하는 방법이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;TLS (Transport Layer Security)&lt;/span&gt;&lt;br /&gt;이메일 전송 중에 내용을 암호화하여, 이메일이 가로채지더라도 내용이 보호되도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 메일 발송 기능을 구현하기 전 어떤 식으로 작동하는 건지 원리에 대해서 알아보았다. 물론 위에서 적은 내용들 중 내가 핸들링할 사항은 많지 않겠지만, 동작원리 및 전체적인 그림에 대해서 알고 가는 것이 중요하다고 생각하는데 잘 정리가 된 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 프로젝트 내에서 메일 발송 기능을 어떤식으로 구현할 건지도 적어보려 했으나, 내용이 길어져 다음 글에 써보도록 하겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;</description>
      <category>회고</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/38</guid>
      <comments>https://macdduck.tistory.com/38#entry38comment</comments>
      <pubDate>Sat, 12 Oct 2024 00:46:03 +0900</pubDate>
    </item>
    <item>
      <title>토큰 유효성 검사 관련 리팩토링</title>
      <link>https://macdduck.tistory.com/37</link>
      <description>&lt;section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하며&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이번 프로젝트에도 인증, 인가 부분을 포함한 부분을 맡게 되었다. 이제는 개념이 많이 잡힌 것 같고, 기존 프로젝트 때 잘못했던 부분들을 파악할 수 있게 되고 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt; 토큰 유효성 검사 시 예외 처리 부분을 잘못하고 있었다는 것을 발견했는데 그 부분에 대해서 얘기해 보겠다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section&gt;&lt;/section&gt;
&lt;section&gt;
&lt;div class=&quot;toc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#a&quot;&gt;1. 문제상황&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;h2 id=&quot;a&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;1. 문제상황&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKAaVD/btsJW20NOyn/swYUyxlZ1bnkm0Of8ZTSwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKAaVD/btsJW20NOyn/swYUyxlZ1bnkm0Of8ZTSwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKAaVD/btsJW20NOyn/swYUyxlZ1bnkm0Of8ZTSwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKAaVD%2FbtsJW20NOyn%2FswYUyxlZ1bnkm0Of8ZTSwK%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;1299&quot; height=&quot;268&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wHEUk/btsJW7HMkZG/xK48yRLnYlkbFXkSDKzBKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wHEUk/btsJW7HMkZG/xK48yRLnYlkbFXkSDKzBKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wHEUk/btsJW7HMkZG/xK48yRLnYlkbFXkSDKzBKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwHEUk%2FbtsJW7HMkZG%2FxK48yRLnYlkbFXkSDKzBKk%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;1205&quot; height=&quot;398&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 토큰에 대한 유효성 검사를 할 때 실행되는 JwtUtil 내 메서드이다. 어떤 예외가 발생하는 지 알기 위해서 주로 발생하는 예외에 대해서 try~catch문으로 감싸서 처리를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 특히, 만료된 토큰인지 검증하는 메서드는 토큰 재발급시 단독으로 사용될 것 같아 분리해서 처리를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 아래 로그는 만료된 토큰으로 api요청을 보냈을 때 발생하는 에러인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&amp;nbsp; 잘못 생각했던 부분은 2가지였다. &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;2395&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBKOpl/btsJW4dfY9z/xlO6fDoTxhPk6ZpJsAxAh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBKOpl/btsJW4dfY9z/xlO6fDoTxhPk6ZpJsAxAh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBKOpl/btsJW4dfY9z/xlO6fDoTxhPk6ZpJsAxAh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBKOpl%2FbtsJW4dfY9z%2FxlO6fDoTxhPk6ZpJsAxAh1%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;2395&quot; height=&quot;187&quot; data-origin-width=&quot;2395&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt; 첫번째는, 내가 원하는 대로 log.error가 찍히지 않는 상황이다.&lt;/span&gt; 분명 ExpiredJwtException이 발생하고 있는데&lt;br /&gt;위 isNotExpiredAccessTeken메서드의 catch문에서 적은 log.error가 찍히지 않고 있다. 심지어 print는 찍히고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTmRGt/btsJWSc2fyF/Zq7YKNCXJHEQ3tqnG7ICN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTmRGt/btsJWSc2fyF/Zq7YKNCXJHEQ3tqnG7ICN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTmRGt/btsJWSc2fyF/Zq7YKNCXJHEQ3tqnG7ICN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTmRGt%2FbtsJWSc2fyF%2FZq7YKNCXJHEQ3tqnG7ICN1%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;1638&quot; height=&quot;172&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 이유는 뒤에 getUserIdFromAccessToken(token) 메서드 실행 시, 만료된 토큰이므로 여기서 또 ExpiredJwtException이 발생하기 때문이었다. 따라서 이 부분을 수정하거나, 제거해야 했다.&lt;/p&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: #9feec3;&quot;&gt;방법은 3가지 정도가 떠올랐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. SecurirtyContextHolder에서 id를 가져오기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Jwt에서 Payload를 추출하고, 거기서 id를 가져오기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. id 찍는 부분을 삭제하기.&lt;/p&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; 1번. Auth서비스에서 Security의존성을 가지고 있지 않으므로 불가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2번. id를 로그에 저장하려면 이 방법이 가장 적합해 보였다. 다만, 이 로그를 가지고 어떤 인사이트를 얻을 수 있느냐를 생각해 봤을 때, 굳이? 라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 성별, 연령별, 직업 등의 빅데이터를 기반으로 토큰이 만료됐을 때, api요청을 보내는 빈도를 분석할 것도 아니고, 만약 유의미한 결과가 도출됐다고 해도, 각 집단 별 토큰의 만료기간을 다르게 할 것도 아니다. 결정적으로 우리 서비스는 일단 회원가입 시 사용자를 군집화할 데이터를 받지도 않고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;따라서 3번. 아예 id를 찍는 부분을 제거하기로 결정했다&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;2175&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRWNBE/btsJYERXBJM/3pjQ7Yey2Ja7UBrhAHQVZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRWNBE/btsJYERXBJM/3pjQ7Yey2Ja7UBrhAHQVZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRWNBE/btsJYERXBJM/3pjQ7Yey2Ja7UBrhAHQVZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRWNBE%2FbtsJYERXBJM%2F3pjQ7Yey2Ja7UBrhAHQVZ1%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;2175&quot; height=&quot;153&quot; data-origin-width=&quot;2175&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 그 결과 이렇게 원하는 대로 에러 로그가 찍히게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #99cefa;&quot;&gt;잘못 생각했던 부분 두번쨰는, 토큰 유효성 검사 메서드에서 만료된 토큰인지 확인하는 메서드를 분리했던 점이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; AccessToken에서 id를 가져오는 메서드에서 알 수 있듯이, 아래 사진처럼 굳이 jwt의 Expiration에서 값을 가져와서 현재시간과 비교하지 않아도, 만료된 토큰이면 ExpiredJwtException이 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXhLHY/btsJXPNt5Kr/0xJt4qqWkFmq8uzUvl6nhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXhLHY/btsJXPNt5Kr/0xJt4qqWkFmq8uzUvl6nhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXhLHY/btsJXPNt5Kr/0xJt4qqWkFmq8uzUvl6nhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXhLHY%2FbtsJXPNt5Kr%2F0xJt4qqWkFmq8uzUvl6nhK%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;1299&quot; height=&quot;268&quot; data-origin-width=&quot;1299&quot; data-origin-height=&quot;268&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; 즉, 아래 사진처럼 코드를 작성하면, 만료된 토큰인지 알 수 있는 로그만 찍히지 않을 뿐, false가 반환돼 service단에서 예외가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMKAtD/btsJXwHogyG/BlGJzGs0wvKViqrhk6mGP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMKAtD/btsJXwHogyG/BlGJzGs0wvKViqrhk6mGP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMKAtD/btsJXwHogyG/BlGJzGs0wvKViqrhk6mGP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMKAtD%2FbtsJXwHogyG%2FBlGJzGs0wvKViqrhk6mGP1%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;1062&quot; height=&quot;484&quot; data-origin-width=&quot;1062&quot; data-origin-height=&quot;484&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #f6e199;&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-origin-width=&quot;1078&quot; data-origin-height=&quot;705&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzkz2/btsJ0essUzU/ckfW2nEyY2IWJkGILr7xjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzkz2/btsJ0essUzU/ckfW2nEyY2IWJkGILr7xjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzkz2/btsJ0essUzU/ckfW2nEyY2IWJkGILr7xjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpzkz2%2FbtsJ0essUzU%2FckfW2nEyY2IWJkGILr7xjK%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;1078&quot; height=&quot;705&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;705&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Auth서비스에 불필요한 요청이 가지 않도록 Gateway에서 캐싱처리를 하고 api테스트 중 위와 같은 상황을 인지하여, 코드를 수정하게 되었다. 나름 잘 해결된 것 같고, 현재는 Gateway에 캐싱된 데이터가 있는데도 불구하고, Auth서비스로 verify요청이 가는 문제상황에 직면했다.. 아직 이유를 찾지는 못하였는데, 디버깅을 통하여 빨리 해결해 보겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;</description>
      <category>회고</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/37</guid>
      <comments>https://macdduck.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 7 Oct 2024 18:18:08 +0900</pubDate>
    </item>
    <item>
      <title>AI 검증 비즈니스 프로젝트 - 로그인 관련 트러블 슈팅</title>
      <link>https://macdduck.tistory.com/36</link>
      <description>&lt;section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하며&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; Spring Security와 JWT를 사용하여 회원가입, 로그인을 구현하고자 했다. 회원가입을 완료하고, 로그인을 했더니 403 FORBIDDEN에러가 발생했다. 확인 결과 UsernamePasswordAuthenticationFilter를 상속해서 커스텀한 Filter를 호출하지 못하고 있었다. 왜 안되는지 몇 시간 동안 씨름을 하다 결국 내부 코드를 까서 원인을 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 어떤 문제가 있었고 어떻게 찾았는지, 그리고 어떻게 해결했는지 정리해 보겠다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 id=&quot;a&quot; data-ke-size=&quot;size26&quot;&gt;1. 전체적인 흐름 정리&lt;/h2&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; 먼저 jwt를 사용할 때의 전체적인 흐름에 대해서 먼저 정리해 보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1) &lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;SpringSecurity의존성을 추가&lt;/span&gt;하게 되면 기존 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;톰켓의 Servlet 컨테이너의 필터 체인에 DelegatingFilter를 등록&lt;/span&gt;하게 된다. 이름에서 알 수 있듯이 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;DelegatingFilter는 모든 요청을 가로채서 SecurityFilterChain에 넘긴&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2) &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;jwt를 사용하려면 기존 security의 기본 동작 방식을 disabled 시켜줘야 한다.&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;1261&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmOa7l/btsJkaxhce1/Xz2tH2nHRkQ9r4jOB2K7j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmOa7l/btsJkaxhce1/Xz2tH2nHRkQ9r4jOB2K7j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmOa7l/btsJkaxhce1/Xz2tH2nHRkQ9r4jOB2K7j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmOa7l%2FbtsJkaxhce1%2FXz2tH2nHRkQ9r4jOB2K7j0%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;1261&quot; height=&quot;262&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vgZXN/btsJh7CtczT/CVq63ae2yQMwBUys4lzke0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vgZXN/btsJh7CtczT/CVq63ae2yQMwBUys4lzke0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vgZXN/btsJh7CtczT/CVq63ae2yQMwBUys4lzke0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvgZXN%2FbtsJh7CtczT%2FCVq63ae2yQMwBUys4lzke0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;256&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;256&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;3) &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;formLogin을 disabled로 해주고, 세션 옵션을 stateless로 바꿔주면 SpringSecurityFilter의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;UsernamePasswordAuthenticationFilter가 동작을 하지 않게 된&lt;/span&gt;다.&lt;/span&gt; 이 필터는 로그인 요청을 처리하는 필터인데, 인증 정보를 검증하여, 인증 과정을 처리한다. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;이 필터가 동작을 하지 않으므로, 강제로 커스텀해서 JWT인증 시 작동하도록 설정을 해줘야&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;4) &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;따라서 UsernamePasswordAuthenticationFilter를 상속받아서 커스텀을 하고, 추가로 발급된 토큰에 대해서 인증, 인가 처리를 하는 Filter도 커스텀하여, SecurityConfig파일에 Filter를 추가했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1805&quot; data-origin-height=&quot;971&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0STx4/btsJiqBzxpy/HkQePxXsED51AhVvQZlip0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0STx4/btsJiqBzxpy/HkQePxXsED51AhVvQZlip0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0STx4/btsJiqBzxpy/HkQePxXsED51AhVvQZlip0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0STx4%2FbtsJiqBzxpy%2FHkQePxXsED51AhVvQZlip0%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;1805&quot; height=&quot;971&quot; data-origin-width=&quot;1805&quot; data-origin-height=&quot;971&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;978&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDoxvT/btsJigy7mNX/NErKAdSFN3DnirwD0rwZQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDoxvT/btsJigy7mNX/NErKAdSFN3DnirwD0rwZQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDoxvT/btsJigy7mNX/NErKAdSFN3DnirwD0rwZQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDoxvT%2FbtsJigy7mNX%2FNErKAdSFN3DnirwD0rwZQk%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;1872&quot; height=&quot;978&quot; data-origin-width=&quot;1872&quot; data-origin-height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1325&quot; data-origin-height=&quot;163&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C8Ggc/btsJh6DzPFq/UAKpYPPiWKJHkcLpFlV3j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C8Ggc/btsJh6DzPFq/UAKpYPPiWKJHkcLpFlV3j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C8Ggc/btsJh6DzPFq/UAKpYPPiWKJHkcLpFlV3j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC8Ggc%2FbtsJh6DzPFq%2FUAKpYPPiWKJHkcLpFlV3j0%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;1325&quot; height=&quot;163&quot; data-origin-width=&quot;1325&quot; data-origin-height=&quot;163&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 5) &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;로그인 요청에 대해 테스트를 하기 위해, 회원가입부터 진행하고 로그인을 진행하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CQWKB/btsJi8G3cWh/HcHQGDtOiovkbf5p1us0e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CQWKB/btsJi8G3cWh/HcHQGDtOiovkbf5p1us0e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CQWKB/btsJi8G3cWh/HcHQGDtOiovkbf5p1us0e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCQWKB%2FbtsJi8G3cWh%2FHcHQGDtOiovkbf5p1us0e1%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;2290&quot; height=&quot;1010&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;1010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 6) &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;전에 이미 완료했던 회원가입은 잘 진행됐고 로그인 요청을 하였는데, 403 Forbidden에러가 발생했다.&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;2292&quot; data-origin-height=&quot;980&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uur6F/btsJiNXErrY/EE5K4f6Bp90xMmdSkGrO21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uur6F/btsJiNXErrY/EE5K4f6Bp90xMmdSkGrO21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uur6F/btsJiNXErrY/EE5K4f6Bp90xMmdSkGrO21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuur6F%2FbtsJiNXErrY%2FEE5K4f6Bp90xMmdSkGrO21%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;2292&quot; height=&quot;980&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;980&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;h2 data-ke-size=&quot;size26&quot;&gt;2. 트러블 슈팅 과정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;1) 코드의 논리 확인&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 우선은 어디서 문제가 생겼는지 찾아야 했다... JWT를 처음 사용하는 것이어서 최대한 흐름이 어떻게 되는지 붙잡으면서 코드를 작성했다. 많은 자료조사도 했고, 공식 문서도 확인하면서 어느 정도 납득이 되면 스텝을 밟았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 그럼에도 불구하고 예외가 터지니 내가 이해했던 부분에 대해 신뢰도도 내려가고, 처음부터 다시 확인할 수밖에 없었다. 의존성 주입이 잘 됐는지부터, 전체적인 흐름에 대해 다시 검토를 하였지만, &lt;span style=&quot;background-color: #f6e199;&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;span style=&quot;background-color: #ffc9af;&quot;&gt;2) 로그 찍기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 로직에는 문제가 없어서 더 문제였다. 그래서 찍을 수 있는 모든 곳에 로그를 찍기 시작했다. 그 결과 내가 커스텀해서 등록한 Filter 중&lt;span style=&quot;background-color: #f6e199;&quot;&gt; UnernamePasswordAuthenticationFilter를 상속받아 커스텀 한 필터가 호출되지 않는다는 것을 알아냈&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;2065&quot; data-origin-height=&quot;1015&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAGe0m/btsJhZR3XzE/tFzp5h6aZFNO9MDUHo4GQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAGe0m/btsJhZR3XzE/tFzp5h6aZFNO9MDUHo4GQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAGe0m/btsJhZR3XzE/tFzp5h6aZFNO9MDUHo4GQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAGe0m%2FbtsJhZR3XzE%2FtFzp5h6aZFNO9MDUHo4GQ1%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;2065&quot; height=&quot;1015&quot; data-origin-width=&quot;2065&quot; data-origin-height=&quot;1015&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 부끄러운 로그의 흔적인데,, 해당 로그가 찍히지 않는 것을 발견했다. 그렇지만, 로직도 괜찮았고 해당 필터도 당연히 등록을 해줬기 때문에 몇 시간 동안 뭐가 문제인지 찾지를 못하였다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;3) security 디버그 모드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;@EnableWebSecurity의 debug옵션을 true&lt;/span&gt;로 바꾸고 애플리케이션을 실행하니 여러 정보들이 나왔다. 정보들 중에 적용된 filter chain들이 있었는데, 호출이 안된다고 생각했던, JwtAuthentiationFilter가 등록되어 있었다..!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5xDA8/btsJi6bqDde/G3LO6hgO721zZcd0xMfeQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5xDA8/btsJi6bqDde/G3LO6hgO721zZcd0xMfeQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5xDA8/btsJi6bqDde/G3LO6hgO721zZcd0xMfeQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5xDA8%2FbtsJi6bqDde%2FG3LO6hgO721zZcd0xMfeQ0%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;586&quot; height=&quot;219&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;767&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Mos0/btsJienR1Rw/Zl9kbNqI808JjfNOTyyWuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Mos0/btsJienR1Rw/Zl9kbNqI808JjfNOTyyWuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Mos0/btsJienR1Rw/Zl9kbNqI808JjfNOTyyWuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Mos0%2FbtsJienR1Rw%2FZl9kbNqI808JjfNOTyyWuK%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;926&quot; height=&quot;767&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;767&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;4) 인텔리제이 디버거&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 따라서 AuthenticationFilter에 문제가 있는 것 같으니, 바로 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;위 필터인 JwtAuthorizationFilter에 브레이크 포인트&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;2186&quot; data-origin-height=&quot;865&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lDkAD/btsJjmkSzJb/JRgBpEFwye74lZJ8iYKKKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lDkAD/btsJjmkSzJb/JRgBpEFwye74lZJ8iYKKKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lDkAD/btsJjmkSzJb/JRgBpEFwye74lZJ8iYKKKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlDkAD%2FbtsJjmkSzJb%2FJRgBpEFwye74lZJ8iYKKKk%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;2186&quot; height=&quot;865&quot; data-origin-width=&quot;2186&quot; data-origin-height=&quot;865&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &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;2461&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mU3ee/btsJih5QUUh/yzNs5ufxcWJ80OBG7sVxA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mU3ee/btsJih5QUUh/yzNs5ufxcWJ80OBG7sVxA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mU3ee/btsJih5QUUh/yzNs5ufxcWJ80OBG7sVxA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmU3ee%2FbtsJih5QUUh%2FyzNs5ufxcWJ80OBG7sVxA0%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;2461&quot; height=&quot;591&quot; data-origin-width=&quot;2461&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;&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;span style=&quot;background-color: #ffc9af;&quot;&gt;5) 직접 코드 내부 들여다보기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 그래도 위에서 말한 필터 주변에서 문제가 있는 것 같으니, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;상속받은 필터의 코드 내부를 들여다보기로&lt;/span&gt; 하였다. UsernamePasswordAuthenticationFilter인데 이미 글이 너무 길어졌으니, 다음 글에서 트러블 슈팅 과정을 마무리하겠다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;section&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 글을 쓰며 전체적인 흐름에 대해 다시 정리된 기분이다. JWT를 통해 로그인, 회원가입을 구현해 보았다면, 위 글을 읽고 어디서 문제가 있었는지 이미 찾았을 수도 있다. 다음 글에서 마저 정리해 보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 그리고 디버깅의 중요성에 대해서 다시 한번 알게 되었다. 뭔가 모르겠다 싶으면, 디버거를 사용해서 디버깅도 해보고, 내부 코드를 보는 것에 대해 두려워하지 말아야겠다. 엄청 오랜만에 본 것 같은데, 너무 재밌었다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/36</guid>
      <comments>https://macdduck.tistory.com/36#entry36comment</comments>
      <pubDate>Wed, 28 Aug 2024 23:16:22 +0900</pubDate>
    </item>
    <item>
      <title>로그인, 회원가입 구현을 위해 필요한 개념 목록</title>
      <link>https://macdduck.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;spring security 사용의 이유. 장점.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 vs jwt &amp;rarr; cs 인증 방식 완성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwt토큰을 담는 위치 cookie vs reqeust header&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;refresh token 관련 설정 및 장단점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flow 정리&lt;/p&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/Security</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/33</guid>
      <comments>https://macdduck.tistory.com/33#entry33comment</comments>
      <pubDate>Sun, 25 Aug 2024 22:26:23 +0900</pubDate>
    </item>
    <item>
      <title>SQLD 벼락치기...</title>
      <link>https://macdduck.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;2-29 다시 보기&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: #c1bef9;&quot;&gt;엔티티 매핑 관련 문제는 NULL인 것 제외 혹은 VALID CUSTOMER 같은 것 생각!! 중복 제외도 생각!&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;SELECT 절에 DISTINCT는 항상 SELECT 바로 뒤에 위치한다.&lt;/p&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: #9feec3;&quot;&gt;별칭(ALIAS)&lt;/span&gt;에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;공백, 특수기호가 포함&lt;/span&gt;되거나 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;대소문자를 구분&lt;/span&gt;하고 싶을 때 &lt;span style=&quot;background-color: #ffc1c8;&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;TRUNCATE는 DDL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT절에서 컬럼 별칭을 사용하면 ORDER BY 절에서만 사용 가능&lt;/p&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: #9feec3;&quot;&gt;LPAD('X', 5, 'Y')&lt;/span&gt; &amp;rarr; X의 왼쪽에 Y를 삽입해서 5개를 만들어라&lt;/p&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: #9feec3;&quot;&gt;INSTR('A', 'B', x, y)&lt;/span&gt; &amp;rarr; A에서 B를 x번째부터 찾는데, y번째 발견된 위치를 반환해라&lt;/p&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: #9feec3;&quot;&gt;CEIL()&lt;/span&gt; &amp;rarr; 올림. CEIL(3)은 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;ROUND(X, -3)&lt;/span&gt; &amp;rarr; 3번째에서 반올림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;SIGN()&lt;/span&gt; &amp;rarr; 양수 음수 판별, 양수면 1, 0이면 0, 음수면 -1 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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;NVL&lt;/span&gt;(A, B), &lt;span style=&quot;background-color: #9feec3;&quot;&gt;ISNULL&lt;/span&gt;(A, B) &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;A가 NULL이면 B&lt;/span&gt;, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;NOT NULL이면 A&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;A와 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;background-color: #9feec3;&quot;&gt;COALESCE(A, B, ...)&lt;/span&gt; &amp;rarr; NULL이 아닌 가장 앞에 있는 값 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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;NULLIF(A, B)&lt;/span&gt; &amp;rarr; A와 B가 같으면 NULL이 return. 같지 않으면 A 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;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;DECODE(A, B, C, D, .... )&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;A가 B와 같으면 C&lt;/span&gt;, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;같지 않으면 D&lt;/span&gt; return&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;DECODE(A, B, C)&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;A가 B와 같으면 C&lt;/span&gt;, &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;같지 않으면 NULL&lt;/span&gt; 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;TRIM()함수는 제거해 나가다가, 제거하지 않을 문자를 만나면 스탑!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INITCAP() &amp;rarr; 카멜 표기법으로 바꿈&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPLACE(A, B, C) &amp;rarr; A에서 B를 C로 치환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REPLACE(A, B) &amp;rarr; A에서 B를 모두 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TO_CHAR(100, '9999.99') &amp;rarr; 공백100.00&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TO_CHAR(100, '0000.00') &amp;rarr; 0100.00&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 9는 공백 / 0은 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;background-color: #9feec3;&quot;&gt;INNER JOIN 사용 시, USING이나 ON을 반드시 사용해야 한다&lt;/span&gt;. &lt;span style=&quot;background-color: #ffc1c8;&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;CASE문에서 WHEN 앞에 비교대상이 들어가면 타입이 무조건 맞아야 함.... 1-18&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;CASE문에서&lt;/span&gt; CASE ~ WHEN ~ THEN ~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; WHEN ~ THEN~ ELSE ~. // 이렇게 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;ELSE는 마지막에 한 번만 올 수 있음&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: #ffc1c8;&quot;&gt;오라클에서 100+'1' 은 101&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;일(DAY)만 쓰게 되면 나머지 년월 시분초 등은 현재 날짜를 따름...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날짜 +- 1에서 1은 하루&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날짜 계산시 30/24/60의 값은 30 * 1/24/60으로 보면 됨 &amp;rarr; 30 * 1시간/60 &amp;rarr; 30분&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CASE WHEN DEPTNO = 10 THEN 1 ELSE 0 END&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CASE WHEN DEPTNO = 10 THEN 1 END &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;1이 아니면 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;background-color: #9feec3;&quot;&gt;연산자 우선순위&lt;/span&gt; &amp;rarr; NOT, AND, OR 순&lt;/p&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: #c1bef9;&quot;&gt;NULL을 포함한 SUM&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NULL로만 구성된 SUM은 NULL&lt;/span&gt; return / &lt;span style=&quot;background-color: #f6e199;&quot;&gt;포함이 됐으면 NULL은 무시&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL과 사칙연산 &amp;rarr; NULL 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;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;NULL을 포함한 COUNT&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;NULL은 카운트하지 않음!!&lt;/span&gt;&lt;/span&gt; / &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;NULL로만 구성된 COUNT는 0&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;background-color: #c1bef9;&quot;&gt;WHERE 절이 FALSE이면 아무것도 출력되지 않음!!&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: #c1bef9;&quot;&gt;ORDER BY와 NULL&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;오라클&lt;/span&gt;에서는 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;NULLS LAST가 DEFAULT&lt;/span&gt;(오름차순)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 내림차순으로 ORDER BY 를 하면 거꾸로 되니까 NULL이 첫번째로 옴....&lt;/p&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: #9feec3;&quot;&gt;MOD(X, Y)&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;X를 Y로 나눈 나머지&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;GROUP BY절에는 컬럼 별칭을 사용할 수 없다..... &amp;rarr; 별칭을 지정할 수 없다는 게 아니라, SELECT절에서 지정한 컬럼 별칭을 사용할 수 없다는 뜻...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY절에서만 유일하게 사용 가능하다.&lt;/p&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: #9feec3;&quot;&gt;OFFSET N&lt;/span&gt; &amp;rarr; N 행을 건너 뜀&lt;/p&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: #9feec3;&quot;&gt;GROUP BY절에서는 그룹함수를 사용할 수 없다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;GROUP BY절에서는 명시되지 않은 컬럼을 그룹함수 없이 SELECT절에서 사용할 수 없다&lt;/span&gt; 1- 24 - 3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt; &lt;span style=&quot;background-color: #9feec3; color: #333333; text-align: start;&quot;&gt;GROUP BY절에서&lt;/span&gt; 나열되는 컬럼 순서는 중요하지 않다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;GROUP BY절에 사용한 컬럼만 ORDER BY 절에 사용할 수 있&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: #9feec3;&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;GROUP BY절에서 NULL도 묶임...!&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;background-color: #9feec3;&quot;&gt;공집합에 대해 GROUP BY를 하면 공집합&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;공집합을 COUNT()하면 공집합&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: #9feec3;&quot;&gt;공집합은 아무것도 없는 것이고, NULL은 비어 있는 것이어서&lt;/span&gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&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;background-color: #c1bef9;&quot;&gt;GROUP BY의 결과가 아무것도 없을 때 COUNT를 하면 공집합&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;단순히 WHERE절을 만족시키지 않을 때 COUNT를 하면 0&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;SELECT절에서 컬럼 별칭을 정의하면, &lt;span style=&quot;background-color: #f6e199;&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;background-color: #9feec3;&quot;&gt;&amp;lt;&amp;gt;과 !=&lt;/span&gt;은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;같음&lt;/span&gt;. 같지 않으면 TRUE return&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;비교 연산자와 NULL&lt;/span&gt; &amp;rarr; NULL을 포함하면 FALSE / &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NULL = NULL&lt;/span&gt; 또는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NULL != NULL&lt;/span&gt;이 &lt;span style=&quot;background-color: #f6e199;&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;FROM절에서 , 로 이어져 있으면, JOIN임....&lt;/p&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: #9feec3;&quot;&gt;LEFT OUTER JOIN&lt;/span&gt; &amp;rarr; A LEFT OUTER JOIN B ON 조건이면, 조건을 만족하지 않는 A컬럼은 유지되고, 나머지 컬럼은 NULL로 채워짐&lt;/p&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: #9feec3;&quot;&gt;FULL OUTER JOIN&lt;/span&gt; &amp;rarr; UNION으로 됨.. &lt;span style=&quot;background-color: #f6e199;&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;background-color: #9feec3;&quot;&gt;CROSS JOIN&lt;/span&gt; &amp;rarr; 카디널리티의 곱 (모든 경우의 수)&lt;/p&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: #9feec3;&quot;&gt;NATURAL JOIN&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;컬럼의 이름이 같은 놈들&lt;/span&gt;끼리 JOIN&amp;nbsp; / &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;주의!!!&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NULL과 NULL은 같다고 할 수 없음!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;USING이나 ON을 사용할 수 없음!!!!&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: #9feec3;&quot;&gt;IN 연산자와 NULL&lt;/span&gt; &amp;rarr; A &lt;span style=&quot;background-color: #f6e199;&quot;&gt;IN&lt;/span&gt; (X, Y, Z)이면 A = X &lt;span style=&quot;background-color: #f6e199;&quot;&gt;or&lt;/span&gt; A = Y &lt;span style=&quot;background-color: #f6e199;&quot;&gt;or&lt;/span&gt; A = Z이므로 X, Y, Z 중에 NULL이 오면 반환 제외 1- 31&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;NOT IN 연산자와 NULL&lt;/span&gt; &amp;rarr; A &lt;span style=&quot;background-color: #f6e199;&quot;&gt;NOT IN&lt;/span&gt; (X, Y, Z)이면 A != X &lt;span style=&quot;background-color: #f6e199;&quot;&gt;and&lt;/span&gt; A != Y &lt;span style=&quot;background-color: #f6e199;&quot;&gt;and&lt;/span&gt; A != Z이므로, X, Y, Z에 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;하나라도 NULL이 오면&lt;/span&gt; &lt;span style=&quot;background-color: #c1bef9;&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;background-color: #9feec3;&quot;&gt;EXISTS(서브쿼리)&lt;/span&gt; &amp;rarr; 서브쿼리의 결과가 존재하면 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;NOT EXISTS(서브쿼리)&lt;/span&gt; &amp;rarr; 서브쿼리의 결과가 존재하지 않으면 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 서브쿼리내 SELECT A 에서 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;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;날짜 대소비교에서 2020/02 보다 크면, 2020/02/01 이후이면 됨(크거나 같으면 2월 1일 포함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE A &amp;lt;= ALL(30, 40, 50) &amp;rarr; A가 30 보다 작거나 같아야 함&lt;/p&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: #9feec3;&quot;&gt;서브쿼리가 있을 때&lt;/span&gt; 무조건 서브 쿼리가 먼저 실행되는 게 아니라, &lt;span style=&quot;background-color: #f6e199;&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;background-color: #9feec3;&quot;&gt;JOIN시 USING (필수)&lt;/span&gt;을 사용하는 경우 &amp;rarr; 두 테이블의 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;공통된 컬럼을 기준으로 JOIN할 때&lt;/span&gt;... &lt;span style=&quot;background-color: #f6e199;&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;background-color: #ffc1c8;&quot;&gt;INNER JOIN을 걸면 구매를 안 한 고객은 생략되고 구매가 안 된 서비스는 생략 됨&lt;/span&gt;.. 1 - 35&lt;/p&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: #ffc1c8;&quot;&gt;LEFT OUTER JOIN을 걸고 COUNT를 해주면 됨...! 근데 COUNT를 하더라도 무엇을 하냐에 따라 차이가 있음!!&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: #9feec3;&quot;&gt;SELECT TOP(2) WITH TIES&lt;/span&gt; &amp;rarr; 2등이 여러명이더라도 2등까지 모두 출력!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROLLBACK TO SAVE1; &amp;rarr; SAVE POINT로 롤백!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;ROLLBACK&lt;/span&gt;; &amp;rarr; 아직 &lt;span style=&quot;background-color: #f6e199;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;NUMBER(5, 2)&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;XXX.XX&lt;/span&gt; // 정수 부분은 무조건 최대 3자리수! 소수 부분은 아무리 많이 들어와도 자동으로 2자리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블 컬럼 별칭을 지정할 때 A, &quot;A&quot;는 다른 것임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HAVING절에서는 그룹함수가 없는 일반 조건을 사용할 수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TRUNC(A, B) &amp;rarr; 숫자일 때, 소수점 아래 B까지 버림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 날짜일 때, TRUNC(8월 24일, 'MM') 이면 월의 첫째날인 8월 1일이 됨 // &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;DEFAULT는 DAY값 버림&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;RRMMDD &amp;rarr; RR이 1~49이면 2000년대, 50~99이면 1900년대&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTMMDD &amp;rarr; YY는 무조건 2000년대 이후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SYSDATE - BIRTHDAY) / 365 &amp;rarr; (오늘 날짜 - 생일) / 365 = 만 나이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY절에 SELECT절에 정의되지 않은 컬럼을 사용할 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-----------------------------------------------------------------------------------------------------------------------------------------------------------------&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;그룹 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;GROUPING SETS(A, B, . . .)&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;A,&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;B,&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;. . .&lt;/span&gt;로 GROUP BY 결과&lt;/p&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: #9feec3;&quot;&gt;ROLLUP(A, B)&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;A,&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;(A, B),&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체()&lt;/span&gt;로 GROUP BY 결과 / 전체는 GROUPING SETS로 표현했을 때, ()이나 NULL?&lt;/p&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: #9feec3;&quot;&gt;CUBE(A, B)&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;A,&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;B,&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;(A, B),&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체()&lt;/span&gt;로 GROUP BY 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;여기서 전체는 전체 총계 의미!!! 아마 SUM값 구하고 나머지는 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;background-color: #c1bef9;&quot;&gt;윈도우 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 골격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;윈도우함수([대상]) &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; OVER(&lt;span style=&quot;background-color: #99cefa;&quot;&gt;[PARTITION BY 컬럼]&lt;/span&gt; // 출력할 총 데이터 수 변화 없이 그룹연산 수행할 GROUP BY 컬럼&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;[ORDER BY 컬럼 ASC | DESC]&lt;/span&gt; // RANK의 경우 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;[ROWS | RANGE BETWEEN A AND B]&lt;/span&gt;); // 연산 범위 설정, ORDER BY절 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PARTITION BY는 ㅇㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ORDER BY 도 ㅇㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;ROWS | RANGE BETWEEN A AND 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;background-color: #99cefa;&quot;&gt;ROWS&lt;/span&gt;: 값이 같더라도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;각 행씩 연산&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;RANGE&lt;/span&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;같은 값의 경우 하나의 RANGE로 묶어서&lt;/span&gt; 동시 연산(&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;DEFAULT&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;BETWEEN A AND B &amp;rarr; &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;DEFAULT는 처음부터 현재행까지&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: #99cefa;&quot;&gt;A) 시작점 정의&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;background-color: #9feec3;&quot;&gt;CURRENT ROW&lt;/span&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;현재행&lt;/span&gt;부터&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;UNBOUNDED PRECEDING&lt;/span&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;처음&lt;/span&gt;부터(&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;DEFAULT&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;N PRECEDING&lt;/span&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;N 이전&lt;/span&gt;부터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;B) 마지막 시점 정의&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;background-color: #9feec3;&quot;&gt;CURRENT ROW&lt;/span&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;현재행&lt;/span&gt;까지(&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;DEFAULT&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;UNBOUNDED FOLLOWING&lt;/span&gt;: &lt;span style=&quot;background-color: #f6e199;&quot;&gt;마지막&lt;/span&gt;까지&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;N FOLLOWING&lt;/span&gt;:&lt;span style=&quot;background-color: #f6e199;&quot;&gt; N 이후&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;background-color: #ffc9af;&quot;&gt;윈도우 함수 종류&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;순위 관련 함수&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;RANK(특정 값) WITHIN GROUP&lt;/span&gt;: 특정 값에 대한 순위 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;RANK() &lt;/span&gt;OVER(): 내가 평소에 생각하던 랭킹 시스템. &amp;rarr; ORDER BY절 필수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;DENSE_RANK() &lt;/span&gt;OVER(): &lt;span style=&quot;background-color: #f6e199;&quot;&gt;1등이 여러 명이더라도&lt;/span&gt; 그 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;다음 순위가 2등&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;ROW_NUMBER() &lt;/span&gt;&lt;/span&gt;OVER(): 그냥 나열한 순서대로.. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;1순위가 두 명이어도 순서대로&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;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;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;LAG() &lt;/span&gt;OVER(): 이전 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;LEAD() &lt;/span&gt;OVER(): 이후 값&lt;/p&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: #ffc1c8;&quot;&gt;DEFAULT는 처음부터 현재행까지&lt;/span&gt;임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;FIRST_VALUE() &lt;/span&gt;OVER(): 처음 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;LAST_VALUE() &lt;/span&gt;OVER(): 마지막 값&lt;/p&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: #9feec3;&quot;&gt;NTILE(특정 값)&lt;/span&gt; OVER(): &lt;span style=&quot;background-color: #f6e199;&quot;&gt;특정 값이 2&lt;/span&gt;이면 컬럼들을 &lt;span style=&quot;background-color: #f6e199;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;TOP N QUERY&lt;/span&gt; &amp;rarr; 페이징 처리를 효과적으로 수행하기 위해 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROUNUM&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RANK&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FETCH&lt;/p&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: #c1bef9;&quot;&gt;계층형 질의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 골격&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FROM 테이블명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;START WITH&lt;/span&gt; &lt;span style=&quot;background-color: #99cefa;&quot;&gt;시작 조건&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;CONNECT BY PRIOR&lt;/span&gt; &lt;span style=&quot;background-color: #99cefa;&quot;&gt;연결조건&lt;/span&gt;; // A = B 이면 상위레벨의 A 와 하위레벨의 B가 같은 것을 찾으면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;현재 읽은 컬럼을 지정하는 구문&lt;/span&gt; &amp;rarr; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;PRIOR&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: #c1bef9;&quot;&gt;PIVOT과 UNPIVOT&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PIVOT: LONG DATA &amp;rarr; WIDE DATA&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNPIVOT: WIDE DATA &amp;rarr; LONG DATA&lt;/p&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: #ffc9af;&quot;&gt;PIVOT 기본 골격&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT * FROM &lt;span style=&quot;background-color: #99cefa;&quot;&gt;테이블명 또는 서브쿼리&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;PIVOT&lt;/span&gt; (&lt;span style=&quot;background-color: #99cefa;&quot;&gt;VALUE 컬럼명&lt;/span&gt;&amp;nbsp; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;FOR&lt;/span&gt; &lt;span style=&quot;background-color: #99cefa;&quot;&gt;UNSTACK 컬럼명&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;IN&lt;/span&gt; (값1, 값2, 값3));&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;STACK 컬럼&lt;/span&gt;은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;FROM절에서 선언한 컬럼 중 VALUE와 UNSTACK컬럼을 제외한 모든 컬럼&lt;/span&gt;!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;따라서 FROM절을 반드시 제한해줘야 함!&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: #ffc9af;&quot;&gt;UNPIVOT 기본 골격&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT * FROM 테이블명 또는 서브쿼리 // UNPIVOT은 여기를 굳이 제한할 필요는 없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;UNPIVOT&lt;/span&gt; (&lt;span style=&quot;background-color: #99cefa;&quot;&gt;VALUE 컬럼명&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;FOR&lt;/span&gt; &lt;span style=&quot;background-color: #99cefa;&quot;&gt;STACK 컬럼명&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;IN&lt;/span&gt; (&quot;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;값1&quot;, &quot;값2&quot;, ...&lt;/span&gt; )); // 컬럼 이름은 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;문자이므로 쌍따옴표&lt;/span&gt;로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 단, &lt;span style=&quot;background-color: #ffc1c8;&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;background-color: #c1bef9;&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>연습장</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/32</guid>
      <comments>https://macdduck.tistory.com/32#entry32comment</comments>
      <pubDate>Sat, 24 Aug 2024 01:42:15 +0900</pubDate>
    </item>
    <item>
      <title>SQLD 정리...</title>
      <link>https://macdduck.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. SQL 기본&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL과 사칙연산은 NULL return;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NULL과 비교연산은 FALSE 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;2. SQL 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-9 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;서브쿼리&lt;/span&gt; &amp;rarr; 서브쿼리 절에 ORDER BY 사용 불가!&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;WHER절&lt;/span&gt; 서브쿼리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일행 서브쿼리(서브쿼리 결과가 1개)&lt;/li&gt;
&lt;li&gt;다중행 서브쿼리(서브쿼리 결과가 여러 개)&lt;/li&gt;
&lt;li&gt;다중컬럼 서브쿼리(서브쿼리 결과와 여러 컬럼 비교, 대소비교 불가)&lt;/li&gt;
&lt;li&gt;상호연관 서브쿼리(메인과 서브 비교)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;인라인뷰&lt;/span&gt;(FROM절에서 테이블처럼 사용)&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;스칼라&lt;/span&gt;(SELECT절에서 컬럼처럼 사용)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 결과는 '단일행' 서브쿼리 형태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 데이터가 NULL일 때 생략 없이 출력 like OUTER 조인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-10 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;집합 연산자&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;합집합&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UNION &amp;rarr; 중복 제거&lt;/li&gt;
&lt;li&gt;UNION ALL&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;교집합&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;INTERSECT&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. &lt;span style=&quot;background-color: #ffc9af;&quot;&gt;차집합&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MINUS&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;주의 사항&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컬럼 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;수, 순서, 타입&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;일치해야&lt;/span&gt; 사용 가능&lt;/li&gt;
&lt;li&gt;컬럼 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;사이즈(행의 수, 데이터 길이) 등은 달라도&lt;/span&gt; OK&lt;/li&gt;
&lt;li&gt;맨 뒤에는 가능하나, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;첫 번째에 ORDER BY 사용 불가&lt;/span&gt;!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-11 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;그룹 함수&lt;/span&gt; &amp;rarr; NULL은 무시하고 연산!&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;GROUP BY절에 나오는 함수&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;GROUPPING SETS(A, B, ...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;A별&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;B별&lt;/span&gt; 그룹 연산 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UNION ALL로 대체 가능 &amp;rarr; SELECT절에 NULL 필요&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;2.&lt;span style=&quot;background-color: #99cefa;&quot;&gt; ROLLUP(A, B)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;A&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;(A, B),&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체&lt;/span&gt;로 묶음! 따라서 순서 중요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 UNION ALL로 대체 가능. 필요한 부분 NULL로 채움&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;3. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;CUBE(A, B)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;A&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;B&lt;/span&gt;, &lt;span style=&quot;background-color: #f6e199;&quot;&gt;(A, B),&lt;/span&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;전체&lt;/span&gt;로 묶음!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;GROUPING SETS(A, B, ...)로 대체가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;UNION ALL로도 대체가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-12 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;윈도우 함수&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc9af;&quot;&gt;순위 관련 함수&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;RANK WITH GROUP&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;RANK() OVER()&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;DENSE_RANK&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;span style=&quot;background-color: #99cefa;&quot;&gt;ROW_NUMBER&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gg&lt;/p&gt;</description>
      <category>연습장</category>
      <author>macdduck</author>
      <guid isPermaLink="true">https://macdduck.tistory.com/31</guid>
      <comments>https://macdduck.tistory.com/31#entry31comment</comments>
      <pubDate>Fri, 23 Aug 2024 20:00:24 +0900</pubDate>
    </item>
  </channel>
</rss>