<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>아무거나</title>
    <link>https://oceansea.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 4 Apr 2026 10:32:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>강방당</managingEditor>
    <item>
      <title>[Spring] SpringBoot에서 SSE(Server-Sent Events)기반 실시간 알림 구현하기</title>
      <link>https://oceansea.tistory.com/88</link>
      <description>&lt;h1 style=&quot;border-left: 20px solid #bdb2ff; border-right: 20px solid #bdb2ff; background-color: #f8f9fa; padding: 15px; text-align: center; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SpringBoot에서&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SSE(Server-Sent&amp;nbsp;Events)기반&amp;nbsp;실시간&amp;nbsp;알림&amp;nbsp;구현하기&lt;/span&gt;&lt;/h1&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구현 계기&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기서 워크스페이스 초대를 할 때, 초대 받은 사람에게 초대 알림이 갈 수 있도록 알림기능을 구현해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciIEKb/btsPgTX5OFN/7wIRziP3ABhiu3vemXcPr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciIEKb/btsPgTX5OFN/7wIRziP3ABhiu3vemXcPr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciIEKb/btsPgTX5OFN/7wIRziP3ABhiu3vemXcPr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciIEKb%2FbtsPgTX5OFN%2F7wIRziP3ABhiu3vemXcPr0%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;536&quot; height=&quot;482&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해당 이미지는 현재 개발중인 프로젝트의 알림창 화면이다. 이렇게 사용자에게 워크스페이스 초대들이 가게되고, 개발하는 단계에서 사용자에게 온 초대알림 목록을 조회해야 할 뿐만 아니라, 알림 기능을 구현해야 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;알림을 보내는 여러 기술들이 있지만, 해당 프로젝트에서는 SSE(Server-Sent Events)가 가장 적합하다고 느껴 SSE를 선택하게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;왜 SSE를 선택했는가?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;232&quot; data-start=&quot;176&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;워크스페이스 초대 알림을 실시간으로 전달하기 위해 다음 세가지 기술들을 고려했었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;317&quot; data-start=&quot;234&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;249&quot; data-start=&quot;234&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;280&quot; data-start=&quot;250&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;SSE (Server-Sent Events)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;317&quot; data-start=&quot;281&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;FCM (Firebase Cloud Messaging)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;border-left: 8px solid #6b705c; background-color: #f2f2f1; padding: 10px; padding-left: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. WebSocket&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 클라이언트 &amp;harr; 서버 간 실시간 양방향 메시지 전송 가능 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 연결 유지 및 사용자 세션 관리가 복잡 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 단순 알림에 사용하기에는 구현 부담이 큼 &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;=&amp;gt; 실시간 공동 작업, 채팅, 실시간 대시보드 등에 적합&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;border-left: 8px solid #6b705c; background-color: #f2f2f1; padding: 10px; padding-left: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. SSE (Server-Sent Events)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 단방향 푸시에 최적화된 가볍고 안정적인 방식 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 구현이 간단하고 브라우저에서 기본 지원 (EventSource API) &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- SSE는 접속 유지 중인 사용자에게 최적화&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;=&amp;gt; 알림, 서버 상태 업데이트, 단발성 이벤트 전달에 적합&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;border-left: 8px solid #6b705c; background-color: #f2f2f1; padding: 10px; padding-left: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. FCM (Firebase Cloud Messaging) &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 모바일/웹 푸시 알림에 특화된 메시징 플랫폼 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 푸시 알림 인프라를 Google이 관리 (외부 서비스에 의존)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 앱이 꺼져 있어도 백그라운드 푸시 가능 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 사용자 토큰 관리, Android/iOS/Web 각각 설정 필요 &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;=&amp;gt; 브라우저 푸시, 마케팅 알림, 백그라운드 푸시에 적합&lt;/span&gt;&lt;/div&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1609&quot; data-start=&quot;1565&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 프로젝트의 워크스페이스 초대 알림은 클라이언트 1명에게만 보내는 단발성 개인 알림이며, 알림 수신 후 UI에서 바로 확인만 하면 되므로 단방향 통신으로 충분했다. 또한 웹 서비스로 모바일 푸시 또한 필요하지 않았다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;1609&quot; data-start=&quot;1565&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1800&quot; data-start=&quot;1737&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결과적으로 이러한 요건에 가장 적합한 기술이 SSE라고 판단했고, 실제로 구현해보니 생각보다 간단하고 확장성이 좋아서 옳은 선택을 한 것 같다는 생각이 들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;377&quot; data-start=&quot;319&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SSE 개념&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SSE (Server-Sent Events)는 서버에서 클라이언트로 데이터를 일방적으로 전송하는 이벤트 기반 기술이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;HTML5 표준 기술로, HTTP 기반 연결을 유지한 채 서버가 클라이언트에게 실시간으로 데이터를 푸시할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단향향 통신으로 서버 -&amp;gt; 클라이언트로만 데이터 전송이 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;HTTP/1.1을 기반으로 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연결이 끊어지면 브라우저의 EventSource는 자동으로 재연결을 시도하며, 서버는 retry 필드를 통해 재연결 간격을 제어할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ye12s/btsPgcDYlnG/jWIXivWL0jEdAYmscQv9m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ye12s/btsPgcDYlnG/jWIXivWL0jEdAYmscQv9m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ye12s/btsPgcDYlnG/jWIXivWL0jEdAYmscQv9m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fye12s%2FbtsPgcDYlnG%2FjWIXivWL0jEdAYmscQv9m0%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;655&quot; height=&quot;230&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Serif KR';&quot;&gt;위 사진은 SSE 요청과 응답의 흐름을 이미지로 나타낸 것이다.&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: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;1) 먼저 클라이언트가 연결 수립 url로 SSE 요청(connection)을 보낸다. &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: center;&quot;&gt;(JWT를 포함, GET 방식)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;2) 서버는 초대 이벤트 발생 시 해당 클라이언트에게 SseEmiter를 통해 data를 전송한다. &lt;/span&gt;서버는 text/event-stream 콘텐츠 타입으로 응답을 유지하며, 필요한 시점마다 이벤트를 전송한다.&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(emitter.send())&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3) 클라이언트는 이 연결을 통해 전달되는 데이터를 EventSource API로 수신한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4) 이벤트 수신 후 UI에 반영한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center; font-family: 'Noto Serif KR';&quot;&gt;SseEmitter와 SseEmitter 클래스&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SseEmitter는 Spring Boot에서 SSE(Server-Sent Events)를 구현할 때 사용하는 핵심 클래스로 클라이언트에게 비동기적으로 데이터를 전송하는 역할을 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;border-left: 8px solid #6b705c; background-color: #f2f2f1; padding: 10px; padding-left: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 컨트롤러에서 SseEmitter를 반환하면 Spring은 연결을 유지한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- &lt;span style=&quot;background-color: #f2f2f1; color: #333333; text-align: start;&quot;&gt;SseEmitter&lt;/span&gt; 객체를 생성한 뒤, emitter.send() 호출 시 브라우저로 이벤트가 전송된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;- 기본 타임아웃은 30초 이나, new SseEmitter(Long.MAX_VALUE)로 무제한 설정이 가능하다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp; &amp;nbsp; (하지만 무제한 설정은 연결 해제를 별도로 명시적으로 처리하지 않으면 메모리 누수 위험이 있어 주의가 필요하다.)&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a title=&quot;SseEmitter 공식문서&quot; href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위의 공식문서를 자세히 들여다보면 SseEmitter의 생성자와 메서드들에 대해서 알 수 있다. 주요 생성자와 메서드를 소개하면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[ 생성자 ]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-end=&quot;205&quot; data-start=&quot;179&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Serif KR';&quot;&gt;public SseEmitter()&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;322&quot; data-start=&quot;206&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;258&quot; data-start=&quot;206&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기본 생성자로 기본 타임아웃 30초(30,000ms)로 설정된 SseEmitter 객체를 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;style=border-style: solid; border-width: 15px; background-color: #eeeeee; padding: 10px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;SseEmitter emitter = new SseEmitter();&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;h4 data-end=&quot;362&quot; data-start=&quot;324&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Noto Serif KR';&quot;&gt;public SseEmitter(Long timeout)&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;569&quot; data-start=&quot;363&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;404&quot; data-start=&quot;363&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;원하는 타임아웃(ms)을 지정하여 SseEmitter를 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;404&quot; data-start=&quot;363&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;ms 단위이다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;569&quot; data-start=&quot;513&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;타임아웃이 지나면 자동으로 emitter가 종료되며, onTimeout() 콜백이 호출된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;style=border-style: solid; border-width: 15px; background-color: #eeeeee; padding: 10px;&quot;&gt;&lt;span style=&quot;background-color: #eeeeee; color: #333333; text-align: start; font-family: 'Noto Serif KR';&quot;&gt;SseEmitter&amp;nbsp;emitter&amp;nbsp;=&amp;nbsp;new&amp;nbsp;SseEmitter(Long.MAX_VALUE);&lt;/span&gt;&lt;/div&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;[ 메서드 ]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 185px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 37.2093%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;메서드&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.6744%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;send(Object object)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클라이언트로 단순한 데이터를 전송&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;send(Object object, MediaType mediaType)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전송할 데이터 타입 명시와 함께 데이터 전송&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;(ex. emitter.send(myObject, MediaType.APPLICATION_JSON)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 37.2093%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;send(SseEventBuilder builder)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.6744%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이벤트 이름, ID, retry 시간 등을 설정한 커스텀 이벤트 전송&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;complete()&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클라이언트와의 연결을 명시적으로 종료&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;completeWithError(Throwable t)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;오류와 함께 연결 종료&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;onCompletion(Runnable callback)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연결이 정상적으로 종료될 때 실행할 콜백 등록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;onTimeout(Runnable callback)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;타임아웃 발생 시 실행할 콜백 등록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 37.2093%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;onError(Consumer&amp;lt;Throwable&amp;gt; callback)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 62.6744%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;예외 발생 시 실행할 콜백 등록&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; 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 data-end=&quot;3967&quot; data-start=&quot;3800&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;send(SseEventBuilder builder)메서드는 아래처럼 이벤트를 커스터마이징 해서 사용자에게 보낼 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752426291521&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;emitter.send(SseEmitter.event()
    .name(&quot;invitation&quot;)
    .id(&quot;event-id-123&quot;)
    .data(&quot;워크스페이스 초대 알림&quot;)
    .reconnectTime(5000)); // 5초 후 재연결 시도&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; 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 data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;on~ 관련 메서드들을 정의하여 emitter를 Map에서 제거해야 리소스가 낭비되지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752426377295&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;emitter.onCompletion(() -&amp;gt; {
    // 연결이 정상적으로 종료될 때
    emitters.remove(userId);
});

emitter.onTimeout(() -&amp;gt; {
    // 연결 타임아웃 발생 시
    emitters.remove(userId);
});

emitter.onError(e -&amp;gt; {
    // 예외 발생 시
    log.error(&quot;SSE 전송 오류&quot;, e);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center; font-family: 'Noto Serif KR';&quot;&gt;사용자 개별 알림 전송 구조 (1명에게만 어떻게 알림을 보낼지?)&lt;br /&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;지금까지의 내용을 통해 SseEmitter 객체의 .send() 메서드로 사용자에게 데이터를 보낼 수 있는 구조에 대해서 이해할 수 있을 것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그렇다면 단 한명 (ex. userId = 12)에게만 알림을 어떻게 보낼 수 있을까?&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;예시 로직 설계&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;border-left: 8px solid #6b705c; background-color: #f2f2f1; padding: 10px; padding-left: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;1. 사용자(userId = 12)가 SSE 구독을 시작함&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;2. Map에 해당 유저의 SseEmitter 객체를 저장해놓음 =&amp;gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;maps.put(userId, emitter)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;3. 실제로 알림을 보내야 하는 상황이 오면 Map에 저장되어 있는 해당 사용자(userId = 12)의 SseEmitter 객체를 꺼내옴&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp; &amp;nbsp; =&amp;gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;SseEmitter emitter = maps.get(userId)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;4. 보낼 내용 전송 =&amp;gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;emitter.send(데이터)&lt;/span&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;조금 구체적으로 설명하자면, 연결이 수립되었을시에 사용자의 SseEmitter 객체를 만들어두고, 저장해놓는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 알림을 보내야 하는 상황이 오면 위에서 Map에 저장한 사용자의 SseEmitter 객체를 꺼낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;꺼낸 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SseEmitter객체는 해당 사용자의 SseEmitter 객체이므로, .send() 메서드에 데이터를 담으면 해당 사용자에게만 알림이가게 된다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center; font-family: 'Noto Serif KR';&quot;&gt;실제 코드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;1. Controller 작성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1752427172949&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/workspaces&quot;)
@RequiredArgsConstructor
@Tag(name = &quot;SSE 알림 연결 API&quot;)
public class SseController {

    private final SseServiceImpl sseService;
    private final JwtProvider jwtProvider;

    //알림 수신 구독 API
    @Operation(summary = &quot;알림 수신 구독 API (swagger로 테스트 불가: 무한로딩)&quot;)
    @GetMapping(value = &quot;/notifications&quot;, produces = &quot;text/event-stream&quot;) //SSE임을 명시
    public ResponseEntity&amp;lt;SseEmitter&amp;gt; subscribe(@RequestParam(&quot;token&quot;) String token) {
        Long memberId = jwtProvider.getMemberId(token); // 토큰에서 ID 추출
        SseEmitter emitter = sseService.subscribe(memberId);
        return ResponseEntity.ok(emitter);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3967&quot; data-start=&quot;3800&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사용자가 알림을 구독하겠다는 요청을 보낼 때의 api url을 설계해주었다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3967&quot; data-start=&quot;3800&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;응답은 기존에 ResponseEntity&amp;lt;CustomResponse&amp;lt;응답DTO&amp;gt;&amp;gt; 이런식으로 CustomResponse에 담아서 보냈는데 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;SseEmitter는 스트리밍 객체로 CustomResponse의 Json 형태로 변환하는 것이 불가능&lt;/b&gt;&lt;/span&gt;하다. 따라서 ResponseEntity에 SseEmitter를 감싸서 반환하는 식으로 구성했다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3967&quot; data-start=&quot;3800&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JWT 토큰을 받는 것은 기존의 @AuthenticationPrincipal에서 받아오는 형식을 사용할 수 없기 때문에 &lt;b&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;JWT 토큰을 쿼리 파라미터로 받도록 수정하였다.&lt;/span&gt;&lt;/b&gt; 이것은 프론트엔드 쪽에서 EventSource를 사용하기 때문인데, EventSource 객체는 커스텀헤어 Authorization을 지원하지 않기 때문이다. 보안적으로 권장되는 방식은 아니지만, HTTPS 연결을 사용하고, SSE 구독 요청은 다른 사람이 볼 수 없는 환경에 있으며, 토큰의 짧은 유효 시간 등으로 SSE 구독용 요청만 예외적으로 쿼리 파라미터로 받도록 설정할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3967&quot; data-start=&quot;3800&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;@GetMapping(value = &quot;/notifications&quot;, produces = &quot;text/event-stream&quot;)에서 &lt;b&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;produces = &quot;text/event-stream&quot;를 통해 HTTP의 Content-Type를 명시&lt;/span&gt;&lt;/b&gt;해주었다. SSE는 HTTP의 Content-Type이 반드시 &quot; text/event-stream&quot;이어야 브라우저가 이를 스트리밍 응답으로 인식하기 때문이다. 물론 Spring에서 SseEmitter타입이 반환되면 자동으로 text/event-stream으로 설정해주긴한다. 그렇지만 정확하게 명시해주는 것이 명확한 설정 방식이다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3967&quot; data-start=&quot;3800&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;또한 &lt;b&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;Swagger에서의 테스트가 불가능&lt;/span&gt;&lt;/b&gt; 하다. 구독 요청을 하고 대기하는 방식이므로 Swagger로 테스트시 무한 로딩에 빠지게 될 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;2. Service 코드 작성&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1752427733192&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@Slf4j
public class SseServiceImpl {
    //단일 브라우저 연결(Map&amp;lt;Long, SseEmitter&amp;gt;) 구조
    private final Map&amp;lt;Long, SseEmitter&amp;gt; emitters = new ConcurrentHashMap&amp;lt;&amp;gt;();

    public SseEmitter subscribe(Long memberId) {
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); //타임아웃 설정, 브라우저에서 자동 재연결
        emitters.put(memberId, emitter); //해당 유저의 emitter 객체 저장

        emitter.onCompletion(() -&amp;gt; emitters.remove(memberId));
        emitter.onTimeout(() -&amp;gt; emitters.remove(memberId));
        emitter.onError((e) -&amp;gt; emitters.remove(memberId));

        try {
            emitter.send(SseEmitter.event()
                    .name(&quot;connected&quot;)
                    .data(&quot;SSE 연결 완료&quot;));
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
        return emitter;
    }

    public void sendInvitationAlertToUser(Long memberId, WorkspaceInvitation invitation, Workspace workspace) {
        SseEmitter emitter = emitters.get(memberId);
        if (emitter != null) {
            try {
                // DTO 생성
                SseResponseDto.InvitedNotificationResponseDto dto =
                        SseConverter.toInvitedNotification(invitation, workspace);

                // SSE 알림 전송
                emitter.send(SseEmitter.event()
                        .name(&quot;invitation&quot;) // 이벤트 이름 명확히 구분
                        .data(dto, MediaType.APPLICATION_JSON));

                log.info(&quot;[SSE] Sent invitation notification to user {}&quot;, memberId);

            } catch (IOException e) {
                emitter.completeWithError(e);
                // 연결이 동일한 emitter일 때만 삭제
                if (emitters.get(memberId) == emitter) {
                    emitters.remove(memberId);
                    log.info(&quot;[SSE] Removed emitter for user {} after failure&quot;, memberId);
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&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; data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다음으로 서비스 코드를 작성해주었다. 서비스 코드는 최초 구독 요청을 처리하고 사용자의 SseEmitter 객체를 생성해 Map에 저장하는 subscribe() 메서드와 실제 알림을 발송하는 sendInvitationAlertToUser() 메서드 두개로 나뉜다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;subscribe() 메서드에서는 사용자의 SseEmitter 객체를 생성해 Map에 저장하고, onCompletion(), onTimeout(), onError() 메서드를 통해 연결이 끊길 시점에 emitter를 Map에서 제거하도록 코드를 작성해주었다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;3800&quot; data-end=&quot;3967&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;sendInvitationAlertToUser() 메서드에서는 보내고자 하는 알림 형태에 맞게 사용자에게 응답을 반환할 DTO를 정의해준 뒤, SseEmitter.event()메서드로 data에 해당 커스텀 응답 DTO를 넣어 send() 메서드로 반환해주었다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;3. 응답 DTO와 Converter&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;응답 DTO&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752428162730&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SseResponseDto {
    @Builder
    @Schema(description = &quot;워크스페이스 초대 알림 응답 DTO&quot;)
    public record InvitedNotificationResponseDto(
            @Schema(description = &quot;알림 타입&quot;, example = &quot;INVITATION&quot;)
            String eventType,
            @Schema(description = &quot;워크스페이스 아이디&quot;, example = &quot;12&quot;)
            Long workspaceId,
            @Schema(description = &quot;워크스페이스 이름&quot;, example = &quot;우리팀 프로젝트&quot;)
            String workspaceName,
            @Schema(description = &quot;초대한 시간&quot;, example = &quot;2025-07-08T15:32:00&quot;)
            LocalDateTime invitedAt,
            @Schema(description = &quot;이 이벤트가 수신된 시간&quot;, example = &quot;2025-07-08T15:35:10&quot;)
            LocalDateTime receivedAt
    ) {}
}&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Converter&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1752428188413&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SseConverter {
    public static SseResponseDto.InvitedNotificationResponseDto toInvitedNotification(WorkspaceInvitation invitation, Workspace workspace) {
        return SseResponseDto.InvitedNotificationResponseDto.builder()
                .eventType(&quot;INVITATION&quot;)
                .workspaceId(workspace.getId())
                .workspaceName(workspace.getWorkspaceName())
                .invitedAt(invitation.getSentAt())
                .receivedAt(LocalDateTime.now())
                .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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;4. 실제 알림을 전송하는 Service에서의 활용 코드&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서 작성한 SseService는 다른 사용할 서비스에서 호출되어 사용하게 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아래 코드는 실제 워크스페이스 초대를 보내는 서비스 메서드의 맨 아래에서 SseService의 sendInvitationAlertToUser()를 호출하는 코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1752428464751&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    @Override
    public WorkspaceResponseDto.InviteWorkspaceResponseDto inviteTeamWorkspace(Long workspaceId, Long inviterId, String email) {
        // 워크 스페이스 조회
        // 워크 스페이스가 팀 워크스페이스가 맞는지 확인
        // 초대한 사용자 조회 (inviter)
        // 초대한 사용자가 해당 워크 스페이스에 속해있는지 확인 (inviter)
        // 초대할 사용자 조회 (invitee)
        // 본인 초대 방지
        // 워크스페이스 멤버인지 여부 확인 (이미 속해있으면 예외)
        // 아직 만료되지 않은 초대가 있는지 확인
            //초대 만료 전 초대가 존재하나, 멤버가 그룹에 포함되어 있지 않으면서 ACCEPT or REJECT 상태라면 재전송 가능
        // 초대 토큰 생성 (invitationMailService 에서 중복 여부 확인)
        // 이메일 전송
        // 초대 엔티티 저장
        
        //--------------여기까지 기존의 코드

        // 초대 대상자에게 SSE 알림 전송
        sseService.sendInvitationAlertToUser(invitee.getId(), invitation, workspace);

        // 응답 반환
        return WorkspaceConverter.toInviteResponse(invitation, invitee.getEmail());
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기존에 알림을 보내야하지만, 아직 알림 기능은 구현하지 못했던 초대 메서드의 맨 아랫 부분에 코드 한줄 추가로 알림 기능을 추가할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;border-left: 8px solid #6b705c; background-color: #f2f2f1; padding: 10px; padding-left: 20px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;sseService.sendInvitationAlertToUser(invitee.getId(),&amp;nbsp;invitation,&amp;nbsp;workspace);&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;padding: 5px; border-left: solid 20px #ffc6ff; border-bottom: solid 10px #ffc6ff; font-size: 25px; font-weight: bold;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center; font-family: 'Noto Serif KR';&quot;&gt;정리&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 SSE(Server-Sent Events) 알림 기능은 핵심 비즈니스 로직을 변경하지 않고도 실시간 기능을 유연하게 붙일 수 있는 구조로 설계되었다. 워크스페이스 초대 로직에서 sseService.sendInvitationAlertToUser() 한 줄만 추가함으로써, 도메인 로직과 알림 로직을 완전히 분리한 채, 실시간 알림을 자연스럽게 통합할 수 있었다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;또한 Map&amp;lt;Long, SseEmitter&amp;gt; 형태의 단일 브라우저 연결 구조를 채택한 이유는 시스템 단순화와 성능 최적화 때문이다. 대부분의 사용자들은 한 기기에서 하나의 브라우저 탭으로 서비스를 사용하기 때문에 유저당 하나의 SseEmitter만 유지해도 실시간 알림 수신에는 큰 문제가 없을 것이라 판단했다. 또한 다중 브라우저구조는 유저별 여러 Emitter를 관리해야 하고, 만료된 Emitter 정리를 위한 스케줄러나 이벤트 큐까지 고려해야 하므로 구조가 복잡해지고, 서버 부하가 증가한다. 따라서 초기 단계에서는 단일 브라우저 구조로 충분하며, 새로운 접속이 이전 연결을 자연스럽게 덮어쓰기 때문에(Map 구조) 만료 정리 로직이 필요하지 않고 전체적으로 리소스를 효율적으로 관리할 수 있기 때문이다. 물론 서비스가 확장 되거나 다중 접속 수요가 명확해지면 Map&amp;lt;Long, List&amp;lt;SseEmitter&amp;gt;&amp;gt;로 전환할 계획이 있다.&lt;/span&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;참고자료 )&lt;/b&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a style=&quot;background-color: #ffffff; color: #0969da; text-align: left;&quot; href=&quot;https://docs.spring.io/spring-framework/reference/web/webflux/server-sent-events.html&quot;&gt;Spring SSE 공식 문서&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a style=&quot;background-color: #ffffff; color: #0969da; text-align: left;&quot; href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Server-sent_events&quot;&gt;SSE와 EventSource 소개&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://eunchaan.tistory.com/237&quot;&gt;https://eunchaan.tistory.com/237&lt;/a&gt;&lt;/span&gt;&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <category>eventsource</category>
      <category>Java</category>
      <category>serversentevents</category>
      <category>springboot</category>
      <category>springbootsse</category>
      <category>SSE</category>
      <category>webnotification</category>
      <category>백엔드개발</category>
      <category>실시간서비스</category>
      <category>실시간알림</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/88</guid>
      <comments>https://oceansea.tistory.com/88#entry88comment</comments>
      <pubDate>Mon, 14 Jul 2025 02:54:35 +0900</pubDate>
    </item>
    <item>
      <title>[트러블슈팅] WebSocket - Handshake 403 에러 &amp;quot;WebSocket connection to 'ws://localhost:8080/ws-stomp' failed: Error during WebSocket handshake: Unexpected response code: 403&amp;quot;</title>
      <link>https://oceansea.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 협업 기능을 구현하기 위해 WebSocket을 도입해야 했는데 도입에 앞서, 실제 웹소켓이 잘 동작하는지 테스트 해보기 위해 간단한 간단한 채팅 기능을 만든 뒤 JWT 인증까지 연결한 코드를 작성하고, 프론트엔드 연결 예시 코드를 통해 잘 동작하는지 테스트 해보고자 했다.&lt;/p&gt;
&lt;p data-end=&quot;493&quot; data-start=&quot;385&quot; data-ke-size=&quot;size16&quot;&gt;하지만 개발 중 예상치 못한 문제가 발생했다. 클라이언트에서 WebSocket 연결을 시도할 때마다 403 Forbidden 오류가 발생한 것이다. 로그에는 다음과 같은 메시지가 출력되었다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1750526058592&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;WebSocket connection to 'ws://localhost:8080/ws-stomp' failed: Error during WebSocket handshake: Unexpected response code: 403&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;631&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 프론트엔드 코드의 문제일 것이라 의심했지만, Postman 등에서 동일한 요청을 보냈을 때는 정상적으로 처리되는 점을 확인하며 서버 측의 보안 설정을 의심하게 되었다. (Postman에서는 Connection이 제대로 이루어지는지만 테스트가 가능하고, STOMP를 테스트해볼 수는 없다.)&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;631&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;631&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;764&quot; data-start=&quot;731&quot; data-ke-size=&quot;size26&quot;&gt;WebSocket과 Spring Security의 관계&lt;/h2&gt;
&lt;p data-end=&quot;986&quot; data-start=&quot;766&quot; data-ke-size=&quot;size16&quot;&gt;문제를 해결하기 위해 WebSocket의 작동 방식을 다시 정리해보았다. WebSocket은 처음 연결을 시도할 때 일반적인 HTTP 요청을 통해 handshake 과정을 수행하고, 이후 연결이 수립되면 지속적인 소켓 통신이 이루어진다. 이 초기 handshake 요청도 일반적인 HTTP 요청처럼 Spring Security의 필터 체인을 통과하게 되는데, 문제는 바로 여기에서 발생했다.&lt;/p&gt;
&lt;p data-end=&quot;1199&quot; data-start=&quot;988&quot; data-ke-size=&quot;size16&quot;&gt;프로젝트에서는 JWT를 사용하는 인증 방식을 적용하고 있었고, 모든 요청은 커스텀 JwtFilter를 통해 토큰 검증을 하도록 설정되어 있었다. 그러나 WebSocket의 handshake 요청에는 일반적인 REST 요청처럼 Authorization 헤더를 담을 수 없기 때문에 JwtFilter가 handshake 요청을 처리하지 못하고 403 오류를 반환하고 있었던 것이다.&lt;/p&gt;
&lt;h2 data-end=&quot;1225&quot; data-start=&quot;1201&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;1225&quot; data-start=&quot;1201&quot; data-ke-size=&quot;size26&quot;&gt;시도 1. SecurityConfig에서 permitAll 설정&lt;/h2&gt;
&lt;p data-end=&quot;1338&quot; data-start=&quot;1227&quot; data-ke-size=&quot;size16&quot;&gt;가장 먼저 시도한 해결책은 SecurityFilterChain 안에서 handshake 요청의 경로(/ws-stomp)를 .permitAll() 처리하는 것이었다. 다음과 같이 설정하였다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccqBkO/btsOLFGzHKU/SyW2Nb0ZaJuuKUR5OnSdD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccqBkO/btsOLFGzHKU/SyW2Nb0ZaJuuKUR5OnSdD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccqBkO/btsOLFGzHKU/SyW2Nb0ZaJuuKUR5OnSdD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccqBkO%2FbtsOLFGzHKU%2FSyW2Nb0ZaJuuKUR5OnSdD0%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;377&quot; height=&quot;252&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;allowedUrls에 &quot;/ws-stomp&quot;관련 경로를 추가했고, 기존과 같이
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;.authorizeHttpRequests(authorize -&amp;gt; authorize
        .requestMatchers(allowedUrls).permitAll()​&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하지만 이 설정은 소용이 없었다. 이유는 간단했다. .permitAll()은 SecurityFilterChain &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;내부에서만 동작하는 설정이기 때문에, handshake 요청이 이미 필터에서 거부되고 있다면 이 설정은 도달조차 하지 못한다. 즉, 필터를 통과한 이후의 인가 과정에서만 유효한 것이다.&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;1201&quot; data-end=&quot;1225&quot; data-ke-size=&quot;size26&quot;&gt;시도 2.&amp;nbsp;JwtFilter에서 handshake 우회&lt;/h2&gt;
&lt;p data-end=&quot;1741&quot; data-start=&quot;1675&quot; data-ke-size=&quot;size16&quot;&gt;다음으로는 커스텀 JWT 필터 내에서 WebSocket 요청임을 감지하면 해당 요청만은 필터를 우회하게끔 설정해보았다.&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1750528675433&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String upgrade = request.getHeader(&quot;Upgrade&quot;);
if (&quot;websocket&quot;.equalsIgnoreCase(upgrade)) {
    log.info(&quot;WebSocket 요청 - JwtFilter 우회&quot;);
    filterChain.doFilter(request, response);
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-end=&quot;2167&quot; data-start=&quot;1955&quot; data-ke-size=&quot;size16&quot;&gt;이 방식도 한번 시도해보았다. 실제로 Upgrade: websocket 헤더는 handshake 요청에 포함되기 때문에 이를 통해 WebSocket 요청을 구분할 수 있다. 하지만 여전히 오류는 해결되지 않았다. 왜냐하면 JWT 필터 자체에 진입하기 전에 이미 필터 체인에서 예외가 발생하고 있었기 때문이다. 즉, 해당 필터까지 도달하지 못하는 상황이었던 것이다.&lt;/p&gt;
&lt;p data-end=&quot;2167&quot; data-start=&quot;1955&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2167&quot; data-start=&quot;1955&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;2207&quot; data-start=&quot;2169&quot; data-ke-size=&quot;size26&quot;&gt;에러 해결 - WebSecurityCustomizer로 필터 우회&lt;/h2&gt;
&lt;p data-end=&quot;2399&quot; data-start=&quot;2209&quot; data-ke-size=&quot;size16&quot;&gt;문제의 핵심은 WebSocket handshake 요청이 아예 Spring Security의 필터 체인에 들어가지 않도록 해야 한다는 것이었다. 이 경우에 사용할 수 있는 방법이 바로 WebSecurityCustomizer이다. 이 설정을 통해 특정 요청 경로를 Spring Security 필터 체인 자체에서 제외시킬 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1750528778338&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return web -&amp;gt; web.ignoring().requestMatchers(&quot;/ws-stomp&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2643&quot; data-start=&quot;2540&quot; data-ke-size=&quot;size16&quot;&gt;이 설정을 추가하자마자 문제가 해결되었다. WebSocket handshake 요청이 더 이상 필터에 걸리지 않았고, 클라이언트는 정상적으로 WebSocket 연결을 수립할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;2643&quot; data-start=&quot;2540&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6ZZ5G/btsOL8uLwQE/j0sOq8KILpAkgWnJAL1N2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6ZZ5G/btsOL8uLwQE/j0sOq8KILpAkgWnJAL1N2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6ZZ5G/btsOL8uLwQE/j0sOq8KILpAkgWnJAL1N2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6ZZ5G%2FbtsOL8uLwQE%2Fj0sOq8KILpAkgWnJAL1N2K%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;530&quot; height=&quot;366&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;825&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-end=&quot;2672&quot; data-start=&quot;2645&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;2672&quot; data-start=&quot;2645&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;2672&quot; data-start=&quot;2645&quot; data-ke-size=&quot;size26&quot;&gt;STOMP CONNECT 단계에서 인증 처리&lt;/h2&gt;
&lt;p data-end=&quot;2888&quot; data-start=&quot;2674&quot; data-ke-size=&quot;size16&quot;&gt;WebSocket 연결은 성공했지만, 이 연결이 인증되지 않은 사용자에게 열려 있어서는 안 된다. 그래서 실제 인증 처리는 WebSocket 연결 이후의 STOMP CONNECT 프레임을 통해 수행하도록 하였다. 이를 위해 Spring에서는 ChannelInterceptor를 구현하여 StompCommand.CONNECT 요청을 가로채고 JWT 토큰 검증을 진행한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1750529053451&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
@Component
public class StompHandler implements ChannelInterceptor {
    private final JwtProvider jwtProvider;

    //WebSocket 서버에서 ChannelInterceptor를 사용해 STOMP CONNECT 요청을 가로채고 헤더를 확인
    @Override
    public Message&amp;lt;?&amp;gt; preSend(Message&amp;lt;?&amp;gt; message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            String rawToken = accessor.getFirstNativeHeader(&quot;Authorization&quot;);

            if (rawToken == null || !rawToken.startsWith(&quot;Bearer &quot;)) {
                throw new JwtException(JwtErrorCode.INVALID_TOKEN);
            }

            // &quot;Bearer &quot; 제거
            String token = rawToken.substring(7);

            if (!jwtProvider.isValidToken(token)) {
                throw new JwtException(JwtErrorCode.INVALID_TOKEN);
            }

            Authentication authentication = jwtProvider.getAuthentication(token);
            accessor.setUser(authentication);
        }

        return message;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;3405&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 handshake는 누구나 할 수 있지만, 실제 메시지를 주고받기 위해서는 반드시 유효한 JWT 토큰을 포함한 STOMP CONNECT를 성공해야 한다는 구조를 갖추게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;3405&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;3405&quot; data-start=&quot;3294&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;3412&quot; data-start=&quot;3407&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;3763&quot; data-start=&quot;3449&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;3492&quot; data-start=&quot;3449&quot;&gt;.permitAll()은 필터 체인을 이미 통과한 요청에만 적용된다.&lt;/li&gt;
&lt;li data-end=&quot;3596&quot; data-start=&quot;3493&quot;&gt;WebSocket handshake 요청은 일반 HTTP 요청처럼 필터를 거치기 때문에, 필요시 WebSecurityCustomizer로 필터 체인 자체에서 제외해주어야 한다.&lt;/li&gt;
&lt;li data-end=&quot;3663&quot; data-start=&quot;3597&quot;&gt;handshake 단계에서 필터를 우회하더라도, STOMP CONNECT 단계에서 별도로 인증을 검증할 수 있다.&lt;/li&gt;
&lt;li data-end=&quot;3763&quot; data-start=&quot;3664&quot;&gt;WebSocket + STOMP + JWT 인증을 함께 사용할 경우, handshake는 열고 실제 메시지 송수신은 STOMP 인증을 통해 통제하는 것이 권장되는 구조이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ERROR</category>
      <category>403</category>
      <category>Connect</category>
      <category>Connection</category>
      <category>Handshake</category>
      <category>STOMP</category>
      <category>WebSocket</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/83</guid>
      <comments>https://oceansea.tistory.com/83#entry83comment</comments>
      <pubDate>Sun, 22 Jun 2025 03:05:12 +0900</pubDate>
    </item>
    <item>
      <title>[트러블슈팅] SQL Error: 1364 - Field '컬럼명' doesn't have a default value</title>
      <link>https://oceansea.tistory.com/82</link>
      <description>&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;프론트엔드 측에서 스웨거 동작시 Field 'member_club_id' doesn't have a default value [insert into likes (article_id, member_id)] 라는 에러가 뜬다는 메세지를 받았다.&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmNP4Z/btsM32bMvCN/q11dXmKU3e39xK4kKFXlA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmNP4Z/btsM32bMvCN/q11dXmKU3e39xK4kKFXlA0/img.png&quot; data-alt=&quot;스웨거 내 오류 메세지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmNP4Z/btsM32bMvCN/q11dXmKU3e39xK4kKFXlA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmNP4Z%2FbtsM32bMvCN%2Fq11dXmKU3e39xK4kKFXlA0%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;734&quot; height=&quot;259&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;스웨거 내 오류 메세지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743438130348&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SQL Error: 1364, SQLState: HY000
~.SqlExceptionHelper   : Field 'member_club_id' doesn't have a default value
~.GlobalExceptionHandler     : [WARNING] Internal Server Error : could not execute statement [Field 'member_club_id' doesn't have a default value] [insert into likes (article_id,member_id) values (?,?)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 로컬 및 로컬 스웨거로 동작 및 테스트를 다 해봤는데 배포 서버에서만 이런 에러가 발생하는 것이다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;결론부터 말하자면,&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; SHOW CREATE TABLE [테이블명]; SQL문을 실행해서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;실제 DB의 스키마 구조와 Entity에서 정의한 구조와 같은지&lt;/b&gt;&lt;/span&gt; 한번 확인해보자!!!&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제 현상&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 API 요청 (좋아요 토글) 시 다음과 같은 MySQL 오류 발생: &lt;/p&gt;
&lt;pre id=&quot;code_1743438285499&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Field 'member_club_id' doesn't have a default value
[insert into likes (article_id, member_id)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;로컬(MariaDB)에서는&amp;nbsp;동작, &lt;br /&gt;운영환경(MySQL)에서는 실패했다.&lt;/p&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;그래서 알아본 결과 MariaDB와 MySQL은 같은 쿼리/DDL에 대해 다르게 동작할 수 있고, 특히 NOT NULL + DEFAULT 없음 상황에서 MariaDB가 더 느슨하게 처리하는 경향이 있다고 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 cascade = CascadeType.ALL 제거해보고, 안돼서 orphanRemoval = true도 제거해보았다.&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;다음으로 영속성을 확인해주었다. 좋아요 Likes 테이블을 저장할 때 Member도 함께 연관되어 있고, member과 member_club은 1:n으로 연결되어 있기 때문에&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;JPA는 Member.memberClubs 도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;같이 flush(영속화)하려고 해서 이러한 문제가 발생하는 건 아닐 까 생각이 들어서 아래 4가지를 시도해보았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;member.getMemberClubs().clear()&lt;/li&gt;
&lt;li&gt;Member.builder().id(...) 프록시 사용&lt;/li&gt;
&lt;li&gt;@Transactional(readOnly = true) 사용&lt;/li&gt;
&lt;li&gt;entityManager.detach(member)&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Likes를 저장할 때 Member도 함께 연관되어 있기 때문에&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;703&quot; data-start=&quot;532&quot; data-ke-size=&quot;size16&quot;&gt;JPA는 Member.memberClubs 도 같이 flush(영속화)하려고 하고,&lt;br /&gt;그 안에서 member_club_id 필드가 NOT NULL인데 값이 없기 때문에 DB 레벨에서 에러가 발생하는 줄 알고 여러가지를 시도해보았으나 전부 되지 않아서 골머리를 앓고 있었다...,,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;따라서 의심 대상을 전환 DB 테이블 구조 자체로 이동해보았다. 그래서&amp;nbsp;&lt;br /&gt;SHOW CREATE TABLE likes;를 해보니 로컬 DB와 구조가 달랐다....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포를 초기에 미리해놓고 ddl-auto를 update로 설정해놓아서 entity가 변경되어도 반영되지 않았던 것이다.&lt;br /&gt;따라서 과거에 사용하던 member_club_id 컬럼이 DB에 남아 있어서 데이터 삽입시 저장에 실패했던 것이다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;ALTER TABLE likes DROP FOREIGN KEY [키]; &lt;br /&gt;ALTER&amp;nbsp;TABLE&amp;nbsp;likes&amp;nbsp;DROP&amp;nbsp;COLUMN&amp;nbsp;member_club_id; &lt;br /&gt;이후 Likes insert 시 더 이상 member_club_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;br /&gt;&lt;br /&gt;&lt;br /&gt;교훈)&lt;br /&gt;&lt;b&gt;엔티티 구조를 변경하고 반영 항상 로컬과 운영 서버의 DB에 접근하여 SHOW CREATE TABLE로 실제 반영 여부를 확인해보자..!!!&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>ERROR</category>
      <category>doesn't have a default value</category>
      <category>sql error: 1364</category>
      <category>트러블슈팅</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/82</guid>
      <comments>https://oceansea.tistory.com/82#entry82comment</comments>
      <pubDate>Tue, 1 Apr 2025 01:40:19 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD] 깃허브 액션 사용하기</title>
      <link>https://oceansea.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 액션은 깃허브에서 제공하는 서비스로 코드 원격 저장소에 특정 이벤트가 발생하면 특정 작업을 하거나, 주기적으로 특정 작업을 반복할 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가 코드를 작성해서 깃허브에 업데이트하면 해당 코드에 문제가 없는지 자동으로 코드를 빌드, 테스트 한 이후 배포까지 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;lt;CI&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &quot;.github&quot; 폴더를 열어준다. 만약에 없으면 생성해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/69fpN/btsL3AIvccj/8d5yn49Gek4DUfgBj8BFhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/69fpN/btsL3AIvccj/8d5yn49Gek4DUfgBj8BFhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/69fpN/btsL3AIvccj/8d5yn49Gek4DUfgBj8BFhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F69fpN%2FbtsL3AIvccj%2F8d5yn49Gek4DUfgBj8BFhk%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;310&quot; height=&quot;122&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &quot;.github&quot; 폴더 하단에 &quot;workflows&quot; 폴더를 만들어준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPNVte/btsL4p0EWWz/ZAanTZDd72I2VPdfnuMm0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPNVte/btsL4p0EWWz/ZAanTZDd72I2VPdfnuMm0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPNVte/btsL4p0EWWz/ZAanTZDd72I2VPdfnuMm0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPNVte%2FbtsL4p0EWWz%2FZAanTZDd72I2VPdfnuMm0K%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;392&quot; height=&quot;92&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. workflows 폴더 하위에 &quot;ci.yml&quot; 파일을 만들어준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccGCiG/btsL3jUtEl9/Ji3F04cnCsFzHShpgVMJt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccGCiG/btsL3jUtEl9/Ji3F04cnCsFzHShpgVMJt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccGCiG/btsL3jUtEl9/Ji3F04cnCsFzHShpgVMJt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccGCiG%2FbtsL3jUtEl9%2FJi3F04cnCsFzHShpgVMJt0%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;261&quot; height=&quot;185&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. ci.yml 파일에 아래와 같은 코드를 입력해준다&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;name: CI

on:
  push:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '17'
      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      - name: Build with Gradle
        run: ./gradlew clean build&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 이렇게 작성한 파일을 main에 푸쉬해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. main에 잘 올라갔다면, Action에 아래 사진처럼 추가되고, 워크 플로가 성공적으로 동작하면 초록색 체크 모양으로 표시되게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MzFH7/btsL4Fa7pEF/p1cEtBfIwSlQzrWuqgceW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MzFH7/btsL4Fa7pEF/p1cEtBfIwSlQzrWuqgceW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MzFH7/btsL4Fa7pEF/p1cEtBfIwSlQzrWuqgceW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMzFH7%2FbtsL4Fa7pEF%2Fp1cEtBfIwSlQzrWuqgceW0%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;1157&quot; height=&quot;316&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;lt;CD&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드를 진행하면 총 두개의 jar 파일이 생기는데, plain이 붙지않은 jar 파일을 지속적 배포에 사용할 것이므로 plain이 붙어있는 jar 파일은 배포에서 제외하도록 gradle 파일을 설정해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. build.gradle 파일에 아래 코드를 추가해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1738510538235&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jar {
    enabled = false
}&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. 위에서 만든 ci.yml 파일 이름을 cicd.yml로 변경하고 아래의 코드로 변경해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1738510642598&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: CI/CD

on:
 push:
    branches: [ main ]

jobs:
 build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-java@v3
        with:
          distribution: 'corretto'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew


      - name: Build with Gradle
        run: ./gradlew clean build

      - name: Get current time
        uses: josStorer/get-current-time@v2.0.2
        id: current-time
        with:
           format: YYYY-MM-DDTHH-mm-ss
           utcOffset: &quot;+09:00&quot;
           
      - name: Set artifact
        run: echo &quot;artifact=$(ls ./build/libs)&quot; &amp;gt;&amp;gt; $GITHUB_ENV

      - name: Beanstalk Deploy
      uses: einaregilsson/beanstalk-deploy@v20
      with:
        aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        application_name: 어플리케이션 이름
        environment_name: 환경 이름
        version_label: github-action-${{steps.current-time.outputs.formattedTime}} region: ap-northeast-2
        deployment_package: ./build/libs/${{env.artifact}}&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;3. AWS의 IAM 서비스로 접속해준다. 그 다음 사용자를 클릭해준다. 그리고 사용자 생성을 눌러준다.&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;1917&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIV7U1/btsL3fLk8Yf/RyPE01wRGgog4dukttBakk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIV7U1/btsL3fLk8Yf/RyPE01wRGgog4dukttBakk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIV7U1/btsL3fLk8Yf/RyPE01wRGgog4dukttBakk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIV7U1%2FbtsL3fLk8Yf%2FRyPE01wRGgog4dukttBakk%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;1917&quot; height=&quot;171&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;4. 사용자 이름을 설정해주고, 다음을 눌러준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV40s4/btsL3gXN8ZP/5iO47HIAvPtdOwgeRKUKD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV40s4/btsL3gXN8ZP/5iO47HIAvPtdOwgeRKUKD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV40s4/btsL3gXN8ZP/5iO47HIAvPtdOwgeRKUKD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV40s4%2FbtsL3gXN8ZP%2F5iO47HIAvPtdOwgeRKUKD1%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;550&quot; height=&quot;575&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;575&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 권한 설정은 직접 정책 연결로 선택해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIcCw7/btsL5Bscmp5/TszJHBVPmGX62FRn8R1DYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIcCw7/btsL5Bscmp5/TszJHBVPmGX62FRn8R1DYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIcCw7/btsL5Bscmp5/TszJHBVPmGX62FRn8R1DYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIcCw7%2FbtsL5Bscmp5%2FTszJHBVPmGX62FRn8R1DYk%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;689&quot; height=&quot;522&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 아래의 권한을 선택해주고, 다음을 눌러 사용자를 생성해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VjuAM/btsL48w7noI/kMlpkK9BVrXwK3eFTkHOzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VjuAM/btsL48w7noI/kMlpkK9BVrXwK3eFTkHOzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VjuAM/btsL48w7noI/kMlpkK9BVrXwK3eFTkHOzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVjuAM%2FbtsL48w7noI%2FkMlpkK9BVrXwK3eFTkHOzK%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;1322&quot; height=&quot;300&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 생성을 마치고 사용자를 눌러준다. 그리고 액세스 키 만들기를 눌러준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5K56P/btsL3sqgxdH/6BDcmTvLcT4akMesF1ETs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5K56P/btsL3sqgxdH/6BDcmTvLcT4akMesF1ETs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5K56P/btsL3sqgxdH/6BDcmTvLcT4akMesF1ETs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5K56P%2FbtsL3sqgxdH%2F6BDcmTvLcT4akMesF1ETs1%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;1592&quot; height=&quot;252&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 서드파티 서비스를 클릭해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1297&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Ww9m/btsL4IMqX1u/Zp6KhK4xC0J3KK8y4NxDL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Ww9m/btsL4IMqX1u/Zp6KhK4xC0J3KK8y4NxDL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Ww9m/btsL4IMqX1u/Zp6KhK4xC0J3KK8y4NxDL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Ww9m%2FbtsL4IMqX1u%2FZp6KhK4xC0J3KK8y4NxDL0%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;521&quot; height=&quot;578&quot; data-origin-width=&quot;1297&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 설명 태그는 아래와 같이 달아준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVgNCC/btsL5zVrUji/NoXvTg93YS4DQUHfzcbgf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVgNCC/btsL5zVrUji/NoXvTg93YS4DQUHfzcbgf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVgNCC/btsL5zVrUji/NoXvTg93YS4DQUHfzcbgf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVgNCC%2FbtsL5zVrUji%2FNoXvTg93YS4DQUHfzcbgf1%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;1267&quot; height=&quot;355&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 생성을 완료하면 아래 화면처럼 액세스 키와 비밀번호를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액세스 키와 비밀 액세스 키는 딱 한번만 확인할 수 있으므로 값을 미리 복사하거나, .csv 파일 다운로드를 통해 보관할 수 있다.&lt;/p&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;11. 복사한 값을 등록하기 위해 깃허브 리포지토리에 접속한 뒤 Setting -&amp;gt; Secrets and variables -&amp;gt; Action으로 들어가준다. 그리고 New repository secrets 버튼을 눌러 새로운 비밀키를 등록해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kyew8/btsL4ycdSpc/unnz5KXKZ1BiEi4lflszp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kyew8/btsL4ycdSpc/unnz5KXKZ1BiEi4lflszp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kyew8/btsL4ycdSpc/unnz5KXKZ1BiEi4lflszp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fkyew8%2FbtsL4ycdSpc%2Funnz5KXKZ1BiEi4lflszp0%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;629&quot; height=&quot;191&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0gXdt/btsL53IL1dE/Om8QY6fDx0AB0ienVwuTmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0gXdt/btsL53IL1dE/Om8QY6fDx0AB0ienVwuTmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0gXdt/btsL53IL1dE/Om8QY6fDx0AB0ienVwuTmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0gXdt%2FbtsL53IL1dE%2FOm8QY6fDx0AB0ienVwuTmk%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;647&quot; height=&quot;125&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. 이제 설정이 완료되었으므로 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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13. build 버튼을 누르면 아래와 같이 진행 상황을 확인해볼 수 있다.&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;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xvlaN/btsL4aidnqg/kkSi1id0YKcJkYBkkOAh40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xvlaN/btsL4aidnqg/kkSi1id0YKcJkYBkkOAh40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xvlaN/btsL4aidnqg/kkSi1id0YKcJkYBkkOAh40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxvlaN%2FbtsL4aidnqg%2FkkSi1id0YKcJkYBkkOAh40%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;426&quot; data-origin-width=&quot;1317&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;출처&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://goldenrabbit.co.kr/2023/07/05/github-actions%EC%9C%BC%EB%A1%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%B4-%EB%B3%B4%EA%B8%B0a-k-a-ci-cd-2%ED%8E%B8/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://goldenrabbit.co.kr/2023/07/05/github-actions%EC%9C%BC%EB%A1%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%B4-%EB%B3%B4%EA%B8%B0a-k-a-ci-cd-2%ED%8E%B8/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Server</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/81</guid>
      <comments>https://oceansea.tistory.com/81#entry81comment</comments>
      <pubDate>Mon, 3 Feb 2025 01:10:56 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] Elastic Beanstalk를 이용한 서버 배포</title>
      <link>https://oceansea.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 : AWS에서 제공하는 원격 서버 -&amp;gt; 가상의 PC/서버 한대를 임대&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 오토 스케일링 그룹: 사용자의 요청 횟수에 따라 EC2를 늘이거나 줄인다.&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;RDS: AWS에서 제공하는 원격 데이터베이스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elastic Beanstalk: 위의 서비스들을 한 번에 설정하는 서비스로 일래스틱 빈스토크를 사용하면 서버 업로드용 코드만 작성해도 서버를 쉽게 올릴 수 있고, 그 외의 기능들, 로드 밸런싱, 오토 스케일링, 모니터링, 배포 등을 일래스틱 빈스토크 메뉴 안에 직접 구성하거나 설정 파일로 자동 처리할 수 있다.&lt;/p&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;Elastic Beanstalk를 사용하는 과정&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. 버전 업로드(애플리케이션의 소스를 번들 형태 (ex. war 파일)로 업로드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 환경 생성(일래스틱 빈스토크가 자동으로 환경을 실행하고 코드 실행에 필요한 AWS 리소스를 생성하고 구성)&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;&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;lt;IAM 설정&amp;gt;&lt;/p&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;1. 리전을 서울로 변경해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sfr2i/btsL2IzOodz/H2gESAbOR5svbGVptZa7ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sfr2i/btsL2IzOodz/H2gESAbOR5svbGVptZa7ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sfr2i/btsL2IzOodz/H2gESAbOR5svbGVptZa7ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsfr2i%2FbtsL2IzOodz%2FH2gESAbOR5svbGVptZa7ik%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;538&quot; height=&quot;102&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. IAM을 검색해 들어가 줍니다. IAM은 AWS 리소스에 접근하는 권한을 관리하는 서비스로 일래스틱 빈즈토크 서비스에 부여할 역할을 IAM에서 만들어 주어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1187&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co2Tod/btsL2HnrTeQ/WiYe2uKrFuCVroYYkQJKUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co2Tod/btsL2HnrTeQ/WiYe2uKrFuCVroYYkQJKUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co2Tod/btsL2HnrTeQ/WiYe2uKrFuCVroYYkQJKUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco2Tod%2FbtsL2HnrTeQ%2FWiYe2uKrFuCVroYYkQJKUk%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;1187&quot; height=&quot;223&quot; data-origin-width=&quot;1187&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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. 역할 -&amp;gt; 역할 생성을 눌러주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKo6AP/btsL3AOD1Uv/Dir3ZVNP4ZKopcsYC4TNWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKo6AP/btsL3AOD1Uv/Dir3ZVNP4ZKopcsYC4TNWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKo6AP/btsL3AOD1Uv/Dir3ZVNP4ZKopcsYC4TNWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKo6AP%2FbtsL3AOD1Uv%2FDir3ZVNP4ZKopcsYC4TNWK%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;1917&quot; height=&quot;522&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 신뢰할 수 있는 엔터티를 AWS 서비스로 선택해주고, 사용 사례를 EC2로 선택해준 뒤 다음을 눌러주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/85Xv2/btsL26tHoPI/x5JyOI4zIMCFT3DohKbaf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/85Xv2/btsL26tHoPI/x5JyOI4zIMCFT3DohKbaf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/85Xv2/btsL26tHoPI/x5JyOI4zIMCFT3DohKbaf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F85Xv2%2FbtsL26tHoPI%2Fx5JyOI4zIMCFT3DohKbaf1%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;1527&quot; height=&quot;715&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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. 다음 단계로 넘어가서 2단계 권한 추가에서는 아래와 같이 AWSElasticBeanstalkMulticontainerDocker, AWSElasticBeanstalkWebTier, AWSElasticBeanstalkWorkerTier 이렇게 3개를 추가해줍니다. 권한 경계는 선택하지 않고 다음을 눌러서 넘어가줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mPYfP/btsL4Z0NzLI/KMYgbk2rf5aciEQV4VfyMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mPYfP/btsL4Z0NzLI/KMYgbk2rf5aciEQV4VfyMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mPYfP/btsL4Z0NzLI/KMYgbk2rf5aciEQV4VfyMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmPYfP%2FbtsL4Z0NzLI%2FKMYgbk2rf5aciEQV4VfyMk%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;1512&quot; height=&quot;722&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 다음 3단계에서는 구분할 수 있도록 이름을 지어줍니다, 아래에서는 지금까지 설정한 정보가 잘 설정되었는지 확인할 수 있습니다. 확인되었으면 역할 생성을 눌러 역할을 생성해주시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Eh0Mi/btsL3SIpKY4/d3gkJWKzTPBhUA7nScwiTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Eh0Mi/btsL3SIpKY4/d3gkJWKzTPBhUA7nScwiTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Eh0Mi/btsL3SIpKY4/d3gkJWKzTPBhUA7nScwiTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEh0Mi%2FbtsL3SIpKY4%2Fd3gkJWKzTPBhUA7nScwiTk%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;1157&quot; height=&quot;358&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;lt;Elastic Beanstalk 생성&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Elastic Beanstalk를 검색하고 들어가줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CF32H/btsL4FVUFgz/KnBcENH4JM6bLVXqzTomYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CF32H/btsL4FVUFgz/KnBcENH4JM6bLVXqzTomYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CF32H/btsL4FVUFgz/KnBcENH4JM6bLVXqzTomYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCF32H%2FbtsL4FVUFgz%2FKnBcENH4JM6bLVXqzTomYk%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;1135&quot; height=&quot;226&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 환경에 들어가서 환경 생성을 눌러줍니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TpwQD/btsL4P43I5Z/JxIEqeDXoxPRi6WZHhXTL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TpwQD/btsL4P43I5Z/JxIEqeDXoxPRi6WZHhXTL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TpwQD/btsL4P43I5Z/JxIEqeDXoxPRi6WZHhXTL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTpwQD%2FbtsL4P43I5Z%2FJxIEqeDXoxPRi6WZHhXTL1%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;1915&quot; height=&quot;357&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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;3. 환경 생성에서 애플리케이션 이름을 설정해주고, 플랫폼을 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;&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;4. 서비스 액세스에서는 서비스 역할 - 새 서비스 역할 생성 및 사용을 선택하고, EC2 인스턴스 프로파일에서는 아까 만들었던 IAM을 아래와 같이 설정해줍니다. 그리고 검토 단계로 건너 뛰기를 선택해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgRsgZ/btsL3qk7vXT/pZAo3WZrhPVjFDK5I6OxDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgRsgZ/btsL3qk7vXT/pZAo3WZrhPVjFDK5I6OxDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgRsgZ/btsL3qk7vXT/pZAo3WZrhPVjFDK5I6OxDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgRsgZ%2FbtsL3qk7vXT%2FpZAo3WZrhPVjFDK5I6OxDk%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;1682&quot; height=&quot;728&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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. 설정 정보를 확인하고 제출을 선택해서 Elastic Beanstalk 환경 생성을 마쳐주면 됩니다. 조금 기다려주면 생성이 완료됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgoGc7/btsL2eloN7v/DTiLeyXzKnAu7MWx2nwCmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgoGc7/btsL2eloN7v/DTiLeyXzKnAu7MWx2nwCmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgoGc7/btsL2eloN7v/DTiLeyXzKnAu7MWx2nwCmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgoGc7%2FbtsL2eloN7v%2FDTiLeyXzKnAu7MWx2nwCmk%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;1530&quot; height=&quot;222&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 프로젝트 생성이 완료되면 화면이 전환되는데, 환경을 눌러서 환경 목록을 봐줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 목록에 방금 생성된 환경의 상태가 OK로 보이는지 확인하고 URL을 클릭해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm7YHs/btsL3jzEF1I/eVgfOjeK88HYjr6NrdHdh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm7YHs/btsL3jzEF1I/eVgfOjeK88HYjr6NrdHdh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm7YHs/btsL3jzEF1I/eVgfOjeK88HYjr6NrdHdh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm7YHs%2FbtsL3jzEF1I%2FeVgfOjeK88HYjr6NrdHdh1%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;339&quot; height=&quot;436&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. URL에 잘 접속이 되는지 확인해줍니다. 아래처럼 Congratulations가 잘 나오면 성공입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;647&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTp89/btsL4bOtgbl/Ozvq8CuM1MV0lH5yiw48ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTp89/btsL4bOtgbl/Ozvq8CuM1MV0lH5yiw48ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTp89/btsL4bOtgbl/Ozvq8CuM1MV0lH5yiw48ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTp89%2FbtsL4bOtgbl%2FOzvq8CuM1MV0lH5yiw48ok%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;664&quot; height=&quot;647&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;647&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;일래스틱 빈스토크내에 RDS 생성하기&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일래스틱 빈스토크 창 내의 환경 -&amp;gt; 내가 만든 환경을 클릭해주고 들어가줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmET2n/btsL3kyD5hr/tfuv2cwONbcLu8h829zFP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmET2n/btsL3kyD5hr/tfuv2cwONbcLu8h829zFP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmET2n/btsL3kyD5hr/tfuv2cwONbcLu8h829zFP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmET2n%2FbtsL3kyD5hr%2Ftfuv2cwONbcLu8h829zFP0%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;391&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;391&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;2. 구성을 누르고 네트워킹 및 데이터베이스 메뉴에서 편집을 눌러주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;258&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wiFMB/btsL39XyGU7/379XlvRq0CffByUFsRUNkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wiFMB/btsL39XyGU7/379XlvRq0CffByUFsRUNkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wiFMB/btsL39XyGU7/379XlvRq0CffByUFsRUNkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwiFMB%2FbtsL39XyGU7%2F379XlvRq0CffByUFsRUNkK%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;1918&quot; height=&quot;258&quot; data-origin-width=&quot;1918&quot; data-origin-height=&quot;258&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 스크롤을 쭉 내리다보면 데이터베이스 탭이 보입니다. 여기서 데이터베이스 활성화 버튼을 눌러 활성화를 켜주고, 데이터베이스 설정을 mysql, 인스턴스 클래스를 db.t3.micro로(프리티어) 설정해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckAeFa/btsL3haPPOG/T1PYLz0qOsTA1bd54ir58k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckAeFa/btsL3haPPOG/T1PYLz0qOsTA1bd54ir58k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckAeFa/btsL3haPPOG/T1PYLz0qOsTA1bd54ir58k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckAeFa%2FbtsL3haPPOG%2FT1PYLz0qOsTA1bd54ir58k%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;536&quot; height=&quot;730&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 사용자 이름과 암호도 채워줍니다. 암호는 8자 이상이어야 하며, 사용자 이름과 암호는 꼭 외우고 있어야합니다. 여기까지 했으면 오른쪽 아래의 적용을 눌러 데이터베이스를 생성해주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dueVN1/btsL4QCXNpf/AsuxTSBNO8ZKl4hoim2SXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dueVN1/btsL4QCXNpf/AsuxTSBNO8ZKl4hoim2SXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dueVN1/btsL4QCXNpf/AsuxTSBNO8ZKl4hoim2SXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdueVN1%2FbtsL4QCXNpf%2FAsuxTSBNO8ZKl4hoim2SXk%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;1186&quot; height=&quot;333&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&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. RDS를 검색해서 들어가줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdDk79/btsL40rUbv1/mGkGwo0z0AYj3nYYy6YBTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdDk79/btsL40rUbv1/mGkGwo0z0AYj3nYYy6YBTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdDk79/btsL40rUbv1/mGkGwo0z0AYj3nYYy6YBTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdDk79%2FbtsL40rUbv1%2FmGkGwo0z0AYj3nYYy6YBTk%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;1157&quot; height=&quot;222&quot; data-origin-width=&quot;1157&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 왼쪽 바에서 데이터베이스를 눌러보면 방금 만든 RDS가 생성중인 것을 확인할 수 있습니다. 조금 기다려주면 RDS가 생성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tOFmu/btsL3O7eezt/br6zOqlch0PQTIP2wkPYx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tOFmu/btsL3O7eezt/br6zOqlch0PQTIP2wkPYx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tOFmu/btsL3O7eezt/br6zOqlch0PQTIP2wkPYx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtOFmu%2FbtsL3O7eezt%2Fbr6zOqlch0PQTIP2wkPYx0%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;1598&quot; height=&quot;316&quot; data-origin-width=&quot;1598&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. RDS 생성이 완료된 다음 만들어진 RDS 식별자를 눌러서 새 창에 들어가면&amp;nbsp; RDS 정보를 확인할 수 있습니다. 여기 있는 이 엔드 포인트는 RDS로 연결할 때 사용되니 미리 복사해두면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfxI94/btsL4HlYEAM/TxhhMMPtT0hysZXSw4Sr0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfxI94/btsL4HlYEAM/TxhhMMPtT0hysZXSw4Sr0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfxI94/btsL4HlYEAM/TxhhMMPtT0hysZXSw4Sr0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfxI94%2FbtsL4HlYEAM%2FTxhhMMPtT0hysZXSw4Sr0K%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;741&quot; height=&quot;721&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 다시 일래스틱 빈스토크로 돌아가줍니다. 그리고 환경-&amp;gt;구성으로 들어가줍니다. 그리고 스크롤을 내려 업데이트, 모니터링 및 로깅 탭을 찾아서 편집을 눌러줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/111Z4/btsL26ucIEK/q3Qgyo0xBYKDIJZsho7mM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/111Z4/btsL26ucIEK/q3Qgyo0xBYKDIJZsho7mM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/111Z4/btsL26ucIEK/q3Qgyo0xBYKDIJZsho7mM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F111Z4%2FbtsL26ucIEK%2Fq3Qgyo0xBYKDIJZsho7mM1%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;1915&quot; height=&quot;401&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 편집창에서 스크롤을 내리면 환경 속성을 찾을 수 있습니다. 여기에서 RDS 설정정보를 입력해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경속성을 3개 추가해주고 아래와 같이 설정해주세요.&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: 41.0465%;&quot;&gt;SPRING_DATASOURCE_URL&lt;/td&gt;
&lt;td style=&quot;width: 58.9535%;&quot;&gt;jdbc:mysql://아까 복사해두었던 엔드포인트/DB명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 41.0465%;&quot;&gt;SPRING_DATASOURCE_USERNAME&lt;/td&gt;
&lt;td style=&quot;width: 58.9535%;&quot;&gt;RDS에서 설정한 사용자 이름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 41.0465%;&quot;&gt;SPRING_DATASOURCE_PASSWORD&lt;/td&gt;
&lt;td style=&quot;width: 58.9535%;&quot;&gt;RDS에서 설정한 비밀번호&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPIE8o/btsL484RuD7/VWBVKb89uGvS5bndWSkpRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPIE8o/btsL484RuD7/VWBVKb89uGvS5bndWSkpRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPIE8o/btsL484RuD7/VWBVKb89uGvS5bndWSkpRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPIE8o%2FbtsL484RuD7%2FVWBVKb89uGvS5bndWSkpRk%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;742&quot; height=&quot;683&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 설정한 환경 속성값은 application.properties에서 spring.datasource 항목과 동일한 역할을 합니다. springboot 프로젝트 내에서 spring.datasource 항목들을 설정한 환경 속성 값들로 변경해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;로컬에서 RDS 연결하기&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 로컬에서 RDS를 사용하기 위해서는 보안그룹 설정이 필요합니다. 다시 AWS에서 RDS를 검색해서 들어가주고, 데이터베이스 창으로 이동해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z5zOo/btsL4wrLWc5/BLEifZoZnWbhA9MeZlgEx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z5zOo/btsL4wrLWc5/BLEifZoZnWbhA9MeZlgEx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z5zOo/btsL4wrLWc5/BLEifZoZnWbhA9MeZlgEx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz5zOo%2FbtsL4wrLWc5%2FBLEifZoZnWbhA9MeZlgEx1%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;1506&quot; height=&quot;312&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;2. DB 식별자를 누르고, VPC 보안 그룹 링크를 클릭해줍니다.&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;1173&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mrmwz/btsL4wL5mWR/O4RLHMynuAoDdtrkDGzGtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mrmwz/btsL4wL5mWR/O4RLHMynuAoDdtrkDGzGtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mrmwz/btsL4wL5mWR/O4RLHMynuAoDdtrkDGzGtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMrmwz%2FbtsL4wL5mWR%2FO4RLHMynuAoDdtrkDGzGtk%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;1173&quot; height=&quot;327&quot; data-origin-width=&quot;1173&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 인바운드 규칙에 들어가서 인바운드 규칙 편집 버튼을 눌러줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1515&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mMpXo/btsL4weeGuO/k7w2kpdMS0YkLWt9ZJtAZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mMpXo/btsL4weeGuO/k7w2kpdMS0YkLWt9ZJtAZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mMpXo/btsL4weeGuO/k7w2kpdMS0YkLWt9ZJtAZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmMpXo%2FbtsL4weeGuO%2Fk7w2kpdMS0YkLWt9ZJtAZK%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;1515&quot; height=&quot;237&quot; data-origin-width=&quot;1515&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 규칙 추가를 누르고, 유형을 MYSQL/Aurora, 소스는 내 IP를 선택하고 규칙 저장을 눌러줍니다. 이렇게하면 로컬에서 일래스틱 빈스토크 데이터베이스에 접근할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/elNsSo/btsL3kTi92r/TiHufqdfUtwmjVmbh8kCWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elNsSo/btsL3kTi92r/TiHufqdfUtwmjVmbh8kCWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elNsSo/btsL3kTi92r/TiHufqdfUtwmjVmbh8kCWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelNsSo%2FbtsL3kTi92r%2FTiHufqdfUtwmjVmbh8kCWK%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;1796&quot; height=&quot;277&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. Host에 엔드포인트, User과 Password에 AWS RDS에서 설정해준 값들을 넣고 Test Connection을 해보면 연결이 잘되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tHBVV/btsL5isKWCD/mvWUaIr9OZKIDwNtwOykfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tHBVV/btsL5isKWCD/mvWUaIr9OZKIDwNtwOykfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tHBVV/btsL5isKWCD/mvWUaIr9OZKIDwNtwOykfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtHBVV%2FbtsL5isKWCD%2FmvWUaIr9OZKIDwNtwOykfK%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;754&quot; height=&quot;478&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;625&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;lt;일래스틱 빈스토크에 서비스 배포&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Gradle 탭을 눌러서 Tasks -&amp;gt; build -&amp;gt; build를 더블클릭해 빌드를 진행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nOHf8/btsL4JxE8dM/9CAYqmh2WvuYbSRSWOePxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nOHf8/btsL4JxE8dM/9CAYqmh2WvuYbSRSWOePxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nOHf8/btsL4JxE8dM/9CAYqmh2WvuYbSRSWOePxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnOHf8%2FbtsL4JxE8dM%2F9CAYqmh2WvuYbSRSWOePxK%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;496&quot; height=&quot;288&quot; data-origin-width=&quot;677&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 빌드가 끝나면 build-&amp;gt;libs에 빌드 완성 파일이 생성됩니다. jar 파일 두개가 생기는데 그 중에서 -plain이 없는 jar파일을 적당한 위치에 저장해주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ocs37/btsL5umkiDZ/GawqqhVHiZFUzr4XFFswz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ocs37/btsL5umkiDZ/GawqqhVHiZFUzr4XFFswz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ocs37/btsL5umkiDZ/GawqqhVHiZFUzr4XFFswz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOcs37%2FbtsL5umkiDZ%2FGawqqhVHiZFUzr4XFFswz1%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;347&quot; height=&quot;356&quot; data-origin-width=&quot;416&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 일래스틱 빈즈토크로 돌아가서 생성된 환경의 이름을 클릭합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHSSxI/btsL5u0UXW3/WWktNMeoJKFBGIkxkIt2o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHSSxI/btsL5u0UXW3/WWktNMeoJKFBGIkxkIt2o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHSSxI/btsL5u0UXW3/WWktNMeoJKFBGIkxkIt2o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHSSxI%2FbtsL5u0UXW3%2FWWktNMeoJKFBGIkxkIt2o0%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;632&quot; height=&quot;364&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;416&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;4. 업로드 및 배포를 누르고, 아까 따로 복사해둔 jar 파일을 업로드해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1845&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wURTn/btsL4xEdnxR/mmCm1pq2vHDvd22lFjPgx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wURTn/btsL4xEdnxR/mmCm1pq2vHDvd22lFjPgx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wURTn/btsL4xEdnxR/mmCm1pq2vHDvd22lFjPgx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwURTn%2FbtsL4xEdnxR%2FmmCm1pq2vHDvd22lFjPgx0%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;1845&quot; height=&quot;182&quot; data-origin-width=&quot;1845&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 업로드를 한 뒤에, Elastic Beanstalk의 환경-&amp;gt;구성으로 들어간 뒤, 업데이트, 모니터링 및 로깅의 편집을 선택하고 application.properties의 값들 중 덮어 쓸 값들을 입력해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzig68/btsL3YI3Ah2/RMxyr43cbT2CCyxf6gH4PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzig68/btsL3YI3Ah2/RMxyr43cbT2CCyxf6gH4PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzig68/btsL3YI3Ah2/RMxyr43cbT2CCyxf6gH4PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzig68%2FbtsL3YI3Ah2%2FRMxyr43cbT2CCyxf6gH4PK%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;383&quot; height=&quot;547&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;6. 설정이 완료되면 이제 배포 url을 타고 들어가면 아래처럼 잘 배포된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4iRQG/btsL4qSIOMC/U7i3GugIjo4YK7GTy6Vlq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4iRQG/btsL4qSIOMC/U7i3GugIjo4YK7GTy6Vlq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4iRQG/btsL4qSIOMC/U7i3GugIjo4YK7GTy6Vlq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4iRQG%2FbtsL4qSIOMC%2FU7i3GugIjo4YK7GTy6Vlq0%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;1911&quot; height=&quot;646&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Server</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/80</guid>
      <comments>https://oceansea.tistory.com/80#entry80comment</comments>
      <pubDate>Fri, 31 Jan 2025 21:09:52 +0900</pubDate>
    </item>
    <item>
      <title>[AWS S3] deleteObject 메서드를 통해 삭제 시 버킷에서 삭제되지 않는 오류 해결</title>
      <link>https://oceansea.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 중인 프로젝트에서 이미지 저장이 필요해서 AWS S3 버킷을 만들어서 연동 중이었다.&lt;br /&gt;업로드 기능은 문제 없이 잘 되어서 이미지 업로드 이후 S3를 새로고침하면 버킷의 수가 업로드 한 이미지 수 만큼 잘 늘어나는 것을 확인할 수 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제 기능의 경우 DB에 따로 저장해 둔 이미지 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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;s&gt;1. 버전 관리 문제&lt;/s&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 관리가 활성화되어있으면 deleteObject()가 즉시 삭제하지 않고 &quot;삭제 마커(Deletion Marker)&quot;만 추가하므로 파일이 실제 삭제된 것이 아니라 &quot;숨김 처리&quot;된 것일 가능성이 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 버전 관리를 활성화해서 한번 시도해보고 비활성화해서 한번 시도해봤는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무런 변화가 없었다,,,,,&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;012&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/012.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/012.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;s&gt;2. 파일명이 제대로 SubString 되지 않았을 경우&lt;/s&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일명이 버킷내의 객체명과 일치하지 않으면 S3에서는 일치하는 객체를 찾을 수가 없어 객체가 잘 삭제되지 않는다.&lt;/p&gt;
&lt;pre id=&quot;code_1738226276699&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private String extractFileNameFromUrl(String imageUrl) {
    try {
        URL url = new URL(imageUrl);
        String filePath = url.getPath(); // 경로 추출
        return filePath.substring(filePath.lastIndexOf(&quot;/&quot;) + 1); // 파일명만 추출
    } catch (MalformedURLException e) {
        throw new IllegalArgumentException(&quot;Invalid S3 URL format: &quot; + imageUrl);
    }
}&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;이 파일명이 아닐 수도 있을거라고 생각해서 substring도 조정해보고, 아니면 아예 &lt;u&gt;https://버킷명.s3.ap-northeast-2~&lt;/u&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;3. 파일명 인코딩 문제&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 추가적으로 열심히 찾아본 결과 파일명 인코딩 디코딩 문제일수도 있다고 해서 다시한번 파일명을 유심히 찾아봤는데, 자세히 보니 버킷 내 객체의 파일명과 DB에 저장된 객체의 파일명과 달랐다....!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0oywn/btsL2lxqj63/1Y7mkKtInYyiivp79pyQv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0oywn/btsL2lxqj63/1Y7mkKtInYyiivp79pyQv0/img.png&quot; data-alt=&quot;DB에 저장되어있는 파일명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0oywn/btsL2lxqj63/1Y7mkKtInYyiivp79pyQv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0oywn%2FbtsL2lxqj63%2F1Y7mkKtInYyiivp79pyQv0%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;1318&quot; height=&quot;159&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;DB에 저장되어있는 파일명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SjfxQ/btsL4drcxv6/fdxVTsuEkBi3R14DZnsXq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SjfxQ/btsL4drcxv6/fdxVTsuEkBi3R14DZnsXq0/img.png&quot; data-alt=&quot;버킷에 저장되어있는 파일명&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SjfxQ/btsL4drcxv6/fdxVTsuEkBi3R14DZnsXq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSjfxQ%2FbtsL4drcxv6%2FfdxVTsuEkBi3R14DZnsXq0%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;521&quot; height=&quot;113&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버킷에 저장되어있는 파일명&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 지금까지 객체 삭제에 실패했던 원인은 &lt;b&gt;파일명이 URL 인코딩된 형태로 삭제 요청&lt;/b&gt;이 보내졌기 때문이었다!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서 URLDecoder.decode()를 사용하여 원본 파일명으로 변환해주어야 한다&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &amp;nbsp; URL 인코딩이란?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;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;ASCII 문자만 안전하게 사용 가능&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; A-Z, a-z, 0-9, -, _, .&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인코딩해야 하는 문자:&lt;/b&gt; 공백, 한글, 특수문자 (%20, %EC%95%88%EB%85%95 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  인코딩된 파일과 디코딩된 파일의 차이&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;파일 자체에는 차이가 없음!&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;S3에서 파일을 저장할 때 키(Key)는 URL 인코딩된 상태일 가능성이 높음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;삭제할 때는 반드시 디코딩해서 원래 파일명으로 전달해야 함&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 S3 URL에서 파일명 추출하는 extractFileNameFromUrl 메서드를 아래와 같이 수정해주니 정상적으로 객체가 잘 삭제되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1738226767625&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// S3 URL에서 파일명 추출
    private String extractFileNameFromUrl(String imageUrl) {
        try {
            URL url = new URL(imageUrl);
            String filePath = url.getPath();
            String fileName = filePath.substring(filePath.lastIndexOf(&quot;/&quot;) + 1);
            return URLDecoder.decode(fileName, StandardCharsets.UTF_8.name());
        } catch (MalformedURLException | UnsupportedEncodingException e) {
            throw new ImageUploadException(ImageUploadErrorCode.S3_DELETE_FAILED);
        }
    }&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;</description>
      <category>ERROR</category>
      <category>AWS S3</category>
      <category>DeleteObject</category>
      <category>deleteobject 오류</category>
      <category>파일명 디코드</category>
      <category>파일명 인코드</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/79</guid>
      <comments>https://oceansea.tistory.com/79#entry79comment</comments>
      <pubDate>Thu, 30 Jan 2025 17:47:01 +0900</pubDate>
    </item>
    <item>
      <title>[Java to Kotlin] 코틀린에서 객체 생성 &amp;amp; 의존성 주입</title>
      <link>https://oceansea.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 코틀린으로 마이그레이션 하기 위해 객체 생성 &amp;amp; 의존성 주입을 할 경우의 차이점에 대해서 알아보고자 한다.&lt;br /&gt;자바와 코틀린의 차이를 크게 아래와 같이 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;1️⃣ 객체 생성 방식의 차이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;: 코틀린은 자바보다 객체 생성 방식이 단순하고 간결하여, 불필요한 보일러플레이트 코드가 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 2️⃣ lateinit &amp;amp; by lazy를 활용한 객체 초기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;:&lt;span&gt; 코틀린에서 lateinit, by lazy를 활용하면 초기화 및 생명주기 관리가 더욱 쉬워진다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 3️⃣ 싱글톤 객체(object)를 통한 코드 단순화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;:&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 코틀린에서는 object 키워드 하나만으로 간편하게 싱글톤을 구현할 수 있다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 4️⃣ Spring Boot에서 DI 방식 차이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;:&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 코틀린에서는 Spring Boot에서 @Autowired 없이 constructor 주입을 통해 더 간결한 DI 설정이 가능하다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 5️⃣ 코틀린에서 @Autowired 없이 DI 가능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;:&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; 코틀린의 val, var 활용법을 잘 이해하면 불필요한 getter/setter를 제거할 수 있다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;1) 객체 생성 방식의 차이&lt;/b&gt;&lt;/span&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;Java vs Kotlin 객체 생성 차이점&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 100px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;기능&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Java&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;Kotlin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;기본 객체 생성&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;new 키워드 필요&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;new 없이 생성 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;생성자 선언&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;class 내에 constructor 선언&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;class 선언부에서 바로 정의 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;getter/setter&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;명시적으로 작성해야 함&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;자동 생성됨 (val = final)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;싱글톤 객체&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;static 키워드 필요&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;object 키워드로 간결하게 구현 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Java 예시&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737350232737&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }
}

Person person = new Person(&quot;홍길동&quot;, 25);
System.out.println(person.getName());&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Kotlin 예시&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737350250535&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(val name: String, val age: Int)

val person = Person(&quot;홍길동&quot;, 25)
println(person.name)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&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;new 키워드를 생략할 수 있다.&lt;/li&gt;
&lt;li&gt;val을 사용하면 자동으로 getter가 제공된다. (var의 경우 getter + setter이 자동 생성 된다.)&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;2) lateinit &amp;amp; by lazy를 활용한 객체 초기화&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 필드 초기화를 위해서 null을 사용해야 하는 경우가 많지만, 코틀린에서는 lateinit과 by lazy를 사용하여 객체의 생명주기를 효율적으로 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;lateinit 사용 (Spring DI에서 필드 주입 시 사용)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lateinit은 생성자에서 초기화 할 수 없는 var 속성&lt;/li&gt;
&lt;li&gt;의존성 주입에서 필드 주입시 사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737350501868&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Class UserService {
	lateinit var repository: UserRepository
    
    fun initialize(repo: UserRepository) {
    	repositoty = repo
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;by lazy 사용 (비용이 큰 객체의 초기화 지연) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체가 처음 사용될 때만 초기화 된다.&lt;/li&gt;
&lt;li&gt;초기화 비용이 큰 객체 (데이터베이스 연결, 설정 파일 로드 등)에 유용하다.&lt;/li&gt;
&lt;li&gt;자바에서는 보통 싱글톤 패턴을 사용해야 하지만 코틀린에서는 lateinit과 by lazy를 활용하여 더 간결하게 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737350621946&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ConfigLoader {
	val config: Configuration by lazy {
    	configuration()
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;3) 싱글톤 객체(object)를 통한 코드 단순화 &lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 싱글톤을 구현하려면 static 필드를 활용해야 하지만 코틀린에서는 object 키워드만으로 간단하게 싱글톤 객체를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Java 스타일 싱글톤 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737350877658&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Database {
    private static final Database INSTANCE = new Database();

    private Database() {}

    public static Database getInstance() {
        return INSTANCE;
    }

    public void connect() {
        System.out.println(&quot;Connected to Database&quot;);
    }
}

Database.getInstance().connect();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; Kotlin 스타일 싱글톤 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서는 object 키워드 하나만으로 간단하게 싱글톤 객체를 구현할 수 있다.&lt;/li&gt;
&lt;li&gt;Spring의 @Component, @Service를 사용하는 Bean 관리에도 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737350936990&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object Database {
    fun connect() {
        println(&quot;Connected to Database&quot;)
    }
}
Database.connect()&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;4) Spring Boot에서 DI 방식 차이&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 의존성 주입(DI)을 활용하여 객체를 관리하는데 코틀린에서는 생성자 주입을 기본적으로 사용하고 자바처럼 필드주입을 주로 사용하는 것은 권장되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Java 스타일 DI (생성자 주입)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737351900875&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
	private final UserRepository userRepository
    
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Kotlin 스타일 DI (생성자 주입) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서는 constructor를 생략할 수 있으며 @Autowired 없이도 의존성 주입이 가능하다.&lt;/li&gt;
&lt;li&gt;또한 val을 사용하면 getter가 자동 생성되므로 간결하게 코드를 작성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737351998594&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
class UserService(private val userRepository: UserRepository) {
	 fun getUser(id: Long) = userRepository.findById(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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;&lt;b&gt;5)&lt;span&gt; 코틀린에서 @Autowired 없이 DI 가능&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서는 @Autowired 어노테이션을 필수적으로 사용해야 하지만, 코틀린에서는 생성자 주입을 사용할 경우 스프링이 자동으로 의존성을 주입해준다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Java 스타일 필드 주입&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737352126508&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class UserService {
	@Autowired
    private UserRepository userRepository;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Kotlin 스타일&lt;span&gt; &lt;/span&gt;생성자 주입&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737352163551&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
class UserService(private val userRepository: UserRepository)&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>by lazy</category>
      <category>lateinit</category>
      <category>코틀린 의존성주입</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/78</guid>
      <comments>https://oceansea.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 20 Jan 2025 15:12:25 +0900</pubDate>
    </item>
    <item>
      <title>[Java to Kotlin] 코틀린 : 컬렉션(List, Set, Map)</title>
      <link>https://oceansea.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 List, Set, Map의 컬렉션 프레임 워크를 제공한다. 또한 고차 함수(map, filter, reduce)를 활용한 함수형 프로그래밍을 지원하여 코드를 간결하고 효율적으로 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린의 컬렉션은 크게 불변(Immutable)과 가변(Mutable)로 나눌 수 있는데, 불변 컬렉션은 추가/삭제/수정이 불가능하고, 가변 컬렉션은 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 94px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 15.9302%;&quot;&gt;컬렉션 유형&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 40%;&quot;&gt;불변 컬렉션&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 43.9535%;&quot;&gt;가변 컬렉션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 15.9302%;&quot;&gt;리스트 (List)&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 40%;&quot;&gt;listOf()&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 43.9535%;&quot;&gt;mutableListOf()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 15.9302%;&quot;&gt;세트 (Set)&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 40%;&quot;&gt;setOf()&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 43.9535%;&quot;&gt;mutableSetOf()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 15.9302%;&quot;&gt;맵 (Map)&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 40%;&quot;&gt;mapOf()&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 43.9535%;&quot;&gt;mutableMapOf()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 리스트 (List)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리스트는 요소를 순서대로 저장하는 컬렉션이고, 중복을 허용한다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 불변 리스트 (listOf)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;listOf()로 생성된 리스트는 불변(Immutable) 이므로, 값을 추가하거나 삭제할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344077669&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val names = listOf(&quot;홍길동&quot;, &quot;박문식&quot;, &quot;이몽룡&quot;)
println(names[0]) //홍길동&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 가변 리스트(mutableListOf)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mutableListOf()를 사용하면 요소를 추가, 삭제, 변경할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344131248&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = mutableListOf(1, 2, 3)
numbers.add(4)
numbers.remove(1)
println(numbers) //[2, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;c. 리스트의 반복 (forEach, for-in)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;forEach&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344194076&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = listOf(&quot;Kotlin&quot;, &quot;Java&quot;, &quot;Python&quot;)
list.forEach { println(it) }
list.forEach { aa -&amp;gt; println(aa) }&lt;/code&gt;&lt;/pre&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;for-in&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344273839&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = listOf(&quot;Kotlin&quot;, &quot;Java&quot;, &quot;Python&quot;)

for (item in list) {
	println(item)
}&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 세트 (Set)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Set는 중복을 허용하지 않는 컬렉션이다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 불변 세트 (setOf)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setOf()를 사용하면 중복 요소가 자동으로 제거된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344345732&quot; class=&quot;pgsql&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val uniqueNum = setOf(1, 2, 3, 3, 2)
println(uniqueNum) //[1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 가변 세트(mutableSetOf)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mutableSetOf()를 사용하면 요소를 추가, 삭제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344419281&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val mutableSet = mutableSetOf(&quot;Apple&quot;, &quot;Banana&quot;)
mutableSet.add(&quot;Cherry&quot;)
mutableSet.remove(&quot;Banana&quot;)
println(mutableSet) //[Apple, Cherry]&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 맵 (Map)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;맵은 키와 값 쌍으로 이루어진 컬렉션이다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 불변 맵 (mapOf)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mapOf()를 사용하면 키-값 쌍을 불변 컬렉션으로 저장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344483040&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val userMap = mapOf(&quot;name&quot; to &quot;홍길동&quot;, &quot;age&quot; to 25)
println(userMap[&quot;name&quot;]) //홍길동&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 가변 맵 (mutableMapOf)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mutableMapOf()를 사용하면 값을 추가, 수정, 삭제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344509550&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val userInfo = mutableMapOf(&quot;name&quot; to &quot;Bob&quot;, &quot;age&quot; to 30)
userInfo[&quot;age&quot;] = 31  // 값 변경
userInfo[&quot;city&quot;] = &quot;Seoul&quot; // 새로운 키 추가
println(userInfo) // 출력: {name=Bob, age=31, city=Seoul}&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4) 컬렉션 프레임워크의 메서드 (변환, 필터링, 누적)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린에서 함수를 정의하는 기본 구조는 아래와 같다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 컬렉션 변환 함수 (map, flatMap) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;map을 사용하면 각 요소를 변환하여 새로운 리스트를 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344589270&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map {it * 2}
println(doubled)&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;flatMap을 사용하면 중첩 리스트를 단일 리스트로 변환할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344666258&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val list = listOf(listOf(1, 2), listOf(3, 4))
val flatList = list.flatMap {it}
println(flatList) //[1, 2, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 컬렉션 필터링 (filter, find) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;filter를 사용하면 조건을 만족하는 요소만 추출할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344754310&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) //[2, 4]&lt;/code&gt;&lt;/pre&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;find는 첫번째로 조건을 만족하는 요소만 추출할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344783455&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val names = listOf(&quot;Alice&quot;, &quot;Bob&quot;, &quot;Charlie&quot;)
val nameWithB = names.find { it.startsWith(&quot;B&quot;) }
println(nameWithB) // 출력: Bob&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;c. 컬렉션 요소 누적 처리 (reduce, fold) &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;reduce를 활용하면 누적을 합산하는데, 초기값 없이 첫 번째 요소부터 누적 연산을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344849089&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val sum = numbers.reduce { acc, num -&amp;gt; acc + num }
println(sum) //15&lt;/code&gt;&lt;/pre&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;fold의 경우는 reduce와는 다르게 초기값을 지정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737344873119&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numbers = listOf(1, 2, 3, 4, 5)
val sumWithInitial = numbers.fold(10) { acc, num -&amp;gt; acc + num }
println(sumWithInitial) // 출력: 25&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;</description>
      <category>JAVA</category>
      <category>Kotlin Collection</category>
      <category>List</category>
      <category>map</category>
      <category>mutableListOf</category>
      <category>mutablemapof</category>
      <category>mutablesetof</category>
      <category>set</category>
      <category>코틀린 컬렉션</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/77</guid>
      <comments>https://oceansea.tistory.com/77#entry77comment</comments>
      <pubDate>Mon, 20 Jan 2025 12:48:27 +0900</pubDate>
    </item>
    <item>
      <title>[Java to Kotlin] 코틀린 : 함수(function)</title>
      <link>https://oceansea.tistory.com/76</link>
      <description>&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 함수 선언과 호출&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코틀린에서 함수를 정의하는 기본 구조는 아래와 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;fun 함수이름(매개변수: 타입): 반환타입 { &lt;br /&gt;&amp;nbsp; &amp;nbsp; // 함수 본문 &lt;br /&gt;&amp;nbsp; &amp;nbsp; return 결과값 &lt;br /&gt;}&lt;/blockquote&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 기본 함수 선언&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737084938179&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun add(a: Int, b: Int): Int {
    return a + b
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 함수 호출&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737084945986&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = add(3, 5)
println(result) // 출력: 8&lt;/code&gt;&lt;/pre&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;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;2) 단일 표현식 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 함수의 본문이 단 한 줄만 있을 경우 중괄호 { } 없이 = 을 사용하여 더 코드를 간결하게 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1737085026464&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun multiply(a: Int, b: Int) = a * b

println(multiply(4, 5))&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;3) 반환 타입이 없는 함수 (Unit)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서 반환값이 없는 함수는 Unit 타입을 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서의 void와 비슷하지만 차이점은, Unit은 실체 객체라는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt; &lt;b&gt;a. 기본 Unit 반환 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737085234021&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printHello(): Unit {
    println(&quot;Hello, Kotlin!&quot;)
}&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;그러나 Unit은 생략할 수 있다. (일반적으로 생략하는 것이 권장된다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737085266832&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printHello() {
	println(&quot;Hello, Kotlin!&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. return 없이 종료되는 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Unit을 반환하는 함수는 return을 생략해도 자동으로 return Unit이 적용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737085328134&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printMessage(message: String) {
    println(&quot;Message: $message&quot;)
}

printMessage(&quot;Hello&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;4) 기본 값이 있는 매개변수&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt; &lt;b&gt;a. 기본값이 있는 매개변수 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737085486847&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun greet(name: String = &quot;Guest&quot;) {
	println(&quot;Hello, $name&quot;)
}

greet()         // 출력: Hello, Guest
greet(&quot;홍길동&quot;)  // 출력: Hello, 홍길동&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;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737085526272&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun orderCoffee(type: String = &quot;Americano&quot;, size: String = &quot;Medium&quot;) {
    println(&quot;Order: $size $type&quot;)
}

orderCoffee()                 // 출력: Order: Medium Americano
orderCoffee(&quot;Latte&quot;)          // 출력: Order: Medium Latte
orderCoffee(&quot;Espresso&quot;, &quot;Small&quot;)  // 출력: Order: Small Espresso&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 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 명명된 인자&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값을 사용하는 함수에서 순서에 상관없이 특정 매개변수만 전달하고 싶을 때 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737085574017&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;orderCoffee(size = &quot;Large&quot;) // 출력: Order: Large Americano&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;5) 가변 인자 (vararg)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 vararg 키워드를 사용해서 매개변수의 개수를 동적으로 지정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt; &lt;b&gt;a. vararg를 사용한 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737085754900&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun sum(vararg numbers: Int) : Int {
	return numbers.sum()
}

println(sum(1, 2, 3, 4, 5)) // 출력: 15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. vararg와 일반 매개변수 함께 사용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737085783772&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun printNumbers(prefis: String, vararg numbers: Int) {
    println(&quot;$prefix: ${numbers.joinToString()}&quot;)
}

printNumbers(&quot;Numbers&quot;, 1, 2, 3, 4, 5)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;c. 배열을 vararg에 전달하기 (spread operator * )&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열을 vararg에 전달하려면 * 을 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737085813823&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val numArray = intArrayOf(10, 20, 30)
println(sum(*numArray))&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6) 고차 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고차 함수는 다른 함수를 매개변수로 받거나 함수를 반환하는 함수이다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 기능을 활용하면 함수형 프로그래밍이 가능해진다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; 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;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 고차 함수의 기본 표현&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737336059030&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun operate(a: Int, b: Int, operation: (Int, Int) -&amp;gt; Int): Int {
	return operation(a, b)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1737336464576&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//고차 함수 호출
val result1 = operate(10, 5) { x, y -&amp;gt; x + y }
val result2 = operate(10, 5) { x, y -&amp;gt; x * y }

println(result1) // 출력: 15
println(result2) // 출력: 50&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 함수를 반환하는 고차 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737336615298&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun getOperation(type: String): (Int, Int) -&amp;gt; Int {
	return when(type) {
    	&quot;add&quot; -&amp;gt; { a, b -&amp;gt; a + b }
        &quot;multiply&quot; -&amp;gt; { a, b -&amp;gt; a * b }
        else -&amp;gt; { _, _ -&amp;gt; 0 }
    }
}

val addFunction = getOperation(&quot;add&quot;)
println(addFunction(10, 5)) //15

val multiplyFunction = getOperation(&quot;multiply&quot;)
println(addFunction(10, 5)) //50&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7) 람다 표현식 (Lambda)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;람다 표현식은 익명 함수를 간결하게 표현하는 방법으로, 코틀린에서는 함수를 변수에 저장하거나, 고차 함수의 인자로 전달할 때 람다 표현식을 자주 사용한다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;a. 람다 표현식의 문법&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;val 함수이름: (매개변수 타입) -&amp;gt; 반환 타입 = { 매개변수 -&amp;gt; 실행코드 }&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737336729682&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val square: (Int) -&amp;gt; Int = {num -&amp;gt; num * num}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;b. 여러 줄 람다 표현식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;람다 표현식이 여러 줄이면 {} 안에서 여러 줄을 작성할 수 있다.&lt;/li&gt;
&lt;li&gt;it 키워드는 람다의 단일 매개변수를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737336792451&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val printMessage: (String) -&amp;gt; Unit = {
	println(&quot;메시지 출력:&quot;)
    println(it)
}&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;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;8) 확장 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확장함수란 기존 클래스에 새로운 함수를 추가하는 것으로, 기존의 클래스 소스 코드에 접근하지 않고 새로운 기능을 추가할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;br /&gt;a. 기본적인 확장 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737337105659&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun String.addPrefix(prefix: String): String {
	return &quot;$prefix$this&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1737337169642&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val result = &quot;Kotlin&quot;.addPrefix(&quot;Hello &quot;)
println(result) //Hello Kotlin&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;br /&gt;b. 확장 함수의 활용&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 함수를 사용하면 객체의 기능을 확장하면서도 기존 코드의 변경 없이 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737337232321&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun Int.isEven(): Boolean {
	return this%2 == 0
}

println(10.isEven()) //true&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;br /&gt;c. 확장 함수와 클래스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장 함수를 사용하면 클래스 내부 코드를 변경하지 않고도 기능을 추가할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737337294002&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person(val name: String, val age: Int)

fun Person.introduce() {
	println(&quot;name: $name, age: %age&quot;)
}

val person = Person(&quot;홍길동&quot;, 25)
person.introduce()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;9) 내부 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 함수 내부에 또 다른 함수를 정의할 수 있다. 내부 함수는 해당 함수 안에서만 사용가능하고, 캡슐화를 강화하는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;a. 내부 함수의 기본 구조&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부 함수는 외부함수 내부에서만 사용 가능하므로 불필요한 외부 접근을 막을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737337475469&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun outerFun(x: Int, y: Int): Int {
	fun innerFun(a: Int, b: Int): Int {
    	return a + b
    }
    return innerFun(x, y)
}

println(outerFun(3, 5)) //출력: 8&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;b. 내부 함수의 활용&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 중복되는 할인 로직 적용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737338691858&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun calculateTotalPrice(prices: List&amp;lt;Int&amp;gt;, discount: Int): Int {
    fun applyDiscount(price: Int): Int {
        return price - (price * discount / 100)
    }

    return prices.sumOf { applyDiscount(it) }
}

val prices = listOf(100, 200, 300)
println(calculateTotalPrice(prices, 10)) // 출력: 540&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;10) 인라인 함수 (inline)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;고차 함수는 일반 함수보다 실행 속도가 느리기 때문에 inline 키워드를 사용하면 함수 호출을 줄여 성능을 최적화 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1737342501059&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;inline fun executeOperation(a: Int, b: Int, operation: (Int, Int) -&amp;gt; Int): Int {
    return operation(a, b)
}

val result = executeOperation(10, 5) { x, y -&amp;gt; x * y }
println(result) // 출력: 50&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>kotlin</category>
      <category>함수</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/76</guid>
      <comments>https://oceansea.tistory.com/76#entry76comment</comments>
      <pubDate>Mon, 20 Jan 2025 12:11:25 +0900</pubDate>
    </item>
    <item>
      <title>[Java to Kotlin] 코틀린 : 클래스와 객체</title>
      <link>https://oceansea.tistory.com/75</link>
      <description>&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 클래스 선언 및 객체 생성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 자바와 마찬가지로 class 키워드를 사용해서 클래스를 선언할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기본 클래스 선언&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737080522932&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
	var name: String = &quot;홍길동&quot;
    var age: Int = 25
}&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;u&gt;&lt;b&gt;   new 키워드를 사용하지 않고 Person() 형태로 객체를 생성한다.&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1737080567197&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val person1 = Person()
person1.name = &quot;박문식&quot;
person1.age = 30&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;생성자&lt;/b&gt;&lt;/span&gt;&lt;/p&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;코틀린에서는 주 생성자와 보조 생성자(constructor)를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;주 생성자&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주 생성자는 클래스 헤더에서 선언되고, 자동으로 속성을 초기화할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt; val 또는 var을 생성자 매개변수에 추가하면 자동으로 멤버 변수로 선언된다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737080692085&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Student(val name: String, var age: Int)

val student1 = Student(&quot;홍길동&quot;, 20)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;&lt;b&gt;보조 생성자 (constructor 키워드)&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보조 생성자는 constructor 키워드를 사용하여 추가로 정의할 수 있으며 주 생성자 없이도 클래스를 생성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737080871798&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Car {
	var brand: String
    var year: Int
    
    constructor(brand: String) {
    	this.brand = brand
        this.year = 2024
    }
    
    constructor(brand: String, year: Int) {
    	this.brand = brand
        this.year = year
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1737080889648&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val car1 = Car(&quot;Hyundai&quot;)

val car2 = Car(&quot;Kia&quot;, 2020)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 클래스 멤버 (속성과 함수)&lt;/b&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 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;속성 (Properties)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt; &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;프로퍼티는 필드(Field)와 접근자 메서드(getter, setter)를 하나로 합친 것을 의미한다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;val은 읽기 전용 (getter만 생성)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;var은 읽기/쓰기 가능 (getter + setter 생성)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;커스텀 getter와 setter&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서는 속성에 대한 커스텀 getter와 setter을 정의할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737081196337&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Student {
	var score: Int = 0
    	set(value) {
        	field = if (value &amp;gt;= 0) value else 0
        }
    var isPassed: Boolean
    	get() = score &amp;gt;= 50
}

val student = Student()
student.score = -10
println(student.score) // 0

student.score = 70
println(student.isPassed) // true&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&lt;span&gt; 데이터 클래스 (data class)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서는 data class를 사용하면 기본적인 메서드 equals(), hashCode(), toString(), copy()를 자동으로 생성해준다.&lt;br /&gt;자바에서는 위와 같은 기본 메서드를 직접 구현해야 하지만, 코틀린에서는 따로 구현하지 않고 데이터 클래스를 이용하면 되므로 간편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기본 데이터 클래스 vs 데이터 클래스&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1737081435288&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 일반 클래스
class Person(val name: String, val age: Int)
val person1 = Person(&quot;홍길동&quot;, 30)
println(person1) 
// 출력: Person@6d06d69c&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;pre id=&quot;code_1737081461500&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data class Person(val name: String, val age: Int)

val person1 = Person(&quot;홍길동&quot;, 30)
println(person1) 
// 출력: Person(name=홍길동, age=30)&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;이렇게 데이터 클래스를 이용하면 자동으로 toString()이 적용되는 것을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;데이터 클래스는 기본적으로 equals()와 hashCode()를 자동으로 생성하여, 값이 동일하면 true를 반환한다.&lt;br /&gt;(일반 클래스는 메모리 주소를 비교하기 때문에 false가 나옴)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;copy() 함수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&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;데이터 클래스는 copy() 함수를 제공하여, 기존 객체를 기반으로 새로운 객체를 쉽게 생성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737081614097&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val person1 = Person(&quot;홍길동&quot;, 30)
val person2 = person1.copy(name = &quot;박문식&quot;) // 일부 값만 변경 가능

println(person1) // Person(name=홍길동, age=30)
println(person2) // Person(name=박문식, age=30)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt; data class의 제한 사항&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data class는 특정한 조건을 만족해야 한다.&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;abstract, open, sealed, inner 키워드를 사용할 수 없다.&lt;/li&gt;
&lt;li&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;싱글톤 객체 (object)&lt;/span&gt;&lt;/b&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;코틀린에서는 싱글톤 객체(singleton object) 를 쉽게 만들 수 있도록 object 키워드를 제공한다.&lt;br /&gt;자바에서는 Singleton 패턴을 직접 구현해야 하지만, 코틀린에서는 object 키워드만 사용하면 자동으로 싱글톤이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기본 싱글톤&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;싱글톤 객체는 object 키워드를 사용하여 선언한다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc; color: #000000;&quot;&gt;자바에서 static과 비슷하지만 더 간결하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737083274101&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object Database {
    val name = &quot;MainDB&quot;

    fun connect() {
        println(&quot;Connected to $name&quot;)
    }
}

Database.connect()&lt;/code&gt;&lt;/pre&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;클래스 내부 싱글톤 (companion object)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;companion object는 클래스 내부에서 static 멤버처럼 동작하는 싱글톤 객체이다.&lt;/li&gt;
&lt;li&gt;companion object를 사용하면 클래스 내부에서 정적 멤버처럼 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737083431378&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(val name: String) {
	companion object {
    	fun createGuest(): User {
        	return User(&quot;Guest&quot;)
        }
    }
}​&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상속 (open, override)&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에서의 클래스는 자바와 다르게 기본적으로 final이므로 상속을 허용하려면 open 키워드를 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기본 상속 (open)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서 클래스를 상속하려면, 부모 클래스에 open 키워드를 추가해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737083760600&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class Animal {
	fun eat() {
        println(&quot;먹고 있어요&quot;)
    }
}

class Dog : Animal() {
    fun bark() {
        println(&quot;멍멍!&quot;)
    }
}

val dog = Dog()
dog.eat()  // 부모 클래스의 함수 호출
dog.bark() // 자식 클래스의 함수 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;메서드 오버라이딩&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 클래스에서 부모 클래스의 메서드를 재정의(Overriding) 하려면 override 키워드를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;메서드도 기본적으로 final이므로, 오버라이딩 가능하게 하려면 open을 추가해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737083825478&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class Animal {
    open fun makeSound() {
        println(&quot;동물이 소리를 냅니다&quot;)
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println(&quot;멍멍!&quot;)
    }
}

val dog = Dog()
dog.makeSound()  // 출력: 멍멍!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;속성 오버라이딩&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;속성도 메서드 처럼 오버라이딩이 가능하며, override 키워드를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;open이 없는 속성은 오버라이딩이 불가능하므로 부모 클래스의 속성도 open 키워드를 추가해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737083923346&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class Animal {
	open val type: String = &quot;동물&quot;
}

class Dog : Animal() {
	override val type: String = &quot;강아지&quot;
}

val dog = Dog()
println(dog.type)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;부모 클래스의 메서드 호출 (super)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 클래스에서 부모 클래스의 메서드를 호출하려면 super 키워드를 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737083981693&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class Animal {
    open fun makeSound() {
        println(&quot;동물이 소리를 냅니다&quot;)
    }
}

class Dog : Animal() {
    override fun makeSound() {
        super.makeSound()  // 부모 클래스 메서드 호출
        println(&quot;멍멍!&quot;)   // 추가 기능
    }
}

val dog = Dog()
dog.makeSound()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;추상 클래스 (abstract)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상 클래스(abstract class)는 인스턴스를 생성할 수 없으며, 자식 클래스에서 반드시 오버라이딩 해야하는 함수를 포함할 수 있다.&lt;/li&gt;
&lt;li&gt;추상 클래스는 abstract class로 선언&lt;/li&gt;
&lt;li&gt;추상 함수는 abstract fun으로 선언&lt;/li&gt;
&lt;li&gt;추상 함수는 구현하지 않고 오버라이딩을 필수로 요구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737084153064&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;abstract class Animal {
	abstract fun makeSound() //반드시 오버라이딩 해야 함
    
    fun eat() {
    	printnln(&quot;먹고 있어요&quot;)
    }
}

class Dog : Animal() {
	override fun makeSound() {
    	println(&quot;멍멍&quot;)
    }
}

val dog = Dog()
dog.makeSound()
dog.eat()&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;인터페이스(interface)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코틀린에서는 자바와 마찬가지로 다중 상속을 지원하지 않지만, 인터페이스를 사용하면 가능해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1737084261227&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Animal {
    fun makeSound() // 구현 없이 선언만 가능
}

interface Pet {
    fun play() {
        println(&quot;놀고 있어요&quot;)
    }
}

class Dog : Animal, Pet {
    override fun makeSound() {
        println(&quot;멍멍!&quot;)
    }
}

val dog = Dog()
dog.makeSound() // 멍멍!
dog.play()      // 놀고 있어요&lt;/code&gt;&lt;/pre&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;인터페이스는 interface 키워드를 사용하며, class Dog : Animal, Pet 처럼 여러개를 다중으로 구현할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <category>객체</category>
      <category>상속</category>
      <category>코틀린</category>
      <category>클래스</category>
      <author>강방당</author>
      <guid isPermaLink="true">https://oceansea.tistory.com/75</guid>
      <comments>https://oceansea.tistory.com/75#entry75comment</comments>
      <pubDate>Fri, 17 Jan 2025 12:25:11 +0900</pubDate>
    </item>
  </channel>
</rss>