<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>유혁스쿨</title>
    <link>https://u-it.tistory.com/</link>
    <description>기록이 곧 자산이 되는 세상에서 백엔드 서버 개발을 공부하는 블로그 입니다.
스스로 얼마나 알고 있고 무엇이 부족한지 파악하고 계신지요?
저는 항상 메타인지에 초점을 두고 공부합니다.
처음 접하는 누군가 에게 설명할 때 상대방을 이해 시킬 수 있을 정도가 되어야 스스로도 이해가 된 것이며, 그때 비로서 내 것이 되었다고 말할수 있어요.
이정도면 되겠지 하는 마음가짐을 버리고 스스로에게 관대하지 못하도록 노력하고 있습니다.</description>
    <language>ko</language>
    <pubDate>Sat, 30 May 2026 17:41:17 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>유혁스쿨</managingEditor>
    <image>
      <title>유혁스쿨</title>
      <url>https://tistory1.daumcdn.net/tistory/4008941/attach/97e44bfbb5f0433484463de8960670f6</url>
      <link>https://u-it.tistory.com</link>
    </image>
    <item>
      <title>SBOM 명세 개념 및 Maven(Spring), NPM(Node) 작업 가이드</title>
      <link>https://u-it.tistory.com/534</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;SBOM 이란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;소프트웨어 자재 명세서 Software Bill of Materials의 약자로, 소프트웨어를 구성하는 오픈소스, 라이브러리, 모듈 등 제 3자 구성요소와 종속성 버전, 라이브러리 정보를 기계 판독 가능한 형식으로 나열한 목록을 말한다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;식품 영양 성분 표기처럼 소프트웨어 공급망의 투명성을 확보하고 보안 취약점을 신속히 탐지/대응하기 위한 필수 기술이다.&amp;nbsp;&amp;nbsp;&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;주로 개발, 빌드, 배포 등 다양한 단계에서 생성되어 보안 위험을 관리하는 데 활용된다.&lt;/span&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-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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;SBOM 작성 가이드&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;Maven&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Maven의 경우 CycloneDX 플러그인을 활용하여 SBOM을 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;pom.xml에 정의된 의존성을 포함하여 생성할 수 있다.&amp;nbsp;&amp;nbsp;&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;1.CycloneDX 설치&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/CycloneDX/cyclonedx-cli/releases&quot;&gt;https://github.com/CycloneDX/cyclonedx-cli/releases&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;2. cyclonedx plugin 추가&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769403008459&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
&amp;lt;!-- 생략 --&amp;gt;
&amp;lt;/dependencies&amp;gt;
&amp;lt;build&amp;gt;
    &amp;lt;resources&amp;gt;
    &amp;lt;!-- 생략 --&amp;gt;
    &amp;lt;/resources&amp;gt;
    &amp;lt;plugins&amp;gt;
        &amp;lt;plugin&amp;gt;
            &amp;lt;groupId&amp;gt;org.cyclonedx&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;cyclonedx-maven-plugin&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;2.7.10&amp;lt;/version&amp;gt;
            &amp;lt;executions&amp;gt;
                &amp;lt;execution&amp;gt;
                    &amp;lt;phase&amp;gt;package&amp;lt;/phase&amp;gt;
                    &amp;lt;goals&amp;gt;
                        &amp;lt;goal&amp;gt;makeAggregateBom&amp;lt;/goal&amp;gt;
                    &amp;lt;/goals&amp;gt;
                &amp;lt;/execution&amp;gt;
            &amp;lt;/executions&amp;gt;
            &amp;lt;configuration&amp;gt;
                &amp;lt;includeCompileScope&amp;gt;true&amp;lt;/includeCompileScope&amp;gt;
                &amp;lt;includeRuntimeScope&amp;gt;true&amp;lt;/includeRuntimeScope&amp;gt;
                &amp;lt;includeProvidedScope&amp;gt;false&amp;lt;/includeProvidedScope&amp;gt;
                &amp;lt;includeTestScope&amp;gt;false&amp;lt;/includeTestScope&amp;gt;
                &amp;lt;includeSystemScope&amp;gt;false&amp;lt;/includeSystemScope&amp;gt;
                &amp;lt;outputFormat&amp;gt;json&amp;lt;/outputFormat&amp;gt;
                &amp;lt;outputName&amp;gt;application&amp;lt;/outputName&amp;gt;
            &amp;lt;/configuration&amp;gt;
        &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;3. mvn 명령 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkAxfu/dJMb99SJuf2/3QkL9XmNdYQanD8b2reiv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkAxfu/dJMb99SJuf2/3QkL9XmNdYQanD8b2reiv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkAxfu/dJMb99SJuf2/3QkL9XmNdYQanD8b2reiv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkAxfu%2FdJMb99SJuf2%2F3QkL9XmNdYQanD8b2reiv0%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;268&quot; height=&quot;111&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFudc/dJMcaiB5Yfg/YsztzgFSNaSHkIPCdCaa00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFudc/dJMcaiB5Yfg/YsztzgFSNaSHkIPCdCaa00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFudc/dJMcaiB5Yfg/YsztzgFSNaSHkIPCdCaa00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFudc%2FdJMcaiB5Yfg%2FYsztzgFSNaSHkIPCdCaa00%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;679&quot; height=&quot;116&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1769402956294&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mvn clean cyclonedx:makeAggregateBom -U&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;4. target 디렉토리 application.json 생성 확인&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&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-origin-width=&quot;357&quot; data-origin-height=&quot;54&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oYWFo/dJMcafZHcNu/XTCVSbC3Br1fXrLbqAc3BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oYWFo/dJMcafZHcNu/XTCVSbC3Br1fXrLbqAc3BK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oYWFo/dJMcafZHcNu/XTCVSbC3Br1fXrLbqAc3BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoYWFo%2FdJMcafZHcNu%2FXTCVSbC3Br1fXrLbqAc3BK%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;357&quot; height=&quot;54&quot; data-origin-width=&quot;357&quot; data-origin-height=&quot;54&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;5. spdx json 변환&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;target 디렉토리에 설치한 cyclonedx의 exe 파일을 구성하고 아래 명령을 실행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;(PowerShell로 실행)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769403871555&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.\cyclonedx-win-x64.exe convert `
  --input-file .\application.json `
  --output-file .\application.spdx32.json `
  --output-format spdxjson&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;모든 작업이 정상적으로 완료되었다면 application.spdx32 이름의 json 파일이 생성된 것을 확인할 수 있다.&amp;nbsp;&amp;nbsp;&lt;/span&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-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;NPM&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;NPM은 SPDX&amp;nbsp;플러그인을 활용하여 SBOM을 생성한다.&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;1. npm 버전 확인&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;npm 9 버전 이상 필요하다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;node 버전과 다르니 주의해야한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769404132177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm -v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;2. package-lock.json 생성&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;가장 최신 package.json 기준으로 해당 lock 파일이 필요하다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;최신이 아닐 수 있으니 기존 package-lock.json 파일은 제거하고 파일만 생성하도록 한다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769404185890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install --package-lock-only --ignore-scripts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;3. SPDX 2.3 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1769404201486&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm sbom --sbom-format=spdx &amp;gt; sbom.spdx.json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;4. 파일 생성 확인 및 내용 확인&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;sbom.spdx.json 파일이 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4OS9m/dJMcajnsjN6/sNnUXGOrHVyhJkJrXBI9Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4OS9m/dJMcajnsjN6/sNnUXGOrHVyhJkJrXBI9Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4OS9m/dJMcajnsjN6/sNnUXGOrHVyhJkJrXBI9Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4OS9m%2FdJMcajnsjN6%2FsNnUXGOrHVyhJkJrXBI9Tk%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;400&quot; height=&quot;483&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;만약 npm 버전이 올바르지 않다면 아래와 같은 내용으로 채워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;1259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDrVAm/dJMcag5orma/bb2Gcfuto9PLm6Hj0K95BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDrVAm/dJMcag5orma/bb2Gcfuto9PLm6Hj0K95BK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDrVAm/dJMcag5orma/bb2Gcfuto9PLm6Hj0K95BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDrVAm%2FdJMcag5orma%2Fbb2Gcfuto9PLm6Hj0K95BK%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;1575&quot; height=&quot;1259&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;1259&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;초록색 마킹을 보면 npm 버전이 6.10.3임을 알려주고 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/534</guid>
      <comments>https://u-it.tistory.com/534#entry534comment</comments>
      <pubDate>Mon, 26 Jan 2026 14:06:49 +0900</pubDate>
    </item>
    <item>
      <title>[Tibero] LISTAGG(DISTINCT 컬럼명, &amp;quot;&amp;quot;) 오라클 호환 대응</title>
      <link>https://u-it.tistory.com/533</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상황&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오라클로 개발되어 있는 상태에서 데이터베이스 Vendor만 티베로로 변경하였을때 LISTAGG(DISTINCT ~ )와 같이 LISTAGG 함수 내에 DISTINCT 키워드를 사용할 경우 티베로에서는 오류가 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;(지원하지 않는 문법)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;티베로에서는 서브쿼리를 통해 전처리 DISTINCT 중복제거를 한 뒤, 해당 데이터를 기준으로 XMLAGG 함수를 활용해야한다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763995781531&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE TEST_LISTAGG (
    ID NUMBER,
    KEYWORD VARCHAR2(50)
);

INSERT INTO TEST_LISTAGG VALUES (1, 'apple');
INSERT INTO TEST_LISTAGG VALUES (1, 'banana');
INSERT INTO TEST_LISTAGG VALUES (1, 'apple');
INSERT INTO TEST_LISTAGG VALUES (1, 'cherry');
INSERT INTO TEST_LISTAGG VALUES (2, 'banana');
INSERT INTO TEST_LISTAGG VALUES (2, 'banana');
INSERT INTO TEST_LISTAGG VALUES (2, 'apple');
INSERT INTO TEST_LISTAGG VALUES (2, 'date');

WITH DISTINCT_KEYWORD AS (
    SELECT DISTINCT ID, KEYWORD
    FROM TEST_LISTAGG
)
SELECT
    tl.ID,
    LISTAGG(DISTINCT KEYWORD, ',') WITHIN GROUP (ORDER BY KEYWORD) AS LISTAGG_KEYWORD,
    (
        SELECT RTRIM(
                   XMLCAST(
                       XMLAGG(XMLELEMENT(ITEM, DK.KEYWORD || ',') ORDER BY DK.KEYWORD)
                       AS VARCHAR2(4000)
                   ), ','
               )
        FROM DISTINCT_KEYWORD DK
        WHERE DK.ID = tl.ID
    ) AS XMLAGG_KEYWORD
FROM TEST_LISTAGG tl
GROUP BY tl.ID
ORDER BY tl.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;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오라클에서 실행해보면 LISTAGG_KEYWORD와 XMLAGG_KEYWORD 두 결과가 동일하게 나온다.&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;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러나 위와같은 방식은, Main 쿼리에 Where나 Group By에 의해 많은 조건이 들어갈 경우 서브쿼리에도 동일하게 적용해야하므로, 쿼리가 매우 길어지고 복잡해진다.&amp;nbsp;&amp;nbsp;&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;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가장 효율적인 방법은 DISTINCT 키워드를 제거하고, 백엔드에서 컬렉션 등을 활용하여 중복을 제거하는 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/533</guid>
      <comments>https://u-it.tistory.com/533#entry533comment</comments>
      <pubDate>Mon, 24 Nov 2025 23:55:34 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] CORS: Security 설정 없이 Filter를 통한 Allow</title>
      <link>https://u-it.tistory.com/532</link>
      <description>&lt;h1&gt;상황&lt;/h1&gt;
&lt;p&gt;web.xml에 아래 코드와 같이 HttpSessionEventPublisher만 등록하여 Security의 보안 기능은 활성화 하지 않고,&lt;br&gt;단순히 HttpSession(서블릿 세션)을 Spring의 ApplicationContext로 전파하는 용도로만 사용하고 있다.&lt;br&gt;(Session의 LifeCycle(생성/소멸) 이벤트를 Spring 빈에서 감지)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;listener&amp;gt;
  &amp;lt;listener-class&amp;gt;org.springframework.security.web.session.HttpSessionEventPublisher&amp;lt;/listener-class&amp;gt;
&amp;lt;/listener&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;SecurityConfig 파일을 만든다고 해도 쉽게 CORS 설정을 할 수 없는 경우가 있다.&lt;/p&gt;
&lt;p&gt;이 경우 Filter를 통해 CORS 설정이 가능하다.&lt;/p&gt;
&lt;h2&gt;CorsFilter 구현&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;CORS 설정을 위한 Filter 클래스를 구현한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Component
public class CorsFilter implements Filter {

 @Override
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
     HttpServletRequest request = (HttpServletRequest) req;
     HttpServletResponse response = (HttpServletResponse) res;
     response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;http://localhost:8080&amp;quot;);
     response.setHeader(&amp;quot;Access-Control-Allow-Credentials&amp;quot;, &amp;quot;true&amp;quot;);
     response.setHeader(&amp;quot;Access-Control-Allow-Methods&amp;quot;,&amp;quot;*&amp;quot;);
     response.setHeader(&amp;quot;Access-Control-Max-Age&amp;quot;, &amp;quot;3600&amp;quot;);
     response.setHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;,
             &amp;quot;Origin, X-Requested-With, Content-Type, Accept, Authorization&amp;quot;);
     chain.doFilter(req, res);
 }
}&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;web.xml에 Filter 클래스를 등록해준다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;filter&amp;gt;
&amp;lt;filter-name&amp;gt;CORS&amp;lt;/filter-name&amp;gt;
 &amp;lt;filter-class&amp;gt;com.패키지.CorsFilter&amp;lt;/filter-class&amp;gt;
&amp;lt;/filter&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;예외 오류&lt;/h2&gt;
&lt;p&gt;만약 아래와 같은 오류가 발생했다면, origin 즉, 요청자 측 url이 서버에 허용되지 않아 CORS 오류가 발생한것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Access to XMLHttpRequest at &amp;#39;http://localhost:8888/context/url&amp;#39; from origin &amp;#39;http://localhost:8080&amp;#39; has been blocked by CORS policy: Response to preflight request doesn&amp;#39;t pass access control check: No &amp;#39;Access-Control-Allow-Origin&amp;#39; header is present on the request resource.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 경우 모든 origin(요청자)와 모든 headers를 열어주면 된다.&lt;/p&gt;
&lt;h3&gt;Allow 적용 코드&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;http://localhost:8080&amp;quot;);
response.setHeader(&amp;quot;Access-Control-Allow-Credentials&amp;quot;, &amp;quot;true&amp;quot;);
response.setHeader(&amp;quot;Access-Control-Allow-Methods&amp;quot;,&amp;quot;*&amp;quot;);
response.setHeader(&amp;quot;Access-Control-Max-Age&amp;quot;, &amp;quot;3600&amp;quot;);
response.setHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;,
                  &amp;quot;Origin, X-Requested-With, Content-Type, Accept, Authorization&amp;quot;);
/* 모든 Origin, Headers 허용 */
response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, request.getHeader(&amp;quot;Origin&amp;quot;));
response.setHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;, request.getHeader(&amp;quot;Access-Control-Request-Headers&amp;quot;));
chain.doFilter(req, res);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;해당 설정은 중복될 수 없어 최종 값으로 덮어씌워진다.&lt;br&gt;(&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;을 &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt;과 같이 와일드카드로 적용할 수 있지만, 이 경우 &lt;code&gt;Access-Control-Allow-Credentials&lt;/code&gt; 설정이 true와 함께 쓸 수 없다.&lt;br&gt;자격 증명 즉, 쿠키, 인증헤더 등을 모든 도메인에 허용하는 것은 보안 위험이 있기 때문에 브라우저 규칙에 의해 브라우저가 보안상 요청을 차단한다.)&lt;/p&gt;
&lt;h3&gt;환경변수를 통한 조건부 적용&lt;/h3&gt;
&lt;p&gt;Tomcat의 환경변수를 통해 프로필 정보를 읽어들여 설정해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LnRFP/dJMcaiPe3CC/fEnrEWCKHPRYPKyk5kTgMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LnRFP/dJMcaiPe3CC/fEnrEWCKHPRYPKyk5kTgMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LnRFP/dJMcaiPe3CC/fEnrEWCKHPRYPKyk5kTgMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLnRFP%2FdJMcaiPe3CC%2FfEnrEWCKHPRYPKyk5kTgMK%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;1033&quot; height=&quot;697&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, &amp;quot;http://localhost:9999&amp;quot;);
        response.setHeader(&amp;quot;Access-Control-Allow-Credentials&amp;quot;, &amp;quot;true&amp;quot;);
        response.setHeader(&amp;quot;Access-Control-Allow-Methods&amp;quot;,&amp;quot;*&amp;quot;);
        response.setHeader(&amp;quot;Access-Control-Max-Age&amp;quot;, &amp;quot;3600&amp;quot;);
        response.setHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;,
                &amp;quot;Origin, X-Requested-With, Content-Type, Accept, Authorization&amp;quot;);

        String property = System.getProperty(&amp;quot;spring.profiles.active&amp;quot;);
        if (property != null &amp;amp;&amp;amp; property.equals(&amp;quot;local&amp;quot;)) { /* VM Option: -Dspring.profiles.active=local */
            response.setHeader(&amp;quot;Access-Control-Allow-Origin&amp;quot;, request.getHeader(&amp;quot;Origin&amp;quot;));
            response.setHeader(&amp;quot;Access-Control-Allow-Headers&amp;quot;, request.getHeader(&amp;quot;Access-Control-Request-Headers&amp;quot;));
        }

        chain.doFilter(req, res);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>SpringFramework</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/532</guid>
      <comments>https://u-it.tistory.com/532#entry532comment</comments>
      <pubDate>Mon, 24 Nov 2025 23:35:44 +0900</pubDate>
    </item>
    <item>
      <title>Apache Tomcat 특정 버전 다운로드</title>
      <link>https://u-it.tistory.com/531</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Apache 공식 사이트에는 현재 최신 안정 버전만 메인 다운로드 페이지에 노출되고 있으며, 과거 버전 혹은 특정 버전은 Archive 저장소 형태로 제공되고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. 공식 다운로드 페이지 접속&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; title=&quot;https://tomcat.apache.org/&quot; href=&quot;https://tomcat.apache.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tomcat.apache.org/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #1a5490;&quot;&gt;&lt;b&gt;2. 좌측 Download에서 원하는 Tomcat 메이저 버전을 선택&lt;/b&gt; &lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;우측 &lt;b&gt;Quick Navigation&lt;/b&gt; 버전 확인 후 원하는 버전인지 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj8MHF/dJMcaacuXWr/uaxErOO7zmfcQEHzhzBqFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj8MHF/dJMcaacuXWr/uaxErOO7zmfcQEHzhzBqFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj8MHF/dJMcaacuXWr/uaxErOO7zmfcQEHzhzBqFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj8MHF%2FdJMcaacuXWr%2FuaxErOO7zmfcQEHzhzBqFk%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;193&quot; height=&quot;215&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;215&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;3. 좌측 Download에서 Archives 선택&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwUaol/dJMcac9dUiK/knhvGIiCxownuyZgtKL69k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwUaol/dJMcac9dUiK/knhvGIiCxownuyZgtKL69k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwUaol/dJMcac9dUiK/knhvGIiCxownuyZgtKL69k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwUaol%2FdJMcac9dUiK%2FknhvGIiCxownuyZgtKL69k%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;193&quot; height=&quot;218&quot; data-origin-width=&quot;193&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;4. 페이지에서 원하는 버전 선택&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://archive.apache.org/dist/tomcat/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://archive.apache.org/dist/tomcat/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;원하는 메이저 버전에 해당하는 tomcat 디렉토리 선택&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TttdY/dJMcagRjV8a/vsNrx3zvIKFEUtEb5Yc2U0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TttdY/dJMcagRjV8a/vsNrx3zvIKFEUtEb5Yc2U0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TttdY/dJMcagRjV8a/vsNrx3zvIKFEUtEb5Yc2U0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTttdY%2FdJMcagRjV8a%2FvsNrx3zvIKFEUtEb5Yc2U0%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;789&quot; height=&quot;709&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;원하는 마이너 혹은 패치버전에 해당하는 디렉토리 선택&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tDxka/dJMcaf5WRPi/Vmb3gXBFo8i06AtMbCItGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tDxka/dJMcaf5WRPi/Vmb3gXBFo8i06AtMbCItGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tDxka/dJMcaf5WRPi/Vmb3gXBFo8i06AtMbCItGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtDxka%2FdJMcaf5WRPi%2FVmb3gXBFo8i06AtMbCItGK%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;800&quot; height=&quot;1008&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;1008&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;bin 디렉토리 선택&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE9f8b/dJMcaaDzjWS/hCKTnkTxdX8kItNKrSKAIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE9f8b/dJMcaaDzjWS/hCKTnkTxdX8kItNKrSKAIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE9f8b/dJMcaaDzjWS/hCKTnkTxdX8kItNKrSKAIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE9f8b%2FdJMcaaDzjWS%2FhCKTnkTxdX8kItNKrSKAIK%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;796&quot; height=&quot;516&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; exe 확장자 파일 혹은 tar.gz 확장자 파일 선택(다운로드)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5aNvr/dJMcadUA1Yn/ELpF9uRxHUhkQFPu60ksDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5aNvr/dJMcadUA1Yn/ELpF9uRxHUhkQFPu60ksDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5aNvr/dJMcadUA1Yn/ELpF9uRxHUhkQFPu60ksDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5aNvr%2FdJMcadUA1Yn%2FELpF9uRxHUhkQFPu60ksDk%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;792&quot; height=&quot;1168&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/531</guid>
      <comments>https://u-it.tistory.com/531#entry531comment</comments>
      <pubDate>Thu, 30 Oct 2025 10:15:52 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL/Oracle] TIMESTAMP 타입에 대한 소요 시간(정수) 계산 - 이슈트래킹</title>
      <link>https://u-it.tistory.com/530</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #5733b1; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;실무 사례&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;운영 중인 시스템에서 &lt;b&gt;배치 작업 실행 로그&lt;/b&gt;를 분석하다가, 시작 시간(start_time)과 종료 시간(end_time)을 이용해 &lt;b&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;MySQL의 경우 TIMESTAMPDIFF를 사용하여 간단하게 해결하였으나,&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;오라클의 경우 해당 함수 지원이 되지 않아 &lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;단순 숫자 연산으로 처리하였는데, 이&amp;nbsp;&lt;/span&gt;경우 날짜 차이 계산이 정확하지 않은 이슈가 있었다.&lt;/span&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-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; 예시로 다음과 같은 테이블과 데이터를 준비한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755273237342&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 샘플 테이블
CREATE TABLE JOB_EXECUTION_LOG (
    job_id       INT,
    start_time   TIMESTAMP,
    end_time     TIMESTAMP
);

INSERT INTO JOB_EXECUTION_LOG VALUES
(1, '2025-08-15 10:00:00', '2025-08-15 10:05:30'),
(2, '2025-08-15 23:59:50', '2025-08-16 00:00:10');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;MYSQL&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;1862&quot; data-start=&quot;1830&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;MySQL&amp;nbsp;&lt;/span&gt; TIMESTAMPDIFF 사용법&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1755273752591&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2)&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2069&quot; data-start=&quot;1928&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; unit 값 &lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; 예시 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1999&quot; data-start=&quot;1975&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1985&quot; data-start=&quot;1975&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;SECOND&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;1992&quot; data-start=&quot;1985&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;초 차이&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;1999&quot; data-start=&quot;1992&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;330&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2022&quot; data-start=&quot;2000&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2010&quot; data-start=&quot;2000&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;MINUTE&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;2017&quot; data-start=&quot;2010&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;분 차이&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;2022&quot; data-start=&quot;2017&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;5&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2046&quot; data-start=&quot;2023&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2033&quot; data-start=&quot;2023&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;HOUR&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;2041&quot; data-start=&quot;2033&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;시간 차이&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;2046&quot; data-start=&quot;2041&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2069&quot; data-start=&quot;2047&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2057&quot; data-start=&quot;2047&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;DAY&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;2064&quot; data-start=&quot;2057&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;일 차이&lt;/span&gt;&lt;/td&gt;
&lt;td data-end=&quot;2069&quot; data-start=&quot;2064&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&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-start=&quot;1830&quot; data-end=&quot;1862&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;MySQL&amp;nbsp;&lt;/span&gt;&amp;nbsp;TIMESTAMPDIFF 적용 쿼리&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1755273806458&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT TIMESTAMPDIFF(SECOND, start_time, end_time) AS elapsed_seconds
FROM JOB_EXECUTION_LOG
LIMIT 1;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오라클&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;오라클의 문제 사례&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1755273265427&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT TO_NUMBER(TO_NUMBER(TO_CHAR(end_time, 'YYYYMMDDHH24MISS'))
       - TO_NUMBER(TO_CHAR(start_time, 'YYYYMMDDHH24MISS')))
FROM JOB_EXECUTION_LOG
WHERE ROWNUM &amp;lt;= 1;&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 data-end=&quot;957&quot; data-start=&quot;922&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;TO_CHAR로 문자열 변환 후 단순 숫자 차를 계산&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1049&quot; data-start=&quot;958&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;20250815235950 - 20250816000010 같이 &lt;b&gt;날짜 형식 숫자&lt;/b&gt;를 빼면 일자&amp;middot;월&amp;middot;년 변화가 반영되지 않고 단순 자리수 차이만 계산됨&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1082&quot; data-start=&quot;1050&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;하루를 넘어가는 경우, 음수 또는 엉뚱한 값이 나옴&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3206&quot; data-start=&quot;3171&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;DATE로 CAST해야 하는 이유 (Oracle)&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3472&quot; data-start=&quot;3208&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3252&quot; data-start=&quot;3208&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Oracle의 TIMESTAMP는 &lt;b&gt;날짜 + 나노초 단위&lt;/b&gt;까지 저장됨&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3320&quot; data-start=&quot;3253&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;TIMESTAMP끼리 뺄셈을 바로 하면 INTERVAL 타입이 반환되는데, 이를 숫자로 바로 연산하면 복잡해짐&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3388&quot; data-start=&quot;3321&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;DATE 타입으로 캐스팅하면 &lt;b&gt;소수 형태의 일(day)&lt;/b&gt; 값으로 단순화되어, 일/시/분/초 단위 계산이 쉬워짐&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3472&quot; data-start=&quot;3389&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;특히 배치 실행 시간 계산처럼 &lt;b&gt;초 단위 정확도&lt;/b&gt;가 필요한 경우, (DATE 차이) &amp;times; 86400 방식이 가장 간단하고 안정적임&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;오라클 올바른 방식&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1755273306370&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT (CAST(end_time AS DATE) - CAST(start_time AS DATE)) * 86400 AS elapsed_seconds
FROM JOB_EXECUTION_LOG
WHERE ROWNUM &amp;lt;= 1;&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 data-end=&quot;1309&quot; data-start=&quot;1253&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;CAST(... AS DATE) : TIMESTAMP &amp;rarr; DATE 변환 (시간까지 포함됨)&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1365&quot; data-start=&quot;1310&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;Oracle에서 DATE 타입끼리 뺄셈 시, 결과는 &lt;b&gt;일(day)&lt;/b&gt; 단위의 소수로 반환됨&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1424&quot; data-start=&quot;1366&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1일 = 24시간 &amp;times; 60분 &amp;times; 60초 = 86400초 이므로, 이를 곱해 &lt;b&gt;초 단위&lt;/b&gt;로 변환&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1437&quot; data-start=&quot;1426&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;예시 결과&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-end=&quot;1737&quot; data-start=&quot;1438&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; job_id &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;start_time &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; end_time &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt; elapsed_seconds &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot; data-end=&quot;1662&quot; data-start=&quot;1588&quot;&gt;
&lt;td style=&quot;height: 17px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1597&quot; data-start=&quot;1588&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot; data-end=&quot;1620&quot; data-start=&quot;1597&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2025-08-15 10:00:00&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot; data-end=&quot;1643&quot; data-start=&quot;1620&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2025-08-15 10:05:30&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot; data-end=&quot;1662&quot; data-start=&quot;1643&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;330&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot; data-end=&quot;1737&quot; data-start=&quot;1663&quot;&gt;
&lt;td style=&quot;height: 17px;&quot; data-col-size=&quot;sm&quot; data-end=&quot;1672&quot; data-start=&quot;1663&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot; data-end=&quot;1695&quot; data-start=&quot;1672&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2025-08-15 23:59:50&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot; data-end=&quot;1718&quot; data-start=&quot;1695&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;2025-08-16 00:00:10&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot; data-end=&quot;1737&quot; data-start=&quot;1718&quot; data-col-size=&quot;sm&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;20&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DataBase</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/530</guid>
      <comments>https://u-it.tistory.com/530#entry530comment</comments>
      <pubDate>Sat, 16 Aug 2025 01:04:42 +0900</pubDate>
    </item>
    <item>
      <title>[타입스크립트] React, Vue 등 FrontEnd에서 Domain 성격의 State를 Model(DTO) 클래스로 관리해보기</title>
      <link>https://u-it.tistory.com/529</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;FrontEnd에서 Domain 성격의 State를 Model(DTO) 클래스로 관리하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;JavaScript에서 Runtime시에만 발생되는 오류에 대한 해결책으로 Compile시점에 오류를 미리 잡을 수 있도록 TypeScript 문법이 탄생했다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;FrontEnd 뷰 템플릿을 사용하며 여러 의문이 들었다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt; &lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;FrontEnd&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt; Framework인 VueJS나 ReactJS Library 에서는 왜 DTO를 따로 쓰지 않는것일까? &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;TypeScript 뿐만 아니라 제대로 관리하려면 자바스크립트에서 최신으로 지원하는 클래스 문법도 사용해야 하는것 아닐까?&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바스크립트에서 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;신버전에&lt;/span&gt; 클래스 문법을 &lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;괜히&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;추가해서 제공해주는것이 아닐텐데?&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 진짜 제대로 관리할거면, 타입스크립트 타입 뿐만 아니라 도메인 성격을 띄우는 Object 타입의 State값들도 자바처럼 클래스로 관리 할 수 있도록 하는게 맞지않을까?&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;자바를 따라 객체를 관리하는것도, 재사용성 면에서는 장점이 있을거라 생각이 들었고 괜한 고집이 생겼다. &lt;/span&gt;&lt;/b&gt;&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: start;&quot;&gt;물론 FrontEnd는 BackEnd API, 디자인, 기획 등등 많은 범위에서 잦은 변경에 노출된다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&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: #8a3db6;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 때문에 타입스크립트만을 사용하는것으로 타협을 한것은 아닐까 생각이 든다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/b&gt;&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래도 구현 해볼래!&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/b&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Model로 관리할 DTO 클래스에 대한 타입 인터페이스를 선언해준다.&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1739702113046&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 도메인 성격이 강한 객체 타입 인터페이스 정의
 * Person.interface.ts파일로 관리
 */
export interface Person {
  name: string;
  age: number;
  getName(): string;
  setName(value: string): void;
  getAge(): number;
  setAge(value: number): void;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인터페이스를 실제로 구현할 구현체 DTO 클래스를 선언한다.&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멤버에 pravte 접근제한자를 적용한 뒤 getter setter를 선언함으로써 캡슐화를 적용한다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739702123928&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 도메인 성격이 강한 객체 클래스 정의
 * PersonDTO.class.ts파일로 관리
 */
import { Person } from './Person.interface';
export class PersonDTO implements Person{
  private _name: string; // _ prefix는 private 변수, 외부에서 접근하지 않음
  private _age: number; // _ prefix는 private 변수, 외부에서 접근하지 않음

  constructor(name: string = &quot;&quot;, age: number = 0) {
    this._name = name;
    this._age = age;
  }

  getName(): string {
    return this._name;
  }

  setName(value: string): void {
    this._name = value;
  }

  getAge(): number {
    return this._age;
  }

  setAge(value: number): void {
    this._age = value;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;React&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리액트의 경우 state의 변경을 감지 하기 위해서는 `&lt;span style=&quot;color: #8a3db6;&quot;&gt;새로운 객체&lt;/span&gt;`를 만들어서 변경된 필드에 대한 초기화를 진행하고 state에 set을 해줘야한다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;DTO로 관리되는 PersonDTO 객체는 private 접근제한자를 통해 캡슐화 되어있기 때문에 필드에 직접 접근할 수 없다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 new 인스턴스로 새로운 인스턴스를 생성하고, setter getter를 통해 필드에 접근하여 값을 초기화 해 준다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;JSX에서는 getter를 통해 접근한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739703843826&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 컴포넌트
 */
import { Person } from './Person.interface';
import { PersonDTO } from './PersonDTO.class';
import { useState, useEffect } from 'react';


export default function App() {

  const [person, setPerson] = useState&amp;lt;Person&amp;gt;(new PersonDTO(&quot;이름&quot;, 30));
  
  const onChangeName = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    const updatePerson = new PersonDTO(); // 새로운 참조 인스턴스 객체 생성
    updatePerson.setName(e.target.value); // Name만 직접 수정
    updatePerson.setAge(person.getAge()); // Age는 기존값 그대로 세팅
    setPerson(updatePerson); // 새로운 인스턴스객체로 초기화
  };
  
  const onChangeAge = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    const updatePerson = new PersonDTO(); // 새로운 참조 인스턴스 객체 생성
    updatePerson.setAge(e.target.value); // Age만 직접 수정
    updatePerson.setName(person.getName()); // Name은 기존값 그대로 세팅
    setPerson(updatePerson); // 새로운 인스턴스객체로 초기화
  };
  
  useEffect(()=&amp;gt; {
    console.log(&quot;person 객체 변경되었습니다.&quot;)
  }, [person])
  
  return (
	&amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;&amp;lt;p&amp;gt;이름: { person.getName() }&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;&amp;lt;p&amp;gt;나이: { person.getAge() }&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;이름 입력: &amp;lt;input type=&quot;text&quot; value={person.getName()} onChange={onChangeName}/&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;나이 입력: &amp;lt;input type=&quot;text&quot; value={person.getAge()} onChange={onChangeAge}/&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;REACT 전체 테스트용 코드&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739710167564&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useEffect } from 'react';

interface Person {
  name: string;
  age: number;
  getName(): string;
  setName(value: string): void;
  getAge(): number;
  setAge(value: number): void;
}

class PersonDTO implements Person{
  private _name: string; // _ prefix는 private 변수, 외부에서 접근하지 않음
  private _age: number; // _ prefix는 private 변수, 외부에서 접근하지 않음

  constructor(name: string = &quot;&quot;, age: number = 0) {
    this._name = name;
    this._age = age;
  }

  getName(): string {
    return this._name;
  }

  setName(value: string): void {
    this._name = value;
  }

  getAge(): number {
    return this._age;
  }

  setAge(value: number): void {
    this._age = value;
  }
}

export default function App() {

  const [person, setPerson] = useState&amp;lt;Person&amp;gt;(new PersonDTO(&quot;이름&quot;, 30));
  
  const onChangeName = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    const updatePerson = new PersonDTO(); // 새로운 참조 인스턴스 객체 생성
    updatePerson.setName(e.target.value); // Name만 직접 수정
    updatePerson.setAge(person.getAge()); // Age는 기존값 그대로 세팅
    setPerson(updatePerson); // 새로운 인스턴스객체로 초기화
  };
  
  const onChangeAge = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    const updatePerson = new PersonDTO(); // 새로운 참조 인스턴스 객체 생성
    updatePerson.setAge(e.target.value); // Age만 직접 수정
    updatePerson.setName(person.getName()); // Name은 기존값 그대로 세팅
    setPerson(updatePerson); // 새로운 인스턴스객체로 초기화
  };
  
  useEffect(()=&amp;gt; {
    console.log(&quot;person 객체 변경되었습니다.&quot;)
  }, [person])
  
  return (
	&amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;&amp;lt;p&amp;gt;이름: { person.getName() }&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;&amp;lt;p&amp;gt;나이: { person.getAge() }&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;이름 입력: &amp;lt;input type=&quot;text&quot; value={person.getName()} onChange={onChangeName}/&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;나이 입력: &amp;lt;input type=&quot;text&quot; value={person.getAge()} onChange={onChangeAge}/&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Vue2 - OptionsAPI&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;vue의 경우에도 private 접근레벨이기 때문에, template영역에서 직접 접근할 수 없다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;react와 마찬가지로 해당 DTO 클래스의 getter로 접근하여 보간법을 통해 값을 바인딩해야한다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;그러나 v-model에도 수정하기위한 필드를 getter로 접근하여 수정하는것은 불가능하다.&amp;nbsp;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;(@Input과 setter를 통해 v-model 대신 디테일하게 코드를 구성할경우에는 가능)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;따라서 v-model까지 고려한다면 computed를 통해 get과 set을 구현함으로써 객체를 직접 접근하지 않고, &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;한번 wrapping함으로써 객체의 값도 초기화 하고, 실제 출력하는 값은 computed를 통해 접근하는 원리로 사용하면 문제가 해결된다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739703895861&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;이름: {{ personName }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;p&amp;gt;나이: {{ personAge }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;이름 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personName&quot;/&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;나이 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personAge&quot;/&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script lang=&quot;ts&quot;&amp;gt;
import Vue from 'vue';
import { PersonDTO } from './PersonDTO.class';
import { Person } from './Person.interface';

export default {
  data() {
    return {
      // PersonDTO 객체로 초기화
      person: new PersonDTO(&quot;John&quot;, 30) as Person,
    };
  },
  computed: {
    personName: {
      get(): string {
        return this.person.getName();
      },
      set(value: string) {
        this.person.setName(value);
      },
    },
    personAge: {
      get(): number {
        return this.person.getAge();
      },
      set(value: number) {
        this.person.setAge(value);
      },
    },
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;전체 테스트용 코드&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739712185107&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;이름: {{ personName }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;p&amp;gt;나이: {{ personAge }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;이름 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personName&quot; /&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;나이 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personAge&quot; /&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script lang=&quot;ts&quot;&amp;gt;
import Vue from &quot;vue&quot;;

interface Person {
  name: string;
  age: number;
  getName(): string;
  setName(value: string): void;
  getAge(): number;
  setAge(value: number): void;
}

class PersonDTO implements Person {
  private _name: string; // _ prefix는 private 변수, 외부에서 접근하지 않음
  private _age: number; // _ prefix는 private 변수, 외부에서 접근하지 않음

  constructor(name: string = &quot;&quot;, age: number = 0) {
    this._name = name;
    this._age = age;
  }

  getName(): string {
    return this._name;
  }

  setName(value: string): void {
    this._name = value;
  }

  getAge(): number {
    return this._age;
  }

  setAge(value: number): void {
    this._age = value;
  }
}

// Vue.extend 방식을 사용하여 컴포넌트 정의
export default {
  data() {
    return {
      // PersonDTO 객체로 초기화
      person: new PersonDTO(&quot;John&quot;, 30) as Person,
    };
  },
  computed: {
    personName: {
      get(): string {
        return this.person.getName();
      },
      set(value: string) {
        this.person.setName(value);
      },
    },
    personAge: {
      get(): number {
        return this.person.getAge();
      },
      set(value: number) {
        this.person.setAge(value);
      },
    },
  },
};
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Vue3 - CompositionAPI (SETUP)&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;vue3도 vue2와 똑같이 computed를 사용해야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739707701294&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;이름: {{ personName }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;p&amp;gt;나이: {{ personAge }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;이름 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personName&quot;/&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;나이 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personAge&quot;/&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { reactive, computed } from &quot;vue&quot;;

import { Person } from &quot;./Person.interface&quot;;
import { PersonDTO } from &quot;./PersonDTO.class&quot;;

const person = reactive&amp;lt;Person&amp;gt;(new PersonDTO(&quot;John&quot;, 30));

// computed를 사용하여 캡슐화된 값에 접근
const personName = computed({
  get: () =&amp;gt; {
    return person.getName()
  },
  set: (value: string) =&amp;gt; {
    person.setName(value)
  }
});

const personAge = computed({
  get: () =&amp;gt; {
    return person.getAge()
  },
  set: (value: number) =&amp;gt; {
    person.setAge(value)
  }
});
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;전체 테스트용 코드&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1739712031888&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;이름: {{ personName }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;p&amp;gt;나이: {{ personAge }}&amp;lt;/p&amp;gt; &amp;lt;!-- computed의 get으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;이름 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personName&quot;/&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
    &amp;lt;div&amp;gt;나이 입력: &amp;lt;input type=&quot;text&quot; v-model=&quot;personAge&quot;/&amp;gt;&amp;lt;/div&amp;gt; &amp;lt;!-- computed의 set으로 접근 --&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { reactive, computed } from &quot;vue&quot;;

interface Person {
  name: string;
  age: number;
  getName(): string;
  setName(value: string): void;
  getAge(): number;
  setAge(value: number): void;
}
class PersonDTO implements Person{
  private _name: string; // _ prefix는 private 변수, 외부에서 접근하지 않음
  private _age: number; // _ prefix는 private 변수, 외부에서 접근하지 않음

  constructor(name: string = &quot;&quot;, age: number = 0) {
    this._name = name;
    this._age = age;
  }

  getName(): string {
    return this._name;
  }

  setName(value: string): void {
    this._name = value;
  }

  getAge(): number {
    return this._age;
  }

  setAge(value: number): void {
    this._age = value;
  }
}

const person = reactive&amp;lt;Person&amp;gt;(new PersonDTO(&quot;John&quot;, 30));

// computed를 사용하여 캡슐화된 값에 접근
const personName = computed({
  get: (): string =&amp;gt; {
    return person.getName()
  },
  set: (value: string) =&amp;gt; {
    person.setName(value)
  }
});

const personAge = computed({
  get: () =&amp;gt; {
    return person.getAge()
  },
  set: (value: number) =&amp;gt; {
    person.setAge(value)
  }
});
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;직접 코드로 구현해보고 느낀점은, 이러하다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각 컴포넌트의 State 값들은 UI중심적이기에 DTO처럼 모델이 강제된다면 DTO와 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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;캡슐화라는 필드 접근 제약 규칙으로 인해 &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;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;`React`에서는 새 객체를 만들어서 setter를 통해 객체를 초기화 해주고 추가로 state에 대한 set을 해줘야하며,&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;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;`Vue`에서는 Template 영역의 input 태그 v-model 디렉티브에 getter 함수를 바인딩 해준다는 것이 매커니즘적 측면에서 말이 안되는 행위고, 실제로 변경도 이루어지지 않기에 Computed로 Wrapping해줘야만 했다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서 한번 더 생각해본것은 API 호출시 Request용 파라미터 객체나 Response용 응답 객체를 DTO 클래스로 사용하는건 어떨까? 였다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 이 역시도 리터럴 객체 선언 후 매개변수 타입과 반환 타입에 타입스크립트의 interface나 type을 지정하는것이 클래스로 강제화 해서 관리하는것과 다를것 없지 않나? 하는 생각이 들었다.&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;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 이유 때문에 FrontEnd에서는 굳이 클래스로 까지 관리하지 않고 타입스크립트의 타입으로 손쉽게 객체를 관리하는것이 아닐까 하는 결론을 내렸다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Typescript</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/529</guid>
      <comments>https://u-it.tistory.com/529#entry529comment</comments>
      <pubDate>Sun, 16 Feb 2025 20:19:27 +0900</pubDate>
    </item>
    <item>
      <title>Error in [eslint] '_' is defined but never used (no-used-vars)</title>
      <link>https://u-it.tistory.com/528</link>
      <description>&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;javascript에서 변수 혹은 참조(모듈)등 선언 후 사용하지 않을 경우 위와같은 에러가 발생하며 아래와 같이 lint 옵션을 설정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;&lt;b&gt;.eslintrc.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735132252754&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  /* 생략 */
  rules: {
    'no-unused-vars': 'off'
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JavaScript</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/528</guid>
      <comments>https://u-it.tistory.com/528#entry528comment</comments>
      <pubDate>Wed, 25 Dec 2024 22:11:03 +0900</pubDate>
    </item>
    <item>
      <title>Error in [eslint] '_' is defined but never used (vue/no-used-vars)</title>
      <link>https://u-it.tistory.com/527</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt; &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;vue&lt;/span&gt; 에서 변수 혹은 참조(모듈, 컴포넌트)등 선언 후 사용하지 않을 경우 위와같은 에러가 발생하며 아래와 같이 lint 옵션을 설정한다.&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;color: #000000; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;.eslintrc.js&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1735048034223&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  /* 생략 */
  rules: {
    'vue/no-unused-vars': 'off'
  }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JavaScript/Vue2</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/527</guid>
      <comments>https://u-it.tistory.com/527#entry527comment</comments>
      <pubDate>Tue, 24 Dec 2024 22:47:46 +0900</pubDate>
    </item>
    <item>
      <title>[eslint] The &amp;quot;___&amp;quot; component has been registered but not use</title>
      <link>https://u-it.tistory.com/526</link>
      <description>&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;vue를 사용할때, 컴포넌트를 import 후 사용하지 않는 컴포넌트에 대해 런타임시&amp;nbsp; &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000; text-align: start;&quot;&gt;eslint에 의해&lt;span&gt; &lt;/span&gt;&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;해결방법은 아래와 같다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;./.eslintrc.js&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734883582115&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  /* 생략 */
  rules: {
  	/* 생략 */
    'vue/no-unused-components': 'off'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript/Vue2</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/526</guid>
      <comments>https://u-it.tistory.com/526#entry526comment</comments>
      <pubDate>Mon, 23 Dec 2024 01:07:23 +0900</pubDate>
    </item>
    <item>
      <title>[ESLint] Parsing error: Unexpected token import ___</title>
      <link>https://u-it.tistory.com/525</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;export한 module을 import 한 뒤 사용하지 않는 경우 발생하는 런타임 에러이다.&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 Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;해결방법은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;./.eslintrc.js&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1734882689404&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  /* 생략 */
  &quot;parserOptions&quot;: {
    &quot;ecmaVersion&quot;: 2015,
    &quot;sourceType&quot;: &quot;module&quot;
  },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript/Vue2</category>
      <author>유혁스쿨</author>
      <guid isPermaLink="true">https://u-it.tistory.com/525</guid>
      <comments>https://u-it.tistory.com/525#entry525comment</comments>
      <pubDate>Mon, 23 Dec 2024 00:51:39 +0900</pubDate>
    </item>
  </channel>
</rss>