<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>흑임자</title>
    <link>https://soo-99.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 28 Jul 2026 13:22:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>기넌</managingEditor>
    <image>
      <title>흑임자</title>
      <url>https://tistory1.daumcdn.net/tistory/4077270/attach/462abc4a3d1e44adacfb35dc00d7830e</url>
      <link>https://soo-99.tistory.com</link>
    </image>
    <item>
      <title>[ERROR] gradle - java version</title>
      <link>https://soo-99.tistory.com/entry/ERROR-gradle-java-version</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IntelliJ&lt;/li&gt;
&lt;li&gt;Gradle 3.3.0&lt;/li&gt;
&lt;li&gt;Java 21&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에러&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project 설정은 모두 자바 21 버전으로 바꿨는데 인식을 못했던 문제&lt;/p&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;intellij 에서 A problem occurred configuring root project 'sbb'.
&amp;gt; Could not resolve all artifacts for configuration ':classpath'.
   &amp;gt; Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.3.0.
     Required by:
         project : &amp;gt; org.springframework.boot:org.springframework.boot.gradle.plugin:3.3.0
      &amp;gt; No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.3.0 was found. The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '8.7' but:
          - Variant 'apiElements' declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PphZr/btsHTDgGPq1/V0h7lK80fR11JipJvWlwOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PphZr/btsHTDgGPq1/V0h7lK80fR11JipJvWlwOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PphZr/btsHTDgGPq1/V0h7lK80fR11JipJvWlwOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPphZr%2FbtsHTDgGPq1%2FV0h7lK80fR11JipJvWlwOk%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;2272&quot; height=&quot;1914&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1914&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle JVM 에서도 자바 버전을 21로 맞춰야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;1650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv4cdo/btsHTSdCc3E/BhRuT7raTYRtPg1C1G2bmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv4cdo/btsHTSdCc3E/BhRuT7raTYRtPg1C1G2bmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv4cdo/btsHTSdCc3E/BhRuT7raTYRtPg1C1G2bmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv4cdo%2FbtsHTSdCc3E%2FBhRuT7raTYRtPg1C1G2bmk%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;2188&quot; height=&quot;1650&quot; data-origin-width=&quot;2188&quot; data-origin-height=&quot;1650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/111</guid>
      <comments>https://soo-99.tistory.com/entry/ERROR-gradle-java-version#entry111comment</comments>
      <pubDate>Sun, 9 Jun 2024 20:39:56 +0900</pubDate>
    </item>
    <item>
      <title>Oracle21c 컨테이너 띄워 사용하기 (Mac M1)</title>
      <link>https://soo-99.tistory.com/entry/Oracle21c-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%9D%84%EC%9B%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-Mac-M1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;환경&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mac M1&lt;/li&gt;
&lt;li&gt;Sonoma 14.0&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Oracle21c&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker 설치&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;homebrew 사용&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre id=&quot;code_1717255120559&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  cask 옵션

macOS용 GUI 애플리케이션을 설치하고 관리&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install --cask docker
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker site&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/&quot;&gt;Install Docker Engine&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717255004905&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install Docker Engine&quot; data-og-description=&quot;Learn how to choose the best method for you to install Docker Engine. This client-server application is available on Linux, Mac, Windows, and as a static binary.&quot; data-og-host=&quot;docs.docker.com&quot; data-og-source-url=&quot;https://docs.docker.com/engine/install/&quot; data-og-url=&quot;https://docs.docker.com/engine/install/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjZBf7/hyWdoDSr1w/27wKbmapPPMNSlhuIQkBS0/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://docs.docker.com/engine/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.docker.com/engine/install/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjZBf7/hyWdoDSr1w/27wKbmapPPMNSlhuIQkBS0/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install Docker Engine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to choose the best method for you to install Docker Engine. This client-server application is available on Linux, Mac, Windows, and as a static binary.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVKXdg/btsHMfGT4VK/dhPrLKqQ3v8DKEBSIE4Yi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVKXdg/btsHMfGT4VK/dhPrLKqQ3v8DKEBSIE4Yi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVKXdg/btsHMfGT4VK/dhPrLKqQ3v8DKEBSIE4Yi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVKXdg%2FbtsHMfGT4VK%2FdhPrLKqQ3v8DKEBSIE4Yi1%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;1568&quot; height=&quot;810&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Colima 설치&lt;/h3&gt;
&lt;pre id=&quot;code_1717255137466&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Colima?

최소한의 설정으로 MacOS에서 컨테이너 런타임 환경을 만들어주는 도구&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;brew install colima
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Colima 에러&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh 연결이 안되는 문제 .. (처음 실행 때는 연결 됐었는데)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 현상&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;➜  Caches colima start --memory 4 --arch x86_64
INFO[0000] starting colima
INFO[0000] runtime: docker
INFO[0002] starting ...                                  context=vm
&amp;gt; [hostagent] Waiting for the essential requirement 1 of 4: &quot;ssh&quot;
...
영원히 ssh 준비만 하고 실행이 안됨
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존에 남아있던 colima 설정 모두 삭제&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;colima delete -f
colima prune -a
rm -rf ~/.colima-*
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추가로 colima 재설치하려면&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;brew uninstall colima
rm -rf ~/Library/Caches/Homebrew
brew install colima
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;vm-type 옵션 추가&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/abiosoft/colima/issues/913&quot;&gt;https://github.com/abiosoft/colima/issues/913&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본으로 QEMU 드라이버를 사용하는데, 다른 드라이버로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;colima start --memory 4 --arch x86_64 --vm-type vz
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Result&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;➜  ~ colima start --memory 4 --arch x86_64 --vm-type vz

INFO[0000] starting colima                              
INFO[0000] runtime: docker                              
INFO[0001] creating and starting ...                     context=vm
INFO[0186] provisioning ...                              context=docker
INFO[0187] starting ...                                  context=docker
INFO[0193] done 
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Oracle21c 컨테이너 실행&lt;/h3&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;docker run --restart unless-stopped --name oracle21 -e ORACLE_PASSWORD={DB 접속 비밀번호} -p {호스트 운영체제의 포트번호}:{컨테이너 내부에서의 포트번호 - 1521} -d gvenzl/oracle-xe
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ccorgi1997/Mac-M1-%EB%A7%A5%EB%B6%81%EC%97%90%EC%84%9C-Oracle-11-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;[Mac] M1 맥북에서 Oracle 11 사용하기&lt;/a&gt;&lt;/p&gt;</description>
      <category>docker</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/109</guid>
      <comments>https://soo-99.tistory.com/entry/Oracle21c-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EB%9D%84%EC%9B%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-Mac-M1#entry109comment</comments>
      <pubDate>Sun, 2 Jun 2024 00:20:35 +0900</pubDate>
    </item>
    <item>
      <title>프로세스와 스레드</title>
      <link>https://soo-99.tistory.com/entry/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;막연하게 프로세스는 프로그램이 메모리에 올라가서 실행 중인 상태,&lt;br /&gt;스레드는 프로세스의 하나의 실행 단위 라고 생각해왔다.&lt;br /&gt;동기화나 세마포어, 뮤텍스 .. 등을 다시 한번 되짚어보며 정리의 필요성을 느꼈다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Program&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 작업을 위해 실행할 수 있는 파일&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Process&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 연속적으로 &lt;b&gt;실행되고 있는 컴퓨터 프로그램&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;메모리&lt;/i&gt; 에 올라와 실행되고 있는 프로그램의 인스턴스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제로부터 &lt;b&gt;시스템 자원을 할당&lt;/b&gt;받은 &lt;b&gt;&lt;i&gt;작업의 단위&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;메모리 &lt;/i&gt;에 올라와 실행된 프로그램&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;할당받는 시스템 자원의 예&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;CPU 시간&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;운영되기 위해 필요한 &lt;b&gt;&lt;i&gt;주소 공간&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Stack&lt;/code&gt;, &lt;code&gt;Heap&lt;/code&gt; 의 구조로 되어있는 &lt;b&gt;&lt;i&gt;독립된 메모리 영역&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스의 독립된 메모리 영역&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2991&quot; data-origin-height=&quot;1588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8r06V/btsEwG9E0lR/AqdlPUotRGQVW4djK5sjY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8r06V/btsEwG9E0lR/AqdlPUotRGQVW4djK5sjY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8r06V/btsEwG9E0lR/AqdlPUotRGQVW4djK5sjY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8r06V%2FbtsEwG9E0lR%2FAqdlPUotRGQVW4djK5sjY1%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;2991&quot; height=&quot;1588&quot; data-origin-width=&quot;2991&quot; data-origin-height=&quot;1588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 프로세스당 최소 1개의 &lt;b&gt;스레드&lt;/b&gt; (메인 스레드)를 가지고 있다.&lt;/li&gt;
&lt;li&gt;각 프로세스는 &lt;b&gt;별도의 주소 공간&lt;/b&gt; 에서 실행되며, 한 프로세스는 다른 프로세스의 변수나 자료구조에 접근할 수 없다.&lt;/li&gt;
&lt;li&gt;한 프로세스가 다른 프로세스의 자원에 접근하려면 프로세스 간 통신 (&lt;b&gt;IPC&lt;/b&gt;, inter-process communication) 이 필요하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이프, 파일, 소켓 등을 이용한 통신 방법 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Thread&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 내에서 실행되는 &lt;b&gt;여러 &lt;i&gt;흐름의 단위&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스가 &lt;b&gt;할당받은 자원을 이용하는 &lt;i&gt;실행의 단위&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스 내의 스레드 영역&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMU7fV/btsEu7GG9Iy/6BUHScxYxOCDFmkfywbC21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMU7fV/btsEu7GG9Iy/6BUHScxYxOCDFmkfywbC21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMU7fV/btsEu7GG9Iy/6BUHScxYxOCDFmkfywbC21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMU7fV%2FbtsEu7GG9Iy%2F6BUHScxYxOCDFmkfywbC21%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;2000&quot; height=&quot;1062&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 프로세스 내에서 &lt;code&gt;Stack&lt;/code&gt; 만 &lt;b&gt;각자&lt;/b&gt; 할당받고 &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Heap&lt;/code&gt; 영역은 &lt;b&gt;공유&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;스레드는 &lt;b&gt;한 프로세스 내에서 동작되는 &lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;여러 실행의 흐름&lt;/span&gt;&lt;/i&gt;&lt;/b&gt; 으로, 프로세스 내의 &lt;b&gt;주소 공간&lt;/b&gt;이나 &lt;b&gt;자원들(힙 공간 등)&lt;/b&gt;을 같은 프로세스 내에 &lt;b&gt;스레드끼리 &lt;i&gt;공유 &lt;/i&gt;&lt;/b&gt;하면서 실행된다.&lt;/li&gt;
&lt;li&gt;같은 프로세스 안에 있는 여러 스레드들은 같은 &lt;b&gt;힙 공간을 공유&lt;/b&gt;하는 반면, &lt;b&gt;프로세스는 다른 프로세스의 메모리에 직접 접근이 불가능(통신하기 위해선 IPC를 이용 -위에서 언급-)&lt;/b&gt;&amp;nbsp;하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이렇게 길게 써놓았지만 결론은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 내 &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Heap&lt;/code&gt; 영역은 &lt;b&gt;공유&lt;/b&gt; 되고, &lt;code&gt;Stack&lt;/code&gt; 만 각자 &lt;b&gt;할당&lt;/b&gt; 받는다. 따라서 &lt;b&gt;프로세스&lt;/b&gt;끼리는 서로의 메모리에 직접 접근이 불가하지만 한 프로세스 내의 &lt;b&gt;형제 스레드&lt;/b&gt;(sibling thread)끼리는 &lt;code&gt;Code&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Heap&lt;/code&gt; 영역은 &lt;b&gt;&lt;i&gt;변경 내역을 즉시 확인&lt;/i&gt;&lt;/b&gt; 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;Stack&lt;/code&gt; 이 독립적으로 할당되는 이유?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택은 함수 호출 시 전달되는 인자, 되돌아갈 주소값 및 함수 내에서 선언하는 변수 등을 저장하기 위해 사용되는 메모리 공간이다.&lt;br /&gt;따라서 스택 메모리 공간이 독립적이라는 것은 독립적인 함수 호출이 가능하다는 것이고, 이는 독립적인 실행 흐름이 추가되는 것이다.&lt;br /&gt;결과적으로 실행 흐름의 추가를 위한 최소 조건이 독립된 스택을 제공하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그 이외의 영역이 공유되는 이유?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코드 영역을 공유&lt;/b&gt;&lt;br /&gt;프로세스는 독립적인 구조이기 때문에 다른 프로세스의 &lt;code&gt;Code&lt;/code&gt; 영역에 있는 함수를 호출할 수 없다.&lt;br /&gt;스레드는 &lt;code&gt;Code&lt;/code&gt; 영역을 공유하기 때문에 두 개 이상의 쓰레드가 자신이 포함된 프로세스의 &lt;code&gt;Code&lt;/code&gt; 영역에 있는 함수를 호출할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 영역과 힙 영역을 공유&lt;/b&gt;&lt;br /&gt;전역 변수와 동적 할당된 메모리 공간을 공유할 수 있고, 이를 통해 쓰레드 간 통신을 할 수 있지만 동시에 메모리에 접근하기 때문에 주의해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;b&gt;PCB(그리고 context switch)&lt;/b&gt;와 &lt;b&gt;공유자원(임계구역) 동기화 (세마포어, 뮤텍스)&lt;/b&gt;에 대해 정리하겟음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html&quot;&gt;https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://brownbears.tistory.com/39&quot;&gt;https://brownbears.tistory.com/39&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS/OS</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/108</guid>
      <comments>https://soo-99.tistory.com/entry/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C#entry108comment</comments>
      <pubDate>Wed, 7 Feb 2024 00:22:09 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] M:N 관계 (@ManyToMany)</title>
      <link>https://soo-99.tistory.com/entry/JPA-MN-%EA%B4%80%EA%B3%84-ManyToMany</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/mycheck12/server&quot;&gt;중고책 판매 사이트 제작 프로젝트&lt;/a&gt;를 하던 중, 회원(User)-채팅방(Chat)의 관계를 다대다로 설정해야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 생각했던 방법은 &lt;code&gt;@ManyToMany&lt;/code&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@ManyToMany&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다대다 단방향&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다대다 관계에서는 기본적으로 두 테이블 사이에 &lt;b&gt;연결 테이블&lt;/b&gt;이 들어간다. 그 연결 테이블은 &lt;code&gt;@JoinTable&lt;/code&gt;에서 지정해줄 수 있다.&lt;br /&gt;단방향 연결은 연관 관계의 주인(&lt;code&gt;User&lt;/code&gt;)에서 &lt;code&gt;joinColumn&lt;/code&gt;(내 쪽에서 연결할 컬럼), &lt;code&gt;inverseJoinColumn&lt;/code&gt;(상대 쪽에서 연결할 컬럼)을 모두 &lt;code&gt;JoinTable&lt;/code&gt; 지정 시에 같이 설정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;user&quot;)
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;user_id&quot;, nullable = false, unique = true)
  private Long userId;

  @Column(name = &quot;username&quot;)
  private String username;

  @Column(name = &quot;password&quot;)
  private String password;

  @ManyToMany
  @JoinTable(name = &quot;user_chat&quot;,
    joinColumns = @JoinColumn(name = &quot;user_id&quot;),
    inverseJoinColumns = @JoinColumn(name = &quot;chat_id&quot;))
  private List&amp;lt;Chat&amp;gt; chats = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;chat&quot;)
public class Chat {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;chat_id&quot;, nullable = false, unique = true)
  private Long chatId;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다대다 양방향&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 Entity가 모두 &lt;code&gt;List&amp;lt;상대 Entity&amp;gt;&lt;/code&gt;를 갖고있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;user&quot;)
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;user_id&quot;, nullable = false, unique = true)
  private Long userId;

  @Column(name = &quot;username&quot;)
  private String username;

  @Column(name = &quot;password&quot;)
  private String password;

  @ManyToMany
  @JoinTable(name = &quot;user_chat&quot;,
    joinColumns = @JoinColumn(name = &quot;user_id&quot;),
    inverseJoinColumns = @JoinColumn(name = &quot;chat_id&quot;))
  private List&amp;lt;Chat&amp;gt; chats = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;chat&quot;)
public class Chat {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;chat_id&quot;, nullable = false, unique = true)
  private Long chatId;

  @ManyToMany(mappedBy = &quot;chats&quot;)
  private List&amp;lt;User&amp;gt; users = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방법 동일하게 연결 테이블이 &lt;code&gt;user_id&lt;/code&gt;, &lt;code&gt;chat_id&lt;/code&gt; 하나씩 참조하게 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;mysql&amp;gt; desc user_chat;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| user_id | bigint | NO   | MUL | NULL    |       |
| chat_id | bigint | NO   | MUL | NULL    |       |
+---------+--------+------+-----+---------+-------+
2 rows in set (0.01 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@ManyToMany 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 두 테이블의 다대다 관계를 나타내기 위한 연결 테이블을 생성하기에 각 테이블의 기본 키만 갖고있다.&lt;br /&gt;그렇기 때문에 확장 가능성을 고려했을 때 좋은 선택이 아니다.&lt;br /&gt;그래서 프로젝트에선 &lt;code&gt;@ManyToMany&lt;/code&gt; 대신 연결 테이블(엔티티)을 직접 생성하고 연결 테이블과 각 테이블을 다대일 관계로 만들었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;연결 테이블 직접 정의 후 다대일 연결(@OneToMany, @ManyToOne)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;User&lt;/code&gt;와 &lt;code&gt;Chat&lt;/code&gt;에 &lt;code&gt;UserChat&lt;/code&gt;(연결 엔티티)의 리스트를 정의하고 &lt;code&gt;mappedBy&lt;/code&gt;에는 각 엔티티의 테이블 이름(&lt;code&gt;user&lt;/code&gt;, &lt;code&gt;chat&lt;/code&gt;)을 지정했다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;user&quot;)
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;user_id&quot;, nullable = false, unique = true)
  private Long userId;

  @Column(name = &quot;username&quot;)
  private String username;

  @Column(name = &quot;password&quot;)
  private String password;

  @OneToMany(mappedBy = &quot;user&quot;)
  private List&amp;lt;UserChat&amp;gt; userChats = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;chat&quot;)
public class Chat {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;chat_id&quot;, nullable = false, unique = true)
  private Long chatId;

  @OneToMany(mappedBy = &quot;chat&quot;)
  private List&amp;lt;UserChat&amp;gt; userChats = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 테이블에는 각 엔티티와 다대일(&lt;code&gt;@ManyToOne&lt;/code&gt;)로 기본키와 연결하도록 했다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
public class UserChat {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;user_chat_id&quot;, nullable = false, unique = true)
  private Long userChatId;

  @ManyToOne
  @JoinColumn(name = &quot;chat_id&quot;)
  private Chat chat;

  @ManyToOne
  @JoinColumn(name = &quot;user_id&quot;)
  private User user;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 테이블의 기본키를 따로 둬서 복합 기본 키일때 생길 수 있는 문제점(쿼리 작성 시 복잡함 등)을 배제하고자 했다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;mysql&amp;gt; desc user_chat;
+--------------+--------+------+-----+---------+----------------+
| Field        | Type   | Null | Key | Default | Extra          |
+--------------+--------+------+-----+---------+----------------+
| user_chat_id | bigint | NO   | PRI | NULL    | auto_increment |
| chat_id      | bigint | YES  | MUL | NULL    |                |
| user_id      | bigint | YES  | MUL | NULL    |                |
+--------------+--------+------+-----+---------+----------------+
3 rows in set (0.00 sec)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@yuseogi0218/JPA-%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%97%B0%EA%B4%80%EA%B4%80%EA%B3%84#%EB%8B%A4%EB%8C%80%EB%8B%A4-%EC%9D%BC%EB%8C%80%EB%8B%A4-%EB%8B%A4%EB%8C%80%EC%9D%BC-%EC%96%91%EB%B0%A9%ED%96%A5&quot;&gt;JPA - 다대다 연관관계&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://u-it.tistory.com/348&quot;&gt;[JPA] @ManyToMany와 한계점 극복&lt;/a&gt;&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/107</guid>
      <comments>https://soo-99.tistory.com/entry/JPA-MN-%EA%B4%80%EA%B3%84-ManyToMany#entry107comment</comments>
      <pubDate>Tue, 30 Jan 2024 20:19:53 +0900</pubDate>
    </item>
    <item>
      <title>Transaction</title>
      <link>https://soo-99.tistory.com/entry/Transaction</link>
      <description>&lt;h3&gt;정의&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;데이터베이스의 상태를 변화시키기 위해 수행하는 작업&lt;/strong&gt; 단위이자 &lt;strong&gt;한번에 모두 수행&lt;/strong&gt;되어야하는 일련의 연산&lt;/p&gt;
&lt;p&gt;상태를 변화 == &lt;strong&gt;SQL 질의어(INSERT, UPDATE, DELETE ... )&lt;/strong&gt;를 통해 DB에 접근&lt;/p&gt;
&lt;h3&gt;트랜잭션 성질 (ACID)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Atomicity(원자성)&lt;/strong&gt;: 트랜잭션 내 연산은 모두 반영되든지(&lt;code&gt;commit&lt;/code&gt;) 아예 하나도 반영되지 않아야한다(&lt;code&gt;rollback&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency(일관성)&lt;/strong&gt;: 실행이 성공적으로 완료되면 언제나 일관성있는 데이터베이스 상태를 유지해야한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isolation(독립성)&lt;/strong&gt;: 둘 이상의 트랜잭션이 병행 수행될 때, 서로의 연산에 끼어들거나 영향을 주면 안된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durability(영속성)&lt;/strong&gt;: 한 번 트랜잭션이 성공적으로 완료되면 해당 결과가 영구적으로 저장되어야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt;는 트랜잭션의 일관성을 보장, &lt;strong&gt;Durability&lt;/strong&gt;는 트랜잭션의 결과가 영구적으로 저장되고 시스템 장애에도 안전하게 유지되는 것&lt;/p&gt;
&lt;h3&gt;연산 및 상태&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;commit&lt;/strong&gt;: 하나의 트랜잭션이 성공적으로 끝났고, DB가 일관성있는 상태일 때 이를 알려주기 위해 사용하는 연산&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;rollback&lt;/strong&gt;: 트랜잭션 처리가 비정상적으로 수행되어 트랜잭션 원자성이 깨진 경우, 트랜잭션 내 연산을 모두 취소하고 트랜잭션을 수행하기 전 상태로 되돌려놓는 연산&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F999C55345B6D2ED308&quot; alt=&quot;img&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Active(활동)&lt;/strong&gt;: 트랜잭션 실행 중인 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failed(실패)&lt;/strong&gt;: 트랜잭션 실행 중 오류가 발생해 중단된 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aborted(철회)&lt;/strong&gt;: 트랜잭션이 비정상적으로 처리되어 rollback 연산을 수행한 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partially Committed(부분 완료)&lt;/strong&gt;: 트랜잭션 마지막 연산까지 정상적으로 처리되었지만 아직 commit은 하지 않은 상태&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Committed(완료)&lt;/strong&gt;: 트랜잭션이 성공적으로 종료되어 Commit 연산을 실행한 후의 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;사용 예제&lt;/h2&gt;
&lt;h3&gt;MySQL&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 트랜잭션 시작
START TRANSACTION;

-- 예제를 위한 테이블 생성
CREATE TABLE account (
    id INT PRIMARY KEY,
    balance DECIMAL(10,2)
);

CREATE TABLE transaction_log (
    id INT PRIMARY KEY,
    account_id INT,
    amount DECIMAL(10,2),
    timestamp TIMESTAMP
);

-- 트랜잭션 내에서 에러 발생 시
BEGIN
    -- 사용자 계좌에 돈을 입금하는 SQL 문장
    UPDATE account SET balance = balance + 1000 WHERE id = 1;

    -- 트랜잭션 로그에 입금 내역을 기록하는 SQL 문장
    INSERT INTO transaction_log (account_id, amount, timestamp) VALUES (1, 1000, NOW());

    -- 여기서 의도적으로 에러 발생 (예: 나누기 0)
    SELECT 1 / 0;

    -- 에러가 발생하지 않았다면 트랜잭션 커밋
    COMMIT;
-- 트랜잭션 롤백
ROLLBACK;
END;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;출처&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://gyoogle.dev/blog/computer-science/data-base/Transaction.html#transaction-%E1%84%80%E1%85%AA%E1%86%AB%E1%84%85%E1%85%B5%E1%84%85%E1%85%B3%E1%86%AF-%E1%84%8B%E1%85%B1%E1%84%92%E1%85%A1%E1%86%AB-dbms%E1%84%8B%E1%85%B4-%E1%84%8C%E1%85%A5%E1%86%AB%E1%84%85%E1%85%A3%E1%86%A8&quot;&gt;gyoogle.dev - tech interview&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://coding-factory.tistory.com/226&quot;&gt;coding-factory.tistory.com&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS/DB</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/106</guid>
      <comments>https://soo-99.tistory.com/entry/Transaction#entry106comment</comments>
      <pubDate>Thu, 18 Jan 2024 23:36:49 +0900</pubDate>
    </item>
    <item>
      <title>index</title>
      <link>https://soo-99.tistory.com/entry/index</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;정의; DB에서 index란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;추가적인 쓰기 작업과 저장 공간을 활용&lt;/b&gt;해 데이터베이스 테이블의 특정 열(데이터)에 대한 &lt;b&gt;검색 속도를 향상&lt;/b&gt;&lt;/span&gt;시키기 위해 사용되는 데이터 구조&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 목적&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정의에 나온 것처럼 &lt;span style=&quot;color: #f89009;&quot;&gt;자주 찾는 데이터의 &lt;b&gt;검색 속도를 향상&lt;/b&gt;시키기 위함&lt;/span&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;규모가 작지 않은 테이블&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE가&lt;/code&gt; 자주 발생하지 않은 컬럼&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOIN&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt; 또는 &lt;code&gt;ORDER BY&lt;/code&gt; 에 자주 사용되는 컬럼&lt;/li&gt;
&lt;li&gt;기본적으로 &lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;B 트리&lt;/span&gt;&lt;/b&gt;로 구현되어있어 정렬되어있다는 전제 하에 이루어지는 이야기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;범위 검색이 필요할 때&lt;/li&gt;
&lt;li&gt;정렬이 필요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인덱스에 기록된 데이터에 대한 &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;검색 속도가 향상&lt;/span&gt;&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;동일하게 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;조회를 사용하는 &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;의 성능&lt;/b&gt;이 향상&lt;/span&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 공간, 일반적으로 DB의 약 10%에 해당하는 공간을 사용해야한다.&lt;br /&gt;따라서 색인해놓는 데이터가 많아질 수록 인덱스 테이블 크기가 커진다.&lt;/li&gt;
&lt;li&gt;검색(조회) 기능에서는 성능 향상을 보일 수 있으나 그 외 삽입, 수정, 삭제 시에 인덱스도 함께 업데이트되어야 하므로 자주 일어난다면 속도가 감소할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 알고리즘&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hash table&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블 기반으로 인덱스는 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;{해싱된 컬럼의 값, 데이터 위치}&lt;/b&gt;&lt;/span&gt; 값을 가진다.&lt;br /&gt;시간복잡도는 &lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;O(1)&lt;/span&gt;&lt;/b&gt;로 빠른 검색을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 인풋값이 1이라도 달라지는 순간 아예 다른 아웃풋을 내는 해시 함수의 특성상, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;부등호 연산에는 적합하지 않다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일반적으로 아래와 같은 알고리즘이 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B tree&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 트리를 확장해 하나의 노드가 가질 수 있는 자식 노드의 최대 숫자가 2보다 큰 트리 구조&lt;br /&gt;DB와 파일 시스템에서 자주 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/B-tree.svg/800px-B-tree.svg.png&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;특징&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트에서 리프까지의 거리가 모두 일정하다.&lt;/li&gt;
&lt;li&gt;하나의 노드에 많은 수의 정보를 가질 수 있다.&lt;/li&gt;
&lt;li&gt;노드는 최대 M개의 자식 노드를 가질 수 있다고 하면, 해당 트리는 M차 B 트리라고 한다.&lt;/li&gt;
&lt;li&gt;M차 B 트리라고 할 때, 노드 당 키는 최대 M-1개 가질 수 있다.&lt;/li&gt;
&lt;li&gt;각 노드는 최소 M/2 개의 자식 노드를 가진다. (루트와 Leaf 노드 제외)&lt;/li&gt;
&lt;li&gt;각 노드는 최소 M/2 - 1 개의 key를 가진다. (루트와 Leaf 노드 제외)&lt;/li&gt;
&lt;li&gt;항상 Leaf 노드에 데이터가 추가된다.&lt;/li&gt;
&lt;li&gt;노드가 넘치면 가운데 Key를 기준으로 좌우 Key를 분할해 가운데 Key는 승진한다.&lt;br /&gt;&amp;nbsp; 노드가 넘친다: 3에서 봤던 특징처럼 하나의 노드 당 최대 key 수가 정해져있는데, 이를 넘었을 때 노드가 넘친다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B+ tree&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B 트리의 변형된 형태로, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;leaf 노드에만 값(데이터)이 저장&lt;/b&gt;&lt;/span&gt;이 되고 그 &lt;b&gt;윗 노드들은 키와 포인터로만 구성되어 인덱스 역할을 수행&lt;/b&gt;한다.&lt;br /&gt;모든 노드가 같은 레벨에 대해 &lt;b&gt;정렬된 키값&lt;/b&gt;을 가지고 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images%2Femplam27%2Fpost%2Fbcbce100-d475-4cda-aebe-946d1813949c%2FB%ED%94%8C%EB%9F%AC%EC%8A%A4%20%ED%8A%B8%EB%A6%AC%20%EA%B8%B0%EB%B3%B8%20%ED%98%95%ED%83%9C.jpg&quot; alt=&quot;img&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;연결 리스트&lt;/i&gt;&lt;/b&gt;&lt;br /&gt;B 트리 때 탐색을 위해 노드를 찾아서 이동해야한다는 단점을 보완하기 위해 &lt;b&gt;leaf 노드들은 연결리스트&lt;/b&gt; 형태이다.&lt;br /&gt;덕분에 옆에 있는 리프 노드를 다시 검색해야할 때 &lt;b&gt;리프노드에서 &lt;span style=&quot;color: #f89009;&quot;&gt;선형 검사&lt;/span&gt;&lt;/b&gt;를 할 수 있다.&lt;br /&gt;** 선형 검사: 데이터 집합에서 특정 값을 찾기 위해 처음부터 순차적으로 검색하는 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트부터 모든 리프 노드까지의 깊이가 같아서 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;O(log N)&lt;/b&gt;&lt;/span&gt;의 시간복잡도를 가진다.&lt;/li&gt;
&lt;li&gt;데이터가 정렬된 상태로 리프 노드에 연결 리스트 구조로 저장되어있어 데이터를 재검색할 때 모든 노드를 다시 다 돌지 않아도 된다.&lt;/li&gt;
&lt;li&gt;리프 노드가 연결 리스트로 구성되어있어, &lt;b&gt;선형 탐색&lt;/b&gt;을 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값(데이터)은 리프 노드에 저장하고, 그 외의 노드는 데이터의 인덱스로 사용하니 값이 많아질수록 메모리 사용량이 높아질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 사용되는 DB 인덱스는 일반적으로 B+ 트리로 구현되어있다.&lt;br /&gt;그래서 B+ 트리의 특징, 장단점을 DB 인덱스가 비슷하게 가져갈 수 있다.&lt;br /&gt;예를 들면, 빠른 검색 지원, 데이터를 정렬된 상태로 유지하며 범위 탐색 지원 등&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mangkyu.tistory.com/96&quot;&gt;https://mangkyu.tistory.com/96&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://velog.io/@chanyoung1998/B%ED%8A%B8%EB%A6%AC&quot;&gt;https://velog.io/@chanyoung1998/B%ED%8A%B8%EB%A6%AC&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://nooblette.tistory.com/entry/B-%ED%8A%B8%EB%A6%ACB-Tree&quot;&gt;https://nooblette.tistory.com/entry/B-%ED%8A%B8%EB%A6%ACB-Tree&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://velog.io/@kyeun95/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-B-Tree%EB%9E%80&quot;&gt;https://velog.io/@kyeun95/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-B-Tree%EB%9E%80&lt;/a&gt;&lt;/p&gt;</description>
      <category>CS/DB</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/105</guid>
      <comments>https://soo-99.tistory.com/entry/index#entry105comment</comments>
      <pubDate>Mon, 8 Jan 2024 05:16:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] OAuth, JWT .. 적용기 (1)</title>
      <link>https://soo-99.tistory.com/entry/Spring-Security-OAuth-JWT-%EC%A0%81%EC%9A%A9%EA%B8%B0-1</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Security&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입, 로그인 기능과 같이 인증/인가 작업을 spring security, JWT로 처리해보기로 했다.&lt;br /&gt;이전부터 OAuth 방식으로 인증, 인가를 구현해보고 싶었는데 spring security를 사용하면 filter나 authenticationProvider, manager 같은 객체 생성, 연결이 어렵지 않을 것 같았다. 그래서 도입해보기로 함.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;spring security 흐름도&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;1251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cq97D/btsCX79BHjh/75RKo0H2j9nMYyQtv5ddc1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cq97D/btsCX79BHjh/75RKo0H2j9nMYyQtv5ddc1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cq97D/btsCX79BHjh/75RKo0H2j9nMYyQtv5ddc1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCq97D%2FbtsCX79BHjh%2F75RKo0H2j9nMYyQtv5ddc1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1535&quot; height=&quot;1251&quot; data-origin-width=&quot;1535&quot; data-origin-height=&quot;1251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 추가&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;User&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;User Entity&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@NoArgsConstructor
@Getter
@Entity
@Table(name = &quot;user&quot;)
@DynamicUpdate
/* 삭제된 데이터는 가져오지 않음 */
@Where(clause = &quot;deleted_at is NULL&quot;)
/* 삭제요청이 들어오면 실제로 데이터를 삭제하지 않고 삭제 시간을 기록한다. */
@SQLDelete(sql = &quot;update user set deleted_at = CURRENT_TIMESTAMP where user_id = ?&quot;)
public class User extends BaseTime {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = &quot;user_id&quot;, nullable = false)
  private Long userId;

  // 사용자 계정 ID
  @Column(name = &quot;username&quot;, nullable = false)
  private String username;

  // 사용자 비밀번호
  @Column(name = &quot;password&quot;, nullable = false)
  private String password;

  // 사용자 닉네임
  @Column(name = &quot;nickname&quot;, nullable = false)
  private String nickname;

  // 사용자의 권한
  @Column(name = &quot;roles&quot;, nullable = false, length = 30)
  private String roles;

  // roles(권한) 데이터가 여러개 있다면 ,로 이어져있어 분리 후 List 구현 객체로 반환
  public List&amp;lt;String&amp;gt; getRoleList() {
    if (this.roles != null &amp;amp;&amp;amp; this.roles.length() &amp;gt; 0) {
      return Arrays.asList(this.roles.split(&quot;,&quot;));
    }
    return new ArrayList&amp;lt;&amp;gt;();
  }

  @Builder
  public User(String username, String password, String nickname, String roles) {
    this.username = username;
    this.password = password;
    this.nickname = nickname;
    this.roles = roles;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PrincipalDetails&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring security에서 사용자 정보를 담은 객체&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Data
public class PrincipalDetails implements UserDetails {

  // 내가 정의한 User Entity
  private User user;

  public PrincipalDetails(User user) {
    this.user = user;
  }

  // 해당 사용자의 모든 권한 가져오기
  @Override
  public Collection&amp;lt;? extends GrantedAuthority&amp;gt; getAuthorities() {
    Collection&amp;lt;GrantedAuthority&amp;gt; authorities = new ArrayList&amp;lt;&amp;gt;();
    user.getRoleList().forEach(role -&amp;gt; {
      authorities.add(()  -&amp;gt; role);
    });;
    return authorities;
  }

  @Override
  public String getPassword() {
    return user.getPassword();
  }

  @Override
  public String getUsername() {
    return user.getUsername();
  }

  @Override
  public boolean isAccountNonExpired() {
    return true;
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @Override
  public boolean isEnabled() {
    return true;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PrincipalDetailsService&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자의 정보를 가져오기 위한 &lt;code&gt;UserDetailsService&lt;/code&gt;을 구현한 클래스로, &lt;code&gt;loadUserByUsername&lt;/code&gt;을 오버라이딩해서 spring security가 추후 &lt;code&gt;PrincipalDetails&lt;/code&gt;을 가져올 수 있도록 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Slf4j
@Service // 필수로 @Service 어노테이션을 달아야한다. 아니면 Spring security가 찾지 못한다.
public class PrincipalDetailsService implements UserDetailsService {

  private final UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    log.info(&quot;[PrincipalDetailsService] loadUserByUsername {}&quot;, username);
    Optional&amp;lt;User&amp;gt; user_optional = userRepository.findByUsername(username);

    // 유저를 찾지 못하면 에러를 던진다.
    if (user_optional.isEmpty()) throw new CustomException(ErrorCode.USER_NOT_FOUND);

    return new PrincipalDetails(user_optional.get());
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Config&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WebSecurityConfig&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring security 설정 파일&lt;br /&gt;&lt;s&gt;.. jwt filter apply 수정 필요&lt;/s&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;code&gt;CSRF&lt;/code&gt;(Cross Site Request Forgery Attack): 사이트 간 요청 위조, 라고 한다. 간단히 말하면 인증된 사용자가 특정 URI로 요청을 보내도록 만드는(유도하는) 공격&lt;br /&gt;e.g.) 이메일이나 웹 사이트에 링크를 걸어두고, 이를 클릭하면 특정 사이트에 제품 구입, 계정 설정, 기록 삭제, 비밀번호 변경, 문자 전송 등과 같은 요청을 보내도록 하는 것&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exceptionHandling()&lt;/code&gt;: 인증 인가 과정에서 에러가 날 떄 핸들링해주기 위한 것으로, 이어서 &lt;code&gt;authenticationEntryPoint&lt;/code&gt;나 &lt;code&gt;accessDeniedHandler&lt;/code&gt;를 설정해줄 수 있다.&lt;/li&gt;
&lt;li&gt;이 외에는 주석 참고&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

  private final JwtTokenProvider jwtTokenProvider;
  private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
  private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

  @Bean
  public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            .formLogin().disable()
            .exceptionHandling()
            .authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .accessDeniedHandler(jwtAccessDeniedHandler)

            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

            .and()
            .apply(new JwtSecurityConfig(jwtTokenProvider))

            .and()
            .authorizeHttpRequests()
            // /api/user/sign-up 이나 /api/auth/login 로 들어오는 요청은 인증 및 인가가 필요 없도록
            .requestMatchers(&quot;/api/user/sign-up&quot;).permitAll()
            .requestMatchers(&quot;/api/auth/login&quot;).permitAll()
            // 그 외로 들어오는 요청은 모두 인증이 필요하다.
            .anyRequest().authenticated();

    return http.build();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller 및 Service&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AuthController&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인과 같은 인증 관련 요청을 핸들링할 controller&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
@RequestMapping(&quot;/api/auth&quot;)
@Slf4j
public class AuthController {

  // 인증 관련 서비스 로직을 다루는 authService
  private final AuthService authService;

  /* Value 값 넣을 때는 static, final 안돼 */
  @Value(&quot;${jwt.AUTHORIZATION_HEADER}&quot;)
  private String AUTHORIZATION_HEADER;

  @Value(&quot;${jwt.REFRESH_HEADER}&quot;)
  private String REFRESH_HEADER;

  private static final String PREFIX = &quot;Bearer &quot;;

  @PostMapping(&quot;/login&quot;)
  public ResponseEntity&amp;lt;Boolean&amp;gt; login(HttpServletResponse response, @RequestBody LoginRequestDTO request) {
    log.info(&quot;로그인 시작&quot;);
    // authService.login()에서 토큰 발급 받기
    TokenDTO tokenDTO = authService.login(request);
    // Authorization: Bearer &amp;lt;access token&amp;gt; 형식으로 헤더에 토큰 넣어 응답하기
    response.setHeader(AUTHORIZATION_HEADER, PREFIX + tokenDTO.getAccessToken());
    log.info(AUTHORIZATION_HEADER + &quot;: &quot; + PREFIX + tokenDTO.getAccessToken());
    // Refresh: Bearer &amp;lt;refresh token&amp;gt; 형식으로 헤더에 토큰 넣어 응답하기
    response.setHeader(REFRESH_HEADER, PREFIX + tokenDTO.getAccessToken());
    log.info(String.valueOf(response));

    // 정상적으로 토큰 발급되었다는 의미에서 true 반환
    return ResponseEntity.status(HttpStatus.OK).body(true);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AuthService&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증, 인가 관련 서비스 로직을 담은 service&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 고치고 싶은건 AuthenticationManager를 여기서 buidler 로 생성하지 않고 securityConfig에서 생성한 예제를 본 것 같다. 그리고 UserDetailsService를 주입해줬던 것 같은데 .. 다시 보고 수정해야겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl implements AuthService {

  // 인증 절차 관리하는 AuthenticationManager를 생성하는 AuthenticationManagerBuilder 객체 생성.
  private final AuthenticationManagerBuilder authenticationManagerBuilder;

  private final JwtTokenProvider jwtTokenProvider;

  @Override
  public TokenDTO login(LoginRequestDTO request) {
    // 사용자의 username, password를 기반으로 Authentication의 구현체인 UsernamePasswordAuthenticationToken 객체를 만든다.
    UsernamePasswordAuthenticationToken authenticationToken =
            new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword());
    log.info(&quot;auth token: &quot; + authenticationToken.getName() + &quot; &quot; + authenticationToken.getCredentials());
    Authentication authentication = 
            authenticationManagerBuilder.getObject() // authenticationManager를 생성해
            .authenticate(authenticationToken); // authenticationToken에 대한 인증 작업을 수행한다.

    // 토큰 생성
    TokenDTO tokenDTO = jwtTokenProvider.generateToken(authentication);

    log.info(&quot;token DTO: {}&quot;, tokenDTO.toString());

    /*
    key-value 형식의 DB에 refresh token 저장해두는 코드 필요
     */

    return tokenDTO;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JwtTokenProvider&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 토큰 발급 및 검증하는 객체&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;@Component
@Slf4j
public class JwtTokenProvider implements InitializingBean {

  @Value(&quot;${jwt.SECRET}&quot;)
  private String secret;

  private static final String AUTHORITIES_KEY = &quot;auth&quot;;
  private static final String PREFIX = &quot;Bearer&quot;;
  private static long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24; // 1일
  private static long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 2; // 2일

  private Key key;

  @Override
  public void afterPropertiesSet() throws Exception {
    // JwtTokenProvider 빈이 생성될 떄 key값 인코딩해서 클래스 객체 key에 넣어주기
    byte[] encodedKey = Base64.getEncoder().encode(secret.getBytes());
    this.key = Keys.hmacShaKeyFor(encodedKey);
  }

  /* 토큰 생성 메서드 */
  public TokenDTO generateToken(Authentication authentication) {
  // 모든 권한 가져와 &quot;,&quot;로 이어 문자열로 변환
    String authorities = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(&quot;,&quot;));

    log.info(&quot;authentication: &quot; + authentication.getName());

    long now = new Date().getTime();

    // access token 생성
    String accessToken = Jwts.builder()
            // 인증에서 가져온 username을 claim sub에 넣어주고
            .setSubject(authentication.getName())
            // auth:권한 형식으로 claim에 추가
            .claim(AUTHORITIES_KEY, authorities)
            // 만료시각은 현재(now)로부터 1일이 지난 때
            .setExpiration(new Date(now + ACCESS_TOKEN_EXPIRE_TIME))
            // key라는 비밀키와 HS256 알고리즘을 사용해 서명
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();

    String refreshToken = Jwts.builder()
            // 만료시각은 현재(now)로부터 2일이 지난 때
            .setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
            // key라는 비밀키와 HS256 알고리즘을 사용해 서명
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();

    log.info(&quot;accessToken: &quot; + accessToken.toString());
    log.info(&quot;refreshToken: &quot; + refreshToken.toString());

    // accessToken과 refreshToken을 DTO 객체에 담아 반환
    return TokenDTO.builder()
            .accessToken(accessToken)
            .refreshToken(refreshToken)
            .build();
  }

  public Authentication getAuthentication(String accessToken) {
    // accessToken에서 claims 빼내기
    Claims claims = parseClaims(accessToken);

    // 만약 claims에 권한이 null이라면 예외 던지기
    if (claims.get(AUTHORITIES_KEY) == null) throw new CustomException(ErrorCode.INVALID_AUTH_TOKEN);

    // claims에서 sub(이전에 넣었던 username)과 권한 목록 가져와셔 User 객체 생성
    PrincipalDetails principal = new PrincipalDetails(
            User.builder()
                    .username(claims.getSubject())
                    .roles(claims.get(AUTHORITIES_KEY).toString())
                    .build()
    );

    // Authentication 구현체인 UsernamePasswordAuthenticationToken 객체 생성
    return new UsernamePasswordAuthenticationToken(principal, &quot;&quot;, principal.getAuthorities());
  }

  /* 유효한 토큰인지 확인 */
  public boolean validateToken(String token) {
    try {
      // key와 주어진 토큰을 검사한다.
      Jwts.parserBuilder()
              .setSigningKey(key)
              .build()
              .parseClaimsJws(token);
      log.info(&quot;유효한 토큰입니다.&quot;);
      // 유효한 토큰이라면 true 반환
      return true;
    } catch (SecurityException | MalformedJwtException e) {
      log.info(&quot;유효하지 않은 토큰입니다.&quot;);
    } catch (ExpiredJwtException e) {
      log.info(&quot;만료된 토큰입니다.&quot;);
    } catch (UnsupportedJwtException e) {
      log.info(&quot;지원되지 않는 토큰입니다.&quot;);
    } catch (IllegalArgumentException e) {
      log.info(&quot;잘못된 토큰입니다.&quot;);
    }
    return false;
  }

  // accessToken에서 claims 뽑아내기
  private Claims parseClaims(String accessToken) {
    try {
      return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
    } catch (ExpiredJwtException e) {
      return e.getClaims();
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JwtAuthenticationFilter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 를 검증하고 인증 정보 설정하는 역할의 필터&lt;br /&gt;한번의 요청에 대해 한번만 실행되어야 하므로 &lt;code&gt;OncePerRequestFilter&lt;/code&gt; 를 상속받아 구현&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

  private final JwtTokenProvider jwtTokenProvider;

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    // 토큰 중 우리가 만들었던 JWT 부분만 가져온다.
    String token = resolveToken(request);

    // token 내용이 있으며 유효한 토큰이라면
    if (token != null &amp;amp;&amp;amp; jwtTokenProvider.validateToken(token)) {
      // token을 기반으로 사용자 정보 객체(UsernamePasswordAuthenticationToken) 생성
      Authentication authentication = jwtTokenProvider.getAuthentication(token);
      // 그리고 인증된 정보는 context에 저장
      SecurityContextHolder.getContext().setAuthentication(authentication);

      log.info(&quot;set Authentication to security context for '{}', uri: {}&quot;, authentication.getName(), request.getRequestURI());
    }
    // 다음 단계 진행
    filterChain.doFilter(request, response);
  }

  /* 토큰 유효한지 확인 + 문자열 처리 */
  private String resolveToken(HttpServletRequest request) {
    // accessToken 가져와서
    String bearerToken = request.getHeader(&quot;Authorization&quot;);
    // bearerToken이 공백을 제외하고 하나 이상의 문자를 갖고있으며 Bearer 로 시작한다면
    if (StringUtils.hasText(bearerToken) &amp;amp;&amp;amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
      /* 앞 &quot;Bearer &quot; 뗸 토큰 내용을 반환 */
      return bearerToken.substring(7);
    }
    /* 예상과 다른 토큰이면 null 반환 */
    return null;
  }

  @Override
  public void destroy() {
    super.destroy();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JWT Error handler&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JwtAccessDeniedHandler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 권한이 존재하지 않는 경우에 403 FORBIDDEN 에러 리턴&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    response.sendError(HttpServletResponse.SC_FORBIDDEN);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JwtAuthenticationEntryPoint&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유요한 자격 증명을 제공하지 않고 접근하려할 때 401 Unauthorized 에러 리턴&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Web/Spring</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/104</guid>
      <comments>https://soo-99.tistory.com/entry/Spring-Security-OAuth-JWT-%EC%A0%81%EC%9A%A9%EA%B8%B0-1#entry104comment</comments>
      <pubDate>Fri, 5 Jan 2024 00:12:49 +0900</pubDate>
    </item>
    <item>
      <title>[TIL] 20231227</title>
      <link>https://soo-99.tistory.com/entry/TIL-20231227</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLTUDc/btsCDgkS82M/X1CpaoS8pbedMxrw5GKJX0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLTUDc/btsCDgkS82M/X1CpaoS8pbedMxrw5GKJX0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLTUDc/btsCDgkS82M/X1CpaoS8pbedMxrw5GKJX0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLTUDc%2FbtsCDgkS82M%2FX1CpaoS8pbedMxrw5GKJX0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;453&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 내용 및 정리할 거리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JWT Token 발급 및 spring security 조금 .. 적용&lt;/li&gt;
&lt;li&gt;spring security 관련 filter 내용&lt;/li&gt;
&lt;li&gt;csrf ?&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>TIL</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/103</guid>
      <comments>https://soo-99.tistory.com/entry/TIL-20231227#entry103comment</comments>
      <pubDate>Wed, 27 Dec 2023 01:49:45 +0900</pubDate>
    </item>
    <item>
      <title>[Exception Handler] Java, SpringBoot 에서의 예외 처리</title>
      <link>https://soo-99.tistory.com/entry/Exception-Handler-Java-SpringBoot-%EC%97%90%EC%84%9C%EC%9D%98-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://soo-99.tistory.com/entry/Exception-Error&quot;&gt;Exception? Error?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 Java에서 Exception을 &lt;code&gt;checked exception&lt;/code&gt;, &lt;code&gt;unchecked exception&lt;/code&gt;으로 나눌 수 있다고 했다.&lt;br /&gt;이번 글은 둘 중 &lt;code&gt;unchecked exception&lt;/code&gt;를 핸들링한 코드를 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 &lt;code&gt;checked exception&lt;/code&gt;은 컴파일 시점에 검증이 들어가기 때문에 명시적으로 예외 처리가 필요하다고 했다.&lt;br /&gt;하지만 &lt;code&gt;checked exception&lt;/code&gt;가 실사용 시점에도 나올 수 있기 때문에 &lt;code&gt;RuntimeException&lt;/code&gt;을 상속받아 &lt;code&gt;unchecked exception&lt;/code&gt;처럼 런타임 시에도 예외를 처리할 수 있도록 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ErrorCode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 나올 수 있는 에러 코드를 HttpStatus와 함께 넘길 메시지를 &lt;code&gt;ENUM&lt;/code&gt; 구조를 사용해 명시했다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public enum ErrorCode {

  /* code: 404 */
  USER_NOT_FOUND(NOT_FOUND, &quot;유저 정보가 존재하지 않습니다.&quot;),
  REVIEW_NOT_FOUND(NOT_FOUND, &quot;해당 리뷰가 존재하지 않습니다.&quot;),
  MOVIE_NOT_FOUND(NOT_FOUND, &quot;해당 영화가 존재하지 않습니다.&quot;);

  private final HttpStatus httpStatus;
  private final String detail;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ExceptionHandler&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외가 발생했을 때, 응답으로 보낼 데이터를 &lt;code&gt;handleCustomException&lt;/code&gt; 메서드에서 가공해 반환하도록 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@ExceptionHandler&lt;/code&gt;는 controller 단에서 발생한 예외를 해당 어노테이션이 달린 메서드에서 처리한다는 의미&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Controller
class Super {

    @ExceptionHandler(FileSystemException.class)
    public ResponseEntity&amp;lt;String&amp;gt; handle(Exception e) {
        ...
    }
}   &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FileSystemException&lt;/code&gt;, &lt;code&gt;Super&lt;/code&gt; class와 같이 &lt;b&gt;특정 예외에, 클래스&lt;/b&gt;에 대해서 처리할 수도 있다.&lt;br /&gt;그런데 이렇게 되면 &lt;span style=&quot;color: #000000;&quot;&gt;모든 클래스에 메서드를 만들게 되고 중복되는 코드도 많아진다.&lt;/span&gt;&lt;br /&gt;그래서 &lt;i&gt;&lt;b&gt;스프링에서 제공되는 전역적으로 에러 처리를 도와주는 기술 &lt;/b&gt;&lt;/i&gt;을 사용했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RestControllerAdvice / ControllerAdvice&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

  @ExceptionHandler(CustomException.class)
  public ResponseEntity&amp;lt;ErrorResponse&amp;gt; handleCustomException(CustomException e) {
    ErrorCode errorCode = e.getErrorCode();
    return ResponseEntity.status(HttpStatus.OK).body(
            ErrorResponse.builder()
                    .name(errorCode.name())
                    .status(errorCode.getHttpStatus().value())
                    .code(errorCode.getHttpStatus().name())
                    .msg(errorCode.getDetail())
                    .build()
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 에러 핸들러에 &lt;code&gt;@RestControllerAdvice&lt;/code&gt; 또는 &lt;code&gt;@ControllerAdvice&lt;/code&gt;를 명시하고, 그 안에 따로 &lt;code&gt;ErrorCode&lt;/code&gt;를 기반으로 에러 응답을 반환하도록 &lt;code&gt;CustomException&lt;/code&gt;을 매개변수로 전달했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CustomException&lt;/h3&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@AllArgsConstructor
@Getter
public class CustomException extends RuntimeException {

  private final ErrorCode errorCode;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;CustomException&lt;/code&gt;은 &lt;code&gt;RuntimeException&lt;/code&gt;을 상속받고 &lt;code&gt;ErrorCode&lt;/code&gt; 객체를 갖고있어, &lt;code&gt;throw new CustomException(USER_NOT_FOUND)&lt;/code&gt; 처럼 unchecked exception을 발생시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;code&gt;GlobalExceptionHandler&lt;/code&gt; 에서 응답 데이터 포맷은 &lt;code&gt;ErrorResponse&lt;/code&gt; 로 &lt;i&gt;에러 이름, 코드, 따로 정한 메시지&lt;/i&gt; 등 원하는 데이터만을 뽑아 보낼 수 있도록 한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Builder
@RequiredArgsConstructor
public class ErrorResponse {

  private final String name;
  private final int status;
  private final String code;
  private final String msg;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;br /&gt;&lt;a href=&quot;https://cheese10yun.github.io/checked-exception/&quot;&gt;https://cheese10yun.github.io/checked-exception/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703272952553&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;blog&quot; data-og-title=&quot;Checked Exception을 대하는 자세 - Yun Blog | 기술 블로그&quot; data-og-description=&quot;Checked Exception을 대하는 자세 - Yun Blog | 기술 블로그&quot; data-og-host=&quot;cheese10yun.github.io&quot; data-og-source-url=&quot;https://cheese10yun.github.io/checked-exception/&quot; data-og-url=&quot;https://cheese10yun.github.io/checked-exception/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/6OQFw/hyUTLFvniP/mFb1wVHgKDjJMXEX59oKRk/img.png?width=652&amp;amp;height=434&amp;amp;face=0_0_652_434,https://scrap.kakaocdn.net/dn/0q0NK/hyUPGsjmjo/HIcKYRbAEu1GBDLmXZFCkk/img.png?width=676&amp;amp;height=263&amp;amp;face=0_0_676_263,https://scrap.kakaocdn.net/dn/bSYpv1/hyUTwIlv1e/qygmabDCEv3k16XuQC1Kq1/img.png?width=618&amp;amp;height=233&amp;amp;face=0_0_618_233&quot;&gt;&lt;a href=&quot;https://cheese10yun.github.io/checked-exception/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cheese10yun.github.io/checked-exception/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/6OQFw/hyUTLFvniP/mFb1wVHgKDjJMXEX59oKRk/img.png?width=652&amp;amp;height=434&amp;amp;face=0_0_652_434,https://scrap.kakaocdn.net/dn/0q0NK/hyUPGsjmjo/HIcKYRbAEu1GBDLmXZFCkk/img.png?width=676&amp;amp;height=263&amp;amp;face=0_0_676_263,https://scrap.kakaocdn.net/dn/bSYpv1/hyUTwIlv1e/qygmabDCEv3k16XuQC1Kq1/img.png?width=618&amp;amp;height=233&amp;amp;face=0_0_618_233');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Checked Exception을 대하는 자세 - Yun Blog | 기술 블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Checked Exception을 대하는 자세 - Yun Blog | 기술 블로그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cheese10yun.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC4EMr/btsCzQyN1pN/saq3Av6Q4NcUVlTuzq5JE1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC4EMr/btsCzQyN1pN/saq3Av6Q4NcUVlTuzq5JE1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC4EMr/btsCzQyN1pN/saq3Av6Q4NcUVlTuzq5JE1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC4EMr%2FbtsCzQyN1pN%2Fsaq3Av6Q4NcUVlTuzq5JE1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;710&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Web/Spring</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/102</guid>
      <comments>https://soo-99.tistory.com/entry/Exception-Handler-Java-SpringBoot-%EC%97%90%EC%84%9C%EC%9D%98-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC#entry102comment</comments>
      <pubDate>Sat, 23 Dec 2023 04:22:49 +0900</pubDate>
    </item>
    <item>
      <title>Exception? Error?</title>
      <link>https://soo-99.tistory.com/entry/Exception-Error</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;오류(Error) 에러(Exception)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/codepark_kr/post/a70025be-d97d-4ba4-81de-bf9b8fe48d2b/ExceptionClassHierarchy.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 &lt;code&gt;Object&lt;/code&gt; - &lt;code&gt;Throwable&lt;/code&gt; 을 상속 받고,&lt;br /&gt;&lt;code&gt;Throwable&lt;/code&gt; 객체는 오류나 예외에 대한 메시지, 정보를 담고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Error&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시스템 수준&lt;/b&gt;에서 일어나는 &lt;b&gt;프로그램&lt;/b&gt; 자체에서 해결하기 어려운 오류&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Exception&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그램 수준&lt;/b&gt;에서 일어나고, &lt;b&gt;개발자나 사용자와 같이 사람에&lt;/b&gt; 의해 나타나는 오류&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런 Exception에 대해서는 애플리케이션 상에서 잡을 수 있다.&lt;br /&gt;이것도 &lt;code&gt;Checked Exception&lt;/code&gt;, &lt;code&gt;Unchecked Exception&lt;/code&gt; 둘로 나뉘는데&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;Checked Exception&lt;/th&gt;
&lt;th&gt;Unchecked Exception&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;예외 처리&lt;/td&gt;
&lt;td&gt;필수&lt;/td&gt;
&lt;td&gt;선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검증&lt;/td&gt;
&lt;td&gt;컴파일 시점&lt;/td&gt;
&lt;td&gt;런타임 시점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;IOException - FileNotFoundException ...&lt;/td&gt;
&lt;td&gt;RuntimeException - IndexOutOfBoundsException, IllegalArgumentException ...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 집고 넘어가고 싶은 것은 &lt;b&gt;트랜잭션&lt;/b&gt;에 관한 것&lt;br /&gt;대부분 블로그에서&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;checked - 트랜잭션 불가능(또는 하지 않아도 됨)
unchecked - 트랜잭션 가능(또는 함)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 설명되어 있는데 이 정보는 비약이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 spring의 트랜잭션 부분 문서 상에&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEdDFy/btsCy6B0trI/6FQZkEQt9MJj9MIgqUN690/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEdDFy/btsCy6B0trI/6FQZkEQt9MJj9MIgqUN690/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEdDFy/btsCy6B0trI/6FQZkEQt9MJj9MIgqUN690/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEdDFy%2FbtsCy6B0trI%2F6FQZkEQt9MJj9MIgqUN690%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;2056&quot; height=&quot;158&quot; data-origin-width=&quot;2056&quot; data-origin-height=&quot;158&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;2074&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3f5rH/btsCyx7IPaZ/XZpzPy7J8U3nBHwtVMCSlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3f5rH/btsCyx7IPaZ/XZpzPy7J8U3nBHwtVMCSlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3f5rH/btsCyx7IPaZ/XZpzPy7J8U3nBHwtVMCSlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3f5rH%2FbtsCyx7IPaZ%2FXZpzPy7J8U3nBHwtVMCSlk%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;2074&quot; height=&quot;122&quot; data-origin-width=&quot;2074&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;기본적으로 RuntimeException(Unchecked), Error는 롤백 옵션이 내제되어있다.
다만, Checked Exception 같은 경우는 기본적으로 롤백이 되지 않는다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분 때문에 다들 오인하는 것 같다고 .. &lt;a href=&quot;https://www.youtube.com/watch?v=_WkMhytqoCc&quot;&gt;출처: 백기선님 영상&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로는 &lt;b&gt;&lt;i&gt;기본값이 저럴 수는 있어도 &lt;code&gt;@Transaction&lt;/code&gt; 상 옵션으로 &lt;code&gt;rollbackFor&lt;/code&gt;와 같은 롤백에 관한 옵션을 줄 수 있다. 따라서, checked exception도 롤백이 가능하고, unchecked도 필요가 없다면 롤백 하지 않을 수 있다.&lt;/i&gt;&lt;/b&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;a href=&quot;https://min103ju.github.io/java/java_exception_rollback/&quot;&gt;https://min103ju.github.io/java/java_exception_rollback/&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://choiblack.tistory.com/39&quot;&gt;https://choiblack.tistory.com/39&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://bcp0109.tistory.com/303&quot;&gt;https://bcp0109.tistory.com/303&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <author>기넌</author>
      <guid isPermaLink="true">https://soo-99.tistory.com/101</guid>
      <comments>https://soo-99.tistory.com/entry/Exception-Error#entry101comment</comments>
      <pubDate>Sat, 23 Dec 2023 02:00:35 +0900</pubDate>
    </item>
  </channel>
</rss>