<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>shield-man.log</title>
        <link>https://velog.io/</link>
        <description>개발자 방패맨의 기술블로그</description>
        <lastBuildDate>Fri, 25 Jul 2025 08:06:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>shield-man.log</title>
            <url>https://velog.velcdn.com/images/shield-man/profile/bd6d0507-c93f-4bc8-bdc9-949ad7715f49/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. shield-man.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/shield-man" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[DevOps] sonarQube 설치부터 Jenkins 연동까지]]></title>
            <link>https://velog.io/@shield-man/sonarQube-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-Jenkins-%EC%97%B0%EB%8F%99%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@shield-man/sonarQube-%EC%84%A4%EC%B9%98%EB%B6%80%ED%84%B0-Jenkins-%EC%97%B0%EB%8F%99%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 25 Jul 2025 08:06:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/shield-man/post/0c932c50-5bfb-48a3-973c-9f16f2ec69e4/image.png" alt="">
<img src="https://velog.velcdn.com/images/shield-man/post/41efc64b-42ca-462b-af2d-07f6791bd367/image.png" alt="">
<img src="https://velog.velcdn.com/images/shield-man/post/868d9723-fb06-4498-82bc-1c66483d70f7/image.png" alt="">
<img src="https://velog.velcdn.com/images/shield-man/post/49c2722c-0543-4f44-8d4f-b5e0b641747f/image.png" alt="">
<img src="https://velog.velcdn.com/images/shield-man/post/5488ee96-7282-4730-b80e-f8adfc2ddb16/image.png" alt="">
<img src="https://velog.velcdn.com/images/shield-man/post/93d72cbd-2552-4877-a21a-13d9450b2f28/image.png" alt="">
<img src="https://velog.velcdn.com/images/shield-man/post/6e2d2760-2814-4516-9c9e-5f7ebb5ed4d2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번외] 이런 표준 패키지도 있어? ]]></title>
            <link>https://velog.io/@shield-man/%EB%B2%88%EC%99%B8-%EC%9D%B4%EB%9F%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%8F%84-%EC%9E%88%EC%96%B4</link>
            <guid>https://velog.io/@shield-man/%EB%B2%88%EC%99%B8-%EC%9D%B4%EB%9F%B0-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%8F%84-%EC%9E%88%EC%96%B4</guid>
            <pubDate>Wed, 30 Apr 2025 08:42:33 GMT</pubDate>
            <description><![CDATA[<p>사건발단 - 2025/04/30 평화로운 점심시간</p>
<p>문득 머리에 스친 한가지 생각.</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/2bef2303-f31e-4796-851d-fe7efa1ed491/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>java 17 표준 패키지중 대중의 사용빈도가 가장 패키지가뭐야? </p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/96836a4e-0ab0-46ac-a622-cb277976b521/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/01406c06-dde0-46ef-8cb9-af1a7d2b6dae/image.png" alt=""></p>
<h2 id="이럴수가">이럴수가!!!</h2>
<p><img src="https://velog.velcdn.com/images/shield-man/post/fa8e2b87-8ec6-43f5-9233-14bfe451d42a/image.png" alt=""></p>
<h4 id="너무나도-재밌어보이는-클래스들">너무나도 재밌어보이는 클래스들!</h4>
<p><del>구경하느라 시간이 다 지나갔다...</del></p>
<h3 id="그렇다면-코딩으로-음악을-만들수는-없을까">그렇다면 코딩으로 음악을 만들수는 없을까?</h3>
<pre><code class="language-java">import javax.sound.midi.*;

public class MidiTest {
    public static void main(String[] args) throws Exception {
        Synthesizer synth = MidiSystem.getSynthesizer();
        synth.open();
        MidiChannel[] channels = synth.getChannels();

        int channel = 0;  // 채널 0번 사용
        int velocity = 100;  // 세기

        // 도레미 (C4, D4, E4) 음계
        int[] notes = {60, 62, 64};  // MIDI note number

        for (int note : notes) {
            channels[channel].noteOn(note, velocity);
            Thread.sleep(500);  // 반 박자 정도 유지
            channels[channel].noteOff(note);
        }

        synth.close();
    }
}</code></pre>
<p>언젠가 코딩음악 해보겠습니다.</p>
<h2 id="번외">번외</h2>
<p>그밖의 외면받은 패키지들…</p>
<table>
<thead>
<tr>
<th>패키지</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>javax.annotation.processing.*</code></td>
<td>컴파일러용 주석 처리기 – 일반 개발자와는 무관</td>
</tr>
<tr>
<td><code>java.util.zip.*</code></td>
<td>대체로 적게 쓰이지만 특정 경우 압축 처리에 사용</td>
</tr>
<tr>
<td><code>java.lang.ref.*</code></td>
<td>직접적으로 다룰 일이 거의 없는 참조 제어 클래스들 (Weak/Phantom 등)</td>
</tr>
<tr>
<td><strong><code>javax.script</code></strong></td>
<td>Java 안에서 스크립트 언어 실행</td>
</tr>
<tr>
<td><strong><code>javax.smartcardio</code></strong></td>
<td>스마트 카드 (IC 카드) 인터페이스</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[보안]로그모니터링 대시보드 만들기 ]]></title>
            <link>https://velog.io/@shield-man/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@shield-man/%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 17 Mar 2025 09:34:32 GMT</pubDate>
            <description><![CDATA[<p>최근 솔루션이 공격을 받았다. 
여러번의 중국발 IP로 공격시도가 확인되었으며 그 과정에서 로그를 많이 찾아보게되었다.
웹 서버 로그를 보다가 필요한 정보를 파싱해서 보고싶은 마음이 생겨 파이썬으로 데이터 가공 후 취합하는 과정을 겪다가 외부교육을 신청하여 수강 후 파케이 파일 변환 및 대시보드를 만들게되었다. </p>
<blockquote>
</blockquote>
<p>210.xx.xx.xx /- - /[16/Dec/2024:18:45:27 +0900] /&quot;GET/ /shield/shield_list.php /HTTP/1.1&quot; /200 /47464 &quot;<a href="https://shield.market.com/shield/shield_list.php&quot;/">https://shield.market.com/shield/shield_list.php&quot;/</a> &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0&quot;</p>
<p>위와 같은 로그정보가 있으면 각각 다음과 같은 정보로 분류 할수 있다.
(물론 로그포맷은 Combined) </p>
<pre><code>1. Client IP
2. Identity , username
3. Timestamp
4. HTTP Method
5. Request URL
6. HTTP Version
7. HTTP Status Code 
8. Response Size 
9. Referrer 
10. User-Agent 
11. logFormat</code></pre><p>생각보다 유용한 정보가 많기에 원하는 데이터를 파싱해서 대시보드를 만들거나 솔루션에 도움이 되는 부분을 DB에 저장해서 분석자료로 활용하기에 좋다고 생각하였다. </p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/bca49cbf-cf30-4621-97b2-8fd5f959111f/image.png" alt=""></p>
<p>가장먼저 파케이 파일 변환을 진행하게되는데,</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/6552a608-6e19-4569-960f-5531a396b62d/image.png" alt=""></p>
<p>보이는 것과 같이 용량이 어마어마하게 차이가난다. 
대용량 로그 파일 관리를 위해 파케이 파일저장은 필수 인 것 같다. </p>
<p>Pandas를 활용해서 data frame을 만들고 파케이파일변환을 했으면 재료준비는 끝. </p>
<p>이제는 양념준비를 해야한다. 
양념은 공격 탐지코드다. </p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/58536005-03dd-47d6-84de-bd88443f2970/image.png" alt=""></p>
<p>각각 공격탐지 패턴은 교육내용과 GPT의 도움을 받아 작성하였다. </p>
<p>이제 플레이팅을 구상해야한다. </p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/7c2473d7-2bdb-48b7-b95f-fdbdf685540a/image.png" alt=""></p>
<p>기본적인 차트 구성은 4개로 진행하고 왼쪽에는 서버를 목록화하여 고를 수 있게 구상하였다. </p>
<p>다음과 같은 과정을 겪어 DATA를 저장하거나 즉시 시각화(대시보드)를 구성하게되었다. 
최신 로그를 가져오기위해 방화벽 예외신청과 같은 자잘한 난관을 겪고 결과적으로 아래와 같은 대시보드를 만들었다. </p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/c30aedfc-8f18-4f54-936e-56d0546b2e43/image.png" alt=""></p>
<blockquote>
<p>결과적으로 6개의 차트, 서버목록을 구성했지만 중요정보가 있기에 일부 가렸습니다.</p>
</blockquote>
<p>결과물을 보니 파이썬의 다양한 라이브러리의 활용도는 무궁무진 한 것 같다. 
기본적으로 Streamlit을 활용해서 빠르게 웹사이트를 구상하고 
차트별 디테일을 챙겼다.</p>
<p>보안도 꾸준하게 관심을 갖고 다양한 것을 해봐야겠다. 
오늘의 요리 끝. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FE] API명세 의존성 낮추기 (OpenAPI Generator)]]></title>
            <link>https://velog.io/@shield-man/FE-API%EB%AA%85%EC%84%B8-%EC%9D%98%EC%A1%B4%EC%84%B1-%EB%82%AE%EC%B6%94%EA%B8%B0-OpenAPI-Generator</link>
            <guid>https://velog.io/@shield-man/FE-API%EB%AA%85%EC%84%B8-%EC%9D%98%EC%A1%B4%EC%84%B1-%EB%82%AE%EC%B6%94%EA%B8%B0-OpenAPI-Generator</guid>
            <pubDate>Fri, 30 Aug 2024 08:05:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/shield-man/post/ed70fa7b-df10-40aa-9345-a25961ee2ec6/image.png" alt=""></p>
<p>API에서 Swagger가 적용이 되었다면,
위와 같이 바뀐 명세 확인을 위해 매번 swagger-ui를 들어가서 확인하는 번거로움이 있었다. </p>
<p>해당 의존성을 털어내고자 code generator를 적용하게 되었다.( Loosely coupling 정말 좋아합니다 )</p>
<h2 id="적용기">적용기</h2>
<h4 id="1-일단-설치">1. 일단 설치</h4>
<pre><code class="language-cmd">npm install @openapitools/openapi-generator-cli -g</code></pre>
<blockquote>
<p>※node 18버전 이상 가능 
(nvm 이용해서 로컬에서 node 버전 여러개 보유 가능합니다.)</p>
</blockquote>
<blockquote>
<p>※Swagger Specification 사용시 </p>
</blockquote>
<pre><code>npm install swagger-codegen -g</code></pre><p>과 같은 다른 라이브러리 설치하여야합니다. </p>
<h4 id="2-packagejson-스크립트-설정">2. package.json 스크립트 설정</h4>
<pre><code class="language-javascript">  &quot;generate&quot;: &quot;openapi-generator-cli generate -g typescript-axios -i https://test-api.com/api-docs -o ./src/api/apidocs -t src/api/apidocs/mustaches -c ./openapi.json --skip-validate-spec&quot;,</code></pre>
<h4 id="3-openapijson-파일-생성">3. openapi.json 파일 생성</h4>
<pre><code class="language-json">{
  &quot;spaces&quot;: 2,
  &quot;modelPackage&quot;: &quot;src/model&quot;,
  &quot;apiPackage&quot;: &quot;src/api&quot;,
  &quot;supportsES6&quot;: true,
  &quot;withNodeImports&quot;: false,
  &quot;useSingleRequestParameter&quot;: true,
  &quot;enumNameSuffix&quot;: &quot;&quot;,
  &quot;withSeparateModelsAndApi&quot;: true
}</code></pre>
<h4 id="4-npm-run-generate">4. npm run generate</h4>
<pre><code>npm run generate</code></pre><p>그러면 제가 설정한 src/api/apidocs 아래 생성된 파일들을 확인 할 수 있습니다. 
아래는 생성된 예제 파일입니다. </p>
<h4 id="5-예제-파일-확인">5. 예제 파일 확인</h4>
<pre><code class="language-java">/* tslint:disable */
/* eslint-disable */
/**
 * TEST- API
 * TEST API 문서입니다.
 *
 * The version of the OpenAPI document: 1.0
 * 
 *
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */


// May contain unused imports in some cases
// @ts-ignore
import type { testResponse } from &#39;./test-response&#39;;

/**
 * 
 * @export
 * @interface testResponse
 */
export interface TestResponse {
    /**
     * 방패크기
     * @type {string}
     * @memberof TestResponse
     */
    &#39;shieldSize&#39;?: string;
    /**
     * 방패종류
     * @type {string}
     * @memberof TestResponse
     */
    &#39;shieldType&#39;?: string;
    /**
     * 방패생산일
     * @type {LocalDateTime}
     * @memberof TestResponse
     */
    &#39;date&#39;?: LocalDateTime;
    /**
     * 방패색깔
     * @type {string}
     * @memberof TestResponse
     */
    &#39;color&#39;?: string;...</code></pre>
<h2 id="결과">결과</h2>
<blockquote>
<ol>
<li>TypeScript를 쓰는 환경이라면 바로 Import해서 사용하면 되고 명세가 바뀔때마다 일일히
swagger-ui에 들어가 확인할 필요가없어집니다.</li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li>해당 변수가 없거나 타입이 맞지 않다면 IDE에서 에러를 표시하기에 고생이 줄었습니다. </li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[소소한 암호화키 DB 분리 ]]></title>
            <link>https://velog.io/@shield-man/%EC%95%94%ED%98%B8%ED%99%94%ED%82%A4-%EC%A0%80%EC%9E%A5-DB-%EB%B6%84%EB%A6%AC</link>
            <guid>https://velog.io/@shield-man/%EC%95%94%ED%98%B8%ED%99%94%ED%82%A4-%EC%A0%80%EC%9E%A5-DB-%EB%B6%84%EB%A6%AC</guid>
            <pubDate>Tue, 02 Apr 2024 06:54:02 GMT</pubDate>
            <description><![CDATA[<H3>1. 기존 환경 분석 </H3>
RDS 도입과 신규 솔루션2의 추가로 인해 문제가 발생하였다.
인프라 분리를 통해 솔루션1과 솔루션2는 서로 다른 서버에 있는 상태이다.
또한 암호화키를 저장, 갱신, 조회 하는 솔루션 (솔루션3) 은 각각 서버에 존재한다. 

<p>하지만 솔루션3의 접속정보는 변하지 않기에 솔루션 1,2 모두 같은 TABLE을 조회하고 수정하고 갱신한다. </p>
<p>아래는 솔루션과 테이블 조회 로직을 간단하게 나타낸 것이다.</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/9390f40e-d2a8-4890-9524-a0997360e91b/image.png" alt=""></p>
<H3>2. 문제 확인 </H3>
여기서 문제가 발생하였다. 
키 갱신의 경우 로그인시 발생하는데 솔루션1에서 키 갱신 후 솔루션1에 갱신한 정보를 업데이트하지만 솔루션2의 경우는 갱신하였는지 모른다. 

<p><img src="https://velog.velcdn.com/images/shield-man/post/69ff908f-2e06-4db6-9a87-95fe418b118b/image.png" alt=""></p>
<p>따라서 RSA테이블을 각각 두기 위해 기존 DB와 RSA테이블을 카피한 RDS를 추가 하였으며
암호화키를 조회,저장,갱신 하는 솔루션3의 소스코드를 하나의 소스로 가져가기 위하여 </p>
<blockquote>
<p>[sol1_properties] , [sol2_properties] </p>
</blockquote>
<p> 와 같이 properties를 통해 DB 접속 정보를 분리하기로 하였다. </p>
 <H3>3. 계획 수립 및 실행 </H3>

<p> 문제해결 계획은 다음과 같다.</p>
<pre><code> [1]
 1-1 기존 RDS를 카피뜬 새로운 RDS 생성 및 RSA 테이블 생성
 1-2 기존 RDS의 RSA테이블 데이터를 새로운 RDS의 RSA테이블로 데이터 마이그레이션.

 [2]
 2-1 sol1_properties는 기존 RDS 접속정보기입
 2-2 sol2_properties는 새로운 RDS 접속정보 기입 

 [3]
 3-1 소스관리의 용이성을 위해 원소스로 가져가는 솔루션3 (RSA키를 갱신,조회,저장 하는 솔루션)
 3-2 솔루션3를 서버1과 서버2에 clone
 3-3 서버1에서 솔루션3 구동시 솔루션1용 sol1_properties로 구동 
 3-4 서버2에서 솔루션3 구동시 솔루션2용 sol2_properties로 구동 
</code></pre><p> 하기와 같은 작업을 운영서버에서 진행하기에 확실한 계획과 사전작업이 수반되었으며
 모든 계획에 문제없이 진행되어 다음과 같이 &lt;DB 분리작업&gt; 은 솔루션 정지 이후 10분내로 완료되었다.</p>
 <H3> 완성된 환경</H3>


<p><img src="https://velog.velcdn.com/images/shield-man/post/2d78a188-4652-401a-9f84-cc24895ca00e/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Toy project] 인프라 설정 ver.0.1.0]]></title>
            <link>https://velog.io/@shield-man/toy-pj-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@shield-man/toy-pj-%EC%9D%B8%ED%94%84%EB%9D%BC-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Mon, 29 Jan 2024 06:43:17 GMT</pubDate>
            <description><![CDATA[<p>퇴근 후 실시간 통신을 하는 서비스를 만들고 싶어 <code>웹 메신저 서비스</code>를 제작하는 프로젝트를 
만들게되었다. 평소 <code>아파치</code>, <code>Oracle DB</code>, <code>Spring boot 2.x</code> , <code>vue 2.x</code> 등을 사용하기에 이번 프로젝트의 목적은 한번도 사용 해본 적 없는 툴, 프레임워크를 사용해서 진행하려고 한다.
3인 프로젝트며 나는 인프라 및 CI/CD 구축 담당으로 참여하게되었다.</p>
<p>(썸네일은 아주 대략적으로 그려본 아키텍쳐 구성도이다. 추후 충분히 바뀔 수 있다)</p>
<p>현재 진행은 </p>
<pre><code>EC2 생성 및 기본 설정 - S3 버킷 생성 및 EC2 연결 - nginx설치
-jdk,docker,mongoDB 설치 - mongoDB 연결확인</code></pre><p>까지 되었으며 아래는 구성과정이다.
딱히 막힌 부분이 없어서 간략하게 글과 그림을 넣도록 하겠다. </p>
<ol>
<li>AWS EC2 생성 및 설정 </li>
</ol>
<blockquote>
</blockquote>
<ol>
<li>AWS EC2 생성 (Amazone Linux 2023) </li>
<li>탄력적 IP부여</li>
<li>SSH 연결 테스트 </li>
<li>보안그룹 설정 </li>
<li>JDK 21설치 </li>
<li>임시 도메인 부여 : <a href="http://npy-project.duckdns.org">http://npy-project.duckdns.org</a></li>
</ol>
<p><img src="https://velog.velcdn.com/images/shield-man/post/8c36bc41-fdce-4295-a98f-3ea6590da506/image.png" alt=""> 보안 그룹 설정 </p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/f61588f0-fb39-4fe7-9654-4efc4e68644c/image.png" alt=""> SSH 연결 테스트 
<img src="https://velog.velcdn.com/images/shield-man/post/b28dc7d9-47fb-460e-bd7e-5b481c1774c5/image.png" alt=""> JDK 21 설치 완료
<img src="https://velog.velcdn.com/images/shield-man/post/4dadabc8-483a-4e3f-ac2e-798d11028ea9/image.png" alt=""></p>
<p>2.AWS S3 생성 및 설정 </p>
<blockquote>
<p>1.버킷 생성 
2.버킷 정책 설정 
3.IAM 정책 추가 
4.IAM 권한 추가 
5.EC2- Bucket 연결 </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/shield-man/post/68fd819b-db55-407f-983c-077a0a2e93b5/image.png" alt=""> 버킷정책</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/53ebbcc3-f4a5-416a-9648-e1e94ef2ceaf/image.png" alt=""> S3 policy</p>
<p>3.Nginx 설치 및 도커 설치</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/85ffc08f-c548-4a81-8ba5-ec95543af2ae/image.png" alt=""></p>
<p>4.mongodb 설치 및 연결확인</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/c6750942-c877-4504-ba69-9af30cfca94c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BE] SSE 적용과 난관 (feat.로드밸런싱)]]></title>
            <link>https://velog.io/@shield-man/Spring-SSE-%EC%A0%81%EC%9A%A9%EA%B3%BC-%EB%82%9C%EA%B4%80</link>
            <guid>https://velog.io/@shield-man/Spring-SSE-%EC%A0%81%EC%9A%A9%EA%B3%BC-%EB%82%9C%EA%B4%80</guid>
            <pubDate>Mon, 22 Jan 2024 02:07:02 GMT</pubDate>
            <description><![CDATA[<p>회사 핵심 솔루션의 추가 기능을 구현하던 중 대용량 파일을 업로드 하는 기능을 추가하게 되었다.
해당 기능의 프로세스는 크게 </p>
<pre><code>1.대용량 zip파일 업로드 ( 최대 300개의 PDF파일로 이루어진 ZIP파일 ,최대 3개)
2.Unzip &amp; PDF Split ( java.util.zip 사용하여 Unzip후 멀티 스레드를 활용하여 2개의 PDF 파일로 스플릿)
3.Split한 파일 저장 </code></pre><p>따라서 Unzip &amp; split 로직과 최대 1800개의 PDF 파일을 저장하는 로직이 한번에 이루어진다.</p>
<p>그러기에 파일업로드시 몆분 가량 로딩바를 바라만 보고있고 정상적으로 진행이 되는지 사용자 입장에서 알수 없을 것이다.</p>
<p>이에 대하여 <code>업로드 로딩바</code> 구현을 하기로 하였다.</p>
<h2>SSE 구현</h2>
실시간 업로드 현황을 보여주기 위해 당연하게 웹소켓 기술을 가장먼저 떠올렸다.
하지만 client는 일방적으로 정보를 받기만 하면 되기에 양방향 통신은 비효율적이라 생각되었다. 그러던 와중 SSE기술을 알게되었는데

<p><code>Server-Sent-Event (SSE)</code> 전체 흐름은 </p>
<pre><code>1. SSE Connect API 호출
2. Connect Complete 응답
3. 이벤트 API 호출
4. 이벤트 data 응답</code></pre><p>심플하다. Connect API와 FileUpload API를 분리하여 아래 예제를 보자면 </p>
<p>가장먼저 SSE 연결, 해제 , 에러처리 관련하여 Service를 구현하고 </p>
<p><code>SseServiceImpl</code></p>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class SseServiceImpl implements SseService {

  private final EmitterRepository emitterRepository;
  private final CodeDetailRepository codeDetailRepository;
  private final CodeDetailLanRepository codeDetailLanRepository;

  private static final Long DEFAULT_TIMEOUT = 5L * 1000 * 60;
  private static final Logger LOGGER = LoggerFactory.getLogger(SseServiceImpl.class);

  @Override
  public SseEmitter sseConnection(String eventId, String lastEventId) {
    SseEmitter sseEmitter = emitterRepository.save(eventId, new SseEmitter(DEFAULT_TIMEOUT));

    // SseEmitter의 완료, 시간 초과, 에러로 인한 전송 불가 시 sseEmitter 삭제
    handleException(sseEmitter, eventId);

    // 연결 직후, 데이터 전송이 없을 시 503 에러 발생. 에러 방지 위한 더미데이터 전송
    sendToClient(eventId, &quot;Successfully Connected ::: &quot; + eventId);

    // 클라이언트가 미수신한 Event 유실 예방, 연결이 끊켰거나 미수신된 데이터를 다 찾아서 보내준다.
    if (StringUtil.isNotEmpty(lastEventId)) {
      Map&lt;String, SseEmitter&gt; events = emitterRepository.findAllStartById(eventId);
      events.entrySet().stream()
          .filter(entry -&gt; lastEventId.compareTo(entry.getKey()) &lt; 0)
          .forEach(entry -&gt; sendToClient(entry.getKey(), entry.getValue()));
    }
    return sseEmitter;
  }

  @Override
  public SseEmitter sseConnection(String eventId,
      String lastEventId, long timeOutInMilliSeconds) {
    SseEmitter sseEmitter = emitterRepository.save(eventId, new SseEmitter(timeOutInMilliSeconds));

    // SseEmitter의 완료, 시간 초과, 에러로 인한 전송 불가 시 sseEmitter 삭제
    handleException(sseEmitter, eventId);

    // 연결 직후, 데이터 전송이 없을 시 503 에러 발생. 에러 방지 위한 더미데이터 전송
    sendToClient(eventId, &quot;Successfully Connected ::: &quot; + eventId);

    return sseEmitter;
  }

  ...//이밖에 에러처리, failResult 등 로직을 구현 
</code></pre>
<p>본격적으로 SSE Connect MVC패턴을 구축한다.</p>
<p><code>Connect controller</code></p>
<pre><code class="language-java">@CrossOrigin(origins = &quot;*&quot;, allowedHeaders = &quot;*&quot;)
@RestController
@RequestMapping(&quot;/sse&quot;)
@RequiredArgsConstructor
public class SseController {

  private final SseService sseService;
  private final CurrentUserData currentUserData;

  @GetMapping(value = &quot;/connect&quot;, produces = &quot;text/event-stream&quot;)
  @Operation(summary = &quot;SSE Emitter를 발급받기위한 API&quot;)
  public SseEmitter sseConnection(@RequestHeader(value = &quot;Last-Event-Id&quot;, required = false, defaultValue = &quot;&quot;) String lastEventId, SseConnectionRequestParam requestParam, HttpServletResponse response){
    return sseService.sseConnection(currentUserData.getUnqUserId(), lastEventId);
  }
}</code></pre>
<p><code>Connect Service</code></p>
<pre><code class="language-java">  @Override
  public SseEmitter sseConnection(String eventId, String lastEventId) {
    SseEmitter sseEmitter = emitterRepository.save(eventId, new SseEmitter(DEFAULT_TIMEOUT));

    // SseEmitter의 완료, 시간 초과, 에러로 인한 전송 불가 시 sseEmitter 삭제
    handleException(sseEmitter, eventId);

    // 연결 직후, 데이터 전송이 없을 시 503 에러 발생. 에러 방지 위한 더미데이터 전송
    sendToClient(eventId, &quot;Successfully Connected ::: &quot; + eventId);

    // 클라이언트가 미수신한 Event 유실 예방, 연결이 끊켰거나 미수신된 데이터를 다 찾아서 보내준다.
    if (StringUtil.isNotEmpty(lastEventId)) {
      Map&lt;String, SseEmitter&gt; events = emitterRepository.findAllStartById(eventId);
      events.entrySet().stream()
          .filter(entry -&gt; lastEventId.compareTo(entry.getKey()) &lt; 0)
          .forEach(entry -&gt; sendToClient(entry.getKey(), entry.getValue()));
    }
    return sseEmitter;
  }</code></pre>
<p> 와 같이 구현하여 Connect api호출시 Connect Complete 메세지와 Emitter를 응답받는다. 
 위에서 사용된 <code>sendToClient</code>는 기본 sseEmitter.send를 커스텀한 메소드이며</p>
<p> <code>sendToClient</code></p>
<pre><code class="language-java">   @Override
  public void sendToClient(String eventId, Object data) {
    SseEmitter sseEmitter = emitterRepository.findByEventId(eventId);
    try {
      sseEmitter.send(SseEmitter.event()
          .id(eventId)
          .name(&quot;Connection&quot;)
          .data(data, MediaType.APPLICATION_JSON));
    } catch (IOException e) {
      sseEmitter.completeWithError(e);
      emitterRepository.deleteAllByKey(eventId);
      LOGGER.error(StringUtil.extractStackTrace(e));
    }
  }</code></pre>
<p>  와 같이 메소드를 구성한다. </p>
<p>  이후 FileUpload API 호출 후 service에서 사용하는 로직에서 응답이 필요한 부분에
  이 <code>sendToClient</code>를 호출하면 된다 .</p>
<p>  <code>Uploadfile Service</code></p>
<pre><code class="language-java">   public List&lt;FileMst&gt; uploadFilesAndSplit(UploadFileInfo uploadFileInfo, 
   String lastEventId, String eventId)
      throws ExecutionException, InterruptedException {
      ```//파일 unzip 로직
      sseService.sendToClient(eventId, fileName + &quot; 파일 Unzip 완료!&quot;);
      ``` //파일 split 및 저장 로직 
      sseService.sendToClient(eventId, fileName + &quot;파일 저장 및 Split 완료!&quot;);
      }</code></pre>
 <h2> 로드밸런싱 환경에서의 난관 </h2>

<p> 하지만 로컬, 개발 환경에서는 하나의 서버만 존재하기에 문제가 되지않지만,
 현재 운영서버에 올라간 솔루션은 AWS의 ALB로 2개의 web server로 로드밸런싱된 환경이며 2개의 API (Connect API, FileUpload API) 가 같은 web server로 갈 것이라는 보장은 없다. 즉, 
 <img src="https://velog.velcdn.com/images/shield-man/post/cebb2ced-8137-4349-80fb-1fc5d3709864/image.png" width="100%" height="50%"></p>
<p>간단한게 위와 같이 connect 는 web 1에 되었지만 FileUpload API는 web 2에 도달 할 수 있다는 것이다. 그 결과 web 1은 아무 응답도 주지않고 time-out이 되버린다. </p>
<h2>해결</h2>

<p>이에 대하여 /conect와 /file-write 를 동시에 즉, Connect API와 FileUpload API를 하나로 합치는 방법을 선택하였다. </p>
<p>ThreadPool을 사용는 java라이브러리인 <code>ExecutorService</code> 을 사용하여 
Connect 먼저 응답 후 FileUpload 로직을 수행하도록 적용하였다.</p>
<p>기존 로직</p>
<pre><code>1. 
[Conect API 호출]-&gt;[Connect controller]-&gt;[Connect Service]-&gt;[SendToClient(연결 완료)]

2. 
[FileUpload API 호출]-&gt;[FileUpload controller]-&gt;[FileUpload Service]-&gt;
[uploadFilesAndSplit]-&gt;[SendToClient(업로드 진행현황)]</code></pre><p>변경 로직</p>
<pre><code>[FileUpload API 호출]-&gt;[FileUpload controller]-&gt;[uploadFiles]-&gt;
[SendToClient(연결 완료)]-&gt;[FileUpload Service]-&gt;
[uploadFilesAndSplit]-&gt;[SendToClient(업로드 진행현황)]
</code></pre><p><code>uploadFiles 메소드</code> </p>
<pre><code class="language-java">  @Override
  public SseEmitter uploadFiles(FileUploadRequestParam requestParam) {
    SseEmitter sseEmitter = new SseEmitter(DEFAULT_TIMEOUT);
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    executorService.execute(() -&gt; {
      uploadFilesAndSplit(requestParam, sseEmitter);
    });
    return sseEmitter;
  }
</code></pre>
<p>1개 사이즈의 ThreadPool 생성후 task를 할당하면 mainThread와 별개로 병렬처리 하게된다. 
따라서 한번의 API호출로 두 로직이 수행되도록 하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] 기본 아키텍쳐 이해 ]]></title>
            <link>https://velog.io/@shield-man/AWS-%EA%B8%B0%EB%B3%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@shield-man/AWS-%EA%B8%B0%EB%B3%B8-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Tue, 09 Jan 2024 01:37:35 GMT</pubDate>
            <description><![CDATA[<img src="https://velog.velcdn.com/images/shield-man/post/69735c2b-da8b-492d-89fd-cf4068edf4ce/image.png" width="50%" height="50%">

<p>VPC : Virtual Private Cloud</p>
<ul>
<li>퍼블릭 클라우드 환경에서 사용할 수 있는 고객 전용 사설 네트워크</li>
</ul>
<p>NAT : Network Address Translation</p>
<ul>
<li><p>IP 패킷의 TCP/UDP 포트 숫자와 소스 및 목적지의 IP 주소 등을 재기록하면서 라우터를 통해 네트워크 트래픽을 주고 받는 기술 ( private IP &lt;-&gt; public IP)</p>
</li>
<li><p>AWS에선 프라이빗 서브넷의 인스턴스가 VPC 외부의 서비스에 연결할 수 있지만 외부 서비스에서 이러한 인스턴스와의 연결을 시작할 수 없도록 NAT 게이트웨이를 사용할 수 있다.</p>
<img src="https://velog.velcdn.com/images/shield-man/post/537d8f6c-0dfb-4c13-8526-292bc04d1e14/image.png" width="50%" height="50%">


</li>
</ul>
<blockquote>
<p>NAS :Network Attached Storage </p>
</blockquote>
<ul>
<li>스토리지를 빠른 속도의 네트워크로 연결하는 방식. LAN(Local Area Network)을 연결하여 사용하기 때문에 비용이 저렴 , 파일 서버 </li>
</ul>
<blockquote>
<p>ELB : Elastic Load Balancer </p>
</blockquote>
<ul>
<li>L4 스위치 </li>
</ul>
<blockquote>
<p>ALB : Application Load Balancer </p>
</blockquote>
<ul>
<li>L7 스위치 </li>
</ul>
<blockquote>
<p>CLOUD FRONT </p>
</blockquote>
<ul>
<li>HTML, CSS, JS 등 정적파일을 빠르게 로드 시키기 위해 사용 </li>
</ul>
<blockquote>
<p>AMAZON S3 :Simple Storage Service </p>
</blockquote>
<ul>
<li>파일 서버 역할 </li>
</ul>
<blockquote>
<p>RDS : Relational DB </p>
</blockquote>
<ul>
<li>AWS에 올라간 관계형 DB </li>
</ul>
<blockquote>
<p>On-premise : 기업이 자체적으로 IT 인프라를 소유, 관리 및 운영하는 경우</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[아파치와 톰캣 ]]></title>
            <link>https://velog.io/@shield-man/%EC%95%84%ED%8C%8C%EC%B9%98%EC%99%80-%ED%86%B0%EC%BA%A3</link>
            <guid>https://velog.io/@shield-man/%EC%95%84%ED%8C%8C%EC%B9%98%EC%99%80-%ED%86%B0%EC%BA%A3</guid>
            <pubDate>Tue, 09 Jan 2024 01:06:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>아파치: 웹 서버용 소프트웨어 , HTTP 서버 , 정적인 데이터 처리 </p>
</blockquote>
<blockquote>
<p>톰캣 : 아파치 재단의 WAS , 동적인 데이터 처리 , 톰캣은 was서버이지만 web서버의 기능도 갖추고 있다. dynamic(동적)인 웹을 만들기 위한 웹 컨테이너, 서블릿 컨테이너라고 불리며, 웹서버에서 정적으로 처리해야할 데이터를 제외한 JSP, ASP, PHP 등은 웹 컨테이너(톰캣)에게 전달한다.</p>
</blockquote>
<p>was = web server+ web container
apache = web  역할 서버
tomcat = was 역할 서버 
apache tomcat = web+was 서버</p>
<p>Q) 그러면 tomcat만써도 되는거아닌가?
A) 하지만 역할 분리 , 성능 등을 고려하여  was와 webServer를 분리한다.</p>
<p>즉 ,</p>
<blockquote>
</blockquote>
<ol>
<li>http 요청이 들어오면</li>
<li>아파치로 전달하고 정적인 데이터는 아파치에서 처리</li>
<li>동적인 데이터는 아파치에서 ajp프로토콜을 사용하여 미리 지정한 소켓을 통해 특정 포트로 tomcat의 서블릿 컨테이너 (spring의 경우 dispatcherServelet) 로 전달 </li>
<li>이후 handler를 통해 이동한 뒤 mvc패턴에 의해 가져온 데이터를 응답.  </li>
</ol>
<img src="https://velog.velcdn.com/images/shield-man/post/6401c1ae-b80f-4e0d-9892-0206389a49f7/image.png" width="50%" height="50%">


<h3> 아파치 톰캣 연동 및 통신과정 </h3>

<pre><code>- 클라이언트 &lt;-80-&gt; 아파치 &lt;-8009-&gt; 톰캣 &lt;-8080-&gt; 클라이언트 (port 임의 설정)

- 아파치와 톰캣 연동은 [mod_jk], [mod_proxy], [mod_proxy_ajp] 3가지 방법 존재 

- mod_jk : 
아파치와 톰캣을 연동하기 위한 아파치의 모듈로써AJP 프로토콜(web&lt;-&gt;was protocol)을 이용하여 
아파치에 들어온 요청 중 톰캣이 처리할 요청을 AJP 포트(일반적으로 8009)를 통해톰캣에 전달하고 
그에 대한 응답을 받는 역할을 수행</code></pre><blockquote>
<p>+서버에 아파치와 톰캣을 올리는 간략한 과정 </p>
</blockquote>
<ol>
<li>OS에 아파치와 톰캣 설치 </li>
<li>mod_jk.so 설치 및 컴파일</li>
<li>workers.properties , httpd.conf 설정 </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] Builder 패턴]]></title>
            <link>https://velog.io/@shield-man/Spring-Boot-Builder-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@shield-man/Spring-Boot-Builder-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 09 Jan 2024 00:42:03 GMT</pubDate>
            <description><![CDATA[<p>실무에서 개발하다보면 코드 작성보다 리딩에 더 많은 시간을 쓰는 것을 알 수 있다.
협업이나 유지보수 방면에서 <strong>가독성</strong>이 좋은 코드를 지향하는 것은 개발자의 필수 교양이라고 생각한다.</p>
<p>당연한 얘기를 하는 것 같지만 오늘 다룰 builder 패턴과 관련이 있다.</p>
<h3>builder 패턴이란? </h3>

<blockquote>
<p>디자인 패턴 중 생성 패턴에 해당 하며 생성 패턴에 속하는 패턴들은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 <strong>시스템과 분리</strong>해준다.</p>
</blockquote>
<p><strong>시스템과 분리</strong>에 포커스를 맞춰서 예시를 들자면
평소에 흔히 사용하는 생성자 패턴과 구현한 클래스가 아래와 같이 있다</p>
<pre><code class="language-java">@getter
@setter  
public class Shield {

  private int length;
  private String color;

  public Shield(int length, String color) {
    this.length = length;
    this.color = color;
  }

}</code></pre>
<pre><code class="language-java">public class ShieldImpl {

  private int length =5;
  private String color = &quot;red&quot;;

    Shield shield1 = new Shield(length, color);
    Shield shield2 = new shield() 
    shield2.setColor(color)
    shield2.setLength(length)
}</code></pre>
<p>현재 2개의 param을 전달하지만 
length,color,height,weight.. 등 param이 늘어날수록 가독성이 떨어지고 어떤 파라미터가 전달되는지 확인하기 쉽지 않다.</p>
<p>또한 setter를 사용하게된다면 코드상 의도파악과 객체의 일관성 유지에 좋지않다. </p>
<p>그렇기에 위 단점들을 보완하기위해 
@builder 어노테이션을 사용하여 다음과 같이 빌더 패턴을 구현 할 수 있다.</p>
<pre><code class="language-java">@getter
@setter  
public class Shield {

  private int length;
  private String color;
  ...

 @builder
  public Shield(int length, String color, ...) {
    this.length = length;
    this.color = color;
    ...
  }

}</code></pre>
<pre><code class="language-java">public class ShieldImpl {

  private int length =5;
  private String color = &quot;red&quot;;
  ...

    Shield shield = shield.builder()
    .color(color)
    .length(length)
    ...
    .build()
}</code></pre>
<p>이와같이 빌더 패턴을 구현하면 param주입이 명확하게 나타나고 있기에
코드 가독성이 증가하게된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크 용어 정리]]></title>
            <link>https://velog.io/@shield-man/%EB%B3%B4%EC%95%88-%EC%9A%A9%EC%96%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@shield-man/%EB%B3%B4%EC%95%88-%EC%9A%A9%EC%96%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 06 Dec 2023 06:55:09 GMT</pubDate>
            <description><![CDATA[<p>이해를 바탕으로 써본 글이며 정리 목적이기에 오류가 있을 수 있음.</p>
<blockquote>
<p><code>SSL (443, Secure Socket Layer)</code></p>
</blockquote>
<ul>
<li>응용 계층 아래 위치 해서 http, ftp 등 통신 프로토콜과 조합해서 사용</li>
</ul>
<blockquote>
<p><code>SSH (22, Secure Shell)</code></p>
</blockquote>
<ul>
<li>원격 호스트 연결 인터넷 프로토콜  , RSA 사용</li>
<li>private key, public key 둘다 보유한 서버가 클라이언트에 public key를 주고 암복화 후 세션키 비교 로직)</li>
</ul>
<blockquote>
<p><code>SSL-VPN</code> </p>
</blockquote>
<ul>
<li>장소나 단말의 종류와 관계없이 내부 네트워크에 접속할 수 있는 SSL 기반의 가상 사설망(VPN). </li>
<li>개인 PC에서 SSL VPN 장비에서 ID 마다 할당해놓은 가상 IP를 부여받고 서버들에 대한 라우팅도 자동생성</li>
</ul>
<blockquote>
<p><code>IPS( Intrusion Prevention System)</code> </p>
</blockquote>
<ul>
<li>기존에 정의되지 않은 침입이 발생하기 전에 실시간으로 침입을 막고 외부 침입으로부터 네트워크와 호스트를 보호하는 예방에 초점이 맞춰진 시스템</li>
<li>주로 시스템 콜(system call)을 검출하는 방법 등 사용 (시스템의 부하, 즉 로그가 많이 쌓일때 시스템콜)</li>
<li>HTTP 대응 패턴 미비 </li>
</ul>
<blockquote>
<p><code>IDS (Intrusion Detection System)</code></p>
</blockquote>
<ul>
<li>네트워크 트래픽에서 의심스러운 활동이 있는지 모니터링하고, 그러한 활동이 발견되면 경보를 발령하는 시스템</li>
<li>주로 시스템 로그나 감사로그를 통하여 침입을 탐지 (실시간 로그감지)</li>
</ul>
<blockquote>
<p><code>WAF (Web Application Firewall)</code></p>
</blockquote>
<ul>
<li>레이어 3 및 4에서 작동 </li>
<li>HTTP 프로토콜 베이스로 하는 공격만 탐지 및 차단 (DOS,DDoS 등)</li>
<li>외부 사용자와 웹 응용 프로그램 사이에 위치하여 모든 HTTP 통신을 분석</li>
<li>웹 애플리케이션에 도달하기 전에 악성 요청을 감지하고 차단</li>
<li>보호 대상 리소스가 HTTP (S) 웹 요청에 응답하는 방식을 제어하는 데 사용</li>
<li>웹 액세스 제어 목록 (ACL) 을 정의한 다음 요청 들어올때마다 WAF에게 전달 ( 미리 요청 차단, 요청 수 세기 등 필요한 로직 적용 )</li>
</ul>
<blockquote>
<p><code>ACL (Access Control List)</code></p>
</blockquote>
<ul>
<li>시스템 리소스와 관련된 권한 목록이다. </li>
<li>목적지 주소, 포트번호, 프로토콜로 특정 패킷을 필터링 하는 트래픽 필터링 기능을 가짐. (접근 제한 ,라우팅 경로 조정 기능 수행)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] Native 쿼리 사용 및 오류 ]]></title>
            <link>https://velog.io/@shield-man/f1w9ko05</link>
            <guid>https://velog.io/@shield-man/f1w9ko05</guid>
            <pubDate>Fri, 01 Dec 2023 00:27:49 GMT</pubDate>
            <description><![CDATA[<p>대시보드에 표현할 통계 데이터를 가져오는 쿼리를 짜야했다. 
무수히 긴 쿼리와 많은 join( 잦은 JOIN사용은 DB성능에 좋지않다. 추후 SQL튜닝에 대해 다룰예정!) 을 사용 해야해서 queryDsl을 사용하려 했다. 하지만 쿼리에서 뷰를 사용하는데 queryDsl은 인라인 뷰를 지원하지 않는다. 이에 대해 2가지 방법을 고려하였다.</p>
<h2 id="1subquery-사용">1.SubQuery 사용</h2>
<p>repository에서 서브쿼리 , <code>@subselect</code>을 이용하여 queryDsl에서 만든 뷰를 entity처럼 사용하기 </p>
<h3 id="예시">예시</h3>
<pre><code class="language-java">@Data
@Entity
@Immutable
@Subselect(
   &quot;SELECT SHIELDNM FROM SHIELDTABLE
 WHERE SHIELDCOLOR =&#39;RED&#39;&quot;
)
@Synchronize({&quot;ShieldTable&quot;})
public class ShieldTableView implements Serializable {

  @Id
  @Column(name = &quot;SHIELD_NM&quot;)
  @Schema(description = &quot;방패 이름&quot;, nullable = false)
  private String shieldNm; 
  ...
}</code></pre>
<p>하지만 @subselect 어노테이션을 사용한 쿼리가 자체가 성능이 매우 안좋은 쿼리이고 다시 또 querydsl에서 사용하게된다면 더욱 좋지않다. 트래픽이 많은 서비스에서는 지양하는게 좋다. </p>
<h2 id="2jpql-사용">2.JPQL 사용</h2>
<p>repository에서 <code>@query</code> 어노테이션을 사용하여 native 쿼리를 이용. 동적으로 받아야할 값들은 <code>@Param</code>으로 받기 </p>
<h3 id="예시-1">예시</h3>
<pre><code class="language-java">public interface ShieldTableRepository 
extends JpaRepository&lt;ShieldTable&gt;,
    QuerydslPredicateExecutor&lt;ShieldTable&gt; {
 @Query(value = &quot;SELECT SHIELDNM FROM SHIELDTABLE
 WHERE SHIELDCOLOR = :shieldColor  nativeQuery = true)
  List&lt;Shield&gt; findByShildNm(
  @Param(&quot;shieldColor&quot;) String shieldColor);
  ...</code></pre>
<p>위 2가지 방법중 성능과 특정 기간동안의 
통계 데이터를 가져오는 것<strong>(동적으로 날짜값을 사용해야하므로)</strong> 을 고려하여
2번을 선택하였다.</p>
<h2 id="3-문제-발생">3. 문제 발생</h2>
<blockquote>
<p>[WARN]org.hibernate.engine.jdbc.spi.SqlExceptionHelper.logExceptions (SqlExceptionHelper.java:137) - SQL Error: 1861, SQLState: 22008
(ORA-01861: 리터럴이 형식 문자열과 일치하지 않음 )</p>
</blockquote>
<p>로컬환경에서는 문제가 없이 쿼리를 수행하고데이터를 잘 가져왔다. 
하지만 개발배포 이후 개발환경에서 문제가 발생!
이에 대하여 검색한 결과 
<strong>OS의 Lang설정이 달라 문자 열을 묵시적으로 날짜로 변환을 못해서 발생하는 오류</strong> 였다.
OS 환경변수인 NLS_LANG 설정에 영향을 받으므로 모든 애플리케이션 서버의 LANG을 세팅해주는 방법도 있었지만 번거로우므로 
쿼리에서 모두 날짜형식을 지정해주는 방법을 택하였다.</p>
<h3 id="예시-2">예시</h3>
<pre><code class="language-SQL">SELECT SHIELDNM 
FROM SHIELDTABLE B
LEFTJOIN (SELECT TO_CHAR(MAKEDT,&#39;YYYY/MM/DD&#39;) AS MAKEDT
FROM SHILEDTABLE) A
ON A.MAKEDT= B.REPAIRDT </code></pre>
<p>에서</p>
<pre><code>ON A.MAKEDT= B.REPAIRDT // X
ON A.TO_CHAR(MAKEDT,&#39;YYYY/MM/DD&#39;)= B.REPAIRDT // 날짜 형식 지정</code></pre><p>으로 바꿔주니 정상적으로 쿼리가 수행되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 롬복(Lombok)]]></title>
            <link>https://velog.io/@shield-man/Spring-%EB%A1%AC%EB%B3%B5Lombok</link>
            <guid>https://velog.io/@shield-man/Spring-%EB%A1%AC%EB%B3%B5Lombok</guid>
            <pubDate>Wed, 29 Nov 2023 01:49:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>롬복(Project Lombok)은 
자동으로 편집기에 연결하고 도구를 구축하여 Java를 더욱 멋지게 만드는 Java 라이브러리입니다.
다시는 다른 getter 또는 equals 메소드를 작성하지 마세요. 
하나의 주석으로 클래스에 모든 기능을 갖춘 빌더, 로깅 변수 자동화 등이 포함됩니다.
출처: <a href="https://projectlombok.org/">https://projectlombok.org/</a></p>
</blockquote>
<p>즉, 자주쓰는 getter, setter 등의 반복 메서드 작성 코드를 줄여주는 코드 다이어트 라이브러리 이다.</p>
<h2 id="예시">예시</h2>
<pre><code class="language-java">public class Shield {

  private int length;

  private String color;

  public void setLength(int length) {
    this.length = length;
  }

  public void setColor(String color) {
    this.color = color;
  }

  public int getLength() {
    return length;
  }

  public String getColor() {
    return color;
  }

  public Shield(int length, String color) {
    this.length = length;
    this.color = color;
  }

}</code></pre>
<p>이와 같은 클래스가 있다면 </p>
<pre><code class="language-java">@Getter
@Setter
@RequiredArgsConstructor
public class Shield {

  private int length;

  private String color;
}</code></pre>
<p>와 같이 <code>@Getter</code>,<code>@Setter</code> 어노테이션을 활용하여 코드 길이를 간소화 시킬 수 있으며 </p>
<pre><code class="language-java">@data
public class Shield {

  private int length;

  private String color;
}</code></pre>
<p>와 같이 <code>@data</code> 어노테이션을 이용하여 한번에 표시 할 수 있다. <code>@data</code>어노테이션은 </p>
<blockquote>
<p><code>@Getter</code>
<code>@Setter</code>
<code>@ToString</code>
<code>@EqualsAndHashCode</code>
<code>@RequiredArgsConstructor</code></p>
</blockquote>
<p>을 자동 적용해준다. </p>
<p>이밖에 <code>@NonNull</code>, <code>@Builder</code> ,<code>@AllArgsConstructor</code>, <code>@NoArgsConstructor</code> 등이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spirng] Ioc와 Bean 그리고 DI]]></title>
            <link>https://velog.io/@shield-man/Spirng-%ED%8A%B9%EC%A7%95%EA%B3%BC-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@shield-man/Spirng-%ED%8A%B9%EC%A7%95%EA%B3%BC-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Wed, 29 Nov 2023 00:54:32 GMT</pubDate>
            <description><![CDATA[<h3 id="1ioc와-bean">1.IoC와 bean</h3>
<p>기본적으로 스프링은 제어의 역전 특징이 있다. 
<code>제어의 역전 (IoC)</code>는 객체에 대한 권한을 프레임워크가 가지고 있으며 객체 생명주기를 프레임워크가 제어한다. 그래서 제어의 역전이라함.
따라서 스프링이 직접 관리 하기 위해 객체를 등록해줘야하는데 이것이 <code>bean</code>이다. 실제로 <code>@bean</code> 어노테이션을 사용해서 등록한다. <code>@controller</code>, <code>@service</code>, <code>@repository</code> 등 이러한 어노테이션들은 component로 간주하여 빈으로 등록된다. 이와 관련하여 빈을 생성하고 의존관계를 설정하는 기능을 담당하는 IoC 컨테이너이자 클래스인 <code>beanfactory</code>가 존재하며 추후에 이와 관련하여 <code>scope</code>,  <code>singleton</code>과 <code>prototype</code> 등에 관하여 다룰 예정이다.</p>
<h3 id="2di-dependency-injection">2.DI (Dependency Injection)</h3>
<p>의존성 주입이라 하는 <code>DI (Dependency Injection)</code>는 <strong>객체의 의존관계를 외부에서 넣어주는 것</strong>을 뜻한다. 
다시 1번을 보자. 스프링은 IOC특징이 있으며 빈을 등록해서 객체를 관리한다. 그 빈들을 <code>스프링 컨테이너</code> 에 담아 bean들을 관리하는데, 여기서 스프링 컨테이너에서 각 빈들에 대한 관계를 정리해줘야 하지않겠는가. 예를 들어 학용품이라는 객체와 연필이라는 객체를 빈으로 등록했으면 필요에 의해 둘을 사용할때 둘의 관계를 알아야 프레임워크가 관리할텐데 그때 필요한 것이 DI이다.** 프레임워크가 객체를 관리(IOC)하고, 실행시 객체 간의 관계를 결정(DI)함 으로써 **<code>객체들간의 결합도를 낮추는 장점</code>이 있다.</p>
<p><img src="https://velog.velcdn.com/images/shield-man/post/e5c9c756-e4af-4888-89fd-d4969cf006d4/image.webp" alt=""></p>
<h3 id="3autowired">3.@Autowired</h3>
<p>DI에 대한 개념을 이해했으니 어떻게 의존성을 주입하는가? 대표적으로 <code>@Autowired</code> 어노테이션을 사용하는 것이다. 특정 클래스에서 필요한 객체를 다룰 때 @Autowirded 를 사용하면 스프링 컨테이너에서 빈으로 등록한 객체의 의존성 주입을 알아서 넣어준다.</p>
<pre><code class="language-java">@Service
public class ShieldRepairServiceImpl implements ShieldRepairService {

 @Autowired
 private HammerRepository hammerRepository;//@Repository를 사용하여 빈으로 등록한 레포지토리</code></pre>
<h3 id="4requiredargsconstructor">4.@RequiredArgsConstructor</h3>
<p>@Autowired에 대해 알아봤으니 <code>@RequiredArgsConstructor</code>에 대해 추가적으로 알아보자.
간단하게 <strong>의존성 주입 종류(생성자,setter,field)</strong>중에 <code>생성자 주입</code>을 자동으로 해준다. 즉 생성자를 만들고 @Autowired를 사용하여 의존성을 주입후, 필드가 새로 생길 떄 마다 생성자를 수정하는 번거로움이 없어지고 자동으로 관리해준다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[벨로그를 시작하기 앞서]]></title>
            <link>https://velog.io/@shield-man/%EB%B2%A8%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%95%9E%EC%84%9C-r7nmkurh</link>
            <guid>https://velog.io/@shield-man/%EB%B2%A8%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%95%9E%EC%84%9C-r7nmkurh</guid>
            <pubDate>Mon, 27 Nov 2023 04:28:40 GMT</pubDate>
            <description><![CDATA[<p>개발자의 길을 걷게 된지 1년이 다 되어가고 있다.
2023년 1월부터 개발을 입문하고 그동안 공부하고 배운 것들을 노션을 통해서 정리하고있었다.
하지만 언제부턴가 정리가 뜸해지고,
정리한 내용들이 다소 난잡하게 분포됨을 느끼고 기술 블로그 운영의 필요성을 느꼈다.</p>
<p>&#39;&#39;&#39;
읽기 쉬운 글쓰기&#39;를 지향 할 것이며 사실에 기반하여 글을 작성하겠습니다.</p>
]]></description>
        </item>
    </channel>
</rss>