<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>왜 그렇게 생각했는가?</title>
    <link>https://chanheess.tistory.com/</link>
    <description>'왜' 그렇게 했는가?에 대한 생각으로 공부 및 작업의 저장관리</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 19:35:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>chanheess</managingEditor>
    <image>
      <title>왜 그렇게 생각했는가?</title>
      <url>https://tistory1.daumcdn.net/tistory/4068511/attach/01ed793d10874c7aabb0271995c5d562</url>
      <link>https://chanheess.tistory.com</link>
    </image>
    <item>
      <title>Docker Compose에서 k3s 기반 MSA로, 전환하기까지</title>
      <link>https://chanheess.tistory.com/284</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;390&quot; data-start=&quot;340&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;k3s 기반 MSA 구조로 전환하기까지&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 프로젝트를 진행하면서 모놀리식 구조의 한계를 체감하게 된 과정과, 이를 해결하기 위해 k3s 기반 MSA 구조로 전환한 경험을 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 빠른 개발과 단순한 운영을 위해 &lt;b&gt;모놀리식 구조&lt;/b&gt;를 선택했습니다. 하지만 서비스가 커지고 요구사항이 늘어나면서, 특정 기능의 장애가 전체 서비스로 확산되거나 작은 변경에도 전체 배포가 필요한 문제를 겪게 되었습니다. 또한 일부 기능에 집중된 부하가 애플리케이션 전체에 영향을 주는 상황도 확인할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 단순히 &amp;ldquo;MSA를 도입했다&amp;rdquo;는 결과보다, 어떤 문제를 겪었고 왜 기존 구조로는 해결이 어려웠는지, 그리고 그 과정에서 아키텍처를 어떤 기준으로 다시 설계했는지에 초점을 맞춰 이야기해 보겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모놀리식 방식에서의 고충&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 서비스는 빠른 개발을 위해 &lt;b&gt;Docker Compose 기반 모놀리식 구조&lt;/b&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;배포 단위가 너무 큼에 따른 영향도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식 구조에서는 배포 단위가 애플리케이션 전체이기 때문에, 일부 기능만 수정했더라도 전체를 다시 빌드하고 배포해야 했습니다. 그만큼 하나의 작은 오류도 특정 기능에만 그치지 않고 전체 배포 실패로 이어질 수 있었고, 실제로 프로젝트 진행 중에도 한 모듈의 문제로 인해 정상 배포에 실패하면서 로컬 환경으로 시연을 대체해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 모든 기능이 하나의 실행 단위에 강하게 결합되어 있다 보니, 변경의 영향 범위를 빠르게 파악하기 어려웠고 배포에 대한 심리적 부담도 클 수밖에 없었습니다. 특히 사소한 수정조차 전체 서비스 안정성과 연결되었기 때문에, 배포는 점점 더 신중하고 무거운 작업이 되었고, 결국 서비스별로 배포와 장애 영향을 분리할 수 있는 구조의 필요성을 크게 느끼게 되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장애 전파 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하 테스트 과정에서 주문 생성 API에 &lt;b&gt;RPS 200 수준의 요청&lt;/b&gt;을 인가하자, CPU 1 Core로 제한된 환경에서 서버 전체가 다운되는 상황이 발생했습니다. 주문&amp;nbsp;기능에&amp;nbsp;순간적으로&amp;nbsp;부하가&amp;nbsp;집중되었지만,&amp;nbsp;영향은&amp;nbsp;해당&amp;nbsp;API에만&amp;nbsp;머무르지&amp;nbsp;않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lCJXG/dJMcadVycKt/EA8ncSvf0okPvcioSWC5W0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lCJXG/dJMcadVycKt/EA8ncSvf0okPvcioSWC5W0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lCJXG/dJMcadVycKt/EA8ncSvf0okPvcioSWC5W0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlCJXG%2FdJMcadVycKt%2FEA8ncSvf0okPvcioSWC5W0%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;512&quot; height=&quot;164&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;164&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 style=&quot;list-style-type: disc; color: #595959;&quot;&gt;주문 생성 API에 RPS 200 부하를 인가했을 때, CPU 1 Core로 제한된 환경에서 전체 서버가 일정 기간 다운&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식 구조에서는 모든 기능이 하나의 애플리케이션 프로세스 안에서 함께 동작하기 때문에, 특정 모듈이 과도하게 CPU와 메모리를 점유하면 다른 기능도 동일한 자원을 공유한 채 영향을 받을 수밖에 없습니다. 결국 일부 기능의 과부하가 전체 서비스의 응답 지연과 장애로 확산되는 구조적 한계를 직접 확인하게 되었습니다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;왜 MSA를 선택했는가?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제들을&amp;nbsp;해결하기&amp;nbsp;위해&amp;nbsp;모놀리식을&amp;nbsp;유지하며&amp;nbsp;개선하는&amp;nbsp;방향과&amp;nbsp;MSA로&amp;nbsp;전환하는&amp;nbsp;방향,&amp;nbsp;두&amp;nbsp;가지를&amp;nbsp;검토했습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;모놀리식 구조 내에서의 개선 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배포&amp;nbsp;문제&lt;/b&gt;의&amp;nbsp;경우,&amp;nbsp;Feature&amp;nbsp;Flag나&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;/p&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;장애&amp;nbsp;전파&amp;nbsp;문제&lt;/b&gt;의&amp;nbsp;경우,&amp;nbsp;Circuit&amp;nbsp;Breaker나&amp;nbsp;Rate&amp;nbsp;Limiting으로&amp;nbsp;특정&amp;nbsp;기능의&amp;nbsp;부하를&amp;nbsp;일부&amp;nbsp;차단할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;하지만&amp;nbsp;단일&amp;nbsp;프로세스&amp;nbsp;안에서&amp;nbsp;CPU와&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MSA 전환을 선택한 이유&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;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;MSA로 전환하면 각 서비스가 독립된 배포 단위와 실행 환경을 갖게 됩니다. 특정 서비스의 장애나 부하가 다른 서비스로 번지지 않고, 변경이 발생한 서비스만 독립적으로 빌드하고 배포할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이는 앞서 경험한 두 문제를 구조적으로 해결할 수 있는 방향이었고, 결국 MSA 전환을 결정하게 되었습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도입 과정에서의 고충&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA 전환을 결정한 후, 오케스트레이션 플랫폼으로&amp;nbsp;&lt;b&gt;k3s&lt;/b&gt;를 선택했습니다. 경량 쿠버네티스인 k3s는 온프레미스 환경에서도 무리 없이 운영할 수 있고, 쿠버네티스의 핵심 기능을 그대로 활용할 수 있다는 점에서 적합한 선택이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Docker Compose 기반의 모놀리식 구조를 서비스 단위로 분리하고, 각 서비스를 독립적인 Pod로 운영할 수 있도록 전환 작업을 진행했습니다. 인프라 구성 자체는 계획대로 마무리되었습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;예상치 못한 팀 전환 비용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 그 다음이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀원들에게도 k3s로의 전환을 요청했지만, &lt;b&gt;기존 환경과의 설정 차이&lt;/b&gt;로 인해 예상보다 많은 시간이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;하나는&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;OS 환경의 차이&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;입니다. k3s는 Linux 기반으로 동작하기 때문에, Windows를 사용하는 팀원에 대한 고려가 필요했습니다.&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;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;다른 하나는&amp;nbsp;&lt;/span&gt;&lt;b&gt;환경 변수 설정의 불일치&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;입니다.&amp;nbsp;&lt;/span&gt;Docker Compose 환경에서는&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;mysql,&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;redis,&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;elasticsearch처럼 컨테이너 이름을 호스트로 사용하지만, k3s 환경에서는&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;modeunsa-infra-mysql,&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;modeunsa-infra-redis처럼 클러스터 내부 서비스 이름을 사용해야 합니다. 이 차이를 인지하지 못한 채 기존 설정을 그대로 사용하면 연결 자체가 실패했고, 원인을 추적하는 과정에서 반복적으로 시간이 낭비되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;환경을 분리하고, 절차를 문서로 남기다&lt;/h3&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;병행했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 환경별 설정 분리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 Docker Compose 개발 환경은 application-dev.yml + .env.dev, 로컬 k3s 환경은 application-k3s-dev.yml + .env.k3s-dev를 사용하도록 나누고, 실행 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;dev&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;prod를 인자로 넘기면 해당 환경의&lt;span&gt;&amp;nbsp;&lt;/span&gt;.env&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일이 자동으로 로드되도록 구성했습니다.&lt;/p&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;get_env_file() {
  local env=&quot;${1:-dev}&quot;
  case &quot;$env&quot; in
    dev|prod)
      echo &quot;$ROOT_DIR/.env.k3s-$env&quot;
      ;;
  esac
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 환경마다 설정 파일을 직접 찾아 수정할 필요 없이, 인자 하나로 실행 환경이 결정되는 구조입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;스크립트로 실행 흐름 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라와 애플리케이션 실행 흐름은 infra.sh와 app.sh로 분리했습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #14181f;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# 인프라 관리
./k8s/infra.sh up [dev|prod]     # 인프라 시작
./k8s/infra.sh down              # 중지 (데이터 유지)
./k8s/infra.sh clean             # 중지 + 데이터 삭제

# 애플리케이션 배포
./k8s/app.sh up [dev|prod]                      # 전체 배포
./k8s/app.sh rollout [dev|prod] [module]         # 특정 모듈만 롤링 업데이트
./k8s/app.sh logs [module]                       # 로그 확인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;프라는 한 번 띄우면 유지되지만, 애플리케이션은 서비스별로 독립 재배포가 가능해야 했기 때문에 두 스크립트를 명확히 구분했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;운영 명령어 공유&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px; font-size: 16px;&quot;&gt;k3s 환경에서 실제로 자주 맞닥뜨리는 상황들을 명령어 모음으로 정리해 팀에 공유했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;특히 rollout restart처럼 쿠버네티스 입문자가 헤매기 쉬운 부분은&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&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-filename=&quot;blob&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;791&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnnec9/dJMcaaSmAuJ/cKYQcMeNZfzYd5sn68YxJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnnec9/dJMcaaSmAuJ/cKYQcMeNZfzYd5sn68YxJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnnec9/dJMcaaSmAuJ/cKYQcMeNZfzYd5sn68YxJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnnec9%2FdJMcaaSmAuJ%2FcKYQcMeNZfzYd5sn68YxJK%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;1111&quot; height=&quot;791&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;791&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;전환 후 달라진 것들&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k3s&amp;nbsp;기반&amp;nbsp;MSA&amp;nbsp;구조로&amp;nbsp;전환한&amp;nbsp;이후,&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;&lt;b&gt;배포&amp;nbsp;측면&lt;/b&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;&lt;/p&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;장애&amp;nbsp;격리&amp;nbsp;측면&lt;/b&gt;에서는 각 서비스가 독립된 Pod로 분리되어 실행되기 때문에, 특정 서비스에 부하가 집중되더라도 다른 서비스는 영향을 받지 않습니다. 더 나아가 부하가 집중되는 서비스만 독립적으로 &lt;b&gt;수평 확장 &lt;/b&gt;할 수 있게 되었습니다. 모놀리식에서는 불가능했던 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 영상은 k6로 &amp;nbsp;Product 서비스에 부하를 인가했을 때, 해당 Pod만 수평적으로 확장되는 과정을 실제로 확인한 것입니다.&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=X8-7nxdBD5s&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/LKk1l/dJMb9frFSLD/krG9NnP2KDhqD5lxWbxgA1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/54vJ2/dJMb9efeotK/ZK5y6wx5svDBjw0Z2kGJT0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/pQ54o/dJMb8UHPze0/iYAT8oARkAtnk42tCa5mZk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;k6 부하테스트&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/X8-7nxdBD5s&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;모놀리식 구조였다면 동일한 부하가 전체 서버 다운으로 이어졌겠지만, MSA 전환 이후에는 Product Pod만 확장되고 나머지 서비스는 영향 없이 정상 동작했습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MSA 전환의 트레이드오프&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전환 이후 긍정적인 변화가 있었던 만큼, 새롭게 생겨난 부담도 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;운영 복잡도 증가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식에서는 하나의 애플리케이션을 관리하면 됐지만, 서비스를 분리하면서 관리해야 할 Helm 차트와 설정 파일이 서비스 수만큼 늘어났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 서버 사양이 높지 않은 환경이었기 때문에, 각 서비스의 메모리 한도를 빡빡하게 설정해야 했고 OOM이 발생할 때마다 해당 서비스의 JVM 옵션과 리소스 설정을 직접 수정하고 재배포하는 작업이 반복되었습니다. Docker Compose 환경에서는 리소스 관리를 크게 신경 쓰지 않아도 됐던 부분이, k3s에서는 서비스별로 하나씩 직접 튜닝해야 하는 작업으로 바뀐 셈이었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로컬 개발 환경의 리소스 부담&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k3s 환경은 여러 서비스를 동시에 Pod로 띄워야 하기 때문에, 로컬 머신의 리소스 요구 사항이 높아졌습니다. 실제로 일부 팀원은 사양 문제로 k3s 환경을 로컬에서 구동하지 못하는 상황이 발생했습니다.&lt;/p&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;숙제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그럼에도 이번 전환을 통해 장애가 전체로 번지지 않는 구조, 변경이 발생한 서비스만 독립적으로 배포할 수 있는 환경을 직접 만들고 운영해본 경험은 다음 아키텍처 결정을 내릴 때 도움이 될 것으로 생각합니다.&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/284</guid>
      <comments>https://chanheess.tistory.com/284#entry284comment</comments>
      <pubDate>Fri, 3 Apr 2026 15:04:58 +0900</pubDate>
    </item>
    <item>
      <title>정말 HTTPS에 대해서 알고 있는가?</title>
      <link>https://chanheess.tistory.com/283</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS는 왜 이런 구조로 만들어졌을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS의 구조를 제대로 이해하려면, &lt;span&gt;각 &lt;/span&gt;&lt;b&gt;보안 요소들이 왜 필요하게 되었는지&lt;/b&gt;&lt;span&gt;를 먼저 이해해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 HTTP의 한계에서 시작해, HTTPS가 지금의 형태로 발전한 과정의 &lt;b&gt;흐름&lt;/b&gt;을 순서대로 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우선 HTTP는 웹에서 데이터를 주고받는 통신 규약이고, HTTPS는 여기에 보안을 강화하기 위해 TLS(SSL) 암호화를 추가한 프로토콜입니다. SSL 인증서를 통해...&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번에 HTTPS의 통신 구조를 이해하기란 어렵습니다. 여러개의 구조들을 파악하고 어떤 것들이 진정으로 보완이 되어서 완성이 되었는지 이해가 되어야 비로소 HTTPS 구조를 이해할 수 있습니다.&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;HTTP&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 HTTP부터 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 웹에서 데이터를 주고받는 기본 통신 규약입니다. 하지만 데이터를 &lt;span&gt;&lt;b&gt;평문(암호화되지 않은 형태)&lt;/b&gt;&lt;/span&gt;으로 주고받기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 제3자가 데이터를 &lt;span&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN7Brh/dJMcaj8lbKt/0BycrFMVICYYBafzBCms2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN7Brh/dJMcaj8lbKt/0BycrFMVICYYBafzBCms2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN7Brh/dJMcaj8lbKt/0BycrFMVICYYBafzBCms2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN7Brh%2FdJMcaj8lbKt%2F0BycrFMVICYYBafzBCms2k%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;500&quot; height=&quot;333&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안을 추가해주기 위해 먼저 대칭키 방식으로 시도해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭키 방식은 &lt;b&gt;하나의 키&lt;/b&gt;를 통해서 클라이언트와 서버간에 데이터를 암호화하고 복호화하는 방식으로 통신을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 해당 키는 어떻게 안전하게 전달해줄 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 대칭키를 네트워크를 통해 그냥 평문으로 전달한다면, 중간에서 해당 키가 탈취되어 제 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;그렇게 보완해주기 위해서 HTTPS는 비대칭키 방식을 추가적으로 사용하게 됩니다.&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;b&gt;세션키&lt;/b&gt;를 암호화해서 전달합니다. 해당 세션키는 서버의 개인키로만 복호화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 세션키를 안전하게 전달 받아서 대칭키 방식과 동일하게 해당 세션키(대칭키)를 가지고 데이터를 암호화, 복호화하여 안전하게 데이터를 주고받습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;ldquo;왜 비대칭만 쓰지 않고 대칭도 같이 쓰는가?&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비대칭키는 보안성은 높지만 연산 속도가 느립니다. 반면 대칭키는 빠르지만 키 교환이 취약합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 HTTPS는 비대칭키로 세션키를 안전하게 교환하고, 실제 데이터 전송은 대칭키로 처리하는 하이브리드 구조를 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL 인증서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만. 해당 공개키를 만약에 제3자가 줘서 서버인척을 한다면? 클라이언트는 데이터를 무방비하게 주게됩니다. 그렇게 SSL 인증서가 나오게 됩니다. SSL 인증서를 통해 공개키가 신뢰할 수 있는 기관(CA)에 의해 발급되었음을 검증하고, &lt;span style=&quot;color: #0e0e0e; text-align: start;&quot;&gt;해당 서버가 실제로 인증서의 도메인과 일치하는지 확인합니다. &lt;/span&gt;최종적으로 신뢰하는 서버에서 키를 주고 받고 데이터를 안전하게 주고 받을 수 있게 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTPS - (RSA 기반 기준)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 HTTPS는 아래와 같은 흐름이 완성됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트-&amp;gt;서버: ClientHello 전송&lt;/li&gt;
&lt;li&gt;서버-&amp;gt;클라이언트: SSL 인증서(공개키 포함) 전달&lt;/li&gt;
&lt;li&gt;클라이언트-&amp;gt;서버: 클라이언트가 인증서를 검증하고 세션키 생성하고, 서버 공개키로 세션키를 암호화 후 전달&lt;/li&gt;
&lt;li&gt;서버는 개인키로 복호화하여 같은 세션키 확보&lt;/li&gt;
&lt;li&gt;이후 모든 데이터는 세션키(대칭키)로 암호화되어 안전하게 통신&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/283</guid>
      <comments>https://chanheess.tistory.com/283#entry283comment</comments>
      <pubDate>Tue, 28 Oct 2025 18:18:31 +0900</pubDate>
    </item>
    <item>
      <title>JPA에서 ID는 언제 만들어지고, 언제 DB에 저장될까?</title>
      <link>https://chanheess.tistory.com/282</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JPA를 사용하다 보면 이런 의문이 생깁니다.&lt;/p&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;&amp;ldquo;&lt;span&gt;save()&lt;/span&gt; 직후 ID가 생기는데, DB에 저장 된건가?&amp;rdquo;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문의 답은 &lt;span&gt;&lt;b&gt;트랜잭션의 동작 흐름&lt;/b&gt;&lt;/span&gt; 안에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;persist()&lt;span&gt;, &lt;/span&gt;flush()&lt;span&gt;, &lt;/span&gt;commit()&lt;span&gt;은 각각 &lt;/span&gt;&amp;ldquo;메모리에 저장 -&amp;gt; SQL 실행 -&amp;gt; 트랜잭션 확정&amp;rdquo; 단계를 의미하며, 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;이 글에서는 트랜잭션 내부에서 &lt;b&gt;ID가 생성되고, DB에 반영되고, 다른 트랜잭션에서 보이게 되는 전 과정&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; 비영속 상태&lt;/h3&gt;
&lt;pre id=&quot;code_1761274702018&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ScheduleEntity schedule = new ScheduleEntity();
System.out.println(schedule.getId()); // 대부분 null (직접 세팅 안 했다면)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 &lt;span&gt;new&lt;/span&gt; 한 상태는 JPA가 아직 관여하지 않은 &lt;span&gt;&lt;b&gt;비영속(Transient)&lt;/b&gt;&lt;/span&gt; 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, &lt;/span&gt;&lt;b&gt;Hibernate가 관리하지 않기 때문에 ID가 세팅되지 않습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID나 @GeneratedValue(strategy = GenerationType.UUID)라면 생성자에서 바로 값이 들어올 수 있지만, &lt;span&gt;일반적으로 &lt;/span&gt;AUTO_INCREMENT&lt;span&gt;, &lt;/span&gt;IDENTITY&lt;span&gt;, &lt;/span&gt;SEQUENCE&lt;span&gt; 전략이라면 &lt;/span&gt;&lt;span&gt;&lt;b&gt;이 시점에는 ID = null&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. &lt;/span&gt;save 호출 시점 (영속 상태)&lt;/h3&gt;
&lt;pre id=&quot;code_1761274809778&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;scheduleRepository.save(schedule);&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;이때 비로소 Hibernate가 엔티티를 &lt;span&gt;&lt;b&gt;영속성 컨텍스트에 등록&lt;/b&gt;&lt;/span&gt;하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@GeneratedValue(strategy = &amp;hellip;)&lt;span&gt; 전략에 따라 &lt;/span&gt;&lt;span&gt;&lt;b&gt;ID를 생성하거나 조회&lt;/b&gt;&lt;/span&gt;&lt;span&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.4419%;&quot;&gt;전략&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;ID 생성 타이밍&lt;/td&gt;
&lt;td style=&quot;width: 66.6279%;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.4419%;&quot;&gt;&lt;span&gt;&lt;b&gt;IDENTITY&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;span&gt;&lt;span&gt;INSERT&lt;/span&gt; SQL 실행 시&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.6279%;&quot;&gt;&lt;span&gt;Hibernate가 즉시 &lt;span&gt;INSERT&lt;/span&gt;를 날리고 DB의 AUTO_INCREMENT 결과를 &lt;span&gt;getGeneratedKeys()&lt;/span&gt;로 받아옴&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.4419%;&quot;&gt;&lt;span&gt;&lt;b&gt;SEQUENCE&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;span&gt;save()&lt;span&gt; 직후&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.6279%;&quot;&gt;&lt;span&gt;DB에서 &lt;span&gt;nextval&lt;/span&gt;을 미리 조회하여 ID를 세팅 (&lt;span&gt;INSERT&lt;/span&gt;는 flush 때 실행)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.4419%;&quot;&gt;&lt;span&gt;&lt;b&gt;TABLE&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;span&gt;save()&lt;span&gt; 직후&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.6279%;&quot;&gt;&lt;span&gt;ID 테이블을 조회해 새 번호를 가져와 세팅 (&lt;span&gt;INSERT&lt;/span&gt;는 flush 때 실행)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 12.4419%;&quot;&gt;&lt;span&gt;&lt;b&gt;UUID&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.814%;&quot;&gt;&lt;span&gt;&lt;span&gt;new&lt;/span&gt; 시점 또는 엔티티 생성자 내부&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 66.6279%;&quot;&gt;&lt;span&gt;save 전에도 이미 존재&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. save()&lt;span&gt;&amp;nbsp;&lt;/span&gt;이후의 상태&lt;/h3&gt;
&lt;pre id=&quot;code_1761275219292&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.out.println(schedule.getId()); // 이제는 값이 있음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;save()&lt;/span&gt; 시점에 이미 Hibernate가 ID를 생성하거나 조회하므로 &lt;b&gt;이제부터 ID는 항상 존재합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다만, 아직 &lt;/span&gt;&lt;b&gt;flush()&lt;/b&gt;&lt;b&gt; 전이라 SQL이 DB에 반영되었을 수도 있고 아닐 수도&lt;/b&gt;&lt;span&gt; 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;span&gt;IDENTITY&lt;/span&gt;면 즉시 반영, &lt;span&gt;SEQUENCE&lt;/span&gt;면 flush 때 반영)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이렇게 id는 생성되는 방식을 알았는데 해당 id를 어느 영역까지 사용할 수 있을까?&lt;/h3&gt;
&lt;pre id=&quot;code_1761276023394&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class IdLifecycleExampleService {

    private final EntityManager em;

    @Transactional
    public void idLifecycleExample() {

        // 1️⃣ new &amp;mdash; 비영속 상태
        ScheduleRepeatEntity repeat = new ScheduleRepeatEntity();
        repeat.setTitle(&quot;before save&quot;);
        System.out.println(&quot;new 이후 &amp;rarr; id = &quot; + repeat.getId()); // ❌ null

        // 2️⃣ persist/save &amp;mdash; 영속성 컨텍스트 등록
        em.persist(repeat);
        System.out.println(&quot;persist 이후 &amp;rarr; id = &quot; + repeat.getId()); // ✅ 존재 (IDENTITY면 즉시, SEQUENCE면 미리 할당)
        // 하지만 아직 flush 전이므로 DB에는 반영되지 않았을 수도 있음

        // 3️⃣ flush &amp;mdash; SQL 실제 전송(DB에 반영)
        em.flush(); // 이 시점에 INSERT SQL 발생
        System.out.println(&quot;flush 이후 &amp;rarr; id = &quot; + repeat.getId()); // ✅ 동일한 id (변화 없음)

        // DB 직접 확인
        Long count = em.createQuery(&quot;SELECT COUNT(r) FROM ScheduleRepeatEntity r&quot;, Long.class).getSingleResult();
        System.out.println(&quot;flush 이후 DB 조회 count = &quot; + count); // ✅ 1 (DB에 반영됨)

        // 4️⃣ commit &amp;mdash; 트랜잭션 종료 후
        // commit은 @Transactional이 끝날 때 자동으로 발생하므로,
        // 여기서 별도 출력은 로그 상에서 확인 가능
    }
}&lt;/code&gt;&lt;/pre&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;b&gt;시점에 따른 id의 존재 여부&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 120px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;시점&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;상태&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;ID 존재 여부&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;DB 반영 여부&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;&lt;span&gt;JDBC 접근 가능 여부&lt;/span&gt;&lt;/span&gt;&lt;span&gt;/ FK 검증&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;new&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;비영속 상태&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;❌ 없음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;persist/save&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;영속 상태 (1차 캐시)&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ 생성됨&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;❌ (flush 전)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;❌&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;flush&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;DB 반영됨 (트랜잭션 미커밋)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ 그대로 유지&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ INSERT 반영&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ 가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;commit&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;DB 확정 (영구 적용)&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ 유지&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ 영구 반영&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 25px;&quot;&gt;&lt;span&gt;✅ 가능 (다른 트랜잭션에서도 조회 가능)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JPA -&amp;gt; DB 전체 데이터 저장 경로&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 243px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 20px;&quot;&gt;단계&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 20px;&quot;&gt;논리적 영역&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 20px;&quot;&gt;물리적 저장 위치&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%; height: 20px;&quot;&gt;주요 역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 45px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 45px;&quot;&gt;&lt;span&gt;persist() 이후&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 45px;&quot;&gt;&lt;span&gt;영속성 컨텍스트 &lt;br /&gt;(1차 캐시 포함)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 45px;&quot;&gt;&lt;span&gt;Spring JVM 힙&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;메모리&lt;/span&gt;&lt;span&gt; (RAM)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%; height: 45px;&quot;&gt;&lt;span&gt;엔티티 객체를 HashMap(&lt;span&gt;EntityKey &amp;rarr; Entity&lt;/span&gt;) 형태로 관리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 43px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 43px;&quot;&gt;&lt;span&gt;flush() 이후&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 43px;&quot;&gt;&lt;span&gt;DB 버퍼 풀 &lt;br /&gt;(InnoDB Buffer Pool)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 43px;&quot;&gt;&lt;span&gt;DB 서버의 메인 메모리 (RAM)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%; height: 43px;&quot;&gt;&lt;span&gt;SQL 실행 결과가 메모리 버퍼에 반영 (아직 디스크 기록 X)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 45px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 45px;&quot;&gt;&lt;span&gt;commit() 시점&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 45px;&quot;&gt;&lt;span&gt;Redo / Undo Log &lt;br /&gt;(트랜잭션 로그 영역)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 45px;&quot;&gt;&lt;span&gt;DB 서버 디스크 (스토리지의 로그 파일 영역)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%; height: 45px;&quot;&gt;&lt;span&gt;트랜잭션 커밋 시 Redo Log를 디스크로 flush(&lt;span&gt;fsync&lt;/span&gt;)하여 복구 가능 상태로 만듦&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 45px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 45px;&quot;&gt;&lt;span&gt;Checkpoint / Lazy Write&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 45px;&quot;&gt;&lt;span&gt;데이터 파일 &lt;br /&gt;(물리적 테이블 저장소)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 45px;&quot;&gt;&lt;span&gt;DB 서버 디스크 (HDD / SSD의 .ibd / .frm 파일)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%; height: 45px;&quot;&gt;&lt;span&gt;Buffer Pool의 Dirty Page가 디스크 파일로 기록됨&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 45px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 45px;&quot;&gt;&lt;span&gt;이후 (다른 트랜잭션 접근 가능)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 17.2093%; height: 45px;&quot;&gt;&lt;span&gt;커밋된 데이터 가시화&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 21.7442%; height: 45px;&quot;&gt;&lt;span&gt;DB 디스크 +&lt;/span&gt;&lt;span&gt; OS&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt; 캐시 메모리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 30.4651%; height: 45px;&quot;&gt;&lt;span&gt;모든 트랜잭션에서 읽을 수 있는 상태&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;persist와 save의 차이&lt;/b&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;상황&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;span&gt;persist&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;save&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;이미 DB에 존재하는 엔티티에 호출 시&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;span&gt;❌ 예외(&lt;/span&gt;EntityExistsException&lt;span&gt;) 발생 가능&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;✅ merge() 동작으로 UPDATE&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA에서 &lt;span&gt;ID&lt;/span&gt;는 단순히 &amp;ldquo;DB에 INSERT하면 생기는 값&amp;rdquo;이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Hibernate는 &lt;/span&gt;&lt;b&gt;@GeneratedValue 전략과 영속성 컨텍스트를 활용해, &lt;/b&gt;&lt;b&gt;DB와 JVM 사이에서 식별자 값을 미리 관리하고 동기화&lt;/b&gt;&lt;span&gt;합니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/282</guid>
      <comments>https://chanheess.tistory.com/282#entry282comment</comments>
      <pubDate>Fri, 24 Oct 2025 13:18:11 +0900</pubDate>
    </item>
    <item>
      <title>맥북 한영키 전환 카라비너 없이 적용하는 방법</title>
      <link>https://chanheess.tistory.com/281</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hidutil-generator.netlify.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hidutil-generator.netlify.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756550958286&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;hidutil key remapping generator&quot; data-og-description=&quot;caps_lockleft_controlleft_shiftleft_optionleft_commandright_controlright_shiftright_optionright_commandfnreturn_or_enterescapedelete_or_backspacedelete_forwardtabspacebarhyphen (-)equal_sign (=)open_bracket [close_bracket ]backslash (\)non_us_poundsemicolo&quot; data-og-host=&quot;hidutil-generator.netlify.app&quot; data-og-source-url=&quot;https://hidutil-generator.netlify.app/&quot; data-og-url=&quot;https://hidutil-generator.netlify.app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://hidutil-generator.netlify.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hidutil-generator.netlify.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;hidutil key remapping generator&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;caps_lockleft_controlleft_shiftleft_optionleft_commandright_controlright_shiftright_optionright_commandfnreturn_or_enterescapedelete_or_backspacedelete_forwardtabspacebarhyphen (-)equal_sign (=)open_bracket [close_bracket ]backslash (\)non_us_poundsemicolo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hidutil-generator.netlify.app&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;여기 링크에서 caplock -&amp;gt; f18로 적용&lt;/p&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;765&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lr5EH/btsQc0Qrw5h/RBz0OpcSkdc2eXUrfUQnq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lr5EH/btsQc0Qrw5h/RBz0OpcSkdc2eXUrfUQnq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lr5EH/btsQc0Qrw5h/RBz0OpcSkdc2eXUrfUQnq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLr5EH%2FbtsQc0Qrw5h%2FRBz0OpcSkdc2eXUrfUQnq1%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;765&quot; height=&quot;645&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 메뉴에서 다음 소스 선택을 f18로 적용&lt;/p&gt;</description>
      <category>Etc/Memo</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/281</guid>
      <comments>https://chanheess.tistory.com/281#entry281comment</comments>
      <pubDate>Sat, 30 Aug 2025 19:50:13 +0900</pubDate>
    </item>
    <item>
      <title>데이터베이스 구조 리팩토링 및 마이그레이션 경험 공유</title>
      <link>https://chanheess.tistory.com/280</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존 데이터베이스의 복잡성과 확장성 한계를 개선하기 위해 데이터베이스 구조를 리팩토링하고, 마이그레이션 및 배포까지 진행한 경험을 공유합니다.&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;설계의 배경&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 유저 중심의 데이터베이스 설계로 구성되어 있었습니다. 캘린더의 종류는 기본 캘린더, 그룹 캘린더가 있었고, 그룹 캘린더는 group_user 테이블을 통해 그룹 안에 어떤 유저들이 포함되어 있는지를 관리하는 방식이었습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그러나 Google Calendar 연동 기능이 추가되면서, 더 이상 유저 중심 구조만으로는 다양한 유형의 캘린더를 명확히 구분하기 어렵다는 판단이 들었습니다. 따라서 구조를 &amp;lsquo;캘린더 중심&amp;rsquo;으로 재설계하기로 결정했습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 과정에서 group_user는 calendar_member로 변경되었고, 캘린더 색상같은 설정에 관한 것도 캘린더별 설정을 위한 calendar_setting 등으로 분리하여 관심사별 테이블 구조로 나누게 되었습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;테이블 리팩토링 방향&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관심사에 따라 다음과 같은 구조 분리를 진행했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;group_user &amp;rarr; calendar_member + calendar_setting (그룹 캘린더 멤버, 캘린더 설정 분리)&lt;/li&gt;
&lt;li&gt;calendar_info &amp;rarr; calendar + calendar_setting (캘린더 정보, 사용자별 설정 분리)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;마이그레이션 접근 방식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블을 정리하려면 테이블의 데이터들을 옮기는 방법을 고민해야 했고, flyway방식이나 Spring JPA 방식을 비교를 해봤지만 &lt;span style=&quot;text-align: start;&quot;&gt;기존 테이블의 구조 변경 범위가 크고&lt;/span&gt;, 이후에 다시 수정할 가능성이 낮았기 때문에 복잡한 도구보다는 빠르게 적용해줄 수 있는 직접 쿼리 작성이 더 적합했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;SQL 쿼리를 활용한 직접 마이그레이션&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 calendar_info 테이블을 그대로 수정하는 대신, 새로운 calendar, calendar_setting 테이블을 만들고 데이터를 옮겼습니다.&lt;br /&gt;처음에는 기존 테이블을 직접 수정하는 방식도 고려했지만, 새롭게 테이블을 생성한 후 데이터를 옮기는 방식이 더 안전하다고 판단했습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;기존 테이블 구조를 직접 수정할 경우, JPA의 엔티티와 DB 테이블 간 매핑 검증 과정에서 오류가 발생할 수 있습니다. 기존 엔티티를 유지한 채 새로운 코드가 먼저 배포되면 스키마 불일치로 인해 서비스 실행 시점에 validation 에러가 발생할 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;반대로, 엔티티를 먼저 수정하고 새 코드를 배포하면 이미 배포된 코드에서 사용하는 기존 필드가 사라지거나 변경되기 때문에, 서비스 중단 가능성이 생기게 됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;따라서, 배포 서버의 DB에서&amp;nbsp;새로운 테이블을 미리 추가하고, 기존 데이터를 삽입한 뒤 백엔드 코드를 배포하여 점진적으로 전환하는 방식이 무중단 배포에 유리하다고 판단했습니다. 이러한 판단에 따라, 테이블을 직접 수정하지 않고 새로 만드는 방식을 선택했고, 마이그레이션 후 기존 테이블은 제거하는 순서로 안정적으로 작업을 진행할 수 있었습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;sql 쿼리&lt;br /&gt;모든 쿼리는 데이터 누락이나 충돌이 없도록 수차례 테스트 환경에서 검증한 뒤, 운영 환경에 적용했습니다.&lt;/p&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;size18&quot;&gt;테이블 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. calendar_info &amp;rarr; calendar&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id, user_id, title, category, &lt;b&gt;color&lt;/b&gt; &amp;rarr; id, user_id, title, category&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE calendar(
	id BIGINT PRIMARY KEY AUTO_INCREMENT,
	user_id BIGINT NOT NULL,
	title VARCHAR(20) NOT NULL,
	category ENUM('USER','GROUP','GOOGLE') NOT NULL,
	FOREIGN KEY (user_id) REFERENCES user(id)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. group_user &amp;rarr; calendar_member&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id, &lt;b&gt;group_id&lt;/b&gt;, &lt;b&gt;group_title&lt;/b&gt;, role, user_id, &lt;b&gt;user_nickname&lt;/b&gt;, &lt;b&gt;color&lt;/b&gt; &amp;rarr; id, calendar_id, user_id, role&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE calendar_member (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    calendar_id BIGINT NOT NULL,
    user_id BIGINT NOT NULL,
    role ENUM('ADMIN','SUB_ADMIN','USER') NOT NULL,
    FOREIGN KEY (calendar_id) REFERENCES calendar(id),
    FOREIGN KEY (user_id) REFERENCES user(id)
);
&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;3. calendar_provider 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE calendar_provider(
	id BIGINT PRIMARY KEY AUTO_INCREMENT,
	calendar_id BIGINT NOT NULL,
	provider_id VARCHAR(256) NOT NULL,
	provider VARCHAR(10) NOT NULL,
	sync_token VARCHAR(128),
	status VARCHAR(10) NOT NULL,
	FOREIGN KEY (calendar_id) REFERENCES calendar(id)
);
&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;4. calendar_setting 추가&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE calendar_setting (
	id BIGINT PRIMARY KEY AUTO_INCREMENT,
	calendar_id BIGINT NOT NULL,
	user_id BIGINT NOT NULL,
	color VARCHAR(10) NOT NULL,
	checked BOOLEAN NOT NULL,
	FOREIGN KEY (calendar_id) REFERENCES calendar(id),
  FOREIGN KEY (user_id) REFERENCES user(id)
);
&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;5. user_provider 추가&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;-- calendar.user_provider definition

CREATE TABLE `user_provider` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL,
  `provider` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_user` (`user_id`),
  CONSTRAINT `fk_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;데이터 삽입&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;calendar 정보 저장하기&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;INSERT INTO calendar (id, user_id, title, category)
SELECT
	ci.id,
	ci.admin_id,
	ci.title,
	ci.category
FROM
	calendar_info ci;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;2.1 기존 캘린더에 저장된 color값 가져오기&lt;/ol&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;INSERT INTO calendar_setting (calendar_id, user_id, color, checked)
SELECT
    ci.id,
    ci.admin_id,
    ci.color,
    TRUE
FROM
    calendar_info ci
WHERE
    ci.category != 'GROUP';
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.2 그룹 캘린더에 저장된 color값 가져오기&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;INSERT INTO calendar_setting (calendar_id, user_id, color, checked)
SELECT
	gu.group_id,
	gu.user_id,
	gu.color,
	TRUE
FROM
	group_user gu;
&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;3. 그룹 캘린더에서 role값 가져오기&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;INSERT INTO calendar_member (calendar_id, user_id, role)
SELECT
	gu.group_id,
	gu.user_id,
	gu.role
FROM
	group_user gu;
&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;4. 기존 유저들의 provider 적용&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;INSERT INTO user_provider (user_id, provider)
SELECT u.id, 'local'
FROM user u
WHERE NOT EXISTS (
    SELECT 1
    FROM user_provider up
    WHERE up.user_id = u.id AND up.provider = 'local'
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/01EeA/btsPCJOKQeS/W2jLHdjKJdw3OeLwKOYRy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/01EeA/btsPCJOKQeS/W2jLHdjKJdw3OeLwKOYRy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/01EeA/btsPCJOKQeS/W2jLHdjKJdw3OeLwKOYRy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F01EeA%2FbtsPCJOKQeS%2FW2jLHdjKJdw3OeLwKOYRy0%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;1310&quot; height=&quot;1002&quot; data-origin-width=&quot;1310&quot; data-origin-height=&quot;1002&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;size18&quot;&gt;&lt;b&gt;배포 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로컬 환경에서의 쿼리 검증&lt;br /&gt;2. 배포 환경에서의 데이터 베이스 마이그레이션&lt;br /&gt;3. github에 코드 최신화&lt;br /&gt;4. jenkins를 통한 코드 배포&lt;br /&gt;5. 대상 테이블 정상 작동 여부 재확인&lt;br /&gt;6. 불필요한 기존 테이블 제거&lt;br /&gt;&amp;nbsp;&lt;br /&gt;운영 환경에 적용되기 전까지 전체 마이그레이션 흐름을 수차례 테스트했기 때문에, 실제 배포 시에는 문제없이 빠르게 진행할 수 있었습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 단순히 테이블 구조만 개선된 것이 아니라 구글 캘린더 연동과 같은 확장 요구사항에 유연하게 대응할 수 있는 구조로 개선됐고, 향후 기능 추가 및 유지보수의 복잡도도 감소했습니다. 앞으로도 구조적인 한계를 미리 인식하고, 마이그레이션까지 고려한 설계를 통해 안정적이고 확장 가능한 시스템을 유지해 나갈 예정입니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/280</guid>
      <comments>https://chanheess.tistory.com/280#entry280comment</comments>
      <pubDate>Wed, 30 Jul 2025 14:16:16 +0900</pubDate>
    </item>
    <item>
      <title>빌드 실패 디버깅</title>
      <link>https://chanheess.tistory.com/278</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;firebase를 통한 알림 서비스를 추가했고 다 완성되었다고 생각해서 빌드를 돌렸는데 실패해서, 디버깅했던 내용을 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 추론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 문제가 하나의 원인 때문이라고 생각했습니다. 그래서 FirebaseConfig가 test코드에서 자동으로 등록되는 게 문제로 보여서 하나만 제외시켜 주면 되겠거니 했는데 아무리 해도 해결이 안 되었습니다. 4시간 동안을 혼자 생각했던 문제 해결방안과 gpt가 제시해 준 문제 해결방안을 시도해 봤지만 결과적으로는 해결되지 않았습니다. 그렇게 다음날에 멘탈을 잡고 천천히 하나씩 문제를 구글링을 통해서 추론해 나갔습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1741318173413&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CalendarApplicationTests &amp;gt; contextLoads() FAILED
java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:180
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException at ConfigurationClassParser.java:544
Caused by: java.lang.IllegalStateException at SpringBootCondition.java:60
Caused by: java.lang.IllegalArgumentException at PropertyPlaceholderHelper.java:180&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;여러 테스트 클래스들의 오류 중에 공통적으로 나타난 문제는 BeanDefinitionStoreException과 IllegalArgumentException at PropertyPlaceholderHelper가 나왔습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;첫 번째 시도&lt;/h3&gt;
&lt;pre id=&quot;code_1741318379645&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableAutoConfiguration(exclude = {
		FirebaseConfig.class
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당&amp;nbsp;코드를&amp;nbsp;사용해서&amp;nbsp;제거해&amp;nbsp;봤지만&lt;/p&gt;
&lt;pre id=&quot;code_1741319361523&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Caused by: java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
	- com.chpark.chcalendar.config.FirebaseConfig&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 옳은 방법은 아니었습니다. 일반적인 @Configuration 클래스라서 자동 구성 클래스가 아니므로 제외할 수 없었습니다. 그렇다면 @Configuration을 적용한 커스텀 파일을 제외하는 방법을 찾아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;두 번째 시도&lt;/h3&gt;
&lt;pre id=&quot;code_1741321408919&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@TestPropertySource(locations = &quot;classpath:application-test.yml&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 test용으로 다른 .env에 있는 환경변수들을 못 불러와서 문제가 생기는 걸 방지하려고 시도해 봤지만 빌드 시에는 해당 설정이 먹지 않아서 실패했습니다.&lt;/p&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;h3 data-ke-size=&quot;size23&quot;&gt;세 번째 시도&lt;/h3&gt;
&lt;pre id=&quot;code_1741328156685&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.chpark.chcalendar;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DotenvInitializer implements ApplicationContextInitializer&amp;lt;ConfigurableApplicationContext&amp;gt; {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        try {
            // 프로젝트 루트의 .env 파일을 읽어옵니다.
            List&amp;lt;String&amp;gt; lines = Files.readAllLines(Paths.get(&quot;.env&quot;));
            Map&amp;lt;String, Object&amp;gt; props = new HashMap&amp;lt;&amp;gt;();
            for (String line : lines) {
                line = line.trim();
                if (line.isEmpty() || line.startsWith(&quot;#&quot;)) {
                    continue;
                }
                String[] parts = line.split(&quot;=&quot;, 2);
                if (parts.length == 2) {
                    props.put(parts[0].trim(), parts[1].trim());
                }
            }
            // &quot;dotenv&quot;라는 이름으로 프로퍼티 소스를 추가합니다.
            applicationContext.getEnvironment().getPropertySources()
                    .addFirst(new MapPropertySource(&quot;dotenv&quot;, props));
        } catch (Exception e) {
            // .env 파일이 없으면 예외를 무시합니다.
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1741328172146&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootTest
@ContextConfiguration(initializers = DotenvInitializer.class)
class CalendarApplicationTests {

	@Test
	void contextLoads() {
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot;&gt;&lt;code&gt;@Value(&quot;${firebase.service-account-file}&quot;)
String serviceAccountFile;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 반대로 .env파일을 인식시켜 줬습니다. FirebaseConfig를 자동 등록을 하려 했지만 .env에 등록된 환경변수를 불러오지 못하니 등록하는 데에 문제가 생겼다고 생각했습니다. 그렇게 DotenvInitializer를 통해서 테스트 실행 전에 .env 파일을 읽어서 FirebaseConfig가 필요한 프로퍼티 값을 올바르게 받아서, placeholder 오류를 해결하게 되었습니다. 결국에는 빌드할 때에도 테스트 코드가 정상적으로 통과되었고 정상적으로 빌드를 할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 해결할지 막막했지만 오류 내용을 꼼꼼히 읽고 어떤 것 때문인지 하나씩 추론해 가면서 해결하다 보면 더욱 빠르게 해결할 수 있음을 더 느끼게 되었습니다. 한길로만 새지 않고 다른 방법도 생각을 빠르게 해야겠다고 느낀 디버깅이었던 것 같습니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/278</guid>
      <comments>https://chanheess.tistory.com/278#entry278comment</comments>
      <pubDate>Fri, 7 Mar 2025 15:31:42 +0900</pubDate>
    </item>
    <item>
      <title>CI/CD? 배포 자동화를 해보자</title>
      <link>https://chanheess.tistory.com/275</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 자동화를 적용하게 되었는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 코드가 완성되면 java 빌드, docker 이미지화, ec2환경에서의 이미지 pull, docker-compose up 하는 과정이 되게 번거로웠습니다. 그래서 자동화를 해서 완성되었을 때 버튼 하나로 빌드부터 배포까지 한번에 완료됐으면 했습니다. 자동화 툴이 여러가지 있었는데 이전 회사에서 사용이라도 해봐서 친근해보이는 젠킨스를 선택했습니다.&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;CI/CD가 무엇인가?&lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;CI/CD는 지속적 통합(Continuous Integration) 및 지속적 제공/배포(Continuous Delivery/Deployment)를 의미하며, 소프트웨어 개발 라이프사이클을 간소화하고 가속화하는 것을 목표로 합니다.&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;지속적 통합(CI)은 코드 변경 사항을 공유 소스 코드 리포지토리에&amp;nbsp;자동으로&amp;nbsp;자주 통합하는 사례를 나타냅니다.&amp;nbsp;지속적 제공&amp;nbsp;및/또는 배포(CD)는 코드 변경 사항의 통합, 테스트, 제공을 나타내는 프로세스로, 두 가지 부분으로 구성됩니다. 지속적 제공에는 자동 프로덕션 배포 기능이 없는 반면, 지속적 배포는 업데이트를 프로덕션 환경에 자동으로 릴리스합니다.&lt;/p&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;b&gt;CI&lt;/b&gt;는 Git 리포지토리에 코드 변경 사항을 자주 통합하여 코드의 안정성과 품질을 유지하는 과정이며,&amp;nbsp;&lt;b&gt;CD&lt;/b&gt;는 Docker를 활용하여 JAR 파일 이미지를 빌드한 뒤, 이를 Docker Hub에 push하고 EC2에서 pull하여 docker-compose를 통해 애플리케이션을 배포하는 과정을 자동화하는 데 적합합니다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 저는 로컬 환경에서 젠킨스를 설치하여 진행했습니다. EC2는 프리 티어를 사용 중이었기 때문에 성능이 충분할지 판단하기 어려웠습니다. 반면, 로컬 환경은 용량이 충분하고 빌드 속도도 빠를 만큼 성능이 적합하다고 판단했습니다. 그리고 젠킨스를 설정하기에 빠르게 설정할 수 있는 환경이라고 생각했습니다.&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;macOS의 로컬 환경에서 젠킨스 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jenkins.io/download/lts/macos/&quot;&gt;https://www.jenkins.io/download/lts/macos/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sample commands:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Install the latest LTS version:&amp;nbsp;brew install jenkins-lts&lt;/li&gt;
&lt;li&gt;Start the Jenkins service:&amp;nbsp;brew services start jenkins-lts&lt;/li&gt;
&lt;li&gt;Restart the Jenkins service:&amp;nbsp;brew services restart jenkins-lts&lt;/li&gt;
&lt;li&gt;Update the Jenkins version:&amp;nbsp;brew upgrade jenkins-lts&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 젠킨스 사이트에서 제공하는 젠킨스 다운 방법와 시작방법을 사용했습니다. homebrew가 없다면 다운해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 후, 젠킨스는 기본적으로 &lt;b&gt;8080 포트&lt;/b&gt;를 사용합니다. 그런데 제 로컬 환경에서 이미 다른 애플리케이션이 8080 포트를 사용하고 있어 충돌이 발생하기에 이를 해결하기 위해 젠킨스 포트를 &lt;b&gt;9090&lt;/b&gt;으로 변경하여 사용했습니다. 이 과정에서 Jenkins 설정 파일 homebrew.mxcl.jenkins-lts.plist를 수정해 포트를 변경했으며, 이후 문제 없이 빌드를 진행할 수 있었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733988518105&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 다운로드 위치를 확인하기 위한 명령
brew --prefix jenkins-lts


// 포트 수정을 위한 명령
vim /opt/homebrew/opt/jenkins-lts/homebrew.mxcl.jenkins-lts.plist&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음 초기 비밀번호를 아래의 명령어로 받아서 설정한 젠킨스 홈페이지로 접속해서 로그인해줍니다. 저의 경우 http://localhost:9090/로 접속했습니다. 초기 플러그인 설정은 젠킨스가 추천하는 것을 사용했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733988761510&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cat $(brew --prefix jenkins-lts)/homebrew.mxcl.jenkins-lts/secrets/initialAdminPassword&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;Jenkins관리 -&amp;gt; System안의&amp;nbsp;Global properties에서 key, value를 추가하시면됩니다.&amp;nbsp;&lt;b&gt;PATH+EXTRA&lt;/b&gt;는 꼭 추가하는게 좋습니다. Gradle Wrapper(gradlew) 실행이나 빌드 도구 및 의존성 관리등이 잘 작동할 수 있게 해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733989120965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;key: PATH+EXTRA
value: /usr/local/bin&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;또한 저는 git main 브랜치를 기준으로 빌드를 돌리려고 했는데, 계속 작동이 안 되던 이유중에 하나가 github에 &lt;span style=&quot;text-align: start;&quot;&gt;gradle/wrapper/&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;gradle-wrapper.jar 파일이 없으면 빌드가 제대로 돌아가지 않으므로 꼭 추가해주셔야 합니다.&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;플러그인 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운해야되는 플러그인들의 목록입니다. 만약에 검색이 안되면 plugin 글자를 제외해서 검색해보거나 이미 다운이 되었는지 확인해보세요.&lt;/p&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;Git plugin&lt;/b&gt;: Jenkins와 Git 리포지토리를 연동하는 데 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GitHub plugin&lt;/b&gt;: GitHub에 파일이 올라가면 자동으로 빌드할 수 있도록 도와주는 Webhook 설정에 유용한데, Webhook을 이용하기 위해서는 외부IP가 필요해서 로컬에서는 설정하기 번거롭기에 설정하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker Pipeline&lt;/b&gt;: Jenkins에서 Docker 컨테이너를 빌드, 실행, 관리할 수 있도록 지원합니다. Docker 기반 CI/CD 작업을 설정하는 데 필수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Docker plugin&lt;/b&gt;: Jenkins와 Docker 데몬 간 통신을 설정하며, Docker 이미지를 빌드하거나 배포 시 필요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle Plugin&lt;/b&gt;: Jenkins에서 Gradle 빌드 스크립트를 직접 실행할 수 있도록 지원합니다. Gradle을 사용하는 프로젝트라면 필수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Amazon EC2 plugin&lt;/b&gt;: Jenkins와 AWS EC2를 통합하여, 빌드 또는 배포 시 필요한 에이전트 노드를 동적으로 생성, 관리, 할당할 수 있도록 지원합니다. 이를 통해 CI/CD 파이프라인에 필요한 컴퓨팅 리소스를 효율적으로 사용할 수 있습니다.&lt;span style=&quot;background-color: #d3e3fd; color: #001d35; text-align: start;&quot;&gt;&lt;/span&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;ssh, 아이디 인증 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jenkins관리 -&amp;gt; Credentials에서 Stores scoped to Jenkins안의 System에서 &lt;b&gt;Global credentials&lt;/b&gt;에서&amp;nbsp;추가하면됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가는 username with password나 ssh username with private key로 상황에 맞게 추가하면되는데 github나 dockerhub, ec2에서의 key설정에 대해서는 다루지 않겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2468&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OlmMr/btsLhhIab7t/gxJoIyL9IAy7vyg5popWwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OlmMr/btsLhhIab7t/gxJoIyL9IAy7vyg5popWwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OlmMr/btsLhhIab7t/gxJoIyL9IAy7vyg5popWwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOlmMr%2FbtsLhhIab7t%2FgxJoIyL9IAy7vyg5popWwk%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;2468&quot; height=&quot;478&quot; data-origin-width=&quot;2468&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우는 위와같이 설정했습니다. Name쪽의 아이디는 가렸습니다.&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 대망의 자동화 빌드 만들기 입니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;젠킨스에서의 설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;새로운 Item으로 들어가서 Pipeline을 선택하고 제목은 해당 빌드에 설명을 대표하는 제목을 해주시면됩니다.&lt;/li&gt;
&lt;li&gt;Pipeline의 Definition을 Pipeline script from SCM으로 설정&lt;/li&gt;
&lt;li&gt;SCM는 Git으로 설정하고 git 레파지토리와 이전에 설정한 git Credentials로 설정&lt;/li&gt;
&lt;li&gt;Branches to build는 빌드할 브랜치를 적는 것 같은데 저는 main으로 정해서 */main로 해놨습니다.&lt;/li&gt;
&lt;li&gt;Script Path는 github에 있는 파일을 가져오는데 'Jenkinsfile'으로 제목을 하고 루트 디렉토리(gitignore위치)에 저장해놨습니다.&lt;a href=&quot;http://localhost:9090/job/chcalendar%20%EB%B0%B0%ED%8F%AC/configure#&quot;&gt;&lt;br /&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;저장!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #14141f; text-align: start;&quot;&gt;Github에서의 설정&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 디렉토리에서 Jenkinsfile 파일을 만들고 아래와 같이 설정해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733991663290&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pipeline {
    agent any
    environment {
        EC2_IP = &quot;${env.EC2_IP}&quot; // Jenkins에 설정된 EC2 IP 환경변수
    }
    stages {
        stage('Checkout Code') {
            steps {
                checkout([$class: 'GitSCM', 
                          branches: [[name: '*/main']],
                          userRemoteConfigs: [[url: 'https://github.com/chanheess/calendar.git', 
                                               credentialsId: 'github']]]) //Git 레파지토리와 credentials에 등록한 github id
            }
        }
        stage('Build Application') {
            steps {
                sh './gradlew clean build' //gradle의 clean 빌드를 해줍니다.
            }
        }
        stage('Build Docker Image') {
            steps {
            	//dockerhub-image는 등록할 이미지 이름을 넣으시면됩니다.
                //특정 플랫폼을 사용하기 위해서 linux/amd64로 push했습니다.
                //플랫폼이 상관없으면 docker build -t dockerhub-image . --push 로 하시면됩니다.
                
                sh 'docker buildx build --platform linux/amd64 -t dockerhub-image . --push'
            }
        }
        stage('Deploy to EC2') {
            steps {
                sshagent(['ec2']) {
                    sh '''
                    ssh -o StrictHostKeyChecking=no ${EC2_IP} &quot; //등록한 환경 변수 ec2 ip
                    docker pull dockerhub-image &amp;amp;&amp;amp;	//등록한 도커 이미지
                    docker-compose down &amp;amp;&amp;amp;
                    docker-compose up -d
                    &quot;
                    '''
                }
            }
        }
    }
    post {
        always {
            echo 'Build and Deployment Process Complete!'
        }
        failure {
            echo 'Build or Deployment Failed. Check Logs.'
        }
    }
}&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;/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;/h2&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;처음에는 Jenkins를 선택했습니다. 사용 경험이 있어 익숙하게 사용할 수 있었고 빠르게 파이프라인을 구성할 수 있었기 때문입니다. 그러나 모든 구간을 완전 자동화하려면 Jenkins Controller 서버를 EC2에 운영해야 했는데, 인스턴스 RAM 자원이 부족해 git push 이벤트를 자동으로 처리하는 구조를 구축하지는 못했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 GitHub Actions를 사용했다면 별도의 서버 관리 없이 &lt;span&gt;&lt;b&gt;저장소와 통합된 워크플로우&lt;/b&gt;&lt;/span&gt;만으로 빌드와 배포를 처리할 수 있었을 것 같습니다. 이 경험을 통해 단순히 익숙하다는 이유로 도구를 선택하기보다는, &lt;span&gt;&lt;b&gt;운영 환경의 제약과 유지보수 비용까지 고려해야 함&lt;/b&gt;&lt;/span&gt;을 배웠습니다. 앞으로는 단기 생산성뿐 아니라 &lt;span&gt;&lt;b&gt;장기적인 운영 효율성과 안정성&lt;/b&gt;&lt;/span&gt;까지 함께 검토할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 내용 중 문제가 있거나 작동하지 않는 부분이 있으면, 댓글로 질문을 남겨주세요. 최대한 빠르게 답변드리겠습니다. 감사합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733984125568&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;CI/CD(CI CD, 지속적 통합/지속적 배포): 개념, 툴, 구축, 차이&quot; data-og-description=&quot;CI/CD는 애플리케이션의 통합 및 테스트 단계부터 제공 및 배포까지 애플리케이션 라이프사이클 전체에서 지속적인 자동화와 지속적인 모니터링을 제공하는 것을 뜻합니다.&quot; data-og-host=&quot;www.redhat.com&quot; data-og-source-url=&quot;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&quot; data-og-url=&quot;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/pRFJ1/hyXKjnf3bV/7KknJExBK5KUPLBQjM7HL0/img.png?width=300&amp;amp;height=190&amp;amp;face=0_0_300_190,https://scrap.kakaocdn.net/dn/bcbpup/hyXKlST8Uc/G48IKNd0xjLMbFSAvD4qSK/img.png?width=300&amp;amp;height=190&amp;amp;face=0_0_300_190&quot;&gt;&lt;a href=&quot;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.redhat.com/ko/topics/devops/what-is-ci-cd&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/pRFJ1/hyXKjnf3bV/7KknJExBK5KUPLBQjM7HL0/img.png?width=300&amp;amp;height=190&amp;amp;face=0_0_300_190,https://scrap.kakaocdn.net/dn/bcbpup/hyXKlST8Uc/G48IKNd0xjLMbFSAvD4qSK/img.png?width=300&amp;amp;height=190&amp;amp;face=0_0_300_190');&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;CI/CD(CI CD, 지속적 통합/지속적 배포): 개념, 툴, 구축, 차이&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CI/CD는 애플리케이션의 통합 및 테스트 단계부터 제공 및 배포까지 애플리케이션 라이프사이클 전체에서 지속적인 자동화와 지속적인 모니터링을 제공하는 것을 뜻합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.redhat.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/275</guid>
      <comments>https://chanheess.tistory.com/275#entry275comment</comments>
      <pubDate>Fri, 13 Dec 2024 13:00:01 +0900</pubDate>
    </item>
    <item>
      <title>EC2를 활용한 HTTPS 및 도메인 설정</title>
      <link>https://chanheess.tistory.com/274</link>
      <description>&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;EC2와 HTTPS를 활용한 배포 과정 정리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음에는 작업 중인 프로젝트에 HTTPS와 도메인을 적용하기 위해 로컬 환경에서 테스트를 진행했습니다. 집에서 공유기를 통해 외부 IP를 할당받아 DuckDNS 도메인을 사용했지만, 문제가 있었습니다. 이동해서 다른 곳에서 작업할 때마다 IP가 변경되었고, 카페와 같은 공용 Wi-Fi에서는 포트 포워딩을 직접 설정하는 것이 거의 불가했습니다.&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&gt;그래서 AWS EC2를 활용해 HTTPS 환경을 구축하기로 결정했습니다. 이렇게 하면 외부 IP와 관련된 문제를 해결할 수 있고, 다른 사람들도 도메인을 통해 프로젝트에 접근할 수 있습니다. 이번 글에서는 EC2 환경으로 배포를 진행하면서 알게 된 점과 작업 흐름을 정리해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ec2&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;aws 가입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AWS 가입 과정에서 인증 문자나 전화가 오지 않는다면, 통신사에서 &lt;/span&gt;&lt;span&gt;&lt;b&gt;해외 전화 수신 차단&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 설정을 확인해야 합니다. 저도 이 문제로 처음에 가입이 지연되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ec2를 만들어보자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;AWS Management Console 검색창에서 &lt;/span&gt;&lt;span&gt;&lt;b&gt;EC2&lt;/b&gt;&lt;/span&gt;&lt;span&gt;를 검색한 뒤, &lt;/span&gt;&lt;span&gt;&lt;b&gt;인스턴스 시작&lt;/b&gt;&lt;/span&gt;&lt;span&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;2088&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GWe3N/btsLfGPf8Sb/JyBTfCIkqHemnZR1KfWNW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GWe3N/btsLfGPf8Sb/JyBTfCIkqHemnZR1KfWNW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GWe3N/btsLfGPf8Sb/JyBTfCIkqHemnZR1KfWNW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGWe3N%2FbtsLfGPf8Sb%2FJyBTfCIkqHemnZR1KfWNW0%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;2088&quot; height=&quot;472&quot; data-origin-width=&quot;2088&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;설정은 아래와 같이 진행합니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;text-align: center; caret-color: transparent; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dn/ciHa96/btsLfMoc076/MkTjIC8LmxptaNMic0UiwK/img.png&quot; width=&quot;854&quot; height=&quot;427&quot; data-origin-width=&quot;2646&quot; data-origin-height=&quot;1322&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-pm-slice=&quot;3 3 [&amp;quot;ordered_list&amp;quot;,{&amp;quot;spread&amp;quot;:true,&amp;quot;startingNumber&amp;quot;:1,&amp;quot;start&amp;quot;:541,&amp;quot;end&amp;quot;:1057},&amp;quot;regular_list_item&amp;quot;,{&amp;quot;start&amp;quot;:612,&amp;quot;end&amp;quot;:1012}]&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이름 및 태그&lt;/b&gt;: 서버 이름을 용도에 맞게 작성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애플리케이션 및 OS 이미지&lt;/b&gt;: 프리 티어를 기준으로 x86을 선택했습니다. 저는 Amazon Linux를 사용했지만, Ubuntu도 추천드립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인스턴스 유형&lt;/b&gt;: t2.micro를 선택했습니다. 프리 티어는 &quot;프리 티어 사용 가능&quot;이라고 표시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;키 페어&lt;/b&gt;: 새 키 페어를 생성하거나 기존 키를 사용합니다. 생성한 .pem 파일은 SSH로 로그인할 때 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 설정&lt;/b&gt;: 보안 그룹에 HTTP, HTTPS, SSH를 허용하도록 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스토리지 구성&lt;/b&gt;: 기본 8GB를 사용하거나 필요에 따라 확장 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPhl3W/btsLeJMLbmU/w323Te9YifKA7K6BY60EK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPhl3W/btsLeJMLbmU/w323Te9YifKA7K6BY60EK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPhl3W/btsLeJMLbmU/w323Te9YifKA7K6BY60EK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPhl3W%2FbtsLeJMLbmU%2Fw323Te9YifKA7K6BY60EK0%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;1734&quot; height=&quot;1248&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;1248&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;인스턴스 시작&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 버튼을 클릭하면 EC2 인스턴스 생성이 완료됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리 티어 주의: EC2는 한 달 기준 750시간 무료로 제공되며, 인스턴스를 추가로 생성하면 동시에 누적됩니다. 사용하지 않는 인스턴스는 삭제하거나 중지해야 과금을 방지할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;rds&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RDS 데이터베이스 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS Management Console에서&amp;nbsp;&lt;b&gt;RDS&lt;/b&gt;를 검색한 뒤,&amp;nbsp;&lt;b&gt;데이터베이스 생성&lt;/b&gt;을 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정은 아래와 같이 진행합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;엔진 선택&lt;/b&gt;: MySQL (8.x.x 버전). (원하는 엔진으로 선택)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;템플릿&lt;/b&gt;: 프리 티어를 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DB 인스턴스 식별자&lt;/b&gt;: 데이터베이스 이름 작성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마스터 사용자 이름 및 암호&lt;/b&gt;: DB 로그인용 계정과 암호 설정.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스토리지 구성&lt;/b&gt;: 자동 조정은 비활성화합니다(과금 방지).&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0f141a; text-align: start;&quot;&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;828&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/08dLk/btsLfO7xjbt/3ZttDYOpLenPz2kkKFODOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/08dLk/btsLfO7xjbt/3ZttDYOpLenPz2kkKFODOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/08dLk/btsLfO7xjbt/3ZttDYOpLenPz2kkKFODOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F08dLk%2FbtsLfO7xjbt%2F3ZttDYOpLenPz2kkKFODOk%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;2366&quot; height=&quot;828&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;프리 티어 주의: 추가 스토리지 구성에서 자동 조정을 활성화를 체크 해제해야합니다. 임계값을 초과해서 늘리게되는 상황이 생기게될 때 과금요소가 될 수 있습니다.&lt;/blockquote&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;EC2와 RDS 연결&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-spread=&quot;false&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;연결 설정&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: EC2 컴퓨팅 리소스와 연결되도록 설정하면 동일한 VPC 내에서 자동으로 통신이 가능합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;보안 그룹&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: RDS 생성 시 EC2 인스턴스와의 통신이 가능하도록 보안 그룹이 자동으로 설정됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;포트 설정&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: MySQL 기본 포트인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;3306&lt;/span&gt;&lt;span&gt;을 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2348&quot; data-origin-height=&quot;1314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pzZye/btsLf2j92BX/POQSxnxDQxLx6Ruzw37c4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pzZye/btsLf2j92BX/POQSxnxDQxLx6Ruzw37c4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pzZye/btsLf2j92BX/POQSxnxDQxLx6Ruzw37c4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpzZye%2FbtsLf2j92BX%2FPOQSxnxDQxLx6Ruzw37c4K%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;2348&quot; height=&quot;1314&quot; data-origin-width=&quot;2348&quot; data-origin-height=&quot;1314&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;2302&quot; data-origin-height=&quot;1258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF62vz/btsLgDRMdCP/bkajUb3sgxFpXGIgyyO521/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF62vz/btsLgDRMdCP/bkajUb3sgxFpXGIgyyO521/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF62vz/btsLgDRMdCP/bkajUb3sgxFpXGIgyyO521/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF62vz%2FbtsLgDRMdCP%2FbkajUb3sgxFpXGIgyyO521%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;2302&quot; height=&quot;1258&quot; data-origin-width=&quot;2302&quot; data-origin-height=&quot;1258&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&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sOTbl/btsLf3ckklA/6KFytRluxxezz0UGNnXbl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sOTbl/btsLf3ckklA/6KFytRluxxezz0UGNnXbl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sOTbl/btsLf3ckklA/6KFytRluxxezz0UGNnXbl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsOTbl%2FbtsLf3ckklA%2F6KFytRluxxezz0UGNnXbl0%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;2336&quot; height=&quot;694&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ec2 로그인&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5FiKU/btsLeDzpm0s/dnO9jjv3WabakT9FBjKk70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5FiKU/btsLeDzpm0s/dnO9jjv3WabakT9FBjKk70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5FiKU/btsLeDzpm0s/dnO9jjv3WabakT9FBjKk70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5FiKU%2FbtsLeDzpm0s%2FdnO9jjv3WabakT9FBjKk70%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;1586&quot; height=&quot;564&quot; data-origin-width=&quot;1586&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행중인 인스턴스를 확인해서 연결을 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2564&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caCDoB/btsLe8eDobn/v6BmYCuKAKIKoLCMPrxuUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caCDoB/btsLe8eDobn/v6BmYCuKAKIKoLCMPrxuUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caCDoB/btsLe8eDobn/v6BmYCuKAKIKoLCMPrxuUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaCDoB%2FbtsLe8eDobn%2Fv6BmYCuKAKIKoLCMPrxuUk%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;2564&quot; height=&quot;830&quot; data-origin-width=&quot;2564&quot; data-origin-height=&quot;830&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;이제 터미널을 키고 .pem파일을 저장한 디렉토리에서 해당순서대로 따라하면되는데 예:에 써있는 것을 복사하면 바로 로그인하기 간단합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCkEAS/btsLgtaIJK6/rDv4aMNfLi1CCDetp66fDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCkEAS/btsLgtaIJK6/rDv4aMNfLi1CCDetp66fDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCkEAS/btsLgtaIJK6/rDv4aMNfLi1CCDetp66fDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCkEAS%2FbtsLgtaIJK6%2FrDv4aMNfLi1CCDetp66fDK%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;1144&quot; height=&quot;534&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 나오면 로그인까지 끝입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인을 구매하자?&lt;/h2&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음에는 DuckDNS를 사용했지만, 서버 장애로 인해 일정 시간 사용하지 못하는 불편함을 겪었습니다. 이후, 가비아(Gabia)에서 도메인을 구매하고 AWS의 탄력적 IP(Elastic IP)를 사용해 고정된 외부 IP를 설정했습니다. 탄력적 IP는 하나까지 프리 티어로 제공되므로 꼭 사용하는 것을 추천합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;탄력적 IP 생성&lt;/b&gt;: &lt;/span&gt;&lt;span&gt;AWS 콘솔에서 &lt;/span&gt;&lt;span&gt;&lt;b&gt;Elastic IP&lt;/b&gt;&lt;/span&gt;&lt;span&gt;를 생성한 뒤 EC2 인스턴스에 연결합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;도메인 연결&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;가비아 DNS 설정에서 탄력적 IP를 A 레코드로 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;https설정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Docker와 Certbot, Nginx를 활용해 HTTPS를 설정했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Dockerfile&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733974488619&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 베이스 이미지 설정
FROM openjdk:17-jdk-slim

# 2. 작업 디렉토리 생성
WORKDIR /app

# 3. JAR 파일 복사
COPY build/libs/*.jar app.jar

# 4. 애플리케이션 실행
ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 &lt;b&gt;MySQL 연결이 되지 않아 헤맸습니다.&lt;/b&gt; 원인을 찾아보니, Gradle로 bootJar 빌드를 수행하지 않았기 때문이었습니다. 저는 Dockerfile을 실행하면 JAR 파일 빌드가 자동으로 이루어지는 줄 알았지만, 그렇지 않았습니다. Dockerfile은 이미 빌드된 JAR 파일을 가져와 이미지를 생성할 뿐, 빌드 자체는 별도로 수행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 제가 사용하는 환경은 macOS와 EC2(Amazon Linux x86)였기 때문에, Docker 이미지를 생성할 때 플랫폼을 명시적으로 설정해야 했습니다. 이를 위해 docker buildx를 사용해 플랫폼을 linux/amd64로 지정해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733978267626&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// jar 클린 빌드
./gradlew clean bootJar

// buildx로 특정 플랫폼에 대한 docker image 생성
docker buildx build --platform linux/amd64 -t nickname/springapp:title . --push&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;docker-compose.yml&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733974396781&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
  spring-app:
    image: spring-app-image
    container_name: spring-app
    ports:
      - &quot;8080:8080&quot;
    env_file:
      - .env

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./data/letsencrypt:/etc/letsencrypt
      - ./data/www:/var/www/html
    command: certonly --webroot --webroot-path=/var/www/html --email ${EMAIL} --agree-tos --no-eff-email -d ${DOMAIN}
    entrypoint: &quot;/bin/sh -c 'trap exit TERM; while :; do sleep 6h &amp;amp; wait $${!}; certbot renew; done;'&quot;

  nginx:
    image: nginx
    container_name: nginx
    ports:
      - &quot;80:80&quot;
      - &quot;443:443&quot;
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./data/letsencrypt:/etc/letsencrypt
      - ./data/www:/var/www/html
    depends_on:
      - spring-app
    env_file:
      - .env&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;certbot과 nginx는 docker-compose에 설정을 해줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring-app-image는 도커에서 받아온 이미지의 이름을 적으면됩니다. 환경변수 설정은 .env파일로 관리를 했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;nginx/default.conf&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733975281252&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name yourdomain;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name yourdomain;

    ssl_certificate /etc/letsencrypt/live/yourdomain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain/privkey.pem;

    location / {
        proxy_pass http://spring-app:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx의 default.conf 설정은 위와같이 해주었습니다. yourdomain부분은 사용하고 있는 도메인을 적으시면됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733978447876&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up -d&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;마지막으로 터미널에서 docker-compose up -d를 진행하면 이제 최종적으로 해당 도메인으로 https서비스를 해줄 수 있습니다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 빌드해서 배포까지의 순서를 정리하면 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;gradle jar파일 빌드 -&amp;gt; Dockerfile을 통한 이미지생성 -&amp;gt; DockerHub에 push -&amp;gt; ec2에서 DockerHub에 올라간 이미지 pull -&amp;gt; docker-compose 작동&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;aws를 사용하기 위해서 네트워크적인 기본적인 내용은 라우팅과 라우팅테이블에 대한 내용을 공부하시면 이해하기 쉬울 것 같습니다.&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chanheess.tistory.com/265&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chanheess.tistory.com/265&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733975956488&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;라우팅과 라우팅 테이블의 이해&quot; data-og-description=&quot;서론의문은 AWS의 RDS를 추가해주면서 생기게 되었습니다. 만들면서 연결에 대한 부분을 설정해줄 때 내가 이것들이 정확하게 뭔지 알고 설정을 하는 것인가 아니면 그냥 검색해서 나온 정보들로&quot; data-og-host=&quot;chanheess.tistory.com&quot; data-og-source-url=&quot;https://chanheess.tistory.com/265&quot; data-og-url=&quot;https://chanheess.tistory.com/265&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bZ2OHk/hyXKuWzWSK/QqDSSlZ1yOvtD79gtx9YTk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Mtgiv/hyXKoB3n7n/YJf3QWusKxTWjUpk5IcQnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://chanheess.tistory.com/265&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chanheess.tistory.com/265&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bZ2OHk/hyXKuWzWSK/QqDSSlZ1yOvtD79gtx9YTk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/Mtgiv/hyXKoB3n7n/YJf3QWusKxTWjUpk5IcQnK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;라우팅과 라우팅 테이블의 이해&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서론의문은 AWS의 RDS를 추가해주면서 생기게 되었습니다. 만들면서 연결에 대한 부분을 설정해줄 때 내가 이것들이 정확하게 뭔지 알고 설정을 하는 것인가 아니면 그냥 검색해서 나온 정보들로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chanheess.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chanheess.tistory.com/259&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chanheess.tistory.com/259&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733976181981&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;HTTPS에 대해서&quot; data-og-description=&quot;HTTP와 HTTPS의 차이점HTTP: HTTP는 데이터를 암호화하지 않습니다. 즉, 클라이언트(브라우저)와 서버 간에 주고받는 데이터가 평문(Plain Text) 상태로 전송됩니다. 따라서 네트워크 상에서 중간에 누군&quot; data-og-host=&quot;chanheess.tistory.com&quot; data-og-source-url=&quot;https://chanheess.tistory.com/259&quot; data-og-url=&quot;https://chanheess.tistory.com/259&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/doN14E/hyXKtcjPpM/zTEJp7RQCvJ0nZKM7PatJk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/eS3od/hyXKyY0jfb/npAgdnxIIWfqLr8Wm3kFu1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://chanheess.tistory.com/259&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chanheess.tistory.com/259&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/doN14E/hyXKtcjPpM/zTEJp7RQCvJ0nZKM7PatJk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/eS3od/hyXKyY0jfb/npAgdnxIIWfqLr8Wm3kFu1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;HTTPS에 대해서&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTP와 HTTPS의 차이점HTTP: HTTP는 데이터를 암호화하지 않습니다. 즉, 클라이언트(브라우저)와 서버 간에 주고받는 데이터가 평문(Plain Text) 상태로 전송됩니다. 따라서 네트워크 상에서 중간에 누군&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chanheess.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/274</guid>
      <comments>https://chanheess.tistory.com/274#entry274comment</comments>
      <pubDate>Thu, 12 Dec 2024 13:25:06 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 튜플</title>
      <link>https://chanheess.tistory.com/273</link>
      <description>&lt;pre id=&quot;code_1733798452029&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

class Solution {
    public int[] solution(String s) {
        
        //String 묶음으로 갈무리
        String[] lists = s.substring(2, s.length() - 2).split(&quot;},\\{&quot;);
        
        //크기가 작은 순으로 정렬
        Arrays.sort(lists, Comparator.comparingInt(String::length));
        
        Set&amp;lt;Integer&amp;gt; checkDuplication = new HashSet&amp;lt;&amp;gt;();
        int[] answer = new int[lists.length];
        int indexCount = 0;
        
        for(String list : lists) {
            String[] numbers = list.split(&quot;,&quot;);
            
            for (String value : numbers) {
                int number = Integer.parseInt(value);
                
                //answer에 없는 값만 추가
                if (checkDuplication.add(number)) {
                    answer[indexCount++] = number;
                }
            }
        }        
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 어떤 방법을 사용하던간에 String으로 들어온 값을 각 묶음으로 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 크기가 작은 순으로 정렬을 해준다. 첫번째부터 확인을 해야 다음 값부터 이전 번째의 값들을 소거하면서 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;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;&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;java에서의 String의 사용법을 많이 익혀야겠다고 느꼈다. 더러운듯 깔끔한 함수들인 것같다. set의 add함수가 true, false를 리턴해주는 것을 잘 써먹어야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/64065&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/64065&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/273</guid>
      <comments>https://chanheess.tistory.com/273#entry273comment</comments>
      <pubDate>Tue, 10 Dec 2024 11:47:54 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 [1차] 캐시</title>
      <link>https://chanheess.tistory.com/272</link>
      <description>&lt;pre id=&quot;code_1733714296002&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.LinkedHashMap;
import java.util.Map;

class Solution {
    public int solution(int cacheSize, String[] cities) {
        if (cacheSize == 0) {
            return cities.length * 5; // 캐시 크기가 0이면 모든 요청이 miss
        }

        int answer = 0;

        // LRU 캐시를 위한 LinkedHashMap 설정
        Map&amp;lt;String, Boolean&amp;gt; cachedMap = new LinkedHashMap&amp;lt;String, Boolean&amp;gt;(cacheSize, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry&amp;lt;String, Boolean&amp;gt; eldest) {
                return size() &amp;gt; cacheSize; // 크기를 초과하면 가장 오래된 항목 제거
            }
        };

        for (String city : cities) {
            String cityLowerCase = city.toLowerCase(); // 대소문자 구분 없도록 소문자로 변환

            if (cachedMap.containsKey(cityLowerCase)) {
                answer += 1; // cache hit
            } else {
                answer += 5; // cache miss
            }

            // 캐시에 값 추가 (LRU 순서 업데이트)
            cachedMap.put(cityLowerCase, true);
        }

        return answer;
    }
}&lt;/code&gt;&lt;/pre&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;1. 정수인 cacheSize의 범위가 0~30이다: 이는 곧 0에 대한 처리가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;span style=&quot;text-align: left;&quot;&gt;공백, 숫자, 특수문자 등이 없는 영문자로 구성되며, 대소문자 구분을 하지 않는다: 대소문자에 대한 처리가 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;3. &lt;span style=&quot;text-align: left;&quot;&gt;총 실행시간&quot;을 출력한다: cache hit + cache miss&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;4. 캐시 교체 알고리즘은 LRU를 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;5. cache hit일 경우 실행시간은 1이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;6. cache miss일 경우는 실행시간은 5이다.&lt;/span&gt;&lt;/span&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;우선 LRU에 대한 이해가 필요했습니다. LRU란 &lt;span style=&quot;text-align: start;&quot;&gt;LRU(Least Recently Used)로 이름에서도 알 수 있듯이 가장 최근에 사용된 것 기준으로 정렬되어야하고 메모리 초과시에 오래된 것을 순서로 삭제해주어 새로운 데이터를 입력해줍니다.&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;LRU를 구현하기 위해서는 우선 가장 최근의 것이 쌓이도록 데이터가 저장되어야 하고 삭제시에도 특정 위치를 정확히 삭제해주면서도 list를 정리해줄 필요가 없는 링크드리스트가 적합했습니다. 그런데 Java에서는 해당 기능이 적용된 LinkedHashMap이라는 좋은 자료형이 있어서 해당 것을 사용했습니다. 심지어 메모리가 초과될 경우에 직접 삭제할 필요없이 자동으로 오래된 것을 삭제까지 해줄 수 있는 기능도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cache hit, miss는 활용할 캐시메모리에 저장되어 있다면 cache hit, 캐시에 없는 데이터를 찾기 위해 직접 메인 메모리나 하드디스크에 접근해야될 경우나 데이터가 없을 경우에 cache miss입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대소문자의 처리는 LowerCase로 처리해주었습니다. string에 담아 놓은 것은 매번 LowerCase을 사용할 경우 추가적으로 string을 담기 위한 메모리를 계속 소모하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cacheSize, 0.75f, true에 대한 부분은 cacheSize를 지정, 0.75f는 해시 충돌을 줄이기 위해 해시맵이 얼마나 가득 차면 용량을 늘릴지를 결정하는 값입니다. 이또한 해시맵에 대한 이해가 필요합니다. true 부분은 정렬 기준을 정해주는데 false일 경우 입력 순서를 기준으로 정렬되고 true의 경우 LRU규칙에 걸맞게 접근 순서를 기준으로 정렬해줍니다. 가장 최근에 접근한 항목이 맵의 끝으로 이동합니다.&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;&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;a href=&quot;https://f-lab.kr/insight/understanding-lru-cache-20240605&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://f-lab.kr/insight/understanding-lru-cache-20240605&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733714629339&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;LRU 캐시 알고리즘의 이해와 구현&quot; data-og-description=&quot;이 글은 LRU(Least Recently Used) 캐시 알고리즘의 동작 원리와 구현 방법을 설명합니다. LRU 알고리즘의 장단점과 다양한 응용 분야를 다루며, 자바로 구현한 간단한 예제를 제공합니다.&quot; data-og-host=&quot;f-lab.kr&quot; data-og-source-url=&quot;https://f-lab.kr/insight/understanding-lru-cache-20240605&quot; data-og-url=&quot;https://f-lab.kr/ai-blog/understanding-lru-cache-20240605&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bd8IdK/hyXGBh0a9H/Udjzsj5fHFdVtmHLrJzDEk/img.jpg?width=1792&amp;amp;height=1024&amp;amp;face=0_0_1792_1024&quot;&gt;&lt;a href=&quot;https://f-lab.kr/insight/understanding-lru-cache-20240605&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://f-lab.kr/insight/understanding-lru-cache-20240605&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bd8IdK/hyXGBh0a9H/Udjzsj5fHFdVtmHLrJzDEk/img.jpg?width=1792&amp;amp;height=1024&amp;amp;face=0_0_1792_1024');&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;LRU 캐시 알고리즘의 이해와 구현&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 LRU(Least Recently Used) 캐시 알고리즘의 동작 원리와 구현 방법을 설명합니다. LRU 알고리즘의 장단점과 다양한 응용 분야를 다루며, 자바로 구현한 간단한 예제를 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;f-lab.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17680&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/17680&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/272</guid>
      <comments>https://chanheess.tistory.com/272#entry272comment</comments>
      <pubDate>Mon, 9 Dec 2024 12:43:37 +0900</pubDate>
    </item>
    <item>
      <title>이메일 인증을 위한 Redis 설정과 문제 해결 과정</title>
      <link>https://chanheess.tistory.com/271</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이메일 인증을 위한 Redis 도입 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입 시 이메일 인증은 보안성과 신뢰성을 높이기 위한 중요한 과정입니다. 이를 구현하면서 &quot;이메일 인증 정보는 오래 저장할 필요가 없으니, 영구 저장소가 아닌 일시적인 데이터를 저장할 수 있는 시스템을 선택하는 게 맞겠다&quot;는 판단을 내렸습니다.&lt;br /&gt;이 과정에서 적합한 데이터베이스를 조사하던 중 Redis를 알게 되었고, 다음과 같은 이유로 선택하게 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Key-Value 형태의 NoSQL 데이터베이스&lt;/b&gt;: 복잡한 관계형 데이터를 다룰 필요가 없었기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일시적인 데이터 저장&lt;/b&gt;: 이메일 인증번호와 같은 데이터는 짧은 시간 동안만 유지되므로, Redis의 TTL(Time-To-Live) 기능이 적합했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redis를 사용하며 겪은 시행착오와 해결 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시행착오와 문제 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 Redis를 설정했을 때, Redis와 통신하려는 시도에서 다음과 같은 오류가 발생했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733561008653&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java.net.UnknownHostException: redis: nodename nor servname provided, or not known&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;docker-compose.yml과 application.yml에 Redis의 호스트와 포트를 정확히 설정했음에도&amp;nbsp;&lt;b&gt;UnknownHostException&lt;/b&gt;이 발생했습니다.&amp;nbsp;네트워크 설정이 올바르게 되어 있는지 확인하고, EC2의 보안 그룹은 컨테이너 간 내부 네트워크 통신에는 영향을 미치지 않으므로 인바운드 규칙 설정 문제는 아니었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 구글링하며 Redis 연결에 필요한 요소들을 점검했으나 해결방안에는 문제가 없어보였고, 근본부터 구현이 문제인 것같다고 생각되어 구현에 대한 것을 검색했고 제 코드와 다른 점을 비교했습니다. 결국 다른 코드에서 LettuceConnectionFactory와 RedisConfig 클래스를 추가한 것을 발견했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RedisConfig 클래스의 도입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 기본적으로 Redis와 통신하기 위한 Bean을 자동으로 생성하지 않으므로, Redis 연결을 위한 설정을 명시적으로 추가해야 합니다. 예를 들어, Redis와의 연결을 관리하는 RedisConnectionFactory나, 데이터를 저장하고 가져오기 위한 RedisTemplate과 같은 Bean이 등록되지 않은 상태에서는 Redis와의 통신이 불가능합니다.&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;RedisConfig 코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 해결하기 위해 아래와 같은 RedisConfig 클래스를 작성했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1733558068819&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@RequiredArgsConstructor
@Configuration
public class RedisConfig {

    @Value(&quot;${spring.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.redis.port}&quot;)
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate() {
        RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate = new RedisTemplate&amp;lt;&amp;gt;();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 다음과 같은 역할을 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;LettuceConnectionFactory: Redis 서버의 호스트와 포트 정보를 기반으로 연결을 생성합니다.&lt;/li&gt;
&lt;li&gt;RedisTemplate: Redis와 데이터를 직렬화/역직렬화하며, key-value 형태로 데이터를 저장하거나 불러오도록 도와줍니다.&lt;/li&gt;
&lt;li&gt;StringRedisSerializer: Redis에 저장되는 key와 value를 String 형태로 직렬화/역직렬화합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis는 이메일 인증과 같은 일시적인 데이터를 저장하기에 매우 적합한 솔루션임을 확인했습니다. 특히, RedisTemplate과 LettuceConnectionFactory를 통해 Redis와의 연결 및 데이터를 제어할 수 있었습니다.&lt;br /&gt;이번 경험을 통해 Redis와 Spring의 통합 과정에서 발생할 수 있는 문제를 깊이 이해할 수 있었고, 올바른 설정의 중요성을 다시 한번 깨닫게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/271</guid>
      <comments>https://chanheess.tistory.com/271#entry271comment</comments>
      <pubDate>Sat, 7 Dec 2024 17:51:42 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 괄호 회전하기</title>
      <link>https://chanheess.tistory.com/270</link>
      <description>&lt;pre id=&quot;code_1733368603111&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Stack;

class Solution {
    public int solution(String s) {
        int answer = 0;
        int n = s.length();
        
        if (n == 1) {
            return 0; // 길이가 1이면 올바른 괄호 문자열이 될 수 없음
        }

        for (int i = 0; i &amp;lt; n; i++) {
            if (check(s, i)) {
                answer++;
            }
        }
        return answer;
    }

    // 특정 시작점으로 문자열 검사
    static boolean check(String s, int start) {
        Stack&amp;lt;Character&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
        int n = s.length();

        for (int i = 0; i &amp;lt; n; i++) {
            char ch = s.charAt((start + i) % n);
            
            if (ch == '{' || ch == '(' || ch == '[') {
                stack.push(ch);
            } else {
                if (stack.isEmpty()) {
                    return false;
                }
                char top = stack.pop();
                if ((ch == '}' &amp;amp;&amp;amp; top != '{') ||
                    (ch == ')' &amp;amp;&amp;amp; top != '(') ||
                    (ch == ']' &amp;amp;&amp;amp; top != '[')) {
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문자열의 첫번째 값이 맨뒤로 이동해야된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그리고 그 맨뒤로 이동한 스트링의 값을 확인해야된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그것을 이제 시작점과 i번째의 나눈 나머지를 이용해서 이어 붙여 괄호를 비교해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 String이 새 값을 쓸 때마다 새로운 메모리에 적용하는데, 처음에 생각한게 원초적으로 그냥 subString을 이용해서 첫번째 값을 직접 뒤로 이동시켰다. 그러다보니 값을 계속 새로운 메모리 적용해서 메모리 초과가 나왔다. 다음 생각해본 방법이 이제 이전에 풀었던 문제들에서 아이디어를 얻었는데 %을 이용한 탐색이다. 시작 문자기준해서 i++일 때 문자열의 길이만큼 반복하면 해당 문자열을 한번 탐색된다. 앞으로 이것을 많이 사용할 것 같다. java에서는 정말 String에 대한 것은 메모리를 생각하며 조심히 다뤄야겠다. 물론 stringBuilder를 이용해도 좋았을 것같다.&lt;/p&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/76502&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/76502&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/270</guid>
      <comments>https://chanheess.tistory.com/270#entry270comment</comments>
      <pubDate>Thu, 5 Dec 2024 12:23:51 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 할인행사</title>
      <link>https://chanheess.tistory.com/269</link>
      <description>&lt;pre id=&quot;code_1733283904741&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.HashMap;

class Solution {
    public int solution(String[] want, int[] number, String[] discount) {
        int answer = 0;
        HashMap&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        
        // 처음 10개의 아이템을 카운트
        for (int i = 0; i &amp;lt; 10; i++) {
            map.put(discount[i], map.getOrDefault(discount[i], 0) + 1);
        }
        
        //첫날 확인
        if(checkWant(map, want, number)) {
            answer++;
        }
        
        for(int i = 10; i &amp;lt; discount.length; i++) {
            map.put(discount[i - 10], map.get(discount[i - 10]) - 1);
            map.put(discount[i], map.getOrDefault(discount[i], 0) + 1);
            
            if(checkWant(map, want, number)) {
                answer++;
            }
        }
        
        return answer;
    }
    
    static boolean checkWant(HashMap&amp;lt;String, Integer&amp;gt; map,
                          String[] want, int[] number) {
        
        for(int i = 0; i &amp;lt; want.length; i++) {
            if(map.getOrDefault(want[i], 0) &amp;lt; number[i]) {
                return false;
            }
        }
        
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 조건&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;회원가입 일 수 10&lt;/li&gt;
&lt;li&gt;최대 수량 10&lt;/li&gt;
&lt;li&gt;회원등록시 정현이가 원하는 제품을 모두&lt;b&gt; 할인 받을 수 있는 회원등록 날짜의 총 일수&lt;/b&gt;를 return&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각한 풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 10개를 넣고 한칸씩 이동하면 될 것 같다고 생각하면서 풀었고 바로 해결되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. map으로 회원 기간동안의 물품 개수를 count 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 회원 기간동안에 원하는 물품들이 다 있는지 확인해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131127&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/131127&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733284269462&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131127&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Mn7GH/hyXGCf7nST/UgRG6BU2aj7TlQLBDPNCLk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bu2SSX/hyXGOnnHER/PROcrhYKVVnWn5flFkQKR1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131127&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Mn7GH/hyXGCf7nST/UgRG6BU2aj7TlQLBDPNCLk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bu2SSX/hyXGOnnHER/PROcrhYKVVnWn5flFkQKR1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/269</guid>
      <comments>https://chanheess.tistory.com/269#entry269comment</comments>
      <pubDate>Wed, 4 Dec 2024 12:52:23 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 백준 색종이 만들기</title>
      <link>https://chanheess.tistory.com/268</link>
      <description>&lt;pre id=&quot;code_1733197187857&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.Scanner;

public class Main {
    static int blue = 0, white = 0;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        int[][] list = new int[N][N];
        for (int i = 0; i &amp;lt; N; i++) {
            for (int j = 0; j &amp;lt; N; j++) {
                list[i][j] = sc.nextInt();
            }
        }
        sc.close();

        // 분할 정복 시작
        countPapers(list, 0, 0, N);

        System.out.println(white);
        System.out.println(blue);
    }

    static void countPapers(int[][] list, int x, int y, int size) {
        if (checkPaper(list, x, y, size)) {
            if (list[x][y] == 1) {
                blue++;
            } else {
                white++;
            }
            return;
        }

        int newSize = size / 2;
        countPapers(list, x, y, newSize);                   // 좌상단
        countPapers(list, x, y + newSize, newSize);         // 우상단
        countPapers(list, x + newSize, y, newSize);         // 좌하단
        countPapers(list, x + newSize, y + newSize, newSize); // 우하단
    }

    static boolean checkPaper(int[][] list, int x, int y, int size) {
        int color = list[x][y];
        for (int i = x; i &amp;lt; x + size; i++) {
            for (int j = y; j &amp;lt; y + size; j++) {
                if (list[i][j] != color) {
                    return false;
                }
            }
        }
        return true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 좌우상하 모서리로 구역을 큰부분부터 나눠서 계산한다. 8*8, 4*4, 2*2, 1*1 이런식으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그 구역을 확인했을 때 완료된다면 해당 구역은 더 이상 검사하지않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2630&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2630&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/268</guid>
      <comments>https://chanheess.tistory.com/268#entry268comment</comments>
      <pubDate>Tue, 3 Dec 2024 12:44:43 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 연속 부분 수열 합의 개수</title>
      <link>https://chanheess.tistory.com/267</link>
      <description>&lt;pre id=&quot;code_1733108554020&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//연속 부분 수열만을 다룬다.
import java.util.HashSet;
import java.util.Set;

class Solution {
    public int solution(int[] elements) {
        int answer = 0;
        int count = elements.length;
        int[] list = new int[count * 2];
        Set&amp;lt;Integer&amp;gt; numSet = new HashSet&amp;lt;&amp;gt;();
        
        for(int i = 0; i &amp;lt; count; i++) {
            list[i] = elements[i];
            list[i + count] = elements[i];
        }
        
        while(count &amp;gt; 0) {
            int sumLength = elements.length - count;
            
            for(int i = 0; i &amp;lt; elements.length; i++) {
                int sumNum = 0;
                
                for(int j = i; j &amp;lt;= i + sumLength; j++) {
                    sumNum += list[j];
                }
                
                numSet.add(sumNum);
            }
            
            count--;
        }
        
        answer = numSet.size();
        
        return answer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 연속된 수를 만들기 위해서 뒤에 같은 길이의 수들을 붙여준다. (이게 은근히 코딩테스트 문제에서 많이 쓰이는 것 같다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그리고 elements.length만큼만 사용해서 반복되기 이전의 수들까지만 사용한다. 73114 73114 일때 다시 뒤의 7부터 계산은 필요X&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 3의 길이일 때 7+3+1, 3+1+1, 1+1+4, 1+4+7, 4+7+3 이렇게 계산 11 5 6 12 14 이런 수들의 합을 set에 저장해서 값들이 겹치지 않게 모두 보관해서 set의 길이를 제출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;pre id=&quot;code_1733109334163&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sumNum += elements[j % elements.length];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;%으로 나머지 계산을 해서 list를 따로 만들지 않게 하는 방법도 있지만 코드가 간편해지지만 그만큼 계산을 많이하게되어 계산 시간이 길어진다. list를 따로 사용할 때는 메모리를 그만큼 사용한다. 근데 list의 크기가 최대 1000 * 2로 그리 크지 않기에 상관없어보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDAuEK/btsK2ubvcL1/sEuSke1GK5AyPC7qM1tagK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDAuEK/btsK2ubvcL1/sEuSke1GK5AyPC7qM1tagK/img.png&quot; data-alt=&quot;%계산으로 list를 대체했을 때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDAuEK/btsK2ubvcL1/sEuSke1GK5AyPC7qM1tagK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDAuEK%2FbtsK2ubvcL1%2FsEuSke1GK5AyPC7qM1tagK%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;300&quot; height=&quot;135&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;%계산으로 list를 대체했을 때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Qqy6/btsK3fLnAmI/v9mYHgCkq5fFGYl983LVl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Qqy6/btsK3fLnAmI/v9mYHgCkq5fFGYl983LVl0/img.png&quot; data-alt=&quot;list로 새로 만들어주었을때&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Qqy6/btsK3fLnAmI/v9mYHgCkq5fFGYl983LVl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Qqy6%2FbtsK3fLnAmI%2Fv9mYHgCkq5fFGYl983LVl0%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;300&quot; height=&quot;408&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;list로 새로 만들어주었을때&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라서 맞춰서 사용하는 편이 좋아보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131701&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/131701&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733108578764&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131701&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b4xvbk/hyXGyYvGEE/XOmuSDvYWosf2EbiLVS3Kk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bk7qj7/hyXGKYVZd2/rPIZPkiDl8PSwCFz1whwwK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131701&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131701&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b4xvbk/hyXGyYvGEE/XOmuSDvYWosf2EbiLVS3Kk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bk7qj7/hyXGKYVZd2/rPIZPkiDl8PSwCFz1whwwK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/267</guid>
      <comments>https://chanheess.tistory.com/267#entry267comment</comments>
      <pubDate>Mon, 2 Dec 2024 12:12:01 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 예상 대진표</title>
      <link>https://chanheess.tistory.com/266</link>
      <description>&lt;pre id=&quot;code_1732939476172&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
    public int solution(int n, int a, int b) {
        int answer = 0;
        int divideNum = n / 2;
        int middleNum = n / 2;
        
        while(divideNum &amp;gt; 1) {
            //한쪽에 몰려 있다면?
            if(middleNum &amp;lt; a &amp;amp;&amp;amp; middleNum &amp;lt; b){
                divideNum /= 2;
                middleNum = middleNum + divideNum;
            } 
            else if (middleNum + 1 &amp;gt; a &amp;amp;&amp;amp; middleNum + 1 &amp;gt; b) {
                divideNum /= 2;
                middleNum = middleNum - divideNum;
            }
            //한쪽에 몰려 있지 않다면?
            else {
                answer = (int) (Math.log(divideNum * 2) / Math.log(2));
                break;
            }
        }
        
        if(divideNum == 1) {
            answer = 1;
        }

        return answer;
    }
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀게된 의식의 흐름&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;list pair로 계속 한칸씩 비교? 비효율적이여보임&lt;/li&gt;
&lt;li&gt;일단 반은 갈라봤는데 반을 갈라놨을 때 무조건 나눠진 곳의 끝에 도달해야 마주침&lt;/li&gt;
&lt;li&gt;그러면 반을 갈라놓은 동일한 곳에 있을 때는 어떻게?&lt;/li&gt;
&lt;li&gt;확인해보니 해당 곳에서도 또 반을 가를 수 있는 패턴이 보임&lt;/li&gt;
&lt;li&gt;해당 패턴을 공식화&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 쪽에 쏠릴때마다 마추질 곳의 위치는 n / 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙의 위치는  n / 2 +- (n/2)/2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;divide +- divide / 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;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 규칙을 파악하고 천천히 그 조건들을 적어가면서 푸니까 비효율적인 방식에 대한 생각을 덜하게 되는 것 같다. 그런데 다른 사람이 비트연산으로 O(1)로 풀은 것을 보고 대단하다고 느꼈고 어떤 원리인지 찾아봤는데 신기했다. 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;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12985#&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12985#&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732940068029&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12985#&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/s40eT/hyXDa5G9cJ/VO4gvyMPJjHKM9HKzMl2ck/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/fbh7a/hyXGIsUOQa/5yuYFnbCkNJ5ekMtqqENZ1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12985#&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12985#&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/s40eT/hyXDa5G9cJ/VO4gvyMPJjHKM9HKzMl2ck/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/fbh7a/hyXGIsUOQa/5yuYFnbCkNJ5ekMtqqENZ1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/266</guid>
      <comments>https://chanheess.tistory.com/266#entry266comment</comments>
      <pubDate>Sat, 30 Nov 2024 13:13:50 +0900</pubDate>
    </item>
    <item>
      <title>라우팅과 라우팅 테이블의 이해</title>
      <link>https://chanheess.tistory.com/265</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의문은 AWS의 RDS를 추가해주면서 생기게 되었습니다. 만들면서 연결에 대한 부분을 설정해줄 때 내가 이것들이 정확하게 뭔지 알고 설정을 하는 것인가 아니면 그냥 검색해서 나온 정보들로 그냥 따라서 만드는 것인가 확인했습니다. 아무래도 기본을 정확하게 이해하고 직접 설정을 해주는 편이 좋다고 생각해서 필요한 정보들을 공부하게 되었습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라우팅이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우팅은 네트워크 장치(&lt;span style=&quot;text-align: left;&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;font-family: 'Nanum Gothic'; text-align: start;&quot;&gt;&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷을 목적지로 보낼 때 어느 IP주소로 보낼지 저장합니다. 그 정보들에는 여러가지 구성요소들이 있습니다. 주 목적은 네트워크 대상과 서브넷 마스크를 사용해 패킷의 목적지 네트워크 범위를 식별합니다. 라우팅 테이블은 패킷을 전달하기 전에 어떤 네트워크 범위로 패킷을 보낼지를 정리한 사전 설정된 경로표입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;라우팅 테이블의 구성 요소&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 네트워크 대상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목적지 네트워크의 IP주소를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서브넷 마스크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목적지 네트워크의 범위를 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 네트워크 주소와 호스트 주소를 구분하며, 범위 내의 IP주소를 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;예:&amp;nbsp;255.255.255.0은 네트워크 범위가&amp;nbsp;101.25.67.0 ~ 101.25.67.255임을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;- IPv4 주소는 32비트(4바이트)로 구성되며,&amp;nbsp;&lt;/span&gt;네트워크 부분&lt;span style=&quot;text-align: left;&quot;&gt;과&amp;nbsp;&lt;/span&gt;호스트 부분&lt;span style=&quot;text-align: left;&quot;&gt;으로 나뉩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732764998161&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;101.25.67.7    &amp;rarr; 01100101.00011001.01000011.00000111
255.255.255.0  &amp;rarr; 11111111.11111111.11111111.00000000
결과:            01100101.00011001.01000011.00000000
네트워크 주소:  101.25.67.0&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;서브넷 마스크에 대해서 위의 부분만 보고서는 정확하게 감이 오지 않아서 더 파고 들어봤습니다. 서브넷 마스크의 끝 번호는 0으로 사용하는데 어떻게 IP 주소를 특정해내는 것인지 궁금했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 주소 범위의 분리&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 주소(0번): 네트워크 식별, &lt;span style=&quot;text-align: left;&quot;&gt;한 네트워크 범위를 식별하기 위한 대표 주소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;호스트 주소(1~254): 네트워크 내에서 각각의 장치를 식별.&lt;/li&gt;
&lt;li&gt;브로드캐스트 주소(255): 네트워크 내 모든 장치로 패킷을 전송.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브넷 마스크의 255.255.255.0과 네트워크 대상의 IP주소를 &lt;span style=&quot;text-align: left;&quot;&gt;비트 연산(AND)을 이용해서 IP주소의 끝 번호를 0으로 만들게 하여 네트워크 주소인 0번으로 처리해주기 위함입니다. 이렇게 생성된 네트워크 주소는 해당 네트워크 범위를 식별하고, 패킷 전달 경로를 결정하는 데 사용됩니다.&lt;/span&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&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;3. 게이트웨이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패킷이 현재 네트워크를 벗어나 다른 네트워크로 전달될 때 거치는 중간 장치의 IP주소를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패킷이 직접 전달되지 못하는 경우, 다음으로 전달할 네트워크 장치를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 주로 라우터 또는 네트워크 장치의 내부 IP로 설정되며, 외부 네트워크로 가는 출입구 역할을 합니다.&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;4. 인터페이스&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;5. 메트릭&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. PC1의 패킷 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PC1(10.0.0.4)에서 목적지 IP가 101.25.67.7인 패킷을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 패킷에 목적지가 따로 적혀있고 라우팅 테이블을 참고해서 목적지의 범위 내에 있는 IP인지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 라우팅 테이블 조회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PC1은 자신의 라우팅 테이블을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 목적지&amp;nbsp;101.25.67.0/255.255.255.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;3. 게이트웨이로 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 라우팅 테이블에 따르면, 101.25.67.0/24 네트워크로 가는 패킷은 게이트웨이에 설정된 IP주소인 10.0.0.2로 보내야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인터페이스&amp;nbsp;eth3를 통해 패킷을 게이트웨이로 전달합니다.&lt;/p&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. Router A의 라우팅 테이블 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Router A는 패킷을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자신의 라우팅 테이블을 조회하여,&amp;nbsp;101.25.67.0&amp;nbsp;네트워크로 가는 경로를 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이 경우,&amp;nbsp;&lt;b&gt;직접 연결된 네트워크&lt;/b&gt;(Connected)로 확인되므로, PC3(101.25.67.7)로 직접 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 패킷 전달 완료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PC3(101.25.67.7)가 패킷을 수신합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷을 보내는 과정인 라우팅과 라우팅 테이블을 통해 패킷이 어떻게 외부로 이동하게 되는지를 알아보았습니다. 위 내용을 바탕으로 EC2와 RDS간의 연결을 설정한다거나하는 네트워크 설정 등에서 많은 도움을 얻어가셨으면 좋겠습니다. 감사합니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/265</guid>
      <comments>https://chanheess.tistory.com/265#entry265comment</comments>
      <pubDate>Thu, 28 Nov 2024 18:57:28 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 귤 고르기</title>
      <link>https://chanheess.tistory.com/264</link>
      <description>&lt;pre id=&quot;code_1732500273112&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//최소한의 종류, 최대한의 개수
import java.util.*;

class Solution {
    public int solution(int k, int[] tangerine) {
        int answer = 0;
        Map&amp;lt;Integer, Integer&amp;gt; counting = new HashMap&amp;lt;&amp;gt;();
        
        for(int i : tangerine) {
            counting.put(i, counting.getOrDefault(i, 0) + 1);
        }
        
        List&amp;lt;Integer&amp;gt; valueList = new ArrayList&amp;lt;&amp;gt;(counting.values());
        valueList.sort(Collections.reverseOrder());
        
        for (Integer value : valueList) {
            k -= value;
            answer++;
            
            if(k &amp;lt;= 0) {
                break;
            }
        }
        
        return answer; 
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Map으로 크기별 개수를 센다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. List로 개수별 sort를 한다. 제일 먼저 오는 크기가 뭔지는 상관없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 크기별 개수를 차감하면서 몇가지 크기의 종류가 들어왔는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알게 된 점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Java의 지역 변수는 초기화하지 않으면 사용할 수 없다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- c++에서는 Map에서 그냥 바로 map[x]++ 같은 형식의 계산이 되었는데 java에서는 있으면 값을 덮어쓰는 느낌으로 해주는 것을 알게 되었다.&lt;/p&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;자바 문법에 익숙해지려면 계속 코딩테스트를 해봐야겠다. 그래도 다행인건 c++에서 해봤던 것들을 검색해서 바로 찾을 수 있는게 다행인 것 같다.&lt;/p&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/138476?language=java&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/138476?language=java&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1732501113962&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/138476?language=java&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gRwIF/hyXDiIcPHm/KGqTq4z6ZBV9A6lQtTFzNk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/drBjOY/hyXC9YO0w4/rNQIMPGLxdY151yRnff8dk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/138476?language=java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/138476?language=java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gRwIF/hyXDiIcPHm/KGqTq4z6ZBV9A6lQtTFzNk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/drBjOY/hyXC9YO0w4/rNQIMPGLxdY151yRnff8dk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/264</guid>
      <comments>https://chanheess.tistory.com/264#entry264comment</comments>
      <pubDate>Mon, 25 Nov 2024 11:19:56 +0900</pubDate>
    </item>
    <item>
      <title>JPA 영속성 컨텍스트</title>
      <link>https://chanheess.tistory.com/263</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA강의를 모두 듣고 배운 내용을 정리하고 프로젝트에 적용해주기 위해서 영속성 컨텍스트에 대해서 작성해보고자합니다. 이전에는 그냥 jpaRepository.save로 저장된 객체를 사용하거나 find로 찾아온 엔티티 객체를 사용하면 데이터베이스에 있는 객체에 접근할 수 있겠거니 했었는데 강의를 듣고 그 원리에 대해서 알게 된 것 같습니다. 이번 포스팅을 통해서 트랜젝션과 영속성 컨텍스트가 엔티티 객체를 어떻게 관리하는지 원리를 파악해서 더 효율적으로 엔티티를 관리할 수 있게 되는 계기가 되었으면 좋겠습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;영속성 컨텍스트(Persistence Context)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;영속성 컨텍스트&lt;/b&gt;는 JPA(Java Persistence API)에서 엔티티 객체를 관리하는 일종의 저장소입니다. JPA는 애플리케이션과 데이터베이스 간의 상호작용을 관리하며, 영속성 컨텍스트는 데이터베이스에 저장되기 전의 임시 저장소 역할을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티의 생명 주기를 관리하며, 엔티티를 &lt;b&gt;영속성 관리 상태&lt;/b&gt;로 둠으로써 객체를 안전하게 데이터베이스에 저장하거나 수정, 삭제할 수 있습니다. 이를 통해 데이터 일관성을 유지하며, 데이터베이스와의 통신을 최적화할 수 있는 &lt;b&gt;메모리 상의 데이터 저장소 역할&lt;/b&gt;을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영속성 컨텍스트의 상태와 생명 주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영속성 컨텍스트는 데이터베이스와 상호작용할 4가지의 상태가 있습니다.&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;/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;size18&quot;&gt;영속&lt;/p&gt;
&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;size18&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;p data-ke-size=&quot;size18&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;p data-ke-size=&quot;size16&quot;&gt;예시코드&lt;/p&gt;
&lt;pre id=&quot;code_1730440546031&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private int age;

    // 기본 생성자, getter, setter 생략
}

// 비영속 상태
User user = new User();
user.setName(&quot;John&quot;);
user.setAge(30);

// 영속 상태 - 영속성 컨텍스트에 추가
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.persist(user); // User 엔티티가 영속성 컨텍스트에 등록되어 관리됨

// 커밋 전까지 변경사항 자동 감지 (Dirty Checking)
user.setAge(31); // 나이 변경

em.getTransaction().commit(); // 커밋 시 변경사항이 자동으로 데이터베이스에 반영됨
em.close();

// 준영속 상태 - 영속성 컨텍스트와 분리됨
em.detach(user);

// 삭제 상태
em.getTransaction().begin();
em.remove(user); // 데이터베이스에서 해당 엔티티가 삭제됨
em.getTransaction().commit();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 데이터 JPA를 통한 영속성 컨텍스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;@Transactional&lt;/span&gt; 어노테이션 내의 메소드에서 새로 생성한 객체를 &lt;span&gt;save&lt;/span&gt;하는 경우 해당 객체는 영속 상태로 전환됩니다. &lt;span&gt;EntityManager&lt;/span&gt;를 직접 다루지 않아도, &lt;span&gt;save&lt;/span&gt; 메소드를 통해 &lt;b&gt;스프링 데이터 JPA&lt;/b&gt;가 내부적으로 &lt;span&gt;EntityManager&lt;/span&gt;를 사용해 영속 상태로 관리하기 때문입니다. 즉, &lt;b&gt;스프링 데이터 JPA&lt;/b&gt;는 &lt;span&gt;EntityManager&lt;/span&gt;의 역할을 내부적으로 대신 처리하여 영속성 작업을 수행합니다. 즉 스프링 데이터 JPA를 통한 &lt;span&gt;save&lt;/span&gt;, &lt;span&gt;find&lt;/span&gt;, &lt;span&gt;delete&lt;/span&gt; 등의 메소드를 사용하면 객체가 영속성 컨텍스트에서 관리될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영속성 컨텍스트의 특징&lt;/h3&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;1. 1차 캐시&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영속성 컨텍스트는 1차 캐시를 제공하여 동일 트랜잭션 내에서 조회 시 데이터베이스 쿼리를 줄입니다. 첫 조회 후에는 메모리에 캐시된 값을 사용해 성능을 최적화합니다. 해당 1차 캐시를 사용하기 위해서는 조건이 필요합니다.&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;1차 캐시의 조건&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜젝션 안에서만 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 동일 트랜젝션 안에서만 같은 1차 캐시를 사용 가능하다. 그래서 트랜잭션 전파 속성이 &lt;span&gt;REQUIRED&lt;/span&gt;(기본값)으로 되어 있었다면 트랜젝션 전파가 이뤄진 같은 트랜젝션에서도 1차 캐시를 동일하게 사용가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 엔티티의 기본키를 기준으로 조회하고 반환값이 엔티티여야 해당 객체가 1차 캐시에 저장된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 변경 감지 (Dirty Checking)&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;p data-ke-size=&quot;size18&quot;&gt;영속성 컨텍스트의 함수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;flush()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;영속성 컨텍스트가 현재까지 변경된 엔티티를 &lt;b&gt;데이터베이스에 반영&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션이 끝나지 않더라도 변경 사항이 데이터베이스에 &lt;b&gt;즉시 적용&lt;/b&gt;되지만 &lt;b&gt;1차 캐시는 그대로 유지&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JpaRepository에서 flush가 선언되어 있어서 사용 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;clear()&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- &lt;/span&gt;영속성 컨텍스트가 &lt;b&gt;모든 엔티티를 1차 캐시에서 제거&lt;/b&gt;하고 영속성 컨텍스트를 초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이후에는 트랜잭션 내에서 동일한 엔티티를 조회하더라도 다시 데이터베이스에서 조회합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트랜잭션 중간에 &lt;span&gt;clear()&lt;/span&gt;를 사용하면 1차 캐시가 비워지기 때문에 영속성 컨텍스트가 관리하던 엔티티들이 &lt;b&gt;비영속 상태로 전환&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1730443606602&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PersistenceContext // EntityManager를 주입받음
    private EntityManager entityManager;

// 비영속 상태
User user = new User();
user.setName(&quot;John&quot;);
user.setAge(30);

// JpaRepository에서 flush 사용 예시
scheduleRepository.save(entity);
scheduleRepository.flush(); // 변경 사항을 즉시 데이터베이스에 반영

// 아직 1차캐시에 남아있어서 변경 감지가 됨
entity.setAge(20);

entityManager.clear();  // 1차 캐시 초기화 (트랜잭션 내)
entity.setAge(25);      // 더 이상 영속 상태가 아니므로 변경 감지로 하는 변경이 반영되지 않음
                        // setAge(20); 또한 반영되지 않음&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 지연 로딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계형 엔티티를 필요할 때만 조회하여 메모리와 성능을 최적화합니다. 연관관계 상태일 때만 사용 가능하며 기본 타입이&amp;nbsp;FetchType.EAGER가 되어 있을 수 있으므로 웬만하면 명시적으로 선언해주는게 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&amp;nbsp;User와&amp;nbsp;Order&amp;nbsp;관계에서&amp;nbsp;Order가 필요할 때만 조회합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1730445068516&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import jakarta.persistence.*;
import java.util.List;

@Entity
public class User {

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

    private String name;

    // 지연 로딩 설정
    @OneToMany(mappedBy = &quot;user&quot;, fetch = FetchType.LAZY)
    private List&amp;lt;Order&amp;gt; orders;

    // getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1730445098432&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void loadUserData() {
        // User만 조회 (Order는 아직 조회하지 않음)
        User user = userRepository.findById(1L).orElseThrow();

        System.out.println(&quot;User name: &quot; + user.getName());

        // 이 시점에서 지연 로딩이 수행되어 Order를 조회
        List&amp;lt;Order&amp;gt; orders = user.getOrders();
        System.out.println(&quot;Number of Orders: &quot; + orders.size());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;4.&lt;span&gt; &lt;/span&gt;&lt;/span&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;h3 data-ke-size=&quot;size23&quot;&gt;영속성 컨텍스트를 사용하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;효율적인 데이터베이스 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 객체 상태의 일관성과 성능 최적화를 통해 데이터베이스 자원을 효율적으로 사용하고 데이터베이스와의 통신 비용을 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;데이터 일관성 유지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 엔티티 상태를 트랜잭션 범위 내에서 일관되게 유지하여, 트랜잭션 종료 시점에 안전한 데이터 반영이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;코드의 간결화와 유지보수성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JPA가 제공하는 자동화된 상태 관리 기능을 활용하면 코드가 간결해지고, 복잡한 상태 변환 로직을 작성할 필요가 없어 유지보수가 용이합니다.&lt;/p&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>Backend Programming</category>
      <category>JPA</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/263</guid>
      <comments>https://chanheess.tistory.com/263#entry263comment</comments>
      <pubDate>Fri, 1 Nov 2024 16:39:58 +0900</pubDate>
    </item>
    <item>
      <title>빈은 어떻게 관리되어서 응답을 하는가?</title>
      <link>https://chanheess.tistory.com/262</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 빈(bean)과 의존성 주입, 그리고 여러 클라이언트 요청을 처리하는 과정에서 혼란스러울 수 있습니다. 특히, 여러 사용자의 요청이 하나의 빈으로 처리되는 방식과 그 이유가 궁금해질 수 있습니다. 이 포스팅에서는 이러한 궁금증들을 해결하기 위해, 빈이 어떻게 관리되고 여러 요청을 처리하는지 그 흐름에 대해 작성하고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;빈과 의존성 주입의 기본 개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빈(bean)&lt;/b&gt;이란 Spring 컨테이너에서 관리하는 객체를 말합니다. Spring 프레임워크는 애플리케이션에서 사용할 객체들을 빈으로 등록하고, 이들을 관리합니다. 빈은 기본적으로 &lt;b&gt;싱글톤&lt;/b&gt;으로 관리되며, 이는 애플리케이션 내에서 해당 빈의 인스턴스가 단 하나만 존재한다는 뜻입니다. 이러한 구조는 메모리 사용을 최적화하고 성능을 높이는 데 유리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의존성 주입&lt;/b&gt;(Dependency Injection, DI)은 애플리케이션의 객체들이 필요로 하는 다른 객체(의존성)를 직접 생성하지 않고, Spring이 주입해주는 방식입니다. 이를 통해 객체 간 결합도를 낮추고 코드의 유연성을 높일 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729336286059&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserService {
    private final UserRepository userRepository;

    // 직접 생성
    public UserService() {
        this.userRepository = new UserRepository();
    }

    public String getUserName() {
        return userRepository.findUserName();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;ce25d943-b084-48f4-b912-e55403f068bf&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserService가 UserRepository를 직접 생성한다면, UserRepository의 변경이 UserService에 직접적인 영향을 미치게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;위와 같은 방식은 UserService가 항상 UserRepository를 직접 생성해야 하기 때문에, 새로운 타입의 UserRepository를 사용할 때 UserService도 수정이 필요합니다. 이로 인해 결합도가 높아지고 유지보수가 어렵게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1729339461036&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Service 클래스 - 비즈니스 로직을 담당하는 클래스
@Service
public class UserService {
    private final UserRepository userRepository;

    // 생성자 주입을 통해 UserRepository 의존성을 주입
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserName() {
        return userRepository.findUserName();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같이 의존성 주입을 사용하면 UserService가 UserRepository의 구체적인 구현에 의존하지 않게 됩니다. 새로운 UserRepository 구현체가 추가되더라도 UserService는 변경되지 않아, 유지보수가 더 쉬워집니다.&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 빈설정&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;ApplicationContext&lt;/b&gt;가 생성되고, 빈들이 스캔되고 초기화됩니다. 이때, 기본적으로 빈은 &lt;b&gt;싱글톤&lt;/b&gt;으로 관리되어 하나의 인스턴스만 여러 요청을 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;ApplicationContext는 스프링 IoC 컨테이너로서 애플리케이션 내 객체들의 생명주기를 관리하고, 객체 간의 의존성을 해결해줍니다.&lt;/li&gt;
&lt;li&gt;이후, 톰캣 서버가 실행되어 클라이언트의 요청을 기다립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 요청 처리&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;DispatcherServlet&lt;/b&gt;에 의해 처리됩니다. DispatcherServlet은 요청을 적절한 컨트롤러에 전달하는 역할을 합니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;DispatcherServlet가 무엇일까?&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div data-message-model-slug=&quot;gpt-4o&quot; data-message-id=&quot;9d262fe6-8a4b-430d-9e9f-432f170434a8&quot; data-message-author-role=&quot;assistant&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DispatcherServlet은 Spring MVC 프레임워크의 핵심 요소로, &lt;b&gt;클라이언트 요청을 처리하고 적절한 컨트롤러에 전달하는 역할&lt;/b&gt;을 하는 프론트 컨트롤러입니다. 웹 애플리케이션에서 들어오는 HTTP 요청을 받아서 그 요청을 어떤 컨트롤러(controller)가 처리할지를 결정하고, 그 &lt;b&gt;결과를 뷰(view)에 전달하여 응답을 생성&lt;/b&gt;합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 응답&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비즈니스 로직이 처리된 후, 컨트롤러는 다시 서블릿을 통해 클라이언트에게 데이터를 응답합니다. 이 응답은 일반적으로 JSON 형식으로 클라이언트에게 전달됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;싱글톤 빈이 여러 요청을 처리하는 이유&lt;/h2&gt;
&lt;pre id=&quot;code_1729341057905&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Stateless Service - 상태를 유지하지 않는 서비스
@Service
public class StatelessService {

    // 상태를 저장하지 않고 요청마다 처리
    public int process(int value) {
        return value * 2;  // 간단한 예시로 입력값을 두 배로 만드는 처리
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 프레임워크에서 빈(bean)은 기본적으로 싱글톤(Singleton)으로 관리됩니다. 이는 애플리케이션 내에서 &lt;b&gt;단 하나의 인스턴스&lt;/b&gt;만 생성되고, 해당 인스턴스가 모든 요청에 대해 재사용된다는 것을 의미합니다. 이러한 구조는 메모리 사용을 최적화하고 성능을 향상시키는 데 중요한 역할을 합니다. 싱글톤 빈이 여러 요청을 처리할 수 있는 이유는 그 자체가 상태를 유지하지 않는 객체이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 요청에 대해 &lt;b&gt;고유한 데이터나 상태&lt;/b&gt;를 유지하지 않기 때문에, 여러 요청에 대해서도 동일한 방식으로 처리할 수 있습니다. 즉, 각 요청마다 새로운 상태를 저장하거나 관리하지 않고, 입력으로 주어진 데이터를 처리하여 결과를 반환하는 형태입니다. 이러한 방식의 로직은 비즈니스 계층(Service)에서 주로 많이 사용되며, 여러 클라이언트의 요청을 동시에 처리하더라도 데이터를 공유하지 않으므로 안전하게 재사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729341085272&quot; class=&quot;cs&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Stateful Service - 상태를 유지하는 서비스
@Service
public class StatefulService {

    // 상태를 유지하는 필드
    private int state;

    // 상태를 변경하는 메소드
    public void saveState(int value) {
        this.state = value;  // 상태를 저장
    }

    // 현재 상태를 반환하는 메소드
    public int getState() {
        return this.state;  // 저장된 상태를 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 상태를 유지하는 객체의 경우, 여러 요청이 동일한 인스턴스에 접근할 때 데이터 충돌이 발생할 수 있습니다. 예를 들어, 각 사용자의 로그인 정보나 세션 데이터가 한 인스턴스에 저장되면, 한 사용자의 상태가 다른 사용자에게 영향을 미치는 문제가 발생할 수 있습니다. 만약 요청마다 또는 세션마다 서로 다른 상태를 유지해야 하는 경우라면, 다음과 같은 스코프를 고려해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1729341105377&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Stateful Service - 상태를 유지하는 요청 범위 서비스
@Service
@RequestScope  // 각 HTTP 요청마다 새로운 인스턴스 생성
public class StatefulService {

    private int state;

    public void saveState(int value) {
        this.state = value;  // 상태를 저장
    }

    public int getState() {
        return this.state;  // 저장된 상태를 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;@RequestScope&lt;/b&gt;: 각 HTTP 요청마다 새로운 빈 인스턴스를 생성하여 요청마다 독립된 상태를 유지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@SessionScope&lt;/b&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;Spring의 빈은 기본적으로 &lt;b&gt;싱글톤&lt;/b&gt;으로 관리되며, 이를 통해 메모리 효율성과 성능을 높일 수 있습니다. 다만, 상태를 유지해야 하는 경우에는 싱글톤 빈 대신 &lt;b&gt;@RequestScope&lt;/b&gt;나 &lt;b&gt;@SessionScope&lt;/b&gt;를 사용하여 요청별 또는 세션별로 독립된 객체를 생성할 수 있습니다. 이러한 빈과 의존성 주입의 작동 원리를 이해하면, 애플리케이션 설계 시 결합도를 낮추고 유지보수를 쉽게 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 통해 Spring의 빈과 의존성 주입, 그리고 요청 처리의 흐름을 이해하고, 실무에서 이러한 개념들을 어떻게 적용할 수 있는지 고민할 수 있기를 바랍니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/262</guid>
      <comments>https://chanheess.tistory.com/262#entry262comment</comments>
      <pubDate>Sun, 20 Oct 2024 02:28:22 +0900</pubDate>
    </item>
    <item>
      <title>HTTP란?</title>
      <link>https://chanheess.tistory.com/261</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTP란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP(Hypertext Transfer Protocol)는 웹에서 데이터를 주고받기 위한 프로토콜입니다. 클라이언트(예: 웹 브라우저)가 서버에 요청을 보내고, 서버는 클라이언트에게 응답을 보냄으로써 웹 페이지, 이미지, 동영상 등을 전송하는 구조입니다. HTTP는 비상태적(stateless)이며, 연결이 끊어진 후에는 서버가 클라이언트의 이전 상태를 기억하지 못한다는 특징이 있습니다. 이를 보완하기 위해 세션이나 쿠키 같은 기술이 사용됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP의 주요 특징&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;비상태성 (Stateless)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 비상태적이기 때문에, 각 요청은 독립적입니다. 서버는 이전 요청이나 응답에 대한 상태를 기억하지 않습니다. 클라이언트가 같은 웹사이트에 재접속할 때도 서버는 클라이언트의 이전 상태를 인식하지 못합니다.&lt;/li&gt;
&lt;li&gt;이 때문에 로그인 상태 유지 같은 기능을 위해 쿠키나 세션 관리가 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;b&gt;리퀘스트(Request)&lt;/b&gt;: 클라이언트가 서버에 보내는 요청입니다. 여기에는 메서드(GET, POST, PUT, DELETE 등), URL, 헤더 정보 등이 포함됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리스폰스(Response)&lt;/b&gt;: 서버가 클라이언트에 보내는 응답입니다. 응답 코드(200, 404 등), 헤더, 바디에 콘텐츠가 포함됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTP 메서드&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;GET&lt;/b&gt;: 서버에서 데이터를 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;POST&lt;/b&gt;: 서버에 데이터를 제출하고 처리 요청을 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PUT&lt;/b&gt;: 서버에 리소스를 업로드하거나 수정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DELETE&lt;/b&gt;: 서버에서 리소스를 삭제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTP 상태 코드&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1xx (정보): 요청이 수신되었으며 처리 중임을 나타냅니다.&lt;/li&gt;
&lt;li&gt;2xx (성공): 요청이 성공적으로 처리되었습니다.&lt;/li&gt;
&lt;li&gt;3xx (리다이렉션): 요청된 리소스가 이동되었으며, 클라이언트가 다른 URL을 통해 접근해야 함을 나타냅니다.&lt;/li&gt;
&lt;li&gt;4xx (클라이언트 오류): 클라이언트의 요청에 오류가 있음을 나타냅니다 (예: 404 Not Found).&lt;/li&gt;
&lt;li&gt;5xx (서버 오류): 서버에서 요청을 처리하는 중 오류가 발생했음을 나타냅니다 (예: 500 Internal Server Error).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비보안성&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP는 데이터를 암호화하지 않으므로, 네트워크 중간에서 데이터를 쉽게 가로챌 수 있습니다. 이를 보완하기 위해 HTTPS(SSL/TLS를 통한 HTTP)가 사용됩니다. HTTPS는 데이터를 암호화하여 보안을 강화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP 요청과 응답의 구조&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;HTTP 요청&lt;/b&gt;: 클라이언트가 서버에 보내는 데이터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메서드&lt;/b&gt;: HTTP 요청의 타입(GET, POST 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;URL&lt;/b&gt;: 요청하는 리소스의 주소&lt;/li&gt;
&lt;li&gt;&lt;b&gt;헤더&lt;/b&gt;: 요청에 대한 추가 정보를 담고 있는 키-값 쌍&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바디&lt;/b&gt;: 요청의 본문 (POST 같은 경우에 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTP 응답&lt;/b&gt;: 서버가 클라이언트에게 보내는 데이터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상태 코드&lt;/b&gt;: 요청에 대한 응답 상태 (200 OK, 404 Not Found 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;헤더&lt;/b&gt;: 응답에 대한 추가 정보 (콘텐츠 타입, 쿠키 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바디&lt;/b&gt;: 응답의 실제 데이터 (HTML, JSON 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP의 동작 원리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트가 요청을 보냄&lt;/b&gt;: 사용자가 브라우저에 URL을 입력하면 브라우저는 서버에 요청을 보냅니다. 이 요청에는 사용자의 브라우저 정보, 세션 쿠키 등의 정보가 포함될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&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;클라이언트가 응답을 받음&lt;/b&gt;: 클라이언트는 서버로부터 받은 데이터를 브라우저에 렌더링하여 사용자에게 표시합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTTP의 보안 문제와 HTTPS의 필요성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP는 데이터를 암호화하지 않기 때문에 민감한 정보(로그인 정보, 결제 정보 등)가 노출될 위험이 큽니다. HTTPS는 이러한 데이터를 보호하기 위해 SSL/TLS 프로토콜을 사용하여 데이터를 암호화합니다. 이로 인해 클라이언트와 서버 간의 통신을 안전하게 유지할 수 있으며, 데이터를 도청하거나 중간에 변조하는 것을 방지할 수 있습니다.&lt;/p&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;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/HTTP/Overview&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728115010039&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;HTTP 개요 - HTTP | MDN&quot; data-og-description=&quot;HTTP는 HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜입니다. HTTP는 웹에서 이루어지는 모든 데이터 교환의 기초이며, 클라이언트-서버 프로토콜이기도 합니다. 클라이언트-서버&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Overview&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Overview&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SD9hX/hyXd7zTopB/RavYSwWVR6G8JQwYrJMZ41/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Overview&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SD9hX/hyXd7zTopB/RavYSwWVR6G8JQwYrJMZ41/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&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;HTTP 개요 - HTTP | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;HTTP는 HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜입니다. HTTP는 웹에서 이루어지는 모든 데이터 교환의 기초이며, 클라이언트-서버 프로토콜이기도 합니다. 클라이언트-서버&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/261</guid>
      <comments>https://chanheess.tistory.com/261#entry261comment</comments>
      <pubDate>Sat, 5 Oct 2024 17:28:15 +0900</pubDate>
    </item>
    <item>
      <title>인터넷의 작동 원리</title>
      <link>https://chanheess.tistory.com/260</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;인터넷이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터넷&lt;/b&gt;은 전 세계 컴퓨터들이 서로 연결된 거대한 네트워크입니다. 각 컴퓨터, 서버, 모바일 기기 등은 인터넷을 통해 서로 데이터를 주고받을 수 있습니다. 인터넷은 여러 네트워크가 결합된 네트워크의 집합체로, 이 네트워크들은 다양한 장치와 프로토콜을 통해 연결됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;IP 주소와 도메인 이름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서 모든 장치는 &lt;b&gt;IP 주소&lt;/b&gt;라는 고유한 번호를 가지고 있습니다. 이 IP 주소는 각 장치를 식별하며, 데이터를 보낼 곳을 지정하는 역할을 합니다. 예를 들어, 우리가 웹사이트를 방문할 때, 웹사이트의 서버도 고유의 IP 주소를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 숫자로 이루어진 IP 주소를 기억하는 것은 매우 어렵기 때문에 &lt;b&gt;도메인 이름&lt;/b&gt;이라는 인간이 기억하기 쉬운 이름 체계를 사용합니다. 예를 들어, www.google.com 같은 도메인 이름은 그 뒤에 있는 IP 주소로 변환되어 사용됩니다. 이 변환 작업은 &lt;b&gt;DNS(Domain Name System)&lt;/b&gt; 서버에 의해 처리됩니다. DNS 서버는 도메인 이름을 해당 IP 주소로 변환해주어 우리가 쉽게 웹사이트에 접속할 수 있도록 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터 전송의 기본: 패킷&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서 데이터는 작은 조각들로 나누어져 전송됩니다. 이 조각을 **패킷(packet)**이라고 합니다. 예를 들어, 우리가 웹페이지를 열 때 그 웹페이지의 데이터는 수많은 패킷으로 쪼개져 네트워크를 통해 전송됩니다. 각각의 패킷은 발신지(IP 주소)와 수신지(IP 주소), 그리고 전송하려는 데이터의 일부를 포함하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패킷 기반 통신 덕분에 인터넷은 빠르고 안정적으로 데이터를 전송할 수 있습니다. 패킷들은 서로 다른 경로로 이동하고, 목적지에서 다시 조립되어 원래의 데이터를 복원합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TCP/IP 프로토콜&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷은 &lt;b&gt;TCP/IP(Transmission Control Protocol/Internet Protocol)&lt;/b&gt; 라는 프로토콜을 기반으로 작동합니다. 이는 인터넷에서 데이터가 어떻게 전송되고, 어떻게 조립되며, 어떻게 도착하는지에 대한 규칙을 정의한 프로토콜입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;IP(Internet Protocol)&lt;/b&gt;: IP는 패킷이 올바른 목적지에 도착하도록 경로를 결정하는 역할을 합니다. 각각의 패킷은 IP 주소를 기반으로 경로를 선택하고, 다양한 네트워크를 통해 전송됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TCP(Transmission Control Protocol)&lt;/b&gt;: TCP는 데이터가 손실되지 않고, 순서대로 도착하도록 보장하는 프로토콜입니다. 만약 패킷 중 일부가 손실되거나 도착하지 않으면, TCP는 해당 패킷을 다시 요청하여 완전한 데이터를 보장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;IP의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비신뢰성&lt;/b&gt;: 패킷을 목적지로 보내지만, 손실이나 순서 보장을 하지 않음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비연결성&lt;/b&gt;: 데이터를 전송하기 전에 연결을 설정하지 않음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경로 설정&lt;/b&gt;: 각 패킷에 송신자와 수신자의 IP 주소를 부여하여 목적지로 전달.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TCP의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;신뢰성&lt;/b&gt;: 데이터가 손실되지 않고 순차적으로 도착하도록 보장.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연결 지향적&lt;/b&gt;: &lt;b&gt;3-way handshake&lt;/b&gt;로 송신자와 수신자 간 연결을 설정한 후 데이터 전송.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재전송 및 흐름 제어&lt;/b&gt;: 손실된 패킷을 다시 요청하고, 네트워크 혼잡을 방지하기 위해 전송 속도 조절.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-way handshake가 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-way Handshake&lt;/b&gt;는 TCP에서 신뢰성 있는 연결을 설정하기 위한 &lt;b&gt;3단계&lt;/b&gt;의 과정입니다. 송신자와 수신자가 데이터를 안전하게 주고받을 수 있도록, 통신을 시작하기 전에 양측 간에 연결을 확립하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트 요청 (SYN)&lt;/b&gt;: 클라이언트가 서버에 연결을 요청하는 신호(SYN 패킷)를 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 응답 (SYN-ACK)&lt;/b&gt;: 서버는 클라이언트의 요청을 받아들이며, 자신의 시퀀스 번호와 함께 클라이언트의 요청에 대한 응답(SYN-ACK)을 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 확인 (ACK)&lt;/b&gt;: 클라이언트는 서버의 응답을 확인한 후, 수락을 확인했다는 신호(ACK)를 서버에 다시 보냅니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라우터와 스위치의 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷이 제대로 작동하려면 데이터가 목적지까지 효율적으로 전송되어야 합니다. 이를 위해 &lt;b&gt;라우터&lt;/b&gt;와 &lt;b&gt;스위치&lt;/b&gt;라는 네트워크 장치가 사용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;라우터(Router)&lt;/b&gt;: 라우터는 패킷의 경로를 결정하는 장치입니다. 라우터는 패킷이 목적지에 도달할 수 있도록 네트워크 사이의 경로를 선택하고 패킷을 전달합니다. 여러 네트워크를 연결하는 역할을 하며, 인터넷의 중요한 부분입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스위치(Switch)&lt;/b&gt;: 스위치는 같은 네트워크 내에서 데이터를 전달하는 장치입니다. 예를 들어, 같은 사무실 내 여러 컴퓨터가 데이터를 주고받을 때 스위치가 그 역할을 합니다. 스위치는 목적지 MAC 주소를 기반으로 데이터를 전달합니다.&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;다음은 우리가 웹사이트에 접속할 때 데이터가 전송되는 과정을 단계별로 설명한 것입니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;웹사이트 요청&lt;/b&gt;: 사용자가 브라우저에 URL을 입력하고 엔터를 누릅니다. 이때 도메인 이름이 입력되며, 브라우저는 이 도메인의 IP 주소를 DNS 서버에 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DNS 요청&lt;/b&gt;: DNS 서버는 도메인 이름을 해당 IP 주소로 변환하여 브라우저에 응답합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버로 요청 전송&lt;/b&gt;: 브라우저는 웹 서버에 HTTP/HTTPS 요청을 보냅니다. 요청은 패킷으로 나누어져 인터넷을 통해 서버로 전송됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버에서 응답 전송&lt;/b&gt;: 서버는 요청을 처리한 후, 결과를 다시 패킷으로 나누어 클라이언트(사용자의 브라우저)로 전송합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;브라우저에서 페이지 렌더링&lt;/b&gt;: 클라이언트는 받은 패킷을 조립하여 웹 페이지를 렌더링하고 화면에 표시합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTP와 HTTPS의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서 데이터를 주고받는 방식 중 하나는 &lt;b&gt;HTTP&lt;/b&gt;(Hypertext Transfer Protocol)입니다. 이는 웹사이트를 열 때 사용하는 프로토콜입니다. HTTP는 데이터를 암호화하지 않기 때문에 중간에서 데이터가 탈취될 위험이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTPS&lt;/b&gt;는 이러한 문제를 해결하기 위해 데이터를 암호화하여 전송하는 방식입니다. HTTPS는 &lt;b&gt;SSL/TLS&lt;/b&gt;라는 보안 프로토콜을 사용하여 데이터를 암호화하고, 중간에 누군가가 데이터를 탈취하더라도 내용을 확인할 수 없도록 보호합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인터넷 서비스 제공자(ISP)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 인터넷에 연결하려면 인터넷 서비스 제공자(ISP, Internet Service Provider)를 통해야 합니다. ISP는 각 가정이나 회사에 인터넷 연결을 제공합니다. ISP는 전 세계 네트워크와 연결된 중앙 서버와 네트워크를 운영하며, 이를 통해 사용자들이 인터넷에 접속할 수 있게 해줍니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/260</guid>
      <comments>https://chanheess.tistory.com/260#entry260comment</comments>
      <pubDate>Fri, 4 Oct 2024 23:35:54 +0900</pubDate>
    </item>
    <item>
      <title>HTTPS에 대해서</title>
      <link>https://chanheess.tistory.com/259</link>
      <description>&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;HTTP와 HTTPS의 차이점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;HTTP&lt;/b&gt;: HTTP는 데이터를 암호화하지 않습니다. 즉, 클라이언트(브라우저)와 서버 간에 주고받는 데이터가 평문(Plain Text) 상태로 전송됩니다. 따라서 네트워크 상에서 중간에 누군가가 데이터를 가로챌 경우, 내용을 쉽게 읽을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTPS&lt;/b&gt;: HTTPS는 데이터를&amp;nbsp;&lt;b&gt;암호화&lt;/b&gt;합니다. HTTPS는 SSL/TLS(보안 소켓 계층 또는 전송 계층 보안) 프로토콜을 사용하여&amp;nbsp;&lt;b&gt;브라우저와 서버 간의 데이터를 암호화&lt;/b&gt;한 후 전송합니다. 이를 통해 중간에서 누군가가 데이터를 가로채더라도 내용을 읽을 수 없도록 보호됩니다. 이를&amp;nbsp;&lt;b&gt;종단 간 암호화&lt;/b&gt;라고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그렇다면 HTTPS를 사용하는 목적은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 요청이 안전하게 전송되도록 보장하기 위해서 사용되고, 서버는 따로 요청을 보낸 사용자가 인증된 사용자임을 JWT같은 인증 과정을 통해 확인해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;종단 간 암호화란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종단간 암호화는 메시지를&amp;nbsp;&lt;b&gt;처음부터 끝까지 평문으로 저장하지 않고 암호화하는 안전한 통신 방법&lt;/b&gt;입니다. 클라이언트에서 메시지를 전송하는 단계부터 최종적으로 수신자에 전달되는 단계까지 메시지를 암호화하기 때문에 'End to End Encryption(E2EE)'으로 불리기도 합니다.&amp;nbsp;종단간 암호화 방법은 메시지를 복호화할 수 있는 키를 서버에 저장하지 않고, 수신자 장치에서 저장합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SSL/TLS란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS는&amp;nbsp;&lt;b&gt;SSL(보안 소켓 계층)&lt;/b&gt;&amp;nbsp;또는&amp;nbsp;&lt;b&gt;TLS(전송 계층 보안)&lt;/b&gt;&amp;nbsp;프로토콜을 사용하여 데이터를 암호화합니다. 보안 소켓 계층(SSL)은 네트워크상의 두 디바이스 또는 애플리케이션 간에 보안 연결을 생성하는 통신 프로토콜 또는 규칙 세트입니다. 여기서 SSL은 이전 버전이며, 현재는&amp;nbsp;&lt;b&gt;TLS&lt;/b&gt;가 더 일반적으로 사용되고 있습니다. TLS는 데이터 전송을 암호화하여 통신 중에 제3자가 데이터를 읽거나 변조하지 못하도록 합니다.&amp;nbsp;이를 통해 SSL/TLS는 다음과 같은 역할과 보호 방식을 구현합니다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL/TLS의 주요 역할&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 인증&lt;/b&gt;: SSL/TLS는 서버의 신원을 확인합니다. 클라이언트(브라우저)는 서버가 제공한 SSL/TLS 인증서를 확인하여 서버가 신뢰할 수 있는지 검증합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;핸드셰이크&lt;/b&gt;: SSL/TLS 핸드셰이크는 클라이언트와 서버가 안전한 통신을 할 수 있도록, 세션 키를 협상하는 과정을 담당합니다. 이 과정에서 서버 인증이 이루어지며, 클라이언트와 서버 간에 안전한 연결을 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 암호화&lt;/b&gt;: 핸드셰이크 과정에서 협상된 세션 키를 사용해 클라이언트와 서버 간의 데이터를 암호화하여, 제3자가 데이터를 읽거나 도청하지 못하게 보호합니다. 이 세션 키는 대칭 암호화를 위한 키로, 동일한 키로 데이터를 암호화하고 복호화합니다. 비대칭 암호화는 연산이 복잡하므로, 핸드셰이크 후 대칭 암호화를 사용하여 빠르고 안전하게 데이터를 주고받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 무결성 보장&lt;/b&gt;: SSL/TLS는 전송 중 데이터가 변조되지 않도록 해시 함수를 사용하여 데이터의 무결성을 보장합니다. 이를 통해 클라이언트와 서버는 서로 주고받는 데이터가 중간에서 수정되지 않았음을 확인할 수 있습니다.&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;HTTPS를 사용하려면 SSL/TLS 인증서를 발급받아야 합니다. 이때, 인증서를 발급받으려면&amp;nbsp;&lt;b&gt;도메인 소유권을 확인&lt;/b&gt;해야 합니다. 인증 기관(CA)은 도메인 이름을 기준으로 인증서를 발급하며, 서버의 신원을 확인하는 중요한 절차로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&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;인증서를 신청하려면&amp;nbsp;&lt;b&gt;도메인 이름&lt;/b&gt;이 필요합니다. 이는 사용자가 접속하는 웹사이트 주소를 의미합니다.&lt;/li&gt;
&lt;li&gt;인증 기관(CA)은 도메인 소유 여부를 확인하기 위해 도메인에 특정한 파일을 업로드하거나, 지정된 이메일 주소(예: &lt;a href=&quot;mailto:admin@yourdomain.com&quot;&gt;admin@yourdomain.com&lt;/a&gt;)로 확인 요청을 전송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&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;도메인이 없는 경우,&amp;nbsp;&lt;b&gt;도메인 등록 대행 서비스&lt;/b&gt;(예: AWS Route53, Google Domains, GoDaddy 등)를 통해 도메인을 구매해야 합니다. 물론 무료로 도메인를 구할 수 있는 곳도 있습니다.&lt;/li&gt;
&lt;li&gt;구매한 도메인에 대한 DNS 설정을 통해 인증서를 발급받을 수 있도록 CA에서 요구하는 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS 설정 과정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;인증서 발급&lt;/b&gt;: HTTPS를 사용하려면 SSL/TLS 인증서를 발급받아야 합니다. 인증서에는&amp;nbsp;&lt;b&gt;서버의 도메인 이름과 공개 키 정보가 포함&lt;/b&gt;됩니다. 이를 위해&amp;nbsp;&lt;b&gt;CSR(Certificate Signing Request)&lt;/b&gt;&amp;nbsp;파일을 생성한 후, 인증 기관(CA)에 인증서를 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증 기관(CA)에 신청&lt;/b&gt;: CSR을 제출하면, CA는 서버의 소유 여부를 확인한 후 인증서를 발급합니다. 인증서는 서버의 신원을 확인하고, 데이터를 암호화하는 데 필요한 정보를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증서 설치&lt;/b&gt;: 발급받은 인증서를 웹 서버에 설치합니다. 이 인증서를 통해 브라우저는 서버가 신뢰할 수 있는지 확인하고, 암호화된 연결을 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HTTPS 설정&lt;/b&gt;: 웹 서버(Nginx, Apache 등)에서 HTTPS를 설정한 후, HTTP 요청을 HTTPS로 리디렉션하는 방법을 설정합니다. 이를 통해 사용자는 보안된 HTTPS 프로토콜로만 웹사이트에 접속할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS 요청 및 응답 흐름의 단계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS 프로토콜의 요청 및 응답 흐름은 HTTP와 같은 방식으로 시작됩니다. 단, 중간 단계인 1.5단계를 사용해 두 흐름을 구분합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: 탐색 및 시작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 브라우저에 웹 주소를 입력하거나 이메일 또는 기타 통신에 포함된 링크를 클릭합니다. 주소에는 URL(Uniform Resource Locator)이 포함되어 있습니다. URL을 나타내는 문서를 가져올 때 HTTP를 사용함을 브라우저에 알리기 위해 URL에 HTTP가 포함됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.5단계: 암호화 동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계 1.5에서는 브라우저와 웹 서버가 '암호화 동작'을 수행합니다. 여기에 암호화된 메시지의 교환이 포함됩니다. 이 단계를 수행하려면 CA(인증 기관)를 통해 생성된 웹 서버의 공개 및 비공개 키 쌍을 사용해 TLS 프로토콜에서 복잡한 암호화 기능을 수행해야 합니다. 이 단계에서는 웹사이트를 인증하고 클라이언트용 키와 서버용 키 두 개(세션 키)를 새로 만듭니다. 이러한 세션 키는 메시지를 암호화하고 해독하는 데 사용됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 클라이언트가 서버로 HTTP 요청 메시지 전송&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2단계는 HTTP 2단계 요청 및 응답 흐름과 동일합니다. 클라이언트(예: 브라우저)는 웹 서버로 리디렉션되는 요청 메시지를 작성합니다. 메시지에는 요청 엔터티와 같은 요청에 대한 추가 정보가 포함됩니다. 그러나 HTTP와 달리 요청 메시지를 작성한 후 브라우저에서 메시지를 보내기 전에 HTTP(S) 요청은 세션 키를 사용해 메시지를 암호화해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: 웹 서버가 HTTPS 응답을 클라이언트로 다시 전송&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청이 수신되면 웹 서버는 세션 키를 사용해 메시지를 해독하고 읽습니다. 그런 다음 웹 서버는 응답 메시지를 작성하고, 세션 키를 사용해 암호화한 후 브라우저로 다시 보냅니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4단계: 브라우저에서 메시지 렌더링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암호화된 메시지를 수신하면 브라우저는 세션 키를 사용해 암호를 해독하고 메시지를 읽습니다. 이 단계의 마지막 부분에서는 브라우저에서 응답 메시지를 렌더링하고 브라우저에 웹페이지를 표시합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS 보안 강화 및 성능 최적화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HSTS(HTTP Strict Transport Security)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HSTS는 웹 브라우저가 웹사이트에 HTTPS로만 접속하도록 강제하는 보안 정책입니다. 이를 통해 사용자가 실수로 HTTP로 접속하는 것을 방지하고, 항상 HTTPS를 통해 안전한 연결을 유지할 수 있습니다. 서버는 HSTS 헤더를 사용하여 클라이언트에게 HTTPS로만 접속할 것을 지시합니다. 이를 통해 중간자 공격(MITM) 등의 위험을 줄이고, 웹사이트의 보안을 지속적으로 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세션 키 최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 키 교환 시 최신 암호화 알고리즘(TLS 1.3 등)을 사용하면 성능을 크게 향상시킬 수 있습니다. TLS 1.3은 빠르고 안전한 연결을 제공하며, 핸드셰이크 과정을 간소화해 성능 최적화를 돕습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP/2 지원&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS와 함께 HTTP/2를 사용하면 성능을 더욱 최적화할 수 있습니다. HTTP/2는 다중 요청을 하나의 연결에서 처리할 수 있어 페이지 로딩 속도를 개선하고, 네트워크 자원의 효율적인 사용을 가능하게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OCSP 스테이플링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS에서는 서버의 인증서를 클라이언트가 확인해야 하는데, OCSP 스테이플링은 이 과정을 최적화하는 기술입니다. 이를 통해 인증서 검증 시간을 단축하고, 웹사이트의 응답 속도를 향상시킬 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS는 현대 웹 보안에서 매우 중요한 역할을 합니다. 특히 API 요청 및 민감한 데이터 전송 시, HTTPS는 데이터를 보호하고 중간에서 탈취되는 것을 방지하는 중요한 수단입니다. SSL/TLS를 통해 서버의 신원을 인증하고, 데이터를 안전하게 암호화하며, 데이터 무결성을 보장하는 것이 HTTPS의 핵심입니다. SSL/TLS 인증서를 발급받고, 웹 서버에 설치하여 HTTPS를 구현하는 과정을 통해 웹 애플리케이션의 보안을 강화할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/compare/the-difference-between-https-and-http/&quot;&gt;https://aws.amazon.com/ko/compare/the-difference-between-https-and-http/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/what-is/ssl-certificate/&quot;&gt;https://aws.amazon.com/what-is/ssl-certificate/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.akamai.com/ko/glossary/what-is-https&quot;&gt;https://www.akamai.com/ko/glossary/what-is-https&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/e2ee&quot;&gt;https://docs.tosspayments.com/resources/glossary/e2ee&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/259</guid>
      <comments>https://chanheess.tistory.com/259#entry259comment</comments>
      <pubDate>Sun, 29 Sep 2024 22:23:04 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security, JWT을 사용하여 로그인 서비스 만들기</title>
      <link>https://chanheess.tistory.com/258</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 프로젝트에서 회원가입 기능을 먼저 구현한 후, 로그인 기능을 추가하기 위해 공부하며 작성했습니다. 그 중에서도 로그인할 때 회원을 어떻게 인증할 지에 대해서 검색을 시작해서 Spring Security를 사용하며 JWT를 사용한 인증을 선택했습니다. 특히 JWT의 공부를 시작할 때 버전으로 인해 검색에 어려움이 있었는데 아래의 버전을 사용했으니 참고하시면 되겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;java 17, SpringBoot 3.3.1, Spring 6.1.10, JWT 0.11.5&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;JWT에 대해서&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;JWT란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT (JSON Web Token)는 인증과 정보를 안전하게 전송하기 위해 사용하는 개방형 표준 (RFC 7519)입니다. JWT는 주로 세 가지 구성 요소로 이루어져 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Header&lt;/b&gt;: 토큰의 유형과 사용된 서명 알고리즘을 지정합니다. 일반적으로 {&quot;alg&quot;: &quot;HS256&quot;, &quot;typ&quot;: &quot;JWT&quot;}와 같은 형태입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Payload&lt;/b&gt;: 전송할 데이터(주장)를 포함합니다. 데이터는 키-값 쌍으로 구성되며, 주장을 표현하는 데 사용됩니다. 이 데이터는 누구나 읽을 수 있지만, 민감한 정보를 포함하지 않는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Signature&lt;/b&gt;: Header와 Payload를 인코딩한 후 비밀 키를 사용하여 서명한 것입니다. 이 서명은 토큰의 무결성을 보장하고, 토큰이 변조되지 않았음을 확인하는 데 사용됩니다.&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;JWT 토큰의 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;유저 로그인 요청&lt;/b&gt;&lt;br /&gt;사용자가 로그인 양식에 이메일과 비밀번호를 입력하고 로그인 버튼을 클릭합니다. 이 요청은 서버에 전송됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그인 정보 인증&lt;/b&gt;&lt;br /&gt;서버는 전달받은 로그인 정보를 검증하고, 데이터베이스에 저장된 사용자 정보와 비교하여 인증을 수행합니다.&amp;nbsp;이 과정에서 입력된 비밀번호는 일반적으로 해시화된 비밀번호와 비교됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 토큰 발급&lt;/b&gt;&lt;br /&gt;인증이 성공하면 서버는 사용자를 위한 JWT(JSON Web Token)를 생성합니다. 이 토큰에는 사용자 정보와 만료 시간 등의 정보가 포함되어 있으며, 서버의 비밀 키로 서명됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유저의 저장소에 JWT 토큰 저장&lt;/b&gt;&lt;br /&gt;발급된 JWT 토큰은 사용자의 클라이언트(예: 로컬 저장소, 쿠키, 세션 등)에 저장됩니다. 이를 통해 사용자는 이후의 요청에서 이 토큰을 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유저가 인증이 필요한 데이터를 요청할 때 JWT 토큰과 함께 전송&lt;/b&gt;&lt;br /&gt;사용자가 인증이 필요한 API에 접근할 때, 클라이언트는 저장된 JWT 토큰을 HTTP 요청 헤더의 Authorization 필드에 담아 전송합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버에서 JWT 토큰 인증&lt;/b&gt;&lt;br /&gt;서버는 수신한 JWT 토큰을 검증합니다. 이 과정에서 토큰의 유효성(만료 여부, 서명 검증 등)을 확인합니다. 토큰이 유효하면 해당 사용자의 인증 정보를 SecurityContext에 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인증이 성공하면 데이터 반환&lt;/b&gt;&lt;br /&gt;인증이 성공한 후, 서버는 요청한 데이터를 반환합니다. 이 데이터는 클라이언트에서 사용자에게 표시됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서는 JWT(JSON Web Token)를 사용하기 위해 Gradle에 필요한 의존성을 추가했습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//gradle

// spring security
implementation &quot;org.springframework.boot:spring-boot-starter-security&quot;

// JWT 라이브러리
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;JWT 토큰의 흐름에 따른 로그인 서비스 개발&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;1. 유저 로그인 요청&lt;/h3&gt;
&lt;pre id=&quot;code_1726813414627&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- html --&amp;gt;
&amp;lt;div class=&quot;form-container&quot;&amp;gt;
    &amp;lt;h2&amp;gt;Sign in&amp;lt;/h2&amp;gt;
    &amp;lt;form id=&quot;loginForm&quot;&amp;gt;
        &amp;lt;input type=&quot;text&quot; name=&quot;email&quot; placeholder=&quot;Email&quot; required /&amp;gt;
        &amp;lt;input type=&quot;password&quot; name=&quot;password&quot; placeholder=&quot;Password&quot; required /&amp;gt;
        &amp;lt;button class=&quot;btn-signin&quot; type=&quot;submit&quot;&amp;gt;Sign in&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;button class=&quot;signup-btn&quot; onclick=&quot;location.href='/register'&quot;&amp;gt;Sign up&amp;lt;/button&amp;gt;
    &amp;lt;br&amp;gt;&amp;lt;div th:if=&quot;${message}&quot; style=&quot;color: green;&quot;&amp;gt;[[${message}]]&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
    document.getElementById(&quot;loginForm&quot;).addEventListener(&quot;submit&quot;, function(event) {
        event.preventDefault();

        const formData = new FormData(this);
        const jsonData = JSON.stringify({
            email: formData.get(&quot;email&quot;),
            password: formData.get(&quot;password&quot;)
        });

        fetch(&quot;/login/users&quot;, {
            method: &quot;POST&quot;,
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;
            },
            body: jsonData
        })
        .then(response =&amp;gt; response.json())
        .then(data =&amp;gt; {
            if (data.accessToken) {
                localStorage.setItem('jwtToken', data.accessToken);
                window.location.href = '/index.html';
            } else {
                alert(&quot;Login failed&quot;);
            }
        })
        .catch(error =&amp;gt; console.error(&quot;Error:&quot;, error));
    });
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 로그인 폼에 작성한 내용을 post로 보내고 반환이 되면 해당 토큰을 로컬저장소에 저장합니다. 그리고 지정한 페이지로 이동하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726813558894&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Controller
@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping(&quot;/login/users&quot;)
    public ResponseEntity&amp;lt;?&amp;gt; loginUser(@Validated @RequestBody UserDto userRequest) {
        try {
            String token = userService.loginUser(userRequest);
            return ResponseEntity.ok(new JwtAuthenticationResponseDto(token, &quot;Login successful!&quot;));
        } catch (IllegalArgumentException ex) {
            return ResponseEntity.badRequest().body(&quot;{\&quot;message\&quot;: \&quot;&quot; + ex.getMessage() + &quot;\&quot;}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller에서는 @RestController를 사용했는데 받아야할 토큰인 JWT(JsonWebToken)가 JSON형식이기 때문에 사용했습니다. 그래서 스크립트에서 폼정보를 json에서 변환해서 가져옵니다. 로그인 요청이 성공한다면 토큰을 반환해줍니다.&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. 로그인 정보 인증&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로그인 서비스에서의 유저 정보 인증&lt;/h4&gt;
&lt;pre id=&quot;code_1726814077640&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Service
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;

@Transactional
public String loginUser(UserDto requestUser) {
    try {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(requestUser.getEmail(), requestUser.getPassword())
        );
        return jwtTokenProvider.generateToken(authentication);
    } catch (AuthenticationException e) {
        throw new IllegalArgumentException(&quot;Invalid credentials provided.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;authenticationManager.authenticate 메소드는 UsernamePasswordAuthenticationToken 객체를 생성하여 입력된 이메일과 비밀번호를 사용하여 인증을 시도합니다. 이 객체는 사용자의 인증 정보를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 과정에서 Spring Security는 UserDetailsService를 통해 사용자 정보를 확인하고, 입력된 비밀번호가 저장된 비밀번호와 일치하는지 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;authenticationManager는 Spring Security에서 제공하는 인터페이스로, 전체 인증 프로세스를 담당하며, 인증이 성공하면 Authentication 객체를 반환합니다. 이 객체에는 인증된 사용자의 정보와 권한(예: ROLE_USER, ROLE_ADMIN 등)이 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726821854473&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//SecurityConfig
@Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }&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;AuthenticationConfiguration을 사용하여 AuthenticationManager 인스턴스를 생성하고 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AuthenticationConfiguration은 Spring Security의 인증 관련 설정을 관리하며, getAuthenticationManager() 메서드를 호출하여 현재 애플리케이션의 인증 설정에 따라 적절한 AuthenticationManager를 반환합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;유저 정보 반환&lt;/h4&gt;
&lt;pre id=&quot;code_1726814857208&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        UserEntity userEntity = userRepository.findByEmail(email)
                .orElseThrow(() -&amp;gt; new UsernameNotFoundException(&quot;User not found with email: &quot; + email));

        return new org.springframework.security.core.userdetails.User(
                userEntity.getEmail(),
                userEntity.getPassword(),
                new ArrayList&amp;lt;&amp;gt;()  // 권한 없이 빈 리스트로 처리
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 정보를 반환하기 위해 UserDetailsService 인터페이스를 구현합니다. UserDetailsService는 사용자의 세부 정보를 로드하는 역할을 수행합니다. 그 중 loadUserByUsername 메서드는 사용자의 이메일을 기반으로 데이터베이스에서 해당 사용자를 조회합니다. 유저 정보에는 데이터베이스에서 가져온 사용자 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;이 과정에서 사용자가 존재하지 않는 경우 UsernameNotFoundException이 던져지며, 이로 인해 인증 프로세스가 중단됩니다. 사용자 정보가 존재할 경우, 이메일, 비밀번호, 그리고 권한 정보가 포함된 UserDetails 객체를 반환합니다. 이때 UserDetails는 UsernamePasswordAuthenticationToken을 사용하여 생성된 인증 객체의 정보와 일치해야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UserDetailsService를&amp;nbsp;따로&amp;nbsp;구현한&amp;nbsp;이유&amp;nbsp;&lt;/h4&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;UserDetailsService가&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;/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;3. JWT 토큰 발급&lt;/h3&gt;
&lt;pre id=&quot;code_1726814362350&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//JwtTokenProvider
public String generateToken(Authentication authentication) {
    String username = ((UserDetails) authentication.getPrincipal()).getUsername();
    long EXPIRATION_TIME = 86400000; // 24시간
    return Jwts.builder()
        .setSubject(username)
        .setIssuedAt(new Date())
        .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
        .signWith(key, SignatureAlgorithm.HS512)
        .compact();
}&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;인증이 완료되면 JwtTokenProvider에서 JWT 토큰을 생성합니다. 이 과정에서 JWT에는 사용자 이름, 발급 시간, 만료 시간이 포함되어 보안성이 강화됩니다. JWT를 생성할 때는 안전하게 저장된 암호화된 시크릿 키를 사용하여 토큰의 서명을 생성합니다. 이렇게 함으로써 JWT의 진위성과 무결성을 보장할 수 있습니다.&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;pre id=&quot;code_1726816217074&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//JwtTokenProvider
@Value(&quot;${JWT_SECRET}&quot;)
private String SECRET_KEY;

private Key key;

@PostConstruct
public void init() {
    this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_KEY));
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1726815702896&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//application.yml

jwt:
  secret: ${JWT_SECRET}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시크릿 키는 JWT의 서명을 생성하고 검증하는 데 사용되며, 이 키는 안전하게 보호되어야 합니다. 이 키가 유출될 경우 JWT의 안전성이 무너질 수 있습니다. 저의 경우에는 인텔리제이의 환경 변수에 추가하여 사용하고 있습니다. 시크릿키는 콘솔에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;openssl rand -hex 64&lt;/b&gt;&lt;/span&gt; 명령어를 통해 랜덤한 값을 생성했습니다. 현재는 사용하기 쉽게 인텔리제이에서 환경변수를 이용해서 시크릿 키를 사용하고 있지만, 향후에는 AWS의 시크릿 키 관리 기능을 활용해볼 계획입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@PostConstruct 애너테이션이 붙은 init() 메서드는 Spring의 Dependency Injection이 완료된 후 자동으로 호출됩니다. 이 메서드에서는 환경 변수로부터 읽어온 시크릿 키를 Base64로 디코딩하고, HMAC 키로 변환하여 JWT 서명 및 검증에 사용할 준비를 합니다. HMAC 키를 생성한 후, 이 키를 사용하여 JWT를 서명하고 검증함으로써 JWT의 진위성과 무결성을 보장하는 역할을 하게 됩니다.&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;4. 유저의 저장소에 JWT 토큰 저장&lt;/h3&gt;
&lt;pre id=&quot;code_1726819882792&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fetch(&quot;/login/users&quot;, {
            method: &quot;POST&quot;,
            headers: {
                &quot;Content-Type&quot;: &quot;application/json&quot;
            },
            body: jsonData
        })
        .then(response =&amp;gt; response.json())
        .then(data =&amp;gt; {
            if (data.accessToken) {
                localStorage.setItem('jwtToken', data.accessToken);
                window.location.href = '/index.html';
            } else {
                alert(&quot;Login failed&quot;);
            }
        })
        .catch(error =&amp;gt; console.error(&quot;Error:&quot;, error));&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;localStorage.setItem()을 통해서 반환받은 토큰 정보를 저장해주었습니다. 저장하는 방법은 세션, 쿠키, 로컬저장소가 있는데 저는 로컬 저장소로 사용했습니다. 그러나 민감한 정보를 저장할 때는 보안상의 이유로 쿠키를 이용한 HttpOnly 설정과 같은 다른 방법을 고려해야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;로컬 저장소를 사용하여 JWT 토큰을 저장한 이유는?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;영속성&lt;/b&gt;: 로컬 저장소에 저장된 데이터는 브라우저 종료나 페이지 새로 고침 시에도 유지되므로, 사용자가 페이지를 다시 방문할 때 이전 상태를 유지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용이한 접근성&lt;/b&gt;: JavaScript를 통해 간편하게 접근할 수 있으며, 데이터를 비동기적으로 읽고 쓸 수 있어 사용자 인증 정보를 쉽게 관리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만약에 다른 페이지로 이동하고 싶은데 원하는 페이지로 이동이 안 된다면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 로그인 페이지와 회원가입 페이지만 권한없이 진입을 허용을 했는데, 토큰의 권한을 유지한 채로 html 페이지 이동이 있을경우 인증처리를 어떻게 해야되나 걱정이였습니다. SecurityConfig의 securityFilterChain 메서드에서 authorizeHttpRequests의 허용 사이트에 추가해주고 인증이 필요한 요청시에는 유저 인증을 하도록 처리를 했습니다. 추후에는&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;SPA나 쿠키를 통한 처리를 나중에 해봐야겠습니다.&lt;/span&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;5. 유저가 인증이 필요한 데이터를 요청할 때 JWT 토큰과 함께 전송&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;fetch(`/schedules/date?start=${dateStr}&amp;amp;end=${dateStr}`, {
    method: 'GET',
    headers: {
        'Authorization': 'Bearer ' + localStorage.getItem('jwtToken'),
        'Content-Type': 'application/json'
    }
})&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Security의 자동 인증 처리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 요청 헤더의 Authorization 정보를 자동으로 감지하여 JWT 토큰을 처리합니다. JwtAuthenticationFilter가 요청을 가로채어 다음과 같은 과정을 수행합니다.&lt;span&gt;&lt;/span&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;6. 서버에서 JWT 토큰 인증&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JWT 토큰 인증 흐름&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트 요청&lt;/b&gt;: 클라이언트는 특정 API에 요청을 보낼 때, 로컬 저장소에 저장된 JWT 토큰을 Authorization 헤더에 포함시켜 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요청 수신&lt;/b&gt;: Spring Boot 애플리케이션의 엔드포인트에 요청이 도착하면, SecurityFilterChain이 이를 가로챕니다. 이 체인은 여러 필터를 순차적으로 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 인증 필터&lt;/b&gt;: SecurityFilterChain 내에서 설정된 필터 중 JwtAuthenticationFilter가 요청을 처리합니다. 이 필터는 JWT 토큰을 검증하는 기능을 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 추출 및 검증&lt;/b&gt;: JwtAuthenticationFilter의 doFilterInternal 메서드에서 JWT 토큰을 요청 헤더에서 추출하고(resolveToken), 유효성을 검사합니다(validateToken).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 인증 정보 설정&lt;/b&gt;: 토큰이 유효한 경우, getAuthentication 메서드를 통해 사용자 정보를 추출하여 Authentication 객체를 생성합니다. 이 객체는 SecurityContextHolder에 저장되어, 현재 요청의 인증 정보를 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;요청 처리&lt;/b&gt;: 인증된 사용자는 보호된 자원에 접근할 수 있으며, 유효하지 않은 토큰을 가진 사용자는 접근이 차단됩니다. 필터 체인은 다음 필터로 요청을 전달하고, 최종적으로 컨트롤러로 이동하여 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SecurityFilterChain 설정&lt;/b&gt;: SecurityConfig 클래스에서 SecurityFilterChain을 설정하여, 특정 경로에 대한 접근 권한을 지정합니다. 예를 들어, 로그인 및 회원가입 API는 누구나 접근할 수 있도록 허용하고, 그 외의 모든 요청은 인증이 필요하도록 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 인증을 하기위해서 먼저 JwtAuthenticationFilter, JwtTokenProvider, SecurityConfig의 설정이 필요합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JwtAuthenticationFilter&lt;/h4&gt;
&lt;pre id=&quot;code_1726821216274&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = jwtTokenProvider.resolveToken(request);

        if (token != null &amp;amp;&amp;amp; jwtTokenProvider.validateToken(token)) {
            Authentication auth = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}&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;JwtAuthenticationFilter는 HTTP 요청을 필터링하여 JWT 토큰을 확인하는 역할을 합니다. 이 필터는 사용자가 인증이 필요한 요청을 보낼 때마다 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;doFilterInternal 메서드는 JWT 기반 인증 시스템에서 중요한 역할을 합니다. 이 메서드는 요청을 처리하고, JWT 토큰을 검증하여 사용자 인증 정보를 설정합니다. 이를 통해 인증된 사용자는 보호된 자원에 접근할 수 있으며, 유효하지 않은 토큰을 가진 사용자는 접근이 차단됩니다. Spring Security의 필터 체인에 의해 자동으로 호출되며, 클라이언트의 HTTP 요청이 들어올 때마다 JWT 토큰의 유효성을 검사하고 사용자 인증 정보를 설정하는 역할을 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JwtTokenProvider&lt;/h4&gt;
&lt;pre id=&quot;code_1726821686277&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//JwtTokenProvider
    // HTTP 요청에서 토큰 추출
    public String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(&quot;Authorization&quot;);
        if (bearerToken != null &amp;amp;&amp;amp; bearerToken.startsWith(&quot;Bearer &quot;)) {
            return bearerToken.substring(7); // Bearer 접두사 제거
        }
        return null;
    }

    // JWT 토큰 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); // 토큰 유효성 검사
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false; // 유효하지 않은 경우
        }
    }

    // 토큰에서 Authentication 객체 추출
    public Authentication getAuthentication(String token) {
        String username = getUsernameFromToken(token); // 토큰에서 사용자 이름 추출
        return new UsernamePasswordAuthenticationToken(username, &quot;&quot;, Collections.emptyList()); // 인증 정보 생성
    }&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;JwtTokenProvider는 JWT 토큰을 생성하고 &lt;b&gt;검증하는 역할&lt;/b&gt;을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resolveToken 메서드는 HTTP 요청에서 JWT 토큰을 추출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validateToken는 주어진 JWT 토큰의 유효성을 검사합니다. Jwts.parserBuilder()를 사용하여 JWT의 서명을 검증하고, 토큰의 유효 기간이 만료되지 않았는지 확인합니다. 유효한 경우 true를 반환하고, 예외가 발생하면(예: 토큰이 만료되었거나 잘못된 서명일 경우) false를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getAuthentication 메서드는 유효한 토큰에서 사용자 인증 정보를 생성합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SecurityConfig&lt;/h4&gt;
&lt;pre id=&quot;code_1726822250181&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtTokenProvider jwtTokenProvider;

    @Autowired
    public SecurityConfig(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(sessionManagement -&amp;gt;
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 상태 비저장 세션 설정
                )
                .authorizeHttpRequests(auth -&amp;gt; auth
                        .requestMatchers(&quot;/login/**&quot;, &quot;/register/**&quot;).permitAll() // 로그인, 회원가입 API 접근 허용
                        .anyRequest().authenticated()
                )
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // JWT 필터 추가
        return http.build();
    }
}&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;securityFilterChain 메서드는 HTTP 요청을 처리하는 필터 체인을 구성하고, JWT 기반 인증을 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSRF 보호 비활성화&lt;/b&gt;: csrf(AbstractHttpConfigurer::disable)를 통해 CSRF(Cross-Site Request Forgery) 보호를 비활성화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세션 관리 설정&lt;/b&gt;: sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)를 설정하여, Spring Security가 세션을 관리하지 않도록 합니다. 이는 JWT 토큰이 클라이언트 측에 저장되고, 서버에서는 상태를 유지하지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP 요청 권한 부여&lt;/b&gt;: authorizeHttpRequests를 사용하여 요청에 대한 접근 제어를 설정합니다. /login/** 및 /register/** 경로는 모든 사용자에게 허용되고, 그 외의 요청은 인증된 사용자만 접근할 수 있습니다.&lt;/p&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;JWT 필터 추가&lt;/b&gt;: addFilterBefore 메서드를 사용하여 JwtAuthenticationFilter를 추가합니다. 이 필터는 모든 요청에 대해 JWT 토큰을 검증하고, 인증 정보를 설정합니다. UsernamePasswordAuthenticationFilter 전에 실행되도록 설정하여, 일반 인증 과정 전에 JWT 인증을 처리할 수 있습니다.&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;7.&amp;nbsp; 인증이 성공하면 데이터 반환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증이 성공하면 요청한 API 엔드포인트가 활성화되어 필요한 데이터가 반환되며, 클라이언트는 이 데이터를 받아 처리하게 됩니다.&amp;nbsp;&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 하나하나 이게 뭔가 싶었는데 단계별로 흐름대로 타고 가보니 이해가 되었어서 흐름순으로 최대한 작성해보았습니다. 도움이 되셨으면 좋겠습니다. 위 내용을 학습하기 위해 구글링과 인프런의 무료 JWT 강의를 참고했습니다. 강의 내용이 이전 버전이라 다소 어려웠지만, 기본 개념을 이해하는 데에는 적합했습니다. 만약 최신 버전의 내용을 배우고 싶으시다면, 다른 유료 강의를 추천드립니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 때문에 긴 글이 된 감이 있는데 끝까지 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <category>JWT</category>
      <category>SpringSecurity</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/258</guid>
      <comments>https://chanheess.tistory.com/258#entry258comment</comments>
      <pubDate>Sun, 22 Sep 2024 19:36:54 +0900</pubDate>
    </item>
    <item>
      <title>회원가입 서비스 만들기</title>
      <link>https://chanheess.tistory.com/257</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;잡담&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 프로젝트에 회원을 추가해서 회원마다의 일정을 사용할 수 있도록 회원가입, 로그인에 대해 공부하고 배운 내용들을 정리해서 올려보고자 합니다. 작성된 내용들이 회원가입 서비스를 만들기 위해서 배우고 있는 개발자분들에게 도움이 되었으면합니다. 다만 저 또한 공부하고 작성하는 코드들이여서 부족한 내용이 있을 수도 있으니 참고 정도로 살펴보셨으면 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회원 가입 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 이메일 검증&lt;/h3&gt;
&lt;pre id=&quot;code_1726744355868&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Controller
@PostMapping(&quot;/register/users&quot;)
    public String createUser(@Validated @ModelAttribute UserDto.RegisterRequest userRequest,
                             RedirectAttributes redirectAttributes, Model model) {
        try {
            userService.createUser(userRequest);
        } catch (IllegalArgumentException ex) {
            model.addAttribute(&quot;userRequest&quot;, userRequest);
            model.addAttribute(&quot;errorMessage&quot;, ex.getMessage());
            return &quot;register&quot;;
        }

        redirectAttributes.addFlashAttribute(&quot;message&quot;, &quot;회원가입이 성공적으로 완료되었습니다.&quot;);
        return &quot;redirect:/login&quot;;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폼 형식의 데이터를 받아오고 되돌려줄 때의 편의성을 위해 @RestController 대신 @Controller를 사용했습니다. 검증에 실패했을 경우, 어떤 정보가 잘못 입력되었는지를 반환하고, 사용자가 원래 입력했던 값들을 모델에 담아 뷰에서 사용할 수 있도록 합니다. 또한, 일시적으로 회원가입 메시지를 띄운 후, 입력한 폼을 새로고침했을 때 다시 POST 요청이 발생하지 않도록 하기 위해, 검증에 성공한 경우에는 로그인 페이지로 리다이렉트합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;@ModelAttribute를 사용할 때 @RestController가 아닌 @Controller를 선택한 이유는?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ModelAttribute는 폼 데이터를 객체로 변환하여 서버에서 처리하는 데 사용되며, 이렇게 생성된 객체는 뷰에 전달되어 화면에서 사용할 수 있습니다. 반면, @RestController는 데이터를 JSON 형식으로 반환하므로 뷰를 렌더링하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 폼 데이터를 처리하고 뷰를 렌더링할 때는 @Controller를 사용하고, 데이터만 전달할 때는 @RestController를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726804408996&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- html --&amp;gt;
&amp;lt;div class=&quot;form-container&quot;&amp;gt;
    &amp;lt;h2&amp;gt;Sign up&amp;lt;/h2&amp;gt;
    &amp;lt;form id=&quot;register-form&quot; th:action=&quot;@{/register/users}&quot; th:object=&quot;${userRequest}&quot; method=&quot;post&quot;&amp;gt;
        &amp;lt;input type=&quot;email&quot; th:field=&quot;*{email}&quot; placeholder=&quot;Email&quot; autocomplete=&quot;email&quot; required /&amp;gt;
        &amp;lt;input type=&quot;password&quot; th:field=&quot;*{password}&quot; placeholder=&quot;Password&quot; autocomplete=&quot;new-password&quot; required&amp;gt;
        &amp;lt;input type=&quot;password&quot; id=&quot;confirm-password&quot; placeholder=&quot;Confirm Password&quot; autocomplete=&quot;new-password&quot; required&amp;gt;
        &amp;lt;input type=&quot;text&quot; th:field=&quot;*{nickname}&quot; placeholder=&quot;Nickname&quot; autocomplete=&quot;nickname&quot; required/&amp;gt;
        &amp;lt;small id=&quot;password-error&quot; style=&quot;color:red; display:none;&quot;&amp;gt;Passwords do not match&amp;lt;/small&amp;gt;
        &amp;lt;button type=&quot;submit&quot; class=&quot;signup-btn&quot;&amp;gt;Sign Up&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;button onclick=&quot;location.href='/login'&quot; class=&quot;signin&quot; style=&quot;background-color: #ff5733; color: white;&quot;&amp;gt;Back to Sign In&amp;lt;/button&amp;gt;
    &amp;lt;div th:if=&quot;${errorMessage}&quot; style=&quot;color: red;&quot;&amp;gt;
        &amp;lt;p th:text=&quot;${errorMessage}&quot;&amp;gt;&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검증에 실패했을 경우 반환 받은 값들은 Thymeleaf의 태그들을 사용해서 다시 입력필드에 채워줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//Service
private final PasswordEncoder passwordEncoder;

@Transactional
public void createUser(UserDto.RegisterRequest requestUser) {
    if(userRepository.existsByEmail(requestUser.getEmail())) {
        throw new IllegalArgumentException(&quot;이미 해당 \&quot;이메일\&quot;을 가진 사용자가 존재합니다.&quot;);
    }
    if(userRepository.existsByNickname(requestUser.getNickname())) {
        throw new IllegalArgumentException(&quot;이미 해당 \&quot;닉네임\&quot;을 가진 사용자가 존재합니다.&quot;);
    }

    ScheduleUtility.validateEmail(requestUser.getEmail());
    userRepository.save(new UserEntity(requestUser, passwordEncoder));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해당 이메일이 있는지, 닉네임이 있는지 체크를 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일의 형식과 유효성을 검증하기 위해서 apache의 EmailValidator를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username@domain 구조의 이메일 형식, 올바른 도메인 형식인지를 확인해줍니다. 실제로 존재하는 이메일인지는 판단하기 위해서는 따로 이메일에 메일을 보내 확인을 주고 받는 검증이 추가적으로 필요합니다.&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. 비밀번호 암호화&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
    const password = document.getElementById('password');
    const confirmPassword = document.getElementById('confirm-password');
    const errorMessage = document.getElementById('password-error');
    const form = document.getElementById('register-form');

    confirmPassword.addEventListener('input', function() {
        if (password.value !== confirmPassword.value) {
            errorMessage.style.display = 'block';
        } else {
            errorMessage.style.display = 'none';
        }
    });

    form.addEventListener('submit', function (event) {
        if (password.value !== confirmPassword.value) {
            event.preventDefault();
            errorMessage.style.display = 'block';
            alert('Passwords do not match.');
        }
    });
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호를 유저가 정확하게 적었는지 확인하기 위해서 비밀번호 확인을 시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726805810738&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // BCrypt 알고리즘을 사용하는 PasswordEncoder
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SecurityConfig 클래스&lt;/b&gt;는 Spring Security의 전반적인 설정을 관리합니다. 이 클래스 내에 @Bean으로 PasswordEncoder를 정의함으로써, Spring의 컨테이너에서 해당 객체를 생성하고 관리할 수 있게 됩니다. 이를 통해 Spring Security는 비밀번호를 암호화하거나 검증할 때 사용할 수 있는 PasswordEncoder 인스턴스를 제공받게 됩니다.&lt;/p&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;BCryptPasswordEncoder&lt;/b&gt;를 사용하면 비밀번호를 해시 형태로 암호화하여 데이터베이스에 저장할 수 있습니다. 이 과정에서 단방향 암호화가 적용되므로, 암호화된 비밀번호는 원래 비밀번호로 복원할 수 없게 되어 보안이 강화됩니다. BCrypt는 Blowfish 알고리즘을 기반으로 하는 해시 함수로, 민감한 데이터를 안전하게 저장하기 위해 Blowfish의 암호화 메커니즘을 사용하여 단방향 해시를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;//Entity
public UserEntity(UserDto.RegisterRequest request, PasswordEncoder passwordEncoder) {
    this.email = request.getEmail();
    this.password = passwordEncoder.encode(request.getPassword());
    this.nickname = request.getNickname();
}

public boolean checkPassword(String plainPassword, PasswordEncoder passwordEncoder) {
    return passwordEncoder.matches(plainPassword, this.password);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티의 생성자에서 passwordEncode를 사용하여 비밀번호를 암호화 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 암호화된 비밀번호를 userRepository의 save를 통해 데이터베이스에 저장해줍니다.&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;3. 정리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;회원 가입 폼을 만든다.&lt;/li&gt;
&lt;li&gt;Controller에서 해당 폼에 대한 데이터들을 받아준다.&lt;/li&gt;
&lt;li&gt;Service에서 해당 데이터를 검증한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이메일의 형식에 대한 검증은 EmailValidator으로 검증하고 동일한 이메일이 있는지 확인은 데이터베이스에서 찾아본다.&lt;/li&gt;
&lt;li&gt;닉네임이 있는지 확인은 데이터베이스에서 찾아본다.&lt;/li&gt;
&lt;li&gt;비밀번호는 &lt;b&gt;BCryptPasswordEncoder&lt;/b&gt;를 사용해서 단방향으로 암호화하여 데이터베이스에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 회원가입을 만들면서 로그인과 회원가입을 책임을 분리해서 사용해야겠다고 생각해서 작업을 회원가입 먼저 시작했습니다. 캘린더 프로젝트 특성상 아이디는 이메일로 하는게 좋아보여 이메일에 대한 검증, 비밀번호에 대한 암호화를 키워드로 검색을 시작해서 공부를 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일은 &lt;b&gt;/^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/ &lt;/b&gt;같은 특정 키워드들이 주로 검색이 되었고 해당 검증을 해주는 것들을 찾아서 해결했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호에 대한 암호화를 검색하면 단방향, 양방향 암호화에 대한 정보들을 얻을 수 있었고 회원들을 관리할 때 단뱡향으로 암호를 저장해서 복호화할 수 없게 해서 저장하고 유저가 비밀번호를 까먹었다면 비밀번호 초기화를 시켜주는 방법을 사용하는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 필요한 내용들을 단계별로 하나씩 익히면서 만드는 과정이 꽤나 재밌었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <category>Java</category>
      <category>spring</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/257</guid>
      <comments>https://chanheess.tistory.com/257#entry257comment</comments>
      <pubDate>Fri, 20 Sep 2024 13:58:47 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] 프로그래머스 리코쳇 로봇</title>
      <link>https://chanheess.tistory.com/256</link>
      <description>&lt;pre id=&quot;code_1725948056864&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.LinkedList;
import java.util.Queue;

class Solution {
    public int solution(String[] board) {
        int[] goal = new int[2];
        int[] start = new int[2];
        Queue&amp;lt;int[]&amp;gt; nextNode = new LinkedList&amp;lt;&amp;gt;();
        boolean[][] visited = new boolean[board.length][board[0].length()];

        for (int i = 0; i &amp;lt; board.length; i++) {
            for (int j = 0; j &amp;lt; board[i].length(); j++) {
                if (board[i].charAt(j) == 'R') {
                    start[0] = i;
                    start[1] = j;
                }
                if (board[i].charAt(j) == 'G') {
                    goal[0] = i;
                    goal[1] = j;
                }
            }
        }


        nextNode.add(start);
        visited[start[0]][start[1]] = true;
        int moveCount = 0;

        // BFS
        while (!nextNode.isEmpty()) {
            int size = nextNode.size();
            moveCount++;

            for (int i = 0; i &amp;lt; size; i++) {
                int[] currNode = nextNode.poll();

                for (int[] direction : new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}) {
                    int[] newPos = movePosition(board, currNode, direction[0], direction[1]);

                    if (newPos[0] == goal[0] &amp;amp;&amp;amp; newPos[1] == goal[1]) {
                        return moveCount;
                    }

                    if (!visited[newPos[0]][newPos[1]]) {
                        visited[newPos[0]][newPos[1]] = true;
                        nextNode.add(newPos);
                    }
                }
            }
        }

        return -1; // 도착하지 못한 경우
    }

    public int[] movePosition(String[] board, int[] pos, int x, int y) {
        int nextX = pos[0];
        int nextY = pos[1];

        while (true) {
            int newX = nextX + x;
            int newY = nextY + y;

            if (newX &amp;lt; 0 || newX &amp;gt;= board.length ||
                newY &amp;lt; 0 || newY &amp;gt;= board[0].length() || board[newX].charAt(newY) == 'D') {
                break;
            }

            nextX = newX;
            nextY = newY;
        }

        return new int[]{nextX, nextY};
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 한 방향으로만 전진해서 배열의 끝이나 'D'에 도착했을 때의 위치를 반환하는 메서드를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해당 메서드를 가지고 bfs를 하게되는데 queue에 저장된 각 위치에서 상하좌우를 검색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 방문한 곳은 다음 queue에 넣지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. goal 위치에 도착했다면 몇번 반복했는지 반환해주고 queue를 다 이동할 동안 찾지 못했다면 -1을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딩테스트로 java를 익히는 중인데 코딩테스트로 연습하면서 하니까 오래걸리긴하나 모르던 점들을 알게 되었다. String은 c++에서의 const와 비슷한 성질을 지닌 값이였다. String내용을 수정 불가하다는 것은 const와 같았으나 java에서의 경우 String객체를 새로 대입해주는 것은 가능했다.&lt;/p&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/169199&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/169199&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725948078522&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/169199&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLIJOn/hyW2WSIC79/0xqDfby6iuCYPMzsHQtUQk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/zTVD6/hyWZgk0f6o/zcilJR14SgKxp1ysZNqxa1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/169199&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/169199&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLIJOn/hyW2WSIC79/0xqDfby6iuCYPMzsHQtUQk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/zTVD6/hyWZgk0f6o/zcilJR14SgKxp1ysZNqxa1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/256</guid>
      <comments>https://chanheess.tistory.com/256#entry256comment</comments>
      <pubDate>Tue, 10 Sep 2024 15:19:44 +0900</pubDate>
    </item>
    <item>
      <title>DTO에서 NotNull설정이 되어 있을 때 PATCH를 어떻게 해줘야할까?</title>
      <link>https://chanheess.tistory.com/255</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;현재 상황 및 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 프로젝트를 만들면서 일정 설정, 반복 설정, 알림 설정을 한 곳에서 받아 처리하려고 했습니다. 그러나 PUT을 사용하여 구현할 경우, 현 구조에서는 반복 설정이 바뀌지 않았음에도 반복 내용을 삭제하고 재등록해야 하는 문제가 있었습니다. 이를 해결하기 위해 PATCH를 사용하여 특정 기능만 수정할 수 있도록 하려고 했습니다. 그러나 일정 설정의 필수 정보들이 NotNull로 설정되어 있어, PATCH로 데이터를 보낼 때 어떻게 처리해야 할지 고민이 되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1724652532429&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ScheduleDto {
    private int id;

    @NotNull
    private String title;

    private String description;

    @NotNull
    private LocalDateTime startAt;

    @NotNull
    private LocalDateTime endAt;

    private Integer repeatId;
}&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;일정을 설정할 때의 값들입니다. 일정을 등록하기 위해서 꼭 필요한 정보들은 NotNull로 설정해주기 위해서 valid처리를 저렇게 해주었습니다.&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;PATCH를 사용하려는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PATCH를 사용하게 되면, 수정하려는 값 하나만 전송할 수 있어 데이터 크기가 감소합니다. 특히, 반복 일정에 대해 최대 500개의 일정 수정이 필요할 경우, PUT을 사용해 모든 내용을 수정하면 전송 데이터의 크기가 커질 수 있습니다. 이 문제를 해결하기 위해 PATCH를 사용하면 네트워크 대역폭을 절약할 수 있습니다.&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1724665538376&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ValidGroup {
    public interface CreateGroup {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ScheduleDto {

    private int id;

    @NotNull(groups = ValidGroup.CreateGroup.class)
    private String title;

    private String description;

    @NotNull(groups = ValidGroup.CreateGroup.class)
    private LocalDateTime startAt;

    @NotNull(groups = ValidGroup.CreateGroup.class)
    private LocalDateTime endAt;

    private Integer repeatId;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1724668356426&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping
    public ResponseEntity&amp;lt;ScheduleDto.Response&amp;gt; createSchedule(
    @Validated(ValidGroup.CreateGroup.class)
    @RequestBody ScheduleDto.Request schedule) { }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;NotNull 처리와 PATCH의 적용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;PATCH&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;사용할&lt;span&gt; &lt;/span&gt;때&lt;span&gt; NotNull&lt;/span&gt;로&lt;span&gt; &lt;/span&gt;설정된&lt;span&gt; &lt;/span&gt;필드를&lt;span&gt; &lt;/span&gt;처리하는&lt;span&gt; &lt;/span&gt;방법으로&lt;span&gt; Group Validation&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;도입했습니다&lt;span&gt;. &lt;/span&gt;수정&lt;span&gt; &lt;/span&gt;시에는&lt;span&gt; &lt;/span&gt;유효성&lt;span&gt; &lt;/span&gt;검사를&lt;span&gt; &lt;/span&gt;할&lt;span&gt; &lt;/span&gt;필요가&lt;span&gt; &lt;/span&gt;없으므로&lt;span&gt;, create &lt;/span&gt;시에만&lt;span&gt; &lt;/span&gt;유효성&lt;span&gt; &lt;/span&gt;검사를&lt;span&gt; &lt;/span&gt;하도록&lt;span&gt; &lt;/span&gt;설정했습니다&lt;span&gt;. &lt;/span&gt;이를&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; PATCH &lt;/span&gt;요청에서&lt;span&gt; @Validated&lt;/span&gt;만&lt;span&gt; &lt;/span&gt;작성하면&lt;span&gt; NotNull&lt;/span&gt;로&lt;span&gt; &lt;/span&gt;설정된&lt;span&gt; &lt;/span&gt;필드들이&lt;span&gt; &lt;/span&gt;유효성&lt;span&gt; &lt;/span&gt;검사를&lt;span&gt; &lt;/span&gt;하지&lt;span&gt; &lt;/span&gt;않게&lt;span&gt; &lt;/span&gt;되어&lt;span&gt; &lt;/span&gt;원하는&lt;span&gt; &lt;/span&gt;값만&lt;span&gt; &lt;/span&gt;수정할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;POST용과 PATCH용 DTO로 나눠서 하지 않은 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;POST&lt;/span&gt;용과&lt;span&gt; PATCH&lt;/span&gt;용&lt;span&gt; DTO&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;따로&lt;span&gt; &lt;/span&gt;생성하는&lt;span&gt; &lt;/span&gt;방법도&lt;span&gt; &lt;/span&gt;고려할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt;, &lt;/span&gt;현재&lt;span&gt; &lt;/span&gt;상황에서는&lt;span&gt; &lt;/span&gt;변수들이&lt;span&gt; &lt;/span&gt;모두&lt;span&gt; &lt;/span&gt;동일한&lt;span&gt; &lt;/span&gt;데이터&lt;span&gt; &lt;/span&gt;타입과&lt;span&gt; &lt;/span&gt;구조를&lt;span&gt; &lt;/span&gt;가지고&lt;span&gt; &lt;/span&gt;있어&lt;span&gt; NotNull &lt;/span&gt;옵션만이&lt;span&gt; &lt;/span&gt;다릅니다&lt;span&gt;. &lt;/span&gt;따라서&lt;span&gt; &lt;/span&gt;두&lt;span&gt; DTO&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;따로&lt;span&gt; &lt;/span&gt;만들기보다는&lt;span&gt;, Group Validation&lt;/span&gt;으로&lt;span&gt; &lt;/span&gt;묶어&lt;span&gt; &lt;/span&gt;관리하는&lt;span&gt; &lt;/span&gt;방안을&lt;span&gt; &lt;/span&gt;채택했습니다&lt;span&gt;. &lt;/span&gt;이를&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; &lt;/span&gt;코드&lt;span&gt; &lt;/span&gt;중복을&lt;span&gt; &lt;/span&gt;줄이고&lt;span&gt;, &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;상황에&lt;span&gt; &lt;/span&gt;맞는&lt;span&gt; &lt;/span&gt;유효성&lt;span&gt; &lt;/span&gt;검사를&lt;span&gt; &lt;/span&gt;적용할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/255</guid>
      <comments>https://chanheess.tistory.com/255#entry255comment</comments>
      <pubDate>Mon, 26 Aug 2024 19:51:11 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 프로그래머스 멀리뛰기</title>
      <link>https://chanheess.tistory.com/254</link>
      <description>&lt;pre id=&quot;code_1720834285360&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//dfs으로 하면 시간초과
// % 1234567이니까 무조건이다.
/*
1
1

2
11
2

3
111
21
12

4
1111
112
121
211
22

5
11111
1112
1121
1211
2111
221
122
212

6
111111
11112
11121
11211
12111
21111
2211
2112
2121
1122
222

dp[n] = dp[n-1] + dp[n-2]


*/

class Solution {
    
    public long solution(int n) {
        long[] dp = new long[2001];
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        
        for(int i = 3; i &amp;lt;= n; i++) {
            dp[i] = (dp[i - 1] + dp[i - 2]) % 1234567;
        }
        
        return dp[n];
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 우선 % 1234567을 한다는건 경우의수가 엄청나다는 것이니 탐색으로는 풀 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 하나씩 규칙을 확인해봤다. 처음에는 1 + (n/2 ~ n-1의합) + n%2==0일경우 +1 이런식으로 풀었는데 틀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 다시보니 dpn은 (dp-1) + (dp-2)였다. 그렇게 dp로 문제를 해결했다.&lt;/p&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;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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12914&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12914&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1720835481121&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12914&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dWx0oa/hyWzDGG1IE/qnPZd9fRVMmdOK7BKkaiXK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/gV71V/hyWzqN7qq7/GMCN1HFUiYJhLCqa8COnS0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12914&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12914&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dWx0oa/hyWzDGG1IE/qnPZd9fRVMmdOK7BKkaiXK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/gV71V/hyWzqN7qq7/GMCN1HFUiYJhLCqa8COnS0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <category>dp</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/254</guid>
      <comments>https://chanheess.tistory.com/254#entry254comment</comments>
      <pubDate>Sat, 13 Jul 2024 10:36:44 +0900</pubDate>
    </item>
    <item>
      <title>RESTful API란?</title>
      <link>https://chanheess.tistory.com/253</link>
      <description>&lt;h2 id=&quot;seo-faq-pairs#what-is-rest&quot; style=&quot;color: #232f3e; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;RESTful API란 무엇인가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful API는 Representational State Transfer의 약자로, 웹 표준을 기반으로 서버와 클라이언트 사이의 상호작용을 정의하는 방법입니다. REST 원칙을 따르는 API는 HTTP 프로토콜을 사용하여 데이터를 주고받으며, 웹 개발에서 널리 사용됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;REST 아키텍처 스타일의 몇 가지 원칙&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;균일한 인터페이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;균일한 인터페이스는 모든 RESTful 웹 서비스 디자인의 기본입니다. 이는 서버가 표준 형식으로 정보를 전송함을 나타냅니다. 형식이 지정된 리소스를 REST에서 표현이라고 부릅니다. 예를 들어,&amp;nbsp;리소스를 동적으로 검색할 수 있도록 표현에 하이퍼링크를 넣어 전송합니다.&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;&lt;b&gt;자원 식별&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URI를 통해 리소스를 고유하게 식별합니다.&lt;/li&gt;
&lt;li&gt;예: &lt;a href=&quot;https://api.example.com/users/1&quot;&gt;api.example.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;예: JSON 형식으로 사용자 정보를 표현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자원 조작&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP 메서드(POST, GET, PUT, DELETE 등)를 통해 리소스를 조작합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET 요청: GET&amp;nbsp;&lt;a href=&quot;https://api.example.com/users/1&quot;&gt;https://api.example.com/users/1&lt;/a&gt;&amp;nbsp;(사용자 정보 조회)&lt;/li&gt;
&lt;li&gt;POST 요청: POST&amp;nbsp;&lt;a href=&quot;https://api.example.com/users&quot;&gt;https://api.example.com/users&lt;/a&gt;&amp;nbsp;(새 사용자 생성)&lt;/li&gt;
&lt;li&gt;PUT 요청: PUT&amp;nbsp;&lt;a href=&quot;https://api.example.com/users/1&quot;&gt;https://api.example.com/users/1&lt;/a&gt;&amp;nbsp;(사용자 정보 수정)&lt;/li&gt;
&lt;li&gt;DELETE 요청: DELETE&amp;nbsp;&lt;a href=&quot;https://api.example.com/users/1&quot;&gt;https://api.example.com/users/1&lt;/a&gt;&amp;nbsp;(사용자 삭제)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;li&gt;&lt;b&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;/ol&gt;
&lt;pre id=&quot;code_1719966017475&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;id&quot;: 1,
    &quot;name&quot;: &quot;John Doe&quot;,
    &quot;email&quot;: &quot;john.doe@example.com&quot;,
    &quot;links&quot;: {
        &quot;self&quot;: &quot;/users/1&quot;,
        &quot;orders&quot;: &quot;/users/1/orders&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;무상태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST 아키텍처에서 무상태는 서버가 이전의 모든 요청과&amp;nbsp;&lt;span data-token-index=&quot;1&quot;&gt;독립적으로&lt;/span&gt;&amp;nbsp;모든 클라이언트 요청을 완료하는 통신 방법을 나타냅니다.&amp;nbsp; 이 REST API 설계 제약 조건은 서버가 매번 요청을 완전히 이해해서 이행할 수 있음을 의미합니다. 각 요청은 필요한 모든 정보를 포함하고 있어야 하며, 서버는 이전 요청의 상태를 기억할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째 요청:&lt;/b&gt;&amp;nbsp;클라이언트가 사용자 정보를 요청합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청:&lt;/b&gt;&amp;nbsp;GET&amp;nbsp;&lt;a href=&quot;https://api.example.com/users/1&quot;&gt;https://api.example.com/users/1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 응답:&lt;/b&gt;&amp;nbsp;{&quot;id&quot;:1,&quot;name&quot;:&quot;John &lt;a href=&quot;mailto:Doe%22,%22email%22:%22john.doe@example.com&quot;&gt;Doe&quot;,&quot;email&quot;:&quot;john.doe@example.com&lt;/a&gt;&quot;}&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 요청:&lt;/b&gt;&amp;nbsp;클라이언트가 주문 정보를 요청합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청:&lt;/b&gt;&amp;nbsp;GET&amp;nbsp;&lt;a href=&quot;https://api.example.com/users/1/orders&quot;&gt;https://api.example.com/users/1/orders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 응답:&lt;/b&gt; [&amp;nbsp;{&quot;order_id&quot;:&amp;nbsp;101,&amp;nbsp;&quot;product&quot;:&amp;nbsp;&quot;Laptop&quot;,&amp;nbsp;&quot;quantity&quot;:&amp;nbsp;1}, &lt;br /&gt;{&quot;order_id&quot;: 102, &quot;product&quot;: &quot;Mouse&quot;, &quot;quantity&quot;: 2} ]&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 클라이언트가 요청한 것에 서버는 독립적으로 처리를 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계층화 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층화된 시스템 아키텍처에서 클라이언트는 클라이언트와 서버 사이의 다른 승인된 중개자에게 연결할 수 있으며 여전히 서버로부터도 응답을 받습니다. 서버는 요청을 다른 서버로 전달할 수도 있습니다. 클라이언트 요청을 이행하기 위해 함께 작동하는 보안, 애플리케이션 및 비즈니스 로직과 같은 여러 계층으로 여러 서버에서 실행되도록 RESTful 웹 서비스를 설계할 수 있습니다. 이러한 계층은 클라이언트에 보이지 않는 상태로 유지됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful 웹 서비스는 서버 응답 시간을 개선하기 위해 클라이언트 또는 중개자에 일부 응답을 저장하는 프로세스인 캐싱을 지원합니다. 예를 들어 모든 페이지에 공통 머리글 및 바닥글 이미지가 있는 웹 사이트를 방문한다고 가정해 보겠습니다. 새로운 웹 사이트 페이지를 방문할 때마다 서버는 동일한 이미지를 다시 전송해야 합니다. 이를 피하기 위해 클라이언트는 첫 번째 응답 후에 해당 이미지를 캐싱하거나 저장한 다음 캐시에서 직접 이미지를 사용합니다. RESTful 웹 서비스는 캐시 가능 또는 캐시 불가능으로 정의되는 API 응답을 사용하여 캐싱을 제어합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;온디맨드 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST 아키텍처 스타일에서 서버는 소프트웨어 프로그래밍 코드를 클라이언트에 전송하여 클라이언트 기능을 일시적으로 확장하거나 사용자 지정할 수 있습니다. 예를 들어, 웹 사이트에서 등록 양식을 작성하면 브라우저는 잘못된 전화번호와 같은 실수를 즉시 강조 표시합니다. 서버에서 전송한 코드로 인해 이 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;참고자료&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/restful-api/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/what-is/restful-api/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719987355490&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;RESTful API란 무엇인가요? - RESTful API 설명 - AWS&quot; data-og-description=&quot;RESTful API란 무엇인가요? RESTful API는 두 컴퓨터 시스템이 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 인터페이스입니다. 대부분의 비즈니스 애플리케이션은 다양한 태스크를 수행하&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/what-is/restful-api/&quot; data-og-url=&quot;https://aws.amazon.com/ko/what-is/restful-api/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ewKGk/hyWvOgPDjW/jfdzqTAcHVWWiNTJlrpP00/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ffSfH/hyWvJ0TwEW/HgrHROPCa7ycZqSPkAGzXk/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/restful-api/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/what-is/restful-api/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ewKGk/hyWvOgPDjW/jfdzqTAcHVWWiNTJlrpP00/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/ffSfH/hyWvJ0TwEW/HgrHROPCa7ycZqSPkAGzXk/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109');&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;RESTful API란 무엇인가요? - RESTful API 설명 - AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;RESTful API란 무엇인가요? RESTful API는 두 컴퓨터 시스템이 인터넷을 통해 정보를 안전하게 교환하기 위해 사용하는 인터페이스입니다. 대부분의 비즈니스 애플리케이션은 다양한 태스크를 수행하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-REST-API-%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-REST-API-%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719987330237&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[WEB]   REST API 구성/특징 총 정리&quot; data-og-description=&quot;REST API의 탄생 REST는 Representational State Transfer라는 용어의 약자로서 2000년도에 로이 필딩 (Roy Fielding)의 박사학위 논문에서 최초로 소개되었습니다. 로이 필딩은 HTTP의 주요 저자 중 한 사람으로 그&quot; data-og-host=&quot;inpa.tistory.com&quot; data-og-source-url=&quot;https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-REST-API-%EC%A0%95%EB%A6%AC&quot; data-og-url=&quot;https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-REST-API-%EC%A0%95%EB%A6%AC&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dwjdOO/hyWrNjANJY/64RgEDDBBsOYnmpv8IHpw0/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/dz6EC3/hyWvNCdTht/v6cvdvX82Eqz13kUJpz7e0/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bHbtve/hyWvN93Reo/3Ww5pyCORtZNlTTcDTtl9k/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-REST-API-%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://inpa.tistory.com/entry/WEB-%F0%9F%8C%90-REST-API-%EC%A0%95%EB%A6%AC&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dwjdOO/hyWrNjANJY/64RgEDDBBsOYnmpv8IHpw0/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/dz6EC3/hyWvNCdTht/v6cvdvX82Eqz13kUJpz7e0/img.jpg?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/bHbtve/hyWvN93Reo/3Ww5pyCORtZNlTTcDTtl9k/img.jpg?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&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;[WEB]   REST API 구성/특징 총 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;REST API의 탄생 REST는 Representational State Transfer라는 용어의 약자로서 2000년도에 로이 필딩 (Roy Fielding)의 박사학위 논문에서 최초로 소개되었습니다. 로이 필딩은 HTTP의 주요 저자 중 한 사람으로 그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;inpa.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/253</guid>
      <comments>https://chanheess.tistory.com/253#entry253comment</comments>
      <pubDate>Wed, 3 Jul 2024 15:17:55 +0900</pubDate>
    </item>
    <item>
      <title>서블릿이란 무엇일까?</title>
      <link>https://chanheess.tistory.com/251</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿(Servlet)은 &lt;b&gt;자바를 사용하여 웹 애플리케이션을 개발&lt;/b&gt;할 때, 클라이언트의 요청을 처리하고 응답을 생성하는 &lt;b&gt;서버 측의 프로그램&lt;/b&gt;입니다. 서블릿은 주로 &lt;b&gt;HTTP 요청과 응답을 처리&lt;/b&gt;하는 데 사용되며, 자바 EE (Enterprise Edition) 스펙의 일부입니다.&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;&lt;b&gt;클라이언트 요청 처리&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 브라우저나 다른 HTTP 클라이언트가 웹 서버에 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;웹 서버는 요청을 서블릿 컨테이너(Servlet Container)에 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;li&gt;&lt;b&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;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서블릿의 주요 메서드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 클래스는 HttpServlet 클래스를 상속받아야 하며, 주로 다음 메서드를 오버라이드하여 사용합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;doGet(HttpServletRequest request, HttpServletResponse response): GET 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;doPost(HttpServletRequest request, HttpServletResponse response): POST 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;init(): 서블릿이 초기화될 때 호출됩니다.&lt;/li&gt;
&lt;li&gt;destroy(): 서블릿이 소멸될 때 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서블릿의 사용 예제&lt;/h3&gt;
&lt;pre id=&quot;code_1719323174288&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(&quot;/hello&quot;)
public class HelloServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType(&quot;text/html&quot;);
        response.getWriter().println(&quot;&amp;lt;h1&amp;gt;Hello, World!&amp;lt;/h1&amp;gt;&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제는 /hello URL 패턴에 대한 GET 요청을 처리하는 서블릿입니다. 클라이언트가 /hello URL로 요청을 보내면, 서블릿은 &quot;Hello, World!&quot; 메시지를 포함한 HTML 응답을 생성하여 클라이언트에 반환합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서블릿의 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt;: 서블릿은 자바의 객체 지향 특성을 활용하여 쉽게 확장할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플랫폼 독립성&lt;/b&gt;: 자바로 작성되었기 때문에 다양한 운영 체제에서 실행 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능&lt;/b&gt;: 서블릿은 서버 측에서 실행되기 때문에, 클라이언트 측 스크립트보다 빠르고 효율적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿은 이러한 특성 덕분에 자바 기반의 웹 애플리케이션 개발에 널리 사용됩니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/251</guid>
      <comments>https://chanheess.tistory.com/251#entry251comment</comments>
      <pubDate>Sun, 30 Jun 2024 23:49:23 +0900</pubDate>
    </item>
    <item>
      <title>스프링 빈과 애플리케이션 컨텍스트 그리고 AOP??</title>
      <link>https://chanheess.tistory.com/250</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 빈(Spring Bean)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스프링 빈은 스프링 IoC(Inversion of Control) 컨테이너가 관리하는 자바 객체입니다.&lt;/li&gt;
&lt;li&gt;빈은 인스턴스화된 객체를 의미하며, 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;IoC (Inversion of Control)가 무엇이냐?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍에서 객체의 &lt;b&gt;의존성 관리&lt;/b&gt;를 프레임워크나 컨테이너에 위임하는 설계 원칙입니다. IoC의 핵심 개념은 객체 생성 및 의존성 주입을 개발자가 직접 하지 않고, 외부에서 제어권을 갖고 관리한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성 및 관리&lt;/b&gt;&lt;/p&gt;
&lt;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;빈은 @Component, @Service, @Repository, @Controller 등의 어노테이션을 사용하여 정의할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 이유&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 주입&lt;/b&gt;: 스프링 빈은 &lt;b&gt;의존성 주입&lt;/b&gt;(DI, Dependency Injection)을 통해 객체 간의 결합도를 낮추고, 유연하고 테스트 가능한 코드를 작성할 수 있게 해줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;객체 관리&lt;/b&gt;: 스프링 컨테이너는 빈의 생명주기를 관리하여, &lt;b&gt;객체의 생성과 소멸, 그리고 의존성 주입을 자동으로 처리&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AOP(Aspect-Oriented Programming)&lt;/b&gt;: 스프링 빈은 AOP를 통해 횡단 관심사(로깅, 트랜잭션 관리 등)를 모듈화하여 코드의 분리를 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;AOP ( 관점 지향 프로그래밍 )&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AOP는 객체 지향 프로그래밍의 한계를 극복하고자 나온 패러다임으로, 프로그램의 &lt;b&gt;핵심 기능과 부가 기능을 분리&lt;/b&gt;하여 코드의 모듈성을 향상시키는 기법입니다. AOP를 통해 핵심 비즈니스 로직과 공통적으로 사용되는 로직(예: 로깅, 보안, 트랜잭션 관리 등)을 분리하여 각각을 독립적으로 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 개념&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Aspect (관점)&lt;/b&gt;: 특정 횡단 관심사를 모듈화한 것입니다. 횡단 관심사란 여러 모듈에서 공통적으로 사용되는 로직을 말합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Join Point (조인 포인트)&lt;/b&gt;: AOP에서 횡단 관심사를 적용할 수 있는 지점을 말합니다. 이는 메서드 호출, 객체 생성 등 다양한 지점이 될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Advice (어드바이스)&lt;/b&gt;: 실제로 실행되는 코드로, 횡단 관심사가 구현된 부분입니다. 이는 특정 Join Point에서 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pointcut (포인트컷)&lt;/b&gt;: Join Point를 지정하는 표현식입니다. 어떤 Join Point에서 Advice를 실행할지 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Weaving (위빙)&lt;/b&gt;: 애플리케이션 코드와 횡단 관심사(Advice)를 결합하는 과정입니다. 이는 컴파일 타임, 로드 타임, 런타임 중에 이루어질 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;유지보수성 향상&lt;/b&gt;: 공통 로직을 한 곳에서 관리하여 유지보수를 쉽게 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코드 중복 감소&lt;/b&gt;: 여러 모듈에서 공통적으로 사용되는 로직을 분리하여 코드 중복을 줄입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애플리케이션 컨텍스트(Application Context)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션 컨텍스트는 스프링 프레임워크의 중앙 인터페이스로, &lt;b&gt;빈의 생성, 설정 및 관리&lt;/b&gt; 등의 역할을 담당합니다.&lt;/li&gt;
&lt;li&gt;ApplicationContext는 BeanFactory를 확장한 것으로, 더 많은 기능을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기능&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;빈의 관리&lt;/b&gt;: 빈의 생성, 설정 및 관리 기능을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;국제화&lt;/b&gt;: 메시지 소스(MessageSource)를 통해 국제화를 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 발행&lt;/b&gt;: 애플리케이션 이벤트를 발행하고 리스너를 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리소스 로딩&lt;/b&gt;: 파일, URL 등의 다양한 형태의 리소스를 로딩할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 이유&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;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;확장성&lt;/b&gt;: 애플리케이션의 요구에 따라 컨텍스트를 쉽게 확장하고 맞춤화할 수 있습니다.&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;스프링 애플리케이션이 시작될 때, 애플리케이션 컨텍스트는 설정 파일이나 어노테이션을 기반으로 빈을 스캔하고 인스턴스화합니다. 이 과정에서 의존성 주입이 이루어지고, 필요한 설정이 적용됩니다. 빈은 애플리케이션 컨텍스트에 의해 관리되며, 애플리케이션의 요구에 따라 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ittrue.tistory.com/221&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ittrue.tistory.com/221&lt;/a&gt;&amp;nbsp;[IT&amp;nbsp;is&amp;nbsp;True:티스토리]&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/250</guid>
      <comments>https://chanheess.tistory.com/250#entry250comment</comments>
      <pubDate>Sat, 29 Jun 2024 21:50:26 +0900</pubDate>
    </item>
    <item>
      <title>MVC 패턴이란 무엇일까?</title>
      <link>https://chanheess.tistory.com/247</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MVC (Model-View-Controller) 패턴&lt;/b&gt;은 소프트웨어 설계 패턴 중 하나로, 어플리케이션을 세 가지 주요 컴포넌트로 분리하여 개발의 복잡성을 줄이고, 코드의 재사용성과 유지보수성을 높이는 데 도움을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Model (모델)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;: 어플리케이션의 &lt;b&gt;데이터 구조를 관리&lt;/b&gt;합니다. 데이터베이스와의 상호작용, 데이터 유효성 검사 등의 비즈니스 로직을 담당합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예시&lt;/b&gt;: 사용자 정보, 제품 목록, 주문 내역 등의 데이터 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719299121830&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class User {
    private String name;
    private String email;

    // 생성자, getter, setter
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;View (뷰)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;: &lt;b&gt;사용자 인터페이스(UI)를 담당&lt;/b&gt;합니다. 모델의 데이터를 사용자에게 보여주고, 사용자로부터 &lt;b&gt;입력을 받아 이를 컨트롤러에 전달&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예시&lt;/b&gt;: 웹 페이지, 모바일 앱 화면, 데스크톱 애플리케이션의 GUI 등.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719299132383&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserView {
    public void printUserDetails(String userName, String userEmail) {
        System.out.println(&quot;User: &quot;);
        System.out.println(&quot;Name: &quot; + userName);
        System.out.println(&quot;Email: &quot; + userEmail);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller (컨트롤러)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할&lt;/b&gt;: &lt;b&gt;사용자 입력을 처리&lt;/b&gt;하고, 이를 모델에 반영하거나 모델의 데이터를 뷰에 전달합니다. 뷰와 모델 사이의 상호작용을 중재합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예시&lt;/b&gt;: 로그인 버튼 클릭 시 사용자 인증 처리, 폼 제출 시 데이터 저장 처리 등.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719299142278&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserController {
    private User model;
    private UserView view;

    public UserController(User model, UserView view) {
        this.model = model;
        this.view = view;
    }

    public void setUserName(String name) {
        model.setName(name);
    }

    public String getUserName() {
        return model.getName();
    }

    public void setUserEmail(String email) {
        model.setEmail(email);
    }

    public String getUserEmail() {
        return model.getEmail();
    }

    public void updateView() {
        view.printUserDetails(model.getName(), model.getEmail());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 사용하는 걸까?&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;분리된 관심사(Separation of Concerns)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVC 패턴은 어플리케이션의 관심사를 명확하게 분리하여 &lt;b&gt;각각의 역할을 독립적으로 관리&lt;/b&gt;할 수 있게 합니다. 이를 통해 코드의 가독성과 유지보수성을 높입니다.&lt;/li&gt;
&lt;li&gt;예를 들어, UI 변경이 필요할 때 모델과 컨트롤러에 영향을 주지 않고 뷰만 수정하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;li&gt;&lt;b&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;b&gt;단위 테스트(Unit Test)가 용이&lt;/b&gt;합니다. 모델, 뷰, 컨트롤러 각각을 독립적으로 테스트할 수 있습니다.&lt;/li&gt;
&lt;li&gt;예를 들어, 모델의 비즈니스 로직을 테스트할 때 뷰와 컨트롤러의 구현에 영향을 받지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/247</guid>
      <comments>https://chanheess.tistory.com/247#entry247comment</comments>
      <pubDate>Fri, 28 Jun 2024 20:15:56 +0900</pubDate>
    </item>
    <item>
      <title>스프링의 컨트롤러, 서비스, 레포지토리가 무엇인가?</title>
      <link>https://chanheess.tistory.com/248</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 컨트롤러 (Controller)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요청 처리&lt;/b&gt;: 사용자로부터 들어오는 &lt;b&gt;HTTP 요청을 처리&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;뷰로 전달&lt;/b&gt;: 처리된 결과를 뷰에 전달하여 사용자에게 응답을 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;URL 매핑&lt;/b&gt;: &lt;b&gt;URL 경로를 메서드와 매핑&lt;/b&gt;하여 특정 요청을 특정 메서드가 처리하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719299965859&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/api&quot;)
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping(&quot;/users&quot;)
    public List&amp;lt;User&amp;gt; getAllUsers() {
        return userService.getAllUsers();
    }

    @PostMapping(&quot;/users&quot;)
    public User createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨트롤러는 비즈니스 로직을 직접 포함하지 않으며, &lt;b&gt;요청을 서비스 레이어로 전달하여 처리&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 서비스 (Service)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비즈니스 로직 처리&lt;/b&gt;: 애플리케이션의 주요 &lt;b&gt;비즈니스 로직을 처리&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 조작&lt;/b&gt;: 레포지토리와 상호작용하여 &lt;b&gt;데이터베이스의 데이터를 가져오고, 조작&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트랜잭션 관리&lt;/b&gt;: 하나의 비즈니스 작업 단위를 관리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;pre id=&quot;code_1719299985974&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public List&amp;lt;User&amp;gt; getAllUsers() {
        return userRepository.findAll();
    }

    public User createUser(User user) {
        return userRepository.save(user);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스는 비즈니스 로직을 포함하며, 여러 &lt;b&gt;레포지토리를 사용&lt;/b&gt;할 수 있습니다. 서비스는 컨트롤러와 레포지토리 사이의 중간다리 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 레포지토리 (Repository)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역할&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 접근&lt;/b&gt;: &lt;b&gt;데이터베이스에 접근&lt;/b&gt;하여 데이터를 저장, 조회, 수정, 삭제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CRUD 작업&lt;/b&gt;: 기본적인 CRUD (Create, Read, Update, Delete) 작업을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 연동&lt;/b&gt;: &lt;b&gt;JPA, Hibernate 등을 사용&lt;/b&gt;하여 데이터베이스와 상호작용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1719304468264&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Repository
public interface UserRepository extends JpaRepository&amp;lt;User, Long&amp;gt; {
    // 기본 CRUD 메서드는 JpaRepository에서 제공
    // 추가적인 커스텀 쿼리 메서드도 정의 가능
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레포지토리는 &lt;b&gt;데이터 저장소와의 상호작용을 담당&lt;/b&gt;하며, 비즈니스 로직은 포함하지 않습니다. 단순히 데이터를 저장하고 검색하는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컨트롤러&lt;/b&gt;: &lt;b&gt;사용자 요청을 받아 서비스 레이어에 전달&lt;/b&gt;하고, 결과를 사용자에게 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서비스&lt;/b&gt;: &lt;b&gt;비즈니스 로직을 처리&lt;/b&gt;하며, 여러 레포지토리를 사용하여 데이터를 조작합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레포지토리&lt;/b&gt;: &lt;b&gt;데이터베이스와 상호작용&lt;/b&gt;하여 데이터를 저장, 조회, 수정, 삭제하는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지 컴포넌트는 각각의 책임을 명확히 분리하여 코드의 가독성, 유지보수성, 재사용성을 높입니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/248</guid>
      <comments>https://chanheess.tistory.com/248#entry248comment</comments>
      <pubDate>Thu, 27 Jun 2024 20:15:39 +0900</pubDate>
    </item>
    <item>
      <title>JDBC, JPA가 무엇이냐?</title>
      <link>https://chanheess.tistory.com/249</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDBC (Java Database Connectivity)란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- java에서 데이터베이스와의 상호작용을 가능하게 하는 API입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터베이스 쿼리 결과를 읽고 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b53Fip/btsIcPBmluh/W5L3BPcfsAuFs8rSKTXJak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b53Fip/btsIcPBmluh/W5L3BPcfsAuFs8rSKTXJak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b53Fip/btsIcPBmluh/W5L3BPcfsAuFs8rSKTXJak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb53Fip%2FbtsIcPBmluh%2FW5L3BPcfsAuFs8rSKTXJak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;276&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;276&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;JDBC는 3가지 기능을 표준 인터페이스로 정의하여 제공한다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; 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;&lt;b&gt;연결 관리 (Connection Management)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 연결을 설정하고 유지합니다. DriverManager 클래스를 통해 데이터베이스 URL, 사용자 이름, 비밀번호를 사용하여 데이터베이스에 연결할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령 실행 (Statement Execution)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 쿼리를 실행할 수 있는 다양한 Statement 객체를 제공합니다. 예를 들어, Statement, PreparedStatement, CallableStatement 등이 있습니다. 각 객체는 SQL 명령을 실행하고 결과를 반환받는 데 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 처리 (Result Handling)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 쿼리 실행 후 반환된 결과를 처리할 수 있습니다. ResultSet 객체는 데이터베이스 쿼리 결과를 표 형식으로 제공하며, 이를 통해 데이터를 읽고 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java.sql.Connection - 연결&lt;/li&gt;
&lt;li&gt;java.sql.Statement - SQL을 담은 내용&lt;/li&gt;
&lt;li&gt;java.sql.ResultSet - SQL 요청 응답&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Data JDBC, Spring Data JPA 등과 같은 기술이 등장하면서 JDBC API를 직접적으로 사용하는 일은 줄어들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Spring Data JDBC, Sprind Data JPA와 같은 기술도 데이터베이스와 연동하기 위해 내부적으로 JDBC를 이용하기 때문에 JDBC의 동작 흐름에 대해 알 필요가 있다.&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;1014&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvvCi9/btsIa3HI1Wd/T32IEE2VXfJdjgdOPEdTr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvvCi9/btsIa3HI1Wd/T32IEE2VXfJdjgdOPEdTr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvvCi9/btsIa3HI1Wd/T32IEE2VXfJdjgdOPEdTr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvvCi9%2FbtsIa3HI1Wd%2FT32IEE2VXfJdjgdOPEdTr1%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;1014&quot; height=&quot;173&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPA란? (Java Persistence API)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 애플리케이션에서 &lt;b&gt;관계형 데이터베이스를 사용&lt;/b&gt;할 수 있도록 도와주는 자바 표준 ORM프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA는 데이터베이스의 테이블을 자바 객체로 매핑하여, 객체 지향적으로 데이터베이스 작업을 수행할 수 있게 해줍니다&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ORM &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(Object-Relational Mapping)&lt;/span&gt;이 뭔데?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 테이블과 자바 클래스 간의 매핑을 정의합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;JPA 주요 기능:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;객체-관계 매핑(ORM)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 테이블과 자바 클래스 간의 매핑을 정의합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;b&gt;SQL 쿼리를 직접 작성하지 않고도 데이터베이스 작업을 수행&lt;/b&gt;할 수 있습니다. JPA는 쿼리 생성을 자동화하고, 개발자는 객체 지향적인 방법으로 데이터에 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추상화&lt;/b&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;b&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;b&gt;캐싱&lt;/b&gt;: JPA 구현체는 1차 캐시와 2차 캐시를 지원하여 데이터베이스 접근 빈도를 줄이고 성능을 향상시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지연 로딩&lt;/b&gt;: 지연 로딩(Lazy Loading)을 통해 필요한 시점에만 데이터를 로딩하여 불필요한 데이터 로딩을 방지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;트랜잭션이 뭔데?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 데이터베이스에서 일련의 &lt;b&gt;작업들을 하나의 단위로 묶은 것&lt;/b&gt;입니다. 이러한 트랜잭션은 모두 성공하거나 모두 실패하는 원자적 작업 단위로 처리됩니다. 이는 데이터베이스의 일관성과 무결성을 보장하기 위해 중요합니다.&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;b&gt;JDBC&lt;/b&gt;는 SQL 쿼리를 사용하여 데이터베이스와 직접 상호작용하는 저수준 API입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JPA&lt;/b&gt;는 객체 지향적인 방식으로 &lt;b&gt;SQL 쿼리를 직접 작성하지 않고도&lt;/b&gt; 데이터베이스 작업을 수행할 수 있게 하는 고수준 ORM 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ittrue.tistory.com/250&quot;&gt;https://ittrue.tistory.com/250&lt;/a&gt; [IT is True:티스토리]&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/249</guid>
      <comments>https://chanheess.tistory.com/249#entry249comment</comments>
      <pubDate>Wed, 26 Jun 2024 20:15:19 +0900</pubDate>
    </item>
    <item>
      <title>데이터 베이스 테이블?</title>
      <link>https://chanheess.tistory.com/252</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 테이블(Database Table)은 관계형 데이터베이스에서 데이터를 구조화하여 저장하는 기본 단위입니다. 테이블은 행(Row)과 열(Column)로 구성되며, 각각의 행은 하나의 레코드(Record), 각각의 열은 데이터 필드(Field)를 나타냅니다.&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;&lt;b&gt;열(Column)&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;li&gt;&lt;b&gt;행(Row)&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;li&gt;&lt;b&gt;기본 키(Primary Key)&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;b&gt;반드시 고유&lt;/b&gt;해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;외래 키(Foreign Key)&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;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 학생 정보를 저장하는 테이블(Student)의 예제입니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;ID&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Name&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Age&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Major&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;John Doe&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Computer Sci&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Jane Doe&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;22&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Mathematics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Bob Smith&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;21&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;StudentID&lt;/b&gt;: 기본 키로 각 학생을 고유하게 식별합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Name&lt;/b&gt;: 학생의 이름을 저장하는 열입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Age&lt;/b&gt;: 학생의 나이를 저장하는 열입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Major&lt;/b&gt;: 학생의 전공을 저장하는 열입니다.&lt;/li&gt;
&lt;/ul&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;&lt;b&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;li&gt;&lt;b&gt;데이터 조회&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 쿼리를 사용하여 테이블에서 필요한 데이터를 빠르게 조회할 수 있습니다.&lt;/li&gt;
&lt;li&gt;다양한 조건과 기준을 설정하여 데이터를 검색할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&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;li&gt;&lt;b&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;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 테이블은 데이터를 구조화하여 저장하는 기본 단위로서, 행과 열로 구성되며, 기본 키와 외래 키를 통해 데이터의 무결성을 유지하고 테이블 간의 관계를 정의합니다. 이러한 구조 덕분에 대량의 데이터를 체계적으로 관리하고, 효율적으로 조회 및 수정할 수 있습니다.&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/252</guid>
      <comments>https://chanheess.tistory.com/252#entry252comment</comments>
      <pubDate>Wed, 26 Jun 2024 02:33:38 +0900</pubDate>
    </item>
    <item>
      <title>웹 서버와 웹 애플리케이션 서버</title>
      <link>https://chanheess.tistory.com/246</link>
      <description>&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;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;웹 사이트 또는 애플리케이션을 방문하면 브라우저(클라이언트)가 원격 서버에 데이터를 요청하고 응답을 표시합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;세부적으로는?&lt;/span&gt;&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;&lt;b&gt; 애플리케이션 서버&lt;/b&gt;는 &lt;b&gt;비즈니스 로직&lt;/b&gt;을 추가하여 웹 서버의 응답을 계산합니다. 대부분&amp;nbsp;&lt;b&gt;동적&amp;nbsp;콘텐츠를&amp;nbsp;제공&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정적, 동적 컨텐츠의 예&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미지 파일(예: PNG, GIF 및 JPEG), 다운로드 가능한 문서(PDF), 비디오 및 HTML 파일은 모두 정적 콘텐츠입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 동적으로 생성되는 보고서, 사용자 지정된 데이터 표현, 개인화된 UI, 데이터베이스 결과 및 처리된 HTML은 모두 동적 콘텐츠입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;웹 서버 작동 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 웹 사이트의 코드와 데이터를 호스팅하는 기술입니다. 브라우저에 URL을 입력할 때 이 URL은 실제로 웹 서버의 주소 식별자입니다.&lt;/p&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저는 URL을 사용하여 서버의 IP 주소를 찾습니다.&lt;/li&gt;
&lt;li&gt;브라우저는 정보에 대한 HTTP 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;웹 서버는 데이터 베이스 서버와 통신하여 관련 데이터를 찾습니다.&lt;/li&gt;
&lt;li&gt;웹서버는 HTTP 응답으로 HTML 페이지, 이미지, 비디오 또는 파일과 같은 정적 컨텐츠를 브라우저에 반환합니다.&lt;/li&gt;
&lt;li&gt;그러면 브라우저가 정보를 표시합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;URL(Uniform Resource Locator)이란?&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;&lt;span style=&quot;text-align: start;&quot;&gt;Http란?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;웹 상에서 브라우저와 서버가 데이터를 주고 받을때 사용하는 프로토콜이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애플리케이션 서버 작동 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 서버는 &lt;b&gt;동적 콘텐츠 생성&lt;/b&gt;, 애플리케이션 로직 및 다양한 리소스와의 통합을 지원하여 웹 서버의 기능을 확장합니다. 애플리케이션 코드를 실행하고 메시징 시스템 및 데이터베이스와 같은 다른 소프트웨어 구성 요소와 상호 작용할 수 있는 &lt;b&gt;런타임 환경을 제공&lt;/b&gt;합니다. &lt;b&gt;비즈니스 로직을 사용&lt;/b&gt;하여 웹 서버보다 더 의미 있게 데이터를 변환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;웹&amp;nbsp;사이트에서&amp;nbsp;대화형&amp;nbsp;콘텐츠에&amp;nbsp;액세스하려고&amp;nbsp;할&amp;nbsp;때&amp;nbsp;프로세스는&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;작동합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저는 URL을 사용하여 서버의 IP 주소를 찾습니다.&lt;/li&gt;
&lt;li&gt;브라우저는 정보에 대한 HTTP 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;웹 서버는 요청을 애플리케이션 서버로 전송합니다.&lt;/li&gt;
&lt;li&gt;애플리케이션 서버는 비즈니스 로직을 적용하고 다른 서버 및 서드 파티 시스템과 통신하여 요청을 수행합니다.&lt;/li&gt;
&lt;li&gt;애플리케이션 서버는 새 HTML 페이지를 렌더링하고 이를 응답으로 웹 서버에 반환합니다.&lt;/li&gt;
&lt;li&gt;웹 서버는 브라우저에 응답을 반환합니다.&lt;/li&gt;
&lt;li&gt;브라우저가&amp;nbsp;정보를&amp;nbsp;표시합니다.&lt;/li&gt;
&lt;/ol&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 차이점&lt;/h3&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 144px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.9999%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 38.2559%; text-align: center; height: 18px;&quot;&gt;&lt;b&gt;웹 서버&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%; text-align: center; height: 18px;&quot;&gt;&lt;b&gt;애플리케이션 서버&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 14.9999%; text-align: center; height: 36px;&quot;&gt;다루는 태스크&lt;/td&gt;
&lt;td style=&quot;width: 38.2559%; height: 36px;&quot;&gt;웹 서버는 간단한 요청에 대한 응답을 제공합니다.&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%; height: 36px;&quot;&gt;애플리케이션 서버는 데이터베이스, 서비스 및 엔터프라이즈 시스템의 더 복잡한 콘텐츠를 제공합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 14.9999%; text-align: center; height: 36px;&quot;&gt;사용되는 프로토콜&lt;/td&gt;
&lt;td style=&quot;width: 38.2559%; height: 36px;&quot;&gt;웹 서버는 주로 HTTP를 사용합니다. FTP와 SMTP도 지원합니다.&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%; height: 36px;&quot;&gt;애플리케이션 서버는 많은 프로토콜을 지원합니다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 36px;&quot;&gt;
&lt;td style=&quot;width: 14.9999%; text-align: center; height: 36px;&quot;&gt;콘텐츠 유형&lt;/td&gt;
&lt;td style=&quot;width: 38.2559%; height: 36px;&quot;&gt;웹 서버는 HTML 페이지, 이미지, 비디오 및 파일과 같은 정적 콘텐츠를 제공합니다.&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%; height: 36px;&quot;&gt;애플리케이션 서버는 실시간 업데이트, 개인화된 정보 및 고객 지원과 같은 동적 콘텐츠를 제공합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.9999%; text-align: center; height: 18px;&quot;&gt;멀티스레딩&lt;/td&gt;
&lt;td style=&quot;width: 38.2559%; height: 18px;&quot;&gt;일반적으로 멀티스레딩을 사용하지 않습니다.&lt;/td&gt;
&lt;td style=&quot;width: 46.6279%; height: 18px;&quot;&gt;멀티스레딩을 사용하여 요청을 동시에 처리합니다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/compare/the-difference-between-web-server-and-application-server/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/compare/the-difference-between-web-server-and-application-server/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719296295916&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;웹 서버와 애플리케이션 서버 - 기술 서버 간의 차이점 - AWS&quot; data-og-description=&quot;웹 서버와 애플리케이션 서버의 차이점은 무엇인가요? 웹 서버와 애플리케이션 서버는 인터넷 상에서 데이터와 서비스를 교환할 수 있는 기술입니다. 클라이언트-서버 아키텍처는 인터넷의 기&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/compare/the-difference-between-web-server-and-application-server/&quot; data-og-url=&quot;https://aws.amazon.com/ko/compare/the-difference-between-web-server-and-application-server/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qyPH7/hyWrU895FK/oOj0n5jVf5v90vimxytgVk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bfWtPs/hyWoCWEJAq/Ve6wyuC9e5VPXU6S01uIS1/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/compare/the-difference-between-web-server-and-application-server/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/compare/the-difference-between-web-server-and-application-server/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qyPH7/hyWrU895FK/oOj0n5jVf5v90vimxytgVk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bfWtPs/hyWoCWEJAq/Ve6wyuC9e5VPXU6S01uIS1/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109');&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;웹 서버와 애플리케이션 서버 - 기술 서버 간의 차이점 - AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹 서버와 애플리케이션 서버의 차이점은 무엇인가요? 웹 서버와 애플리케이션 서버는 인터넷 상에서 데이터와 서비스를 교환할 수 있는 기술입니다. 클라이언트-서버 아키텍처는 인터넷의 기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend Programming</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/246</guid>
      <comments>https://chanheess.tistory.com/246#entry246comment</comments>
      <pubDate>Tue, 25 Jun 2024 14:33:20 +0900</pubDate>
    </item>
    <item>
      <title>[java] 백준 11047 동전 0</title>
      <link>https://chanheess.tistory.com/245</link>
      <description>&lt;pre id=&quot;code_1719225429069&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//동전 종류 N
//동전 가치의 합 K
//동전의 개수 나와있지 않지만 많음?
//내림차순으로 최대한 금액넣기

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        int K = in.nextInt();
        int[] coins = new int [N];

        for(int i = 0; i &amp;lt; N; i++) {
            coins[i] = in.nextInt();
        }

        in.close();

        int result = 0;

        for(int i = N - 1; i &amp;gt;= 0; i--) {
            result += K / coins[i];
            K = K % coins[i];

            if (K &amp;lt;= 0) {
                break;
            }
        }

        System.out.println(result);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. input받고 높은 금액부터 최대한 많이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용한 개수를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java 사용의 감 좀 익힐겸 사용했는데 확실히 cpp보다 메모리라던가 시간적으로도 비효율적인것같다. 많이 풀면서 input을 효율적으로 하는 방법이나 array를 어떤 것을 사용한다던가를 익혀봐야겠다.&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/245</guid>
      <comments>https://chanheess.tistory.com/245#entry245comment</comments>
      <pubDate>Mon, 24 Jun 2024 19:39:24 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 스마트 포인터</title>
      <link>https://chanheess.tistory.com/145</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;auto_ptr&lt;/h3&gt;
&lt;pre id=&quot;code_1628135915841&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;memory&amp;gt;

auto_ptr&amp;lt;double&amp;gt; ap(new double);
*ap = 25.5;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- memory헤더 파일을 소스코드에 포함해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리가 해제될 때 ap를 해제하고 ap의 파괴자가 동적메모리를 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;new로 생성한 단일 객체에 대해서만 메모리의 해제를 보장한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- &lt;b&gt;동일한 메모리 위치를 가리키는 객체를 2개 이상 생성하지 않아야 한다&lt;/b&gt;는 점입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;-&lt;/b&gt; C++11이 지원되는 환경이라면 auto_ptr을 완전히 대체하는 unique_ptr을 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628146197545&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;auto_ptr&amp;lt;double&amp;gt; ap(new double);
auto_ptr&amp;lt;double&amp;gt; ap2;
*ap = 25.5;
ap2 = ap;

cout &amp;lt;&amp;lt; *ap;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다음과 같을 때 ap는 ap2에 소유권을 넘기게된다. 더 안전하다고 할 수 있다.&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;unique_ptr&lt;/h3&gt;
&lt;pre id=&quot;code_1628146977405&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unique_ptr&amp;lt;double&amp;gt; ap(new double);
unique_ptr&amp;lt;double&amp;gt; ap2;
*ap = 25.5;
ap2 = ap;	//애초에 컴파일에러

cout &amp;lt;&amp;lt; *ap;

unique_ptr&amp;lt;double[]&amp;gt; ap3(new double(5));
ap3[0] = 2;

unique_ptr&amp;lt;MyClass&amp;gt; ap4 = make_unique&amp;lt;MyClass&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- unique_ptr은 단일 소유권을 가진 스마트 포인터로, 소유권을 가진 유일한 스마트포인터가 소멸될 때 자원을 해제합니다.&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;- 다른 스마트포인터와는 다른 점으로 array로도 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1628175215792&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;unique_ptr&amp;lt;string&amp;gt; demo(const char * s)
{
    unique_ptr,string&amp;gt; temp(new string(s));
    return temp;
}


unique_ptr&amp;lt;string&amp;gt; ps;
ps = demo(&quot;Uniquely special&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다음과같이 함수를 통해 임시 unique_ptr을 리턴하면 ps는 unique_ptr이 리턴한 객체의 소유권을 얻는다.&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;pre id=&quot;code_1717922753702&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

class MyClass {
public:
    MyClass() {
        std::cout &amp;lt;&amp;lt; &quot;MyClass created&quot; &amp;lt;&amp;lt; std::endl;
    }
    ~MyClass() {
        std::cout &amp;lt;&amp;lt; &quot;MyClass destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }
};

int main() {
    std::unique_ptr&amp;lt;MyClass&amp;gt; uniquePtr = std::make_unique&amp;lt;MyClass&amp;gt;();

    // unique_ptr을 shared_ptr로 이동
    std::shared_ptr&amp;lt;MyClass&amp;gt; sharedPtr = std::move(uniquePtr);

    if (!uniquePtr) {
        std::cout &amp;lt;&amp;lt; &quot;uniquePtr is now empty&quot; &amp;lt;&amp;lt; std::endl;
    }

    if (sharedPtr) {
        std::cout &amp;lt;&amp;lt; &quot;sharedPtr now owns the MyClass instance&quot; &amp;lt;&amp;lt; std::endl;
    }

    return 0;
}&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;- move()를 사용하여&amp;nbsp;shared_ptr에도 대입가능하다.&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;shared_ptr&lt;/h3&gt;
&lt;pre id=&quot;code_1628146572567&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;shared_ptr&amp;lt;double&amp;gt; ap(new double);
shared_ptr&amp;lt;double&amp;gt; ap2;
*ap = 25.5;
ap2 = ap;

cout &amp;lt;&amp;lt; *ap;&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;- shared_ptr는 참조를 하게 될 경우 참조카운트를 추가한다. 처음 ap에서 1, ap2에서 같은 객체를 가르킬 때 2로 올라간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- reference counting(참조 카운트) 방식으로 객체의 소유권을 공유하게 되며 이 카운트가 0이 되면 객체가 해제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- shared_ptr의 객체는 복사가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 생포인터도 해당 shared_ptr를 가르킬 수 있지만 shared_ptr이 모두 해제되어 객체가 소멸됐다면 생포인터는 더 이상 nullptr이나 정확한 주소를 가르키고 있지 않고 잘못된 주소를 가르킵니다. 이 경우를 댕글링포인터라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- shared_ptr를 가진 객체가 서로 상호 참조하고 있다면 순환참조를 하게 되어서 카운트가 1이하로 떨어지지않아 삭제되는 문제가 있습니다. 해당 문제를 해결하기 위해서 weak_ptr를 사용합니다.&lt;/p&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;weak_ptr&lt;/h3&gt;
&lt;pre id=&quot;code_1717922710874&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

class MyClass {
public:
    MyClass(int value) : value(value) {
        std::cout &amp;lt;&amp;lt; &quot;MyClass created with value: &quot; &amp;lt;&amp;lt; value &amp;lt;&amp;lt; std::endl;
    }
    ~MyClass() {
        std::cout &amp;lt;&amp;lt; &quot;MyClass destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }
    int value;
};

int main() {
    std::shared_ptr&amp;lt;MyClass&amp;gt; sharedPtr = std::make_shared&amp;lt;MyClass&amp;gt;(42);
    std::weak_ptr&amp;lt;MyClass&amp;gt; weakPtr = sharedPtr;

    // Before shared_ptr is reset
    if (auto lockedPtr = weakPtr.lock()) {
        std::cout &amp;lt;&amp;lt; &quot;Locked value: &quot; &amp;lt;&amp;lt; lockedPtr-&amp;gt;value &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;The object has been destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }

    sharedPtr.reset(); // Destroy the shared pointer

    // After shared_ptr is reset
    if (auto lockedPtr = weakPtr.lock()) {
        std::cout &amp;lt;&amp;lt; &quot;Locked value: &quot; &amp;lt;&amp;lt; lockedPtr-&amp;gt;value &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;The object has been destroyed&quot; &amp;lt;&amp;lt; std::endl;
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;weak_ptr&lt;/span&gt; : &lt;span data-token-index=&quot;2&quot;&gt;shared_ptr&lt;/span&gt;과 함께 사용되어 &lt;span data-token-index=&quot;4&quot;&gt;순환 참조를 방지하고 메모리 누수를 방지&lt;/span&gt;하기 위해 사용되는 스마트 포인터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- shared_ptr의 약한 참조를 제공하므로 shared_ptr의 참조 카운트를 증가시키지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- weak_ptr은 shared_ptr으로 참조되는 객체의 유효성을 검사하고, 객체에 접근할 때 **std::lock()**을 사용하여 안전하게 참조합니다. (참조된 주소가 lock())&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- weak_ptr이 참조하고 있는 메모리가 해제되었을 때 해당 주소는 잘못된 주소를 가르키지만 lock()을 통해서 안전하게 shared_ptr을 가르키고 있는지 아니면 잘못되거난 빈주소라면 nullptr을 가르키도록 되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움사이트 : &lt;a href=&quot;https://psychoria.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://psychoria.tistory.com/42&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C++/Basic</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/145</guid>
      <comments>https://chanheess.tistory.com/145#entry145comment</comments>
      <pubDate>Sun, 9 Jun 2024 17:47:36 +0900</pubDate>
    </item>
    <item>
      <title>우측값(rvalue)에 대하여</title>
      <link>https://chanheess.tistory.com/170</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 rvalue 참조(&amp;amp;&amp;amp;)와 이동연산자(move constructor 및 move assignment operator)를 통해 효율적인 메모리 관리를 할 수 있습니다. 이 글에서는 이러한 개념들을 자세히 설명하고, 이를 통해 성능 최적화 방법을 이해할 수 있도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;rvalue 참조 (&amp;amp;&amp;amp;)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rvalue 참조는 C++11에서 도입된 기능으로, 주로 이동생성자(move constructor)와 이동할당 연산자(move assignment operator)를 구현하는 데 사용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;lvalue와 rvalue&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lvalue는 식별 가능한 위치를 가지는 값입니다. 예를 들어, 변수는 lvalue입니다.&lt;/li&gt;
&lt;li&gt;rvalue는 임시 값으로, 식별 가능한 위치를 가지지 않는 값입니다. 예를 들어, 리터럴이나 연산의 결과는 rvalue입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;rvalue 참조 (&amp;amp;&amp;amp;)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rvalue 참조는 &amp;amp;&amp;amp;를 사용하여 선언되며, rvalue를 참조할 수 있습니다. lvalue를 참조하는 것처럼 rvalue를 참조한다고 생각하면 쉽게 이해할 수 있습니다.&lt;/li&gt;
&lt;li&gt;주로 이동생성자와 이동할당 연산자에서 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1717744209670&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;utility&amp;gt; // std::move를 사용하기 위해 필요

void test(int&amp;amp;&amp;amp; x)
{
    x = 10;
    std::cout &amp;lt;&amp;lt; x;
}

void test2(int x)
{
    x = 10;
    std::cout &amp;lt;&amp;lt; x;
}

int main() 
{
    test(30);   // test 함수 호출: rvalue 참조로 30 전달
    test2(20);  // test2 함수 호출: 값 타입으로 20 전달

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;매개변수 타입&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test 함수는 rvalue 참조 (int&amp;amp;&amp;amp;)를 매개변수로 받습니다.&lt;/li&gt;
&lt;li&gt;test2 함수는 일반적인 값 타입 (int)을 매개변수로 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;값 전달 방식&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test 함수는 rvalue 참조를 통해 값을 전달받습니다. 이는 임시 객체나 이동 가능한 객체를 전달받을 때 주로 사용됩니다.&lt;/li&gt;
&lt;li&gt;test2 함수는 값을 복사하여 전달받습니다. 이는 호출 시 원본 값을 복사하여 함수 내부에서 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 사용 방식&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test 함수는 rvalue 참조를 사용하여 기존의 임시 객체나 이동 가능한 객체를 참조합니다. 따라서 복사가 일어나지 않습니다.&lt;/li&gt;
&lt;li&gt;test2 함수는 값을 복사하여 매개변수로 전달받습니다. 따라서 호출 시 매개변수로 전달되는 값의 복사가 일어납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 두 함수 모두 10을 출력하지만, 값 전달 방식과 메모리 사용 방식에서 차이가 있습니다. test 함수는 rvalue 참조를 사용하여 효율적으로 값을 전달하고 변경하는 반면, test2 함수는 값을 복사하여 전달하고 변경합니다.&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;std::move()에 대해&lt;/h3&gt;
&lt;pre id=&quot;code_1717740885580&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;utility&amp;gt; // std::move를 사용하기 위해 필요

void test(int&amp;amp;&amp;amp; x) {
    x = 10;
    std::cout &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl; // 변경된 x 출력
}

int main() {
    int i = 50;
    test(std::move(i)); // i를 rvalue로 변환하여 전달

    std::cout &amp;lt;&amp;lt; i &amp;lt;&amp;lt; std::endl; // 변경된 i 출력

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;std::move란?&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;std::move()란 객체를 강제로 rvalue로 캐스팅하여 이동을 사용할 수 있게 하는 유틸리티입니다. 이를 통해 복사 대신 이동을 수행하여 자원 소모를 줄이고 성능을 최적화할 수 있습니다.&lt;/li&gt;
&lt;li&gt;std::move()는 lvalue의 값을 rvalue처럼 취급하도록 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;std::move를 통한 lvalue -&amp;gt; rvalue 변환&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;std::move(i)는 변수 i를 rvalue로 캐스팅합니다. i는 원래 lvalue(좌값)입니다.&lt;/li&gt;
&lt;li&gt;std::move는 단순히 i를 rvalue로 취급하도록 캐스팅할 뿐, &lt;b&gt;실제로 데이터를 이동시키는 것은 아닙니다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;rvalue reference&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;test(int&amp;amp;&amp;amp; x) 함수는 rvalue reference를 인자로 받습니다. rvalue reference는 임시 객체나 리소스의 소유권을 넘길 때 주로 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수 호출&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;test(std::move(i))를 호출하면, i의 값(50)이 x로 전달됩니다. 이때 x는 rvalue reference로 i를 가리키게 됩니다.&lt;/li&gt;
&lt;li&gt;함수 내부에서 x = 10을 통해 x의 값을 10으로 변경합니다. 이 변경은 실제로 i에 영향을 미칩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;함수 test 내부에서 std::cout &amp;lt;&amp;lt; x는 10을 출력합니다.&lt;/li&gt;
&lt;li&gt;main 함수에서 std::cout &amp;lt;&amp;lt; i는 10을 출력합니다. 이는 i의 값이 test 함수 호출 중에 변경되었기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;우측 값 참조의 연산자와 생성자의 동작 원리&lt;/h3&gt;
&lt;pre id=&quot;code_1717747165467&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() 
{
    int&amp;amp;&amp;amp; rref = 5; // 'rref'는 임시 객체 '5'를 참조하는 rvalue 참조

    int b = 10;
    rref = std::move(b); // 'b'를 rvalue로 캐스팅하여 'rref'에 이동

    b = 3;
    cout &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; rref &amp;lt;&amp;lt; endl;	//3 10
    rref = 7;
    cout &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; rref;	//3 7

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;첫 번째 예제 (대입 연산자)&lt;/b&gt;: rref는 임시 객체 5를 참조하도록 생성되었습니다. rref = std::move(b)는 rref가 b의 값을 받도록 하지만, rref와 b는 메모리 주소를 공유하지 않습니다. 따라서 b와 rref는 서로 독립적인 값을 가집니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717747314632&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() 
{
    int a = 5;      // 'a'는 lvalue
    int&amp;amp; lref = a;  // 'lref'는 'a'를 참조하는 lvalue 참조

    int b = 10;
    int&amp;amp;&amp;amp; rref = std::move(b); // 'rref'는 b를 참조하는 rvalue 참조

    rref = 7;
    cout &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; rref &amp;lt;&amp;lt; endl; // 출력: 7 7
    b = 3;
    cout &amp;lt;&amp;lt; b &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; rref &amp;lt;&amp;lt; endl; // 출력: 3 3

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 번째 예제 (생성자) &lt;/b&gt;: rref는 처음부터 std::move(b)를 통해 b를 참조하도록 생성되었습니다. 이로 인해 rref와 b는 동일한 메모리 주소를 공유하게 됩니다. 따라서 rref의 값을 변경하면 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Move Semantics&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이동(Move Semantics)은 C++11에서 도입된 기능으로, 객체의 복사 대신 이동을 통해 성능을 최적화할 수 있도록 해줍니다. 이동은 주로 자원의 소유권을 이전할 때 사용되며, 복사보다 훨씬 효율적입니다. 이를 통해 불필요한 복사 연산을 줄이고, 프로그램의 성능을 향상시킬 수 있습니다. 아래의 내용은 Move Semantics를 확인할 수 있는 좋은 예제입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;move()를 사용하여 이동할당&lt;/h3&gt;
&lt;pre id=&quot;code_1637728096887&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;utility&amp;gt; // std::move를 사용하기 위해 필요

class Resource {
public:
    int* data;

    // 기본 생성자
    Resource() : data(new int[10]) {
        std::cout &amp;lt;&amp;lt; &quot;Resource acquired\n&quot;;
    }

    // 소멸자
    ~Resource() {
        delete[] data;
        std::cout &amp;lt;&amp;lt; &quot;Resource destroyed\n&quot;;
    }

    // 복사 생성자
    Resource(const Resource&amp;amp; other) : data(new int[10]) {
        std::copy(other.data, other.data + 10, data);
        std::cout &amp;lt;&amp;lt; &quot;Resource copied\n&quot;;
    }

    // 이동생성자
    Resource(Resource&amp;amp;&amp;amp; other) noexcept : data(nullptr) {
        data = other.data;
        other.data = nullptr;
        std::cout &amp;lt;&amp;lt; &quot;Resource moved\n&quot;;
    }

    // 이동할당 연산자
    Resource&amp;amp; operator=(Resource&amp;amp;&amp;amp; other) noexcept {
        if (this != &amp;amp;other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
            std::cout &amp;lt;&amp;lt; &quot;Resource moved via assignment\n&quot;;
        }
        return *this;
    }

    // 복사할당 연산자(필요시 구현)
    Resource&amp;amp; operator=(const Resource&amp;amp; other) {
        if (this != &amp;amp;other) {
            delete[] data;
            data = new int[10];
            std::copy(other.data, other.data + 10, data);
            std::cout &amp;lt;&amp;lt; &quot;Resource copied via assignment\n&quot;;
        }
        return *this;
    }
};

int main() {
    Resource res1;              // 기본 생성자를 통해 자원 할당
    Resource res2 = res1;       // 복사 생성자를 통해 자원 복사
    Resource res3;              // 기본 생성자를 통해 자원 할당
    res3 = res1;                // 복사할당 연산자를 통해 자원 복사
    Resource res4 = std::move(res1); // 이동생성자를 통해 자원 이동
    Resource res5;              // 기본 생성자를 통해 자원 할당
    res5 = std::move(res4);     // 이동할당 연산자를 통해 자원 이동

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 연산자, 생성자의 설명&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 생성자&lt;/b&gt;는 Resource 객체를 생성하고 data 포인터에 동적으로 할당된 int 배열을 초기화합니다. &quot;Resource acquired&quot; 메시지를 출력합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복사 생성자&lt;/b&gt;는 res1을 res2로 복사하여 새로운 Resource 객체를 생성합니다. 복사 생성자는 res1의 데이터를 복사하여 res2에 새로운 int 배열을 할당하고 데이터를 복사합니다. &quot;Resource copied&quot; 메시지를 출력합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복사 할당 연산자&lt;/b&gt;는 이미 생성된 res3 객체에 res1의 데이터를 복사합니다. 복사할당 연산자는 res1의 데이터를 res3의 data에 복사합니다. &quot;Resource copied via assignment&quot; 메시지를 출력합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이동 생성자&lt;/b&gt;는 res1의 자원을 res4로 이동시켜 새로운 Resource 객체를 생성합니다. 이동 생성자는 res1의 data 포인터를 res4로 이동시키고, res1의 data 포인터를 nullptr로 설정합니다. &quot;Resource moved&quot; 메시지를 출력합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이동 할당 연산자&lt;/b&gt;는 이미 생성된 res5 객체에 res4의 자원을 이동시킵니다. 이동할당 연산자는 res4의 data 포인터를 res5로 이동시키고, res4의 data 포인터를 nullptr로 설정합니다. &quot;Resource moved via assignment&quot; 메시지를 출력합니다.&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;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;rvalue 참조 (&amp;amp;&amp;amp;)&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;rvalue는 우측값의 참조입니다. 해당 값을 참조하여 생성된 변수는 원본 메모리를 참조하므로, 값이 변경될 때 참조된 곳과 함께 변경됩니다.&lt;/li&gt;
&lt;li&gt;생성 시 rvalue 참조를 사용하면, 원본 값을 직접 참조하므로 효율적인 메모리 사용이 가능합니다.&lt;/li&gt;
&lt;li&gt;반면, rvalue 참조를 연산자에서 대입할 경우에는 메모리 주소를 공유하지 않으므로 서로의 값 변경에 영향을 미치지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;std::move()&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;std::move()는 객체를 강제로 rvalue로 캐스팅하여 이동을 사용할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;이동 생성자나 이동 할당 연산자를 통해 기존 객체의 메모리 주소를 새로운 객체에 대입하고, 기존 객체의 자원을 해제하여 이전 객체의 자원을 비워줍니다. 이를 통해 메모리 할당 및 해제를 최소화하고 효율적으로 리소스를 이전할 수 있습니다.&lt;/li&gt;
&lt;li&gt;또한, 우측값 참조에 값을 넣어줄 때 std::move()를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 추가 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이동을 지원할 객체는 const로 선언하지 말아야한다. const 객체에 대한 이동 요청은 복사 연산으로 변환된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ...&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보편 참조(Universal Reference)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편참조는 C++11에서 도입된 개념으로, 함수 템플릿의 매개변수에서 사용됩니다. 이는 T&amp;amp;&amp;amp; 형식의 참조를 의미하며, lvalue와 rvalue를 모두 참조할 수 있는 참조 유형입니다. 보편 참조는 템플릿 타입 유추와 결합되어 동작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1717750925163&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;utility&amp;gt; // std::forward를 사용하기 위해 필요

template&amp;lt;typename T&amp;gt;
void func(T&amp;amp;&amp;amp; arg) {
    handle(std::forward&amp;lt;T&amp;gt;(arg));
}

void handle(int&amp;amp; x) {
    std::cout &amp;lt;&amp;lt; &quot;Lvalue reference: &quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;
}

void handle(int&amp;amp;&amp;amp; x) {
    std::cout &amp;lt;&amp;lt; &quot;Rvalue reference: &quot; &amp;lt;&amp;lt; x &amp;lt;&amp;lt; std::endl;
}

int main() {
    int a = 10;
    func(a);         // Lvalue 전달
    func(20);        // Rvalue 전달
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;템플릿 함수 func&lt;/b&gt;: T&amp;amp;&amp;amp; 형식의 매개변수 arg를 받습니다. 이는 보편 참조입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;std::forward&amp;lt;T&amp;gt;(arg)&lt;/b&gt;: 템플릿 인자를 그대로 전달하여 적절한 함수 오버로드가 선택되도록 합니다. lvalue는 lvalue로, rvalue는 rvalue로 전달됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;handle 함수&lt;/b&gt;: lvalue 참조와 rvalue 참조를 각각 처리하는 오버로드된 함수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 func(a)는 lvalue 참조를 전달하며, func(20)는 rvalue 참조를 전달합니다. 보편 참조를 사용함으로써 func 함수는 lvalue와 rvalue 모두를 유연하게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고사이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://modoocode.com/189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://modoocode.com/189&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1633417330992&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;씹어먹는 C++ 토막글 ① - Rvalue(우측값) 레퍼런스에 관해&quot; data-og-description=&quot;&quot; data-og-host=&quot;modoocode.com&quot; data-og-source-url=&quot;https://modoocode.com/189&quot; data-og-url=&quot;https://modoocode.com/189&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://modoocode.com/189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://modoocode.com/189&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;씹어먹는 C++ 토막글 ① - Rvalue(우측값) 레퍼런스에 관해&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;modoocode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C++/Effective Modern</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/170</guid>
      <comments>https://chanheess.tistory.com/170#entry170comment</comments>
      <pubDate>Fri, 7 Jun 2024 17:32:08 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 1149 RGB거리</title>
      <link>https://chanheess.tistory.com/244</link>
      <description>&lt;pre id=&quot;code_1717488724031&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

int main() 
{
    int n;
    cin &amp;gt;&amp;gt; n;
    vector&amp;lt;int&amp;gt; house(3, 0);

    for (int i = 0; i &amp;lt; n; i++) 
    {
        int color[3];
        int result[3];
        cin &amp;gt;&amp;gt; color[0] &amp;gt;&amp;gt; color[1] &amp;gt;&amp;gt; color[2];

        result[0] = min(house[1] + color[0], house[2] + color[0]);
        result[1] = min(house[0] + color[1], house[2] + color[1]);
        result[2] = min(house[0] + color[2], house[1] + color[2]);

        house[0] = result[0];
        house[1] = result[1];
        house[2] = result[2];
    }

    cout &amp;lt;&amp;lt; min({ house[0], house[1], house[2] });

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 단서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- N만큼의 집&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 빨강, 초록, 파랑의 3가지색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 색에 대한 정보를 합쳤을 때 색마다 -+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;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 3개의 색깔과 색깔 * n만큼의 색깔 칠하기 입력이 이뤄진다. 여기서 경우의 수를 따져본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번째 줄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A / B / C&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번째 줄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(B,C) A / (A,C) B / (A,B) C&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번째 줄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A : (A,C), B / ((A,B), C&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B : &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(B,C) A / (A,B) C&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;C : (A,C), B / (B,C) 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;2. 점화식을 세워보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A는 C와 B에 적립된 값중 작은 값,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B는 A와 C에 적립된 값중 작은 값,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C는 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;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠르게 규칙을 찾는 것이 dp를 빠르게 해결하는 방법인 것같다. 그 과정을 빠르게 찾는 방법은 그냥 모든 경우의 수를 경우에 따라 3~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;a href=&quot;https://www.acmicpc.net/problem/1149&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1149&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>dp</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/244</guid>
      <comments>https://chanheess.tistory.com/244#entry244comment</comments>
      <pubDate>Tue, 4 Jun 2024 17:26:16 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 2156 포도주 시식</title>
      <link>https://chanheess.tistory.com/243</link>
      <description>&lt;pre id=&quot;code_1716967964157&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

//포도주를 선택하면 다 마시고 원래 위치에 놓는다.
//1~n까지 번호
//n개의 포도주
// 
//연속으로 놓여있는 3잔을 모두 마실 수는 없다.
//가장 많은 양의 포도주를 마셔라.
//1이상 10000이하의 n
//입력
//첫째줄 n개
//둘째줄 n + 1번째까지 포도주의 양 0이상 1000이하

int main() 
{
    int n;
    cin &amp;gt;&amp;gt; n;
    vector&amp;lt;int&amp;gt; grape(n, 0);
    vector&amp;lt;int&amp;gt; dp(n, 0);

    for (int i = 0; i &amp;lt; n; i++)
    {
        cin &amp;gt;&amp;gt; grape[i];
    }

    //dp0 grape0
    //dp1 grape1 + dp0
    //dp2 grape2 + dp0(grape0) or grape2 + grape1 or dp1
    //dp3 grape3 + dp1 or grape3 + grape2 + dp0 or dp2 
    //dp[n] = grape[n] + dp[n-2] or grape[n] + grape[n-1] + dp[n-3] or dp[n-1]

    if (n &amp;gt; 0)
    {
        dp[0] = grape[0];
    }
    if (n &amp;gt; 1)
    {
        dp[1] = grape[1] + dp[0];
    }
    if (n &amp;gt; 2)
    {
        dp[2] = max({ grape[2] + grape[0], grape[2] + grape[1], dp[1] });
    }

    for (int i = 3; i &amp;lt; dp.size(); i++)
    {
        dp[i] = max({ grape[i] + dp[i - 2], grape[i] + grape[i - 1] + dp[i - 3], dp[i - 1] });
    }

    cout &amp;lt;&amp;lt; dp[n - 1];

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 모든 경우의 수를 다 따져가며 써본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그 경우의 수를 dp적 방식으로 쌓아나가본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 완성된 점화식을 적는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 경우의 수를 따질 때 이해가 안가는 부분이 있다면 그냥 dp는 이럴것이다 생각하고 넘기면 오히려 잘 풀리는 것 같다. 일단 최대한의 규칙을 찾아보는게 좋은 것 같다. 위 문제의 형식은 그래도 많이 나오는 것 같다. 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;a href=&quot;https://www.acmicpc.net/problem/2156&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2156&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/243</guid>
      <comments>https://chanheess.tistory.com/243#entry243comment</comments>
      <pubDate>Wed, 29 May 2024 16:36:12 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 1912 연속합</title>
      <link>https://chanheess.tistory.com/242</link>
      <description>&lt;pre id=&quot;code_1716778207024&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int main()
{
	int result = -1001;
	vector&amp;lt;int&amp;gt; dp;

	int n;
	cin &amp;gt;&amp;gt; n;

	for (int i = 0; i &amp;lt; n; i++)
	{
		int temp;
		cin &amp;gt;&amp;gt; temp;
		dp.push_back(temp);
	}

	result = max(dp[0], result);

	for (int i = 1; i &amp;lt; n; i++)
	{
		dp[i] = max(dp[i - 1] + dp[i], dp[i]);

		result = max(dp[i], result);
	}

	cout &amp;lt;&amp;lt; result;

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 우선 결과 값을 쌓아가면서 먼저 계산을 해본다. dp적 사고를 통해 이전 값+현재값과 현재값을 비교를해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. max를 계속 업데이트하면서 가장 큰 값을 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 현재 값보다 쌓아온 값이 더 작으면 갱신된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 dp적사고가 되지않는 것 같다. 좀 더 점화식을 세우는 것을 연습해야겠다.&lt;/p&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;a href=&quot;https://www.acmicpc.net/problem/1912&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1912&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/242</guid>
      <comments>https://chanheess.tistory.com/242#entry242comment</comments>
      <pubDate>Mon, 27 May 2024 12:05:28 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 가상함수에 대해</title>
      <link>https://chanheess.tistory.com/241</link>
      <description>&lt;h4 style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size20&quot;&gt;가상함수에 대해서&lt;/h4&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;가상 함수&lt;/b&gt;는 기본 클래스에서 선언된 함수로, 파생클래스에서 이 함수를 재정의(override)할 수 있게 합니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size18&quot;&gt;어떨 때 쓰이는가?&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 다형성을 구현하기 위해 사용되며, 포인터나 참조를 통해 기본 클래스 형식으로 호출된 함수가 실제 객체의 타입에 따라 적절한 파생 클래스의 함수를 호출할 수 있게 합니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size18&quot;&gt;가상 함수의 기본 구문&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 가상 함수를 선언하려면 기본 클래스에서 함수 선언 앞에 'virtual' 키워드를 사용합니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 파생 클래스에서 동일한 함수 시그니처로 함수를 재정의하면, 이 함수는 가상 함수로 동작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1716544913675&quot; class=&quot;cpp&quot; style=&quot;user-select: auto !important;&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Base {
public:
    virtual void show() {
        std::cout &amp;lt;&amp;lt; &quot;Base class show function&quot; &amp;lt;&amp;lt; std::endl;
    }
};

class Derived : public Base {
public:
    virtual void show() override {
        std::cout &amp;lt;&amp;lt; &quot;Derived class show function&quot; &amp;lt;&amp;lt; std::endl;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;동작 원리&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size18&quot;&gt;가상 함수 테이블&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 가상 함수는 가상함수 테이블을 통해 구현됩니다. 가상함수 테이블은 가상 함수 포인터의 배열로, 각 객체의 가상 함수 포인터가 이 테이블을 가리킵니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 각 클래스는 자신의 가상함수 테이블을 가지고 있으며, 가상 함수 호출 시 가상함수 테이블을 통해 적절한 함수가 호출됩니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 컴파일러는 클래스가 가상 함수를 포함하고 있으면 해당 클래스에 대한 가상 함수 테이블을 생성합니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 가상 함수를 호출할 때, 객체의 가상 함수 포인터를 통해 가상 함수 테이블을 참조합니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size18&quot;&gt;실행 시간 결정&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 가상 함수 호출은 실행 시간(런타임 시간)에 결정됩니다. 객체의 실제 타입에 따라 적절한 함수가 호출됩니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 런타임 시간에 호출을 결정하는 이것을 &lt;b&gt;동적 바인딩&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #0d0d0d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;가상 함수 테이블(V-Table)&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;클래스의 모든 객체가 동일한 V-Table을 공유합니다.&lt;/li&gt;
&lt;li&gt;V-Table은 클래스의 가상 함수 주소들을 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가상 함수 포인터(V-Ptr)&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;각 객체의 V-Ptr은 해당 객체의 클래스의 V-Table을 가리킵니다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: left;&quot;&gt;V-Ptr은 객체의 메모리 내에 저장되며, 64비트 시스템에서는 8바이트를 차지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;가상 함수를 많이 선언해도 각 객체는 V-Ptr 하나만을 가지므로 추가적인 메모리 소모는 8바이트로 동일합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1716545185022&quot; class=&quot;cpp&quot; style=&quot;user-select: auto !important;&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Base* b;
Derived d;
b = &amp;amp;d;

b-&amp;gt;show(); // &quot;Derived class show function&quot; 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;위의 코드는 런타임에 객체의 실제 타입이 지정되어 파생클래스의 함수가 호출되는 것을 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size18&quot;&gt;가상 소멸자&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 기본 클래스의 소멸자가 가상 함수가 아니면, 파생 클래스의 소멸자가 호출되지 않을 수 있습니다. 이는 메모리 누수를 일으킬 수 있습니다. 따라서 상속 계층에서 동적 메모리 할당을 사용하는 경우, 기본 클래스의 소멸자를 가상으로 선언해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1716545372109&quot; class=&quot;cpp&quot; style=&quot;user-select: auto !important;&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Base {
public:
    virtual ~Base() {
        std::cout &amp;lt;&amp;lt; &quot;Base destructor&quot; &amp;lt;&amp;lt; std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {
        std::cout &amp;lt;&amp;lt; &quot;Derived destructor&quot; &amp;lt;&amp;lt; std::endl;
    }
};

Base* b = new Derived();
delete b; // &quot;Derived destructor&quot;와 &quot;Base destructor&quot;가 모두 호출됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;위의 코드는 파생클래스의 객체를 가지고 있는 기본 클래스의 가상 소멸자 호출을 보여줍니다.&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size18&quot;&gt;순수 가상 함수와 추상 클래스&lt;/p&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;- 순수 가상함수는 함수 선언에 ' = 0 ' 을 사용해서 선언됩니다. 이는 해당 함수가 구현되지 않았음을 의미하며, 파생클래스에서 반드시 재정의해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1716545530293&quot; class=&quot;cpp&quot; style=&quot;user-select: auto !important;&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Abstract {
public:
    virtual void show() = 0; // 순수 가상 함수
};

class Concrete : public Abstract {
public:
    void show() override {
        std::cout &amp;lt;&amp;lt; &quot;Concrete class show function&quot; &amp;lt;&amp;lt; std::endl;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 순수 가상 함수를 하나 이상 포함하는 클래스는 추상 클래스가 되며, 이 클래스는&lt;span style=&quot;user-select: auto !important;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;인스턴스화&lt;/b&gt;할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 추상 크래스는 구체적인 구현을 가지지 않는 인터페이스 역할을 하며, 이를 상속받는 파생 클래스에서 구체적인 구현을 제공해야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 인스턴스화란 클래스를 기반으로 객체를 생성하는 것을 말합니다. 추상 클래스는 순수 가상 함수를 포함하고 있어, 이 함수의 구현이 없기 때문에 객체를 생성할 수 없습니다. 만약 추상 클래스를 인스턴스화 하려고 하면 컴파일 에러가 발생합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1716545910612&quot; class=&quot;cpp&quot; style=&quot;user-select: auto !important;&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 추상 클래스는 인스턴스화할 수 없음
Abstract a; // 컴파일 에러

// 파생 클래스를 인스턴스화
Concrete c;

// 추상 클래스의 포인터로 파생 클래스 객체를 가리킬 수 있음
Abstract* aPtr = &amp;amp;c;
aPtr-&amp;gt;pureVirtualFunction(); // &quot;Concrete implementation of pureVirtualFunction&quot; 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;user-select: auto !important;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>C++/Basic</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/241</guid>
      <comments>https://chanheess.tistory.com/241#entry241comment</comments>
      <pubDate>Sun, 26 May 2024 18:23:07 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 9935 문자열 폭발</title>
      <link>https://chanheess.tistory.com/240</link>
      <description>&lt;pre id=&quot;code_1715740032199&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int main()
{
	string N = &quot;&quot;;
	string target = &quot;&quot;;
	string result = &quot;&quot;;
	cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; target;

	int targetSize = target.size();

	for(const auto&amp;amp; bomb : N)
	{
		result += bomb;

		if (result.back() == target.back() &amp;amp;&amp;amp; result.size() &amp;gt;= targetSize)
		{
			string temp = result.substr(result.size() - targetSize, targetSize);
			
			if (temp == target)
			{
				result.erase(result.end() - targetSize, result.end());
			}
		}
	}

	if (result == &quot;&quot;)
	{
		cout &amp;lt;&amp;lt; &quot;FRULA&quot;;
	}
	else
	{
		cout &amp;lt;&amp;lt; result;
	}

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문자열을 쌓고 끝의 문자가 같은지 확인한다. 타겟문자가 C4일경우 4이면 확인. 다만 문자열의 길이가 타겟문자열 보다는 길어야 확인을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해당 타겟 문자열을 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/9935&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/9935&lt;/a&gt;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/240</guid>
      <comments>https://chanheess.tistory.com/240#entry240comment</comments>
      <pubDate>Wed, 15 May 2024 11:30:10 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 10816 숫자 카드2</title>
      <link>https://chanheess.tistory.com/239</link>
      <description>&lt;pre id=&quot;code_1715678299961&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

int main() 
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    vector&amp;lt;int&amp;gt; table;
    int N, M, num;

    cin &amp;gt;&amp;gt; N;

    for (int i = 0; i &amp;lt; N; i++)
    {
        cin &amp;gt;&amp;gt; num;
        table.push_back(num);
    }

    sort(table.begin(), table.end());

    cin &amp;gt;&amp;gt; M;

    for (int i = 0; i &amp;lt; M; i++)
    {
        cin &amp;gt;&amp;gt; num;
        cout &amp;lt;&amp;lt; upper_bound(table.begin(), table.end(), num) - lower_bound(table.begin(), table.end(), num) &amp;lt;&amp;lt; &quot; &quot;;
    }

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. vector에 저장하여 정렬해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 해당 수가 있는 처음과 끝의 차를 계산해서 몇개가 있는지를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 아래의 입출력 최적화를 빼면 시간초과난다.&lt;/p&gt;
&lt;pre id=&quot;code_1715678441272&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ios_base::sync_with_stdio(0);
cin.tie(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 최적화가 필요할 때는 꼭 넣어야겠다.&lt;/p&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;a href=&quot;https://www.acmicpc.net/problem/10816&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/10816&lt;/a&gt;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/239</guid>
      <comments>https://chanheess.tistory.com/239#entry239comment</comments>
      <pubDate>Tue, 14 May 2024 18:22:20 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 2225 합분해</title>
      <link>https://chanheess.tistory.com/238</link>
      <description>&lt;pre id=&quot;code_1715569367562&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;string&amp;gt;

using namespace std;

int main()
{
	int N = 0;
	int K = 0;
	cin &amp;gt;&amp;gt; N &amp;gt;&amp;gt; K;

	vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; lists(N, vector&amp;lt;int&amp;gt;(K, 1));

	for (int i = 1; i &amp;lt; K; i++)
	{
		lists[0][i] += lists[0][i - 1];
	}

	for (int i = 1; i &amp;lt; N; i++)
	{
		for (int j = 1; j &amp;lt; K; j++)
		{
			lists[i][j] = (lists[i][j - 1] + lists[i - 1][j]) % 1000000000;
		}
	}

	cout &amp;lt;&amp;lt; lists[N - 1][K - 1];

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답을 모두 list로 나열해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 풀었던 dp 문제들을 생각해보니 과정을 점화식으로 만들기보다 답에서 패턴을 읽어서 점화식을 만들어 나가는 것같다. 다음에 풀때는 답을 먼저 만들고 패턴을 찾아봐야겠다.&lt;/p&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;a href=&quot;https://www.acmicpc.net/problem/2225&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2225&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>dp</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/238</guid>
      <comments>https://chanheess.tistory.com/238#entry238comment</comments>
      <pubDate>Mon, 13 May 2024 12:05:50 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 14889 스타트와 링크</title>
      <link>https://chanheess.tistory.com/237</link>
      <description>&lt;pre id=&quot;code_1714635113813&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace std;

int N = 0;
int result = 1001;
vector&amp;lt;bool&amp;gt; visited;
vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; team;

void DFS(int count, int num)
{
    if (count == N / 2)
    {
        int start = 0;
        int link = 0;

        for (int i = 0; i &amp;lt; N; i++)
        {
            for (int j = 0; j &amp;lt; N; j++)
            {
                if (visited[i] &amp;amp;&amp;amp; visited[j])
                {
                    start += team[i][j];
                }
                if (!visited[i] &amp;amp;&amp;amp; !visited[j])
                {
                    link += team[i][j];
                }
            }
        }

        result = min(result, abs(start - link));

        return;
    }

    for (int i = num; i &amp;lt; N; i++)
    {
        visited[i] = true;
        DFS(count + 1, i + 1);
        visited[i] = false;
    }
}

int main()
{
    cin &amp;gt;&amp;gt; N;

    team = vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt;(N, vector&amp;lt;int&amp;gt;(N, 0));
    visited = vector&amp;lt;bool&amp;gt;(N, false);

    //입력
    for (int i = 0; i &amp;lt; N; i++)
    {
        for (int j = 0; j &amp;lt; N; j++)
        {
            cin &amp;gt;&amp;gt; team[i][j];
        }
    }

    DFS(0, 0);

    cout &amp;lt;&amp;lt; result;

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. DFS로 경우의 수들을 탐색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 반이 차게 되면 다른 반대편을 알 수 있기에 스코어합산의 차를 계산해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14889&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/14889&lt;/a&gt;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <category>dfs</category>
      <category>알고리즘</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/237</guid>
      <comments>https://chanheess.tistory.com/237#entry237comment</comments>
      <pubDate>Thu, 2 May 2024 17:51:55 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 백준 2579 계단 오르기</title>
      <link>https://chanheess.tistory.com/236</link>
      <description>&lt;pre id=&quot;code_1714446136326&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

int main()
{
	vector&amp;lt;int&amp;gt; stair;
	vector&amp;lt;int&amp;gt; dp;
	int count;
	cin &amp;gt;&amp;gt; count;

	stair = vector&amp;lt;int&amp;gt;(count, 0);
	dp = vector&amp;lt;int&amp;gt;(count, 0);

	for (int i = 0; i &amp;lt; count; i++)
	{
		cin &amp;gt;&amp;gt; stair[i];
	}

	dp[0] = stair[0];
	dp[1] = stair[0] + stair[1];
	dp[2] = max(stair[0] + stair[2], stair[1] + stair[2]);

	for (int i = 3; i &amp;lt; count; i++)
	{
		dp[i] = max(dp[i - 2] + stair[i], dp[i - 3] + stair[i] + stair[i - 1]);
	}

	cout &amp;lt;&amp;lt; dp[count - 1];

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 계단의 정보를 저장하고 직접 최대값이 나올 수 있는 방법을 찾아본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 직접 찾는 방법으로 dp를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp0 = arr0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp1 = arr0 + arr1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp2 = arr0 + arr2 or arr1 + arr2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp3 = arr0 + arr1 + arr3 or arr0 + arr2 + arr3&lt;/p&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;dp3 기준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[ i ] = dp[ i - 2 ] + arr[ i ] or dp[ i - 3 ] + arr[ i - 1 ] + arr[ i ]&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;dp4까지 직접 해본다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4 + 3 + 0 + 1), (4 + 0 + 2), (4 + 1 + 2)의 경우가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 arr 0+2와 arr 1+2가 dp2이기 때문에 dp[ i - 2 ] + arr[ i ]가 나오고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4 + 3 + 0 + 1도 마찬가지로 0 + 1는 dp1이기에 dp[ i - 3 ] + arr[ i ] + arr[ i - 1 ]이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr[ i ]은 무조건 독립적으로 가야하니 arr[ i - 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;해당 방법으로 계산하여 마지막 dp가 답이 된다.&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2579&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2579&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Algorithm/Baekjoon</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/236</guid>
      <comments>https://chanheess.tistory.com/236#entry236comment</comments>
      <pubDate>Tue, 30 Apr 2024 12:25:39 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 프로그래머스 요격시스템</title>
      <link>https://chanheess.tistory.com/235</link>
      <description>&lt;pre id=&quot;code_1712119509260&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;algorithm&amp;gt;

using namespace std;

bool sortCompare(vector&amp;lt;int&amp;gt; a, vector&amp;lt;int&amp;gt; b)
{
    return a[1] &amp;lt; b[1];
}

int solution(vector&amp;lt;vector&amp;lt;int&amp;gt;&amp;gt; targets) 
{
    int answer = 0;
    int end = 0;
    
    sort(targets.begin(),targets.end(), sortCompare);
    
    for(int i = 0; i &amp;lt; targets.size(); i++)
    {
        if(targets[i][0] &amp;gt;= end)
        {
            answer++;
            end = targets[i][1];
        }
    }
    
    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 끝지점이 작은순으로 정렬해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 끝지점과 다음 시작점이 맞닿았다면 해당 끝지점으로 바꿔준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCHI5l/btsGjBTtTrh/xtkmM25NQGS4ghQM5KYQDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCHI5l/btsGjBTtTrh/xtkmM25NQGS4ghQM5KYQDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCHI5l/btsGjBTtTrh/xtkmM25NQGS4ghQM5KYQDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCHI5l%2FbtsGjBTtTrh%2FxtkmM25NQGS4ghQM5KYQDk%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;1317&quot; height=&quot;611&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;611&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;0부터 시작해서 첫번째에 1번 범위가 잡히고(1) 2번의 시작점과 1번의 끝지점이 같거나 크므로 다시 변경(2) 2번의 끝지점과 5번의 시작점이 같거나 크므로 다시변경(3)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법으로 총 3번의 겹치는 구간이 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p 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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181188&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/181188&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712280523256&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181188&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dfFJOp/hyVJRF9vTd/iOrJe3wY4lY2OHuvG6z6K1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/go0nd/hyVJ37EfN1/tdohZ6xMQUk44vxv6kltck/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181188&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/181188&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dfFJOp/hyVJRF9vTd/iOrJe3wY4lY2OHuvG6z6K1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/go0nd/hyVJ37EfN1/tdohZ6xMQUk44vxv6kltck/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/235</guid>
      <comments>https://chanheess.tistory.com/235#entry235comment</comments>
      <pubDate>Wed, 3 Apr 2024 13:49:17 +0900</pubDate>
    </item>
    <item>
      <title>[C++] 프로그래머스 이진 변환 반복하기</title>
      <link>https://chanheess.tistory.com/234</link>
      <description>&lt;pre id=&quot;code_1711937480396&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;

using namespace std;

//변환 횟수, 제거한 0개의 개수
//길이로 이진변환

vector&amp;lt;int&amp;gt; solution(string s) 
{
    vector&amp;lt;int&amp;gt; answer(2, 0);
    
    while(s.length() &amp;gt; 1)
    {
        int count = 0;
        
        answer[0]++;
        
        for(int i = 0; i &amp;lt; s.length(); i++)
        {
            if(s[i] == '0')
            {
                answer[1]++;
            }
            else
            {
                count++;
            }
        }
        
        s = &quot;&quot;;
        
        //이진변환
        while(count &amp;gt; 0)
        {
            s += count % 2 == 1 ? &quot;1&quot; : &quot;0&quot;;
            count /= 2;
        }
    }
    
    
    return answer;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 0을 지워가며 지워진 0의 개수 저장 및 1의 개수 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 길이가 1보다 클 경우 계속 반복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;느낀점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bitset을 사용한 풀이가 더 좋아보인다. 그리고 while으로 반복해서 풀었어도 됐을 것같다.&lt;/p&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;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/70129&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/70129&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712280498820&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/70129&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cojtC3/hyVJSdXicQ/npW0VvUZOUJisCXWn8Utu1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bjJVQ4/hyVJSyfXKu/TteX6TM68JHz8bzWNmebT0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/70129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/70129&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cojtC3/hyVJSdXicQ/npW0VvUZOUJisCXWn8Utu1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bjJVQ4/hyVJSyfXKu/TteX6TM68JHz8bzWNmebT0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;</description>
      <category>Algorithm/Programmers</category>
      <author>chanheess</author>
      <guid isPermaLink="true">https://chanheess.tistory.com/234</guid>
      <comments>https://chanheess.tistory.com/234#entry234comment</comments>
      <pubDate>Mon, 1 Apr 2024 11:13:08 +0900</pubDate>
    </item>
  </channel>
</rss>