<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>INFJ의 성장하는 습관</title>
    <link>https://infjin.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 7 May 2026 15:56:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>감성적인 개발자</managingEditor>
    <item>
      <title>모놀리식 아키텍처 vs MSA 시스템의 비교/장단점</title>
      <link>https://infjin.tistory.com/201</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 모놀리식 (&lt;b&gt;Monolithic&lt;/b&gt;)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식은 시스템의 &lt;b&gt;모든 비즈니스 로직이 하나의 서버에 의해 처리&lt;/b&gt;되는 방식으로 배포가쉽고 시스템을 빠르게 만들 수 있다는 장점이 있어 초기 서비스를 만들 때 유용한 방식이다. 하지만 시스템 규모가 커짐에 따라 다음과 같은 단점이 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일장애지점 : &lt;/b&gt;하나의 장애가 발생하면 시스템 전체에 영향을 미침&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트래픽 대응 : &lt;/b&gt;특정 서비스의 트래픽 증가시 대응하기 위해 모든 서비스(시스템 전체) 리소스 스케일링 필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기술에 종속적 : &lt;/b&gt;작은 기능을 수정 해도 전체 배포를 하기 때문에 테스트 시간이 많이 필요하고 오류가 자주 발생하여 신기술 도입 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. MSA (Micro Service Architecture)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모놀리식 아키텍처를 Microservice 단위로 쪼갠 후 독립적으로 구축하는 방식이다. 서비스가 대형화되고 클라우드 서비스가 활성화된 요즘 모놀리식 방식에서 MSA 방식으로 구조 변경을 진행하고있다.&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;개발인원 증가, 사용자 트래픽 대응을 위해 쿠팡은 2013년, 배달의 민족은 2016년에 기존 모놀리식 시스템을 AWS 를 이용한 MSA로 시스템을 전환하였다. 운영 중인 서비스가 급격하게 성장하고 있다면 MSA 도입을 고려해볼만하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RPcnr/btsHWccHrck/CYgNpmaCbeDZKP0dpYxvg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RPcnr/btsHWccHrck/CYgNpmaCbeDZKP0dpYxvg0/img.png&quot; data-alt=&quot;https://spring.io/cloud&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RPcnr/btsHWccHrck/CYgNpmaCbeDZKP0dpYxvg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRPcnr%2FbtsHWccHrck%2FCYgNpmaCbeDZKP0dpYxvg0%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;879&quot; height=&quot;491&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://spring.io/cloud&lt;/figcaption&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;b&gt;구성요소&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Microservice&lt;/b&gt;: 독립적으로 운영되는 Microservice (로그인/결제/배송/리뷰 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;API Gateway&lt;/b&gt;: 요청을 직접 받아 적절한 서비스로 요청을 포워딩&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모니터링서버&lt;/b&gt; : 각각의 서비스들의 상태를 관리하기 위한 서버&lt;/li&gt;
&lt;li&gt;&lt;b&gt;변수관리서버&lt;/b&gt; : 의존성이 강한 변수(DB정보 등)변경시 각각의 서비스의 환경변수 관리필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점 (독립적인&amp;nbsp;서비스&amp;nbsp;운영)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;트래픽&lt;/b&gt; : 특정 서비스에 트래픽이 증가했을 때, 해당 서비스에 대해서만 resource 스케일링을 해줌으로써 트래픽 대응에 대한 자원낭비 방지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;신뢰성&lt;/b&gt; : 특정 서비스에 장애가 발생해도 전체 서비스 실행이 멈추지 않고 운영가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt; : 전체 시스템에 영향을 주지않고도 서비스별로 적절한 라이브러리, 언어, 서버도입 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 인프라 구성 난이도가 높고 서비스가 커질 수록 복잡도가 늘어날 수 있다.&lt;/li&gt;
&lt;li&gt;특정 서비스가 동작안할 경우에도 시스템이 동작함으로서 사용자 혼란 발생 가능 (쇼핑몰에서 로그인 서비스만 다운되고 쇼핑은 여전히 가능할 경우 등)&lt;/li&gt;
&lt;li&gt;별개로 동작하는 Microservice 서비스 관리 및 서비스간 API 호출 비용문제를 고려해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BnS6343GTkY&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MSA, 마이크로서비스 여행기 정리 (배달의 민족)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/coupang-engineering/how-coupang-built-a-microservice-architecture-fd584fff7f2b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;마이크로서비스 아키텍처로의 전환 (쿠팡)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=CM47-1UpgOc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AWS 기반 서버리스 마이크로 서비스 구현 사례 (빙글)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science/Etc</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/201</guid>
      <comments>https://infjin.tistory.com/201#entry201comment</comments>
      <pubDate>Wed, 12 Jun 2024 13:32:40 +0900</pubDate>
    </item>
    <item>
      <title>넥사크로 시간마다 함수 실행시키기 (onTimer/setTimer/killTimer)</title>
      <link>https://infjin.tistory.com/200</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;화면 구성&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oW5Wa/btsGqVkhwv2/MApyR0eF2dJ5gnZGp0pEk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oW5Wa/btsGqVkhwv2/MApyR0eF2dJ5gnZGp0pEk0/img.png&quot; data-alt=&quot;버튼 구성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oW5Wa/btsGqVkhwv2/MApyR0eF2dJ5gnZGp0pEk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoW5Wa%2FbtsGqVkhwv2%2FMApyR0eF2dJ5gnZGp0pEk0%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;613&quot; height=&quot;260&quot; data-origin-width=&quot;613&quot; data-origin-height=&quot;260&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;버튼 구성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Script&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1712554405452&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//좌측버튼
this.ExecSetTimerButton_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo){
	console.log('&amp;gt;&amp;gt; setTimer function');
	this.setTimer(1, 1000);
};

//우측버튼
this.ExecKillTimerButton_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo){
	console.log('&amp;gt;&amp;gt; killTimer function');
	this.killTimer(1);
};

//ontimer 함수
this.화면ID_ontimer = function(obj:nexacro.Form,e:nexacro.TimerEventInfo){
	console.log('&amp;gt;&amp;gt; ontimer function');
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;186&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbSOun/btsGshfOvBr/aItQtnkw1HjhTM6q9ImGzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbSOun/btsGshfOvBr/aItQtnkw1HjhTM6q9ImGzK/img.png&quot; data-alt=&quot;콘솔창&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbSOun/btsGshfOvBr/aItQtnkw1HjhTM6q9ImGzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbSOun%2FbtsGshfOvBr%2FaItQtnkw1HjhTM6q9ImGzK%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;186&quot; height=&quot;65&quot; data-origin-width=&quot;186&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;콘솔창&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/nexacro</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/200</guid>
      <comments>https://infjin.tistory.com/200#entry200comment</comments>
      <pubDate>Mon, 8 Apr 2024 14:35:59 +0900</pubDate>
    </item>
    <item>
      <title>구글메일(gmail)에서 Base64 형태의 이미지 안보이는 경우 (Java SMTP, HTML 전송)</title>
      <link>https://infjin.tistory.com/198</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;메일전송 과정&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;블로그.png&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mkMfl/btsEXtVf0nl/igqfuRxweSq1wWkjB0vdz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mkMfl/btsEXtVf0nl/igqfuRxweSq1wWkjB0vdz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mkMfl/btsEXtVf0nl/igqfuRxweSq1wWkjB0vdz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmkMfl%2FbtsEXtVf0nl%2FigqfuRxweSq1wWkjB0vdz0%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;1181&quot; height=&quot;655&quot; data-filename=&quot;블로그.png&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면의 HTML(스트링)을 gmail로 전송했을 때 &lt;b&gt;텍스트는 보이지만 이미지는 보이지 않는&lt;/b&gt; 현상이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java의 &lt;b&gt;SMTP&lt;/b&gt;를 이용해 전송했으며, 콘솔창을 통해 &amp;lt;img&amp;gt; 태그의 내용(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;메일의 body&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;letter-spacing: 0px;&quot;&gt;개발자 도구를 통해 메일을 확인해보니 이미지 영역의 태그는 존재하지만 &amp;lt;img alt=&quot;&quot;&amp;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;&amp;nbsp;gmail 뿐만 아니라 대부분의 메일 서비스에서는 Base64를 통한 이미지 표시를 지원하지 않는다고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Base64 이미지를 담은 &lt;b&gt;html 파일&lt;/b&gt;을 &lt;b&gt;첨부파일&lt;/b&gt;로 보내도 브라우저에서는 &lt;b&gt;확인 불가능&lt;/b&gt;하며 사용자의 PC에 해당 html 파일을 직접 다운로드 해야만 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;Gmail doesn't support adding images as Base64 strings inside HTML&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;img&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;tags.&lt;/span&gt; &lt;/span&gt;&lt;/blockquote&gt;
&lt;figure id=&quot;og_1707978908989&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Send GMail message with attachment&quot; data-og-description=&quot; &quot; data-og-host=&quot;learn.torq.io&quot; data-og-source-url=&quot;https://learn.torq.io/docs/send-gmail-message-with-image&quot; data-og-url=&quot;https://learn.torq.io/docs/send-gmail-message-with-image&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cKyzGv/hyVjfA2XNm/2X3tfpj5VVWTRsQzCLAsH0/img.png?width=1058&amp;amp;height=1023&amp;amp;face=0_0_1058_1023&quot;&gt;&lt;a href=&quot;https://learn.torq.io/docs/send-gmail-message-with-image&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.torq.io/docs/send-gmail-message-with-image&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cKyzGv/hyVjfA2XNm/2X3tfpj5VVWTRsQzCLAsH0/img.png?width=1058&amp;amp;height=1023&amp;amp;face=0_0_1058_1023');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Send GMail message with attachment&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.torq.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science/Etc</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/198</guid>
      <comments>https://infjin.tistory.com/198#entry198comment</comments>
      <pubDate>Thu, 15 Feb 2024 13:53:08 +0900</pubDate>
    </item>
    <item>
      <title>SQL 쿼리 실행순서 정리</title>
      <link>https://infjin.tistory.com/196</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. FROM&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블이 존재하는지 USER가 해당 테이블에 대한 SELECT 권한이 있는지 먼저 체크한다. 만약 JOIN절이 존재한다면, JOIN을 먼저 수행한다음 FROM절을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. WHERE&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN 혹은 FROM 절에서 가져온 테이블에서 조건에 맞는 ROW 만을 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. GROUP BY&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명시한 컬럼들을 그룹화하며, 집계함수를 이용할 때 주로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. HAVING&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GROUP BY 절이 존재한다면 다음으로 HAVING 절을 수행한다. WHERE 절과 마찬가지로 조건에 맞는 ROW를 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WHERE절은 테이블 상의 모든 필드에 조건에 두지만, HAVING은 GROUP BY에 의해 그룹화된 결과에 대해 조건을 준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705745147802&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;--WHERE 절 사용
SELECT department, salary
FROM   employees
WHERE  salary&amp;gt; 2000;

--HAVING절 사용 
SELECT department, AVG(salary) as avg_salary
FROM   employees
GROUP  BY department
HAVING AVG(salary) &amp;gt; 50000;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. SELECT&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가져온 데이터에서 어떤 행을 출력할지 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. DISTINCT&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복행을 필터링해서 고유한 행만을 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. ORDER BY&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 열을 기준으로 오름차순 또는 내림차순 정렬한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 거의 마지막에 실행되므로 SELECT 절에서 ALIAS를 준 컬럼이 있다면 ORDER BY 절에서 사용 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. LIMIT&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #252525; text-align: start;&quot;&gt;LIMIT 내에 속하는 행만 표시하도록 제한한다. 이전에 평가된 많은 행 중 특정 행만 LIMIT하는 것은 효율적이지 않으므로 권장되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.geeksforgeeks.org/order-of-execution-of-sql-queries/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.geeksforgeeks.org/order-of-execution-of-sql-queries/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Programming/SQL</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/196</guid>
      <comments>https://infjin.tistory.com/196#entry196comment</comments>
      <pubDate>Wed, 17 Jan 2024 23:11:17 +0900</pubDate>
    </item>
    <item>
      <title>[넥사크로] 그리드 Head클릭시 체크박스 전체선택 이벤트 추가</title>
      <link>https://infjin.tistory.com/195</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 그리드 headclick 이벤트 추가 &lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5q2vT/btsyOWh9tFQ/ZQugui7HBd1kRZnOrbwWYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5q2vT/btsyOWh9tFQ/ZQugui7HBd1kRZnOrbwWYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5q2vT/btsyOWh9tFQ/ZQugui7HBd1kRZnOrbwWYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5q2vT%2FbtsyOWh9tFQ%2FZQugui7HBd1kRZnOrbwWYK%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;353&quot; height=&quot;256&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 이벤트 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;head의 체크박스 인덱스는 0번째라고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697680836577&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.Grid00_onheadclick = function(obj:nexacro.Grid,e:nexacro.GridClickEventInfo){
    try {
        if (e.col == 0) { /*체크박스 인덱스 : 0*/ 
        var checkValue;

        if (obj.getCellProperty(&quot;Head&quot;, 0, &quot;text&quot;) == &quot;1&quot;) {
            obj.setCellProperty(&quot;Head&quot;, 0, &quot;text&quot;, &quot;0&quot;);  //체크해제 
            checkValue = &quot;0&quot;;
        } else {
            obj.setCellProperty(&quot;Head&quot;, 0, &quot;text&quot;, &quot;1&quot;);  //체크
            checkValue = &quot;1&quot;;
        }

        this.Dataset00.set_enableevent(false);
        for(var i = 0; i &amp;lt; this.Dataset00.rowcount; i++){
            this.Dataset00.setColumn(i, &quot;CHK&quot; /*체크박스 컬럼*/ , checkValue);
        }
        this.Dataset00.set_enableevent(true);	
        }
    } catch (err) {
    //exception 처리
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이벤트 처리 전후 비교&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onheadclick 이벤트를 지정해주지 않아도 해당기능을 구현하는데는 문제가 없지만 그리드의 데이터 건수가 많아진다면 이벤트 처리에 많은 자원이 사용된다. setColumn()으로 데이터셋의 컬럼이 변경되면 기본적으로 다음과 같은 이벤트가 call된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- cancolumnchange&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- oncolumnchanged&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- onvaluechanged&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 지정하는 과정에서 해당 이벤트가 필요하지 않다면, 반복문에 진입하기 전 enableevent를 false로 변경해 처리속도 향상을 기대해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img style=&quot;max-width: 100%; height: auto;&quot; src=&quot;https://drive.google.com/uc?export=view&amp;amp;id=1MIaFj9UmCTVE0jd13f5dJRn9xtcm5xjm&quot; /&gt;&lt;/p&gt;</description>
      <category>Programming/nexacro</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/195</guid>
      <comments>https://infjin.tistory.com/195#entry195comment</comments>
      <pubDate>Thu, 19 Oct 2023 11:06:07 +0900</pubDate>
    </item>
    <item>
      <title>브라우저에 URL을 입력하면 일어나는 일</title>
      <link>https://infjin.tistory.com/194</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 브라우저 주소창에 &lt;a href=&quot;http://www.naver.com&quot;&gt;www.naver.com&lt;/a&gt;을 입력&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 캐시탐색&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS를 이용해 호스트네임(naver.com)의 서버로 접근하기 위한&amp;nbsp; IP주소를 얻을 수 있다. 그 전에&amp;nbsp;클라이언트 측에 저장된 캐시를 탐색한다. 만약 캐시에 IP 정보가 있다면 DNS로 요청을 하지 않아도 되므로 로딩속도 향상을 기대할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 탐색은 (브라우저 캐시 &amp;rarr; OS 캐시 &amp;rarr; 라우터캐시 &amp;rarr; ISP캐시) 순으로 순차적으로 이루어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;브라우저 캐시&lt;/b&gt;: 이미 방문한 웹 페이지의 데이터를 저장한 캐시이다. 캐시가 존재하면 서버로부터 데이터를 다운로드하지 않고 바로 표시할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OS 캐시&lt;/b&gt;: 운영체제 수준에서 캐시가 구성되어 있는 캐시이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라우터 캐시&lt;/b&gt;: 라우터(공유기)에 저장된 캐시로 DNS 정보 및 웹 페이지 데이터의 일부가 캐시될 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ISP 캐시&lt;/b&gt;: 사용자의 인터넷 서비스 제공업체(ISP)에서도 DNS 정보 및 웹 페이지 데이터를 캐시로 저장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://infjin.tistory.com/174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;- 캐시와 조건부 요청에 관한 글&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. DNS에 IP 주소 요청&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(캐시를 이용할 수 없다면) 로컬 DNS 서버로 요청을 보낸다. 로컬 DNS서버는 루트, TLD, 책임 DNS 서버 등을 거쳐 클라이언트에게 서버의 IP주소를 응답해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://infjin.tistory.com/179&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;- DNS(Domain Name System)의 구조와 동작 원리 참조&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. TCP/IP 연결 (3-way handshaking)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 전송 전 클라이언트-서버 간의 연결이 이루어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. Request(요청)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서버간 연결이 완료되면 클라이언트는 서버로 요청을 보낸다. 클라이언트는 요청이 손실, 변조 되거나 누군가 보는 것을 방지하기 위해 HTTPS/TLS를 이용해 요청을 암호화 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 로드밸런싱&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 시스템의 경우 다수의 서버를 두고 운영할 수 있다. 현재 서버들의 트래픽, 클라이언트의 위치(서울, 부산, 도쿄 등) 등을 고려하여&amp;nbsp;운영 중인 서버 중 클라이언트의 요청을 처리하기 가장 적합한 서버를 찾아 요청을 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. Response(응답)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 요청을 분석하여 필요한 파일, DB정보를 응답 메세지에 담아 HTTP 상태 코드와 함께 Response해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://infjin.tistory.com/169&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;- HTTP 상태코드에 관한 글&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. 브라우저 렌더링&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 서버로 부터 받은 HTML을 이용해 렌더링을한다. 브라우저는 빠르게 응답하기 위해 서버로부터 데이터를 모두 받기 전에도 렌더링을 시작하며 필요한 이미지나 파일이 있을 경우 추가로 요청을 할 수 있다.&lt;/p&gt;</description>
      <category>Computer Science/Network</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/194</guid>
      <comments>https://infjin.tistory.com/194#entry194comment</comments>
      <pubDate>Tue, 29 Aug 2023 22:38:57 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle] Spring, Mybatis에서 대량 데이터  INSERT 수행. multirow, 다건 삽입 수행하기</title>
      <link>https://infjin.tistory.com/192</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대량 데이터 삽입 로직&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;895&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oARup/btsnpFZ7Ru3/0arrrODnyu9OJvhrKlqfhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oARup/btsnpFZ7Ru3/0arrrODnyu9OJvhrKlqfhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oARup/btsnpFZ7Ru3/0arrrODnyu9OJvhrKlqfhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoARup%2FbtsnpFZ7Ru3%2F0arrrODnyu9OJvhrKlqfhK%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;1806&quot; height=&quot;895&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;895&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 입력시 보통 단건 또는 10건 미만으로 입력이 들어오기 때문에 입력되는 ROW수 만큼 INSERT쿼리 호출이 이루어진다. 그러나&amp;nbsp;1만 8천 건 정도 되는 데이터를 한 번에 INSERT 하니, &lt;b&gt;수행 시간이 5분&lt;/b&gt; 이상 소요되었고&amp;nbsp;이대로는 사용할 수 없다고 판단했다.(오류는 나지 않더라도 예외 메세지가 뜨면서 통신이 끊어진것 처럼 보였다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;일반적으로 사용했던 INSERT 로직 (대용량 INSERT에 부적합)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;사용자가 이용할 수 없는 속도를 보여줬기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;삽입하는 ROW 수만큼 쿼리를 호출해 수행하는 기존 INSERT 로직을 이용할 순 없었다.&amp;nbsp; &lt;/b&gt;&lt;span style=&quot;color: #dddddd;&quot;&gt;&lt;s&gt;나중에 들어보니 AS-IS(기존 시스템)에선 10분넘게 걸렸었다고한다...&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698069050579&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//paramList : 화면단에서 넘겨받은 Data
for (Map&amp;lt;String, Object&amp;gt; paramMap : paramList) { 
    dao.insert();
    //쿼리호출 : INSERT INTO TABLE (...) VALUES (...) x row 수 만큼 수행
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; UNION ALL을 이용한 INSERT&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1689161893536&quot; class=&quot;sql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO [TABLE] (
    COL1, COL2, COL3
)
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL UNION ALL
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL UNION ALL
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL UNION ALL
...
SELECT 'VALUE1', 'VALUE2', 'VALUE3' FROM DUAL&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SELECT&lt;/b&gt;문과 &lt;b&gt;UNION ALL&lt;/b&gt;을 활용하면 INSERT 쿼리 한 번에 다건의 ROW를 삽입하면서 속도 향상을 기대해 볼 수 있었기에 해당 쿼리를 사용해 &lt;b&gt;하나의 요청&lt;/b&gt;마다&lt;b&gt; 200건&lt;/b&gt;씩 INSERT를 수행하기로 헀다. 여기에 사용될 SELECT문은 Service단에서 만들어서 dao 쿼리 호출 시에 인자로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;*INSERT ALL 쿼리도 있었지만 UNION ALL을 사용하는 방법이 속도면에서 더 빠르다기에 이 방법을 선택했다.&lt;/i&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Mybatis xml&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1689148020906&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; &amp;lt;insert id=&quot;insertQueryId&quot; parameterType=&quot;java.util.HashMap&quot;&amp;gt;
    INSERT INTO [TABLE] (
        COL1, COL2, COL3
    )
    ${QUERY}
&amp;lt;/insert&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SELECT 문에 작은 따옴표('VALUE')를 사용해야 했기 때문에 파라미터 사용 시&lt;b&gt; #{} 대신 ${}&lt;/b&gt;을 선택했다.&amp;nbsp; ${}을 사용하면 &lt;b&gt;SQL Injection&lt;/b&gt; 공격에 취약해질 수 있지만, 이 경우 &lt;u&gt;데이터가 다수의 사용자에 의해 입력/수정/삭제되지 않으며, 한정된 사용자(1~2명)의 파일 업로드를 통해 생성&lt;/u&gt;되므로 보안적인 측면에서는 상대적으로 낮은 위험성을 가진다고 판단했다.&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Java - Service단&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1689149507865&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Save 로직 일부

List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; dataListSlice = new ArrayList&amp;lt;&amp;gt;();

for (int i = 0; i &amp;lt; paramList.size(); i++) {
    Map&amp;lt;String, Object&amp;gt; element = paramList.get(i);
    dataListSlice.add(element);
    
    // 200개씩 끊어서 입력
    if ((i + 1) % 200 == 0 || i == paramList.size() - 1) {
        Map&amp;lt;String, Object&amp;gt; insertParams = new HashMap&amp;lt;&amp;gt;();
        String insertQuery = generateInsertQuery(dataListSlice);
        insertParams.put(&quot;QUERY&quot;, insertQuery);
        dao.insert(&quot;namespace.insertQueryId&quot;, insertParams);
        dataListSlice.clear();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1689162661588&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String generateInsertQuery(List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; data) {
    StringBuilder queryBuilder = new StringBuilder();
    String insertValueList;
    
    for (int i = 0; i &amp;lt; data.size(); i++) {
        Map&amp;lt;String, Object&amp;gt; dataMap = data.get(i);
        insertValueList = &quot;'&quot; + dataMap.get(&quot;COL1&quot;) + &quot;'&quot; +
                          &quot;, '&quot; + dataMap.get(&quot;COL2&quot;) + &quot;'&quot; +
                          &quot;, '&quot; + dataMap.get(&quot;COL3&quot;) + &quot;'&quot;;

        if (i == data.size() - 1) {
            queryBuilder.append(&quot;SELECT &quot; + insertValueList + &quot; FROM DUAL&quot;);
        } else {
            queryBuilder.append(&quot;SELECT &quot; + insertValueList + &quot; FROM DUAL UNION ALL\n&quot;);
        }
    }
    return queryBuilder.toString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;${QUERY} 파라미터에 전달할 값(SELECT ... FROM DAUL UNION ALL)을 Java단에서 만들어주는 로직이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 데이터를 200개씩 끊어서 INSERT 쿼리를 호출하니 &lt;b&gt;18000&lt;/b&gt;건 정도의 데이터를 입력하는데 &lt;b&gt;5분 &amp;rarr; 30초&lt;/b&gt; 정도로 시간이 단축됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;대량 데이터를 INSERT 하는 방법2&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;INSERT 처리속도를 의미있게 향상시켰지만 ${} 태그를 사용하여 개발한 것이 만족스럽진 않았다. 그러나 Dao단을 추가하여 작업할 정도로 많은 시간을 투자할만한 화면이 아니었고 ${} 태그를 사용하는 데 있어 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;상대적으로&lt;span&gt; 덜 위험한 환경이라 판단했기 때문에 ${} 태그를 이용했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;[추가] &lt;/b&gt;Java단에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;#{COL1_1} ~ #{COL1_200}&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;까지 인자만 생성해주고 &lt;b&gt;xml에 200개의 SELECT UNION ALL&lt;/b&gt;을 작성해주면 &lt;b&gt;${}&lt;/b&gt; 태그를 사용하지 않아도 된다.&lt;/span&gt;&lt;/i&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: #333333; text-align: start;&quot;&gt;조금 더 정석적인 방법으로는 다음과 같다.&lt;span&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Map을 대체할 객체 생성 :&lt;/b&gt; Map의 KEY 항목에 해당하는 필드를 가지는 객체 생성&lt;/p&gt;
&lt;pre id=&quot;code_1689164167129&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Data {
    private String COL1;
    private String COL2;
    private String COL3;
    
    // getter and setter 
    
    // 생성자
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 데이터 처리 :&lt;/b&gt; Data 항목에 컬럼들을 할당하고 List&amp;lt;Data&amp;gt;에 해당 Data를 add&lt;/p&gt;
&lt;pre id=&quot;code_1689164221445&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;Data&amp;gt; dataList = new ArrayList&amp;lt;&amp;gt;();

for (Map&amp;lt;String, Object&amp;gt; paramMap : paramList) {
    Data data = new Data();
    data.setCOL1((String) paramMap.get(&quot;COL1&quot;));
    // ... 다른 필드들 설정
    
    dataList.add(data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. DAO 매서드 수정 :&lt;/b&gt; DAO 메서드의 파라미터를 List&amp;lt;Data&amp;gt; 형태로 수정하여 전달&lt;/p&gt;
&lt;pre id=&quot;code_1689164275932&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public int insert_NEW(List&amp;lt;Data&amp;gt; dataList) {
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Mybatis XML 수정 :&lt;/b&gt; Mybatis XML 상에선 foreach 태그를 이용해 다중 INSERT&lt;/p&gt;
&lt;pre id=&quot;code_1689164339075&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;insert id=&quot;insertQueryId&quot; parameterType=&quot;java.util.List&quot;&amp;gt;
    INSERT INTO [테이블] (
        COL1, COL2, COL3
    )
    VALUES
    &amp;lt;foreach collection=&quot;list&quot; item=&quot;item&quot; separator=&quot;,&quot;&amp;gt;
        (
            #{item.COL1}, #{item.COL2}, #{item.COL3}
        )
    &amp;lt;/foreach&amp;gt;
&amp;lt;/insert&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/SQL</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/192</guid>
      <comments>https://infjin.tistory.com/192#entry192comment</comments>
      <pubDate>Wed, 12 Jul 2023 22:01:36 +0900</pubDate>
    </item>
    <item>
      <title>대용량 데이터 이관시 안정성 확보하기 (데이터분리, 인덱싱)</title>
      <link>https://infjin.tistory.com/191</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wah6n/btskgIMNGr3/qlBRvRPURDiFrP4dY4NtuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wah6n/btskgIMNGr3/qlBRvRPURDiFrP4dY4NtuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wah6n/btskgIMNGr3/qlBRvRPURDiFrP4dY4NtuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwah6n%2FbtskgIMNGr3%2FqlBRvRPURDiFrP4dY4NtuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1322&quot; height=&quot;745&quot; data-origin-width=&quot;1322&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차세대 프로젝트에서 데이터 이관작업을 위해 AS-IS 테이블을 ①우리 개발 DBMS로 그대로 밀어 넣은 다음, ②프로시저를 돌려 개발에 사용되는 테이블에 데이터를 밀어 넣는 작업을 하는 중 DB가 뻗어버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 1000만건이 넘어가고 다른 프로시저들이 함께 돌아가다 보니 프로시저를 한 번에 실행하기 어려웠던 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로시저 분리 및 인덱스 추가&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1341&quot; data-origin-height=&quot;757&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX8GKH/btskg9cUK1R/fjxAKHaTcC9D6s3N5By0A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX8GKH/btskg9cUK1R/fjxAKHaTcC9D6s3N5By0A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX8GKH/btskg9cUK1R/fjxAKHaTcC9D6s3N5By0A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX8GKH%2Fbtskg9cUK1R%2FfjxAKHaTcC9D6s3N5By0A1%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;1341&quot; height=&quot;757&quot; data-origin-width=&quot;1341&quot; data-origin-height=&quot;757&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P1RHU/btskie5453X/kvSDpv50M1iby0i42F80V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P1RHU/btskie5453X/kvSDpv50M1iby0i42F80V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P1RHU/btskie5453X/kvSDpv50M1iby0i42F80V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP1RHU%2Fbtskie5453X%2FkvSDpv50M1iby0i42F80V0%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;710&quot; height=&quot;397&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 테이블 데이터를 조건별로 데이터를 끊어서 밀어넣기로 했다. 그렇다 해도 최대 70만 건의 row를 조회하기 때문에 속도개선을 위해 SELECT 문에 조인 조건으로 사용 중인 컬럼들에 &lt;b&gt;인덱스&lt;/b&gt;를 추가했다. (기간에 해당하는 컬럼은 기존에 조인조건에도 포함되었기 때문에 인덱스로 같이 잡혔다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzllZ9/btskgcBdu1M/PNqREJ52eSOvrpGQcGnzAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzllZ9/btskgcBdu1M/PNqREJ52eSOvrpGQcGnzAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzllZ9/btskgcBdu1M/PNqREJ52eSOvrpGQcGnzAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzllZ9%2FbtskgcBdu1M%2FPNqREJ52eSOvrpGQcGnzAk%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;1330&quot; height=&quot;741&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 데이터 건수가 작아지다 보니 실행 시간이 단축된 게 느껴졌다. 또한 일부 데이터는 PK중복 등 오류로 인해 전송되지 않기도 했는데 만약 프로시저를 한 번에 돌렸다가 오류가 났다면 모든 데이터를 처음부터 다시 이관해야 했을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 이관시 건수가 많거나 용량이 클 경우, 테이블 구조를 파악하는데 시간을 쓰더라도 대용량 데이터를 한 번에 이관하는 방식은 지양하는 것이 바람직해 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming/Etc</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/191</guid>
      <comments>https://infjin.tistory.com/191#entry191comment</comments>
      <pubDate>Sun, 18 Jun 2023 16:04:18 +0900</pubDate>
    </item>
    <item>
      <title>넥사크로 그리드 포커스 해제, row 선택 해제하기</title>
      <link>https://infjin.tistory.com/189</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QKo6s/btscIKyJc7Q/AUre0BZ27IkkgKVGs91Ck1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QKo6s/btscIKyJc7Q/AUre0BZ27IkkgKVGs91Ck1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QKo6s/btscIKyJc7Q/AUre0BZ27IkkgKVGs91Ck1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQKo6s%2FbtscIKyJc7Q%2FAUre0BZ27IkkgKVGs91Ck1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;630&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 화면에 그리드가 몰려있는 경우, &lt;b&gt;모든 그리드의 BODY 영역의 row가 선택&lt;/b&gt;된 상태로 있기 때문에 보기 안좋을 때가 있다. &lt;b&gt;Grid.selectRow()&lt;/b&gt; 메서드를 이용해 N개 의 그리드가 있을 때도 &lt;u&gt;&lt;b&gt;하나의 그리드의 row만 선택&lt;/b&gt;&lt;/u&gt;할 수 있도록 구현해보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;* selectRow()메서드는 그리드의 selecttype 속성값이 &quot;row&quot; 또는 &quot;multirow&quot; 인 경우만 동작한다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;초기 포커스 해제하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회직후 콜백단에서 그리드 row를 선택하지 않도록 해준다. selectRow에 rowIndex 인자로 전달되는 -1 인덱스는 존재하지 않아 row가 선택되지 않지만 &lt;u&gt;기존에 선택되어있는 row를 선택해제 시켜준다.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;633&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF26om/btscHR5Xeu2/uBLJ9laD5JFa3zZnjIIdA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF26om/btscHR5Xeu2/uBLJ9laD5JFa3zZnjIIdA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF26om/btscHR5Xeu2/uBLJ9laD5JFa3zZnjIIdA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF26om%2FbtscHR5Xeu2%2FuBLJ9laD5JFa3zZnjIIdA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;633&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1682489716946&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.init_GridFocus = function(objGrid){
    if(typeof(objGrid) == &quot;object&quot;) {
        objGrid.selectRow(-1);
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;onkillfocus 이벤트 지정해주기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드 포커스가 나갈 때 발생되는 &lt;b&gt;onkillfocus&lt;/b&gt; 이벤트에 selectRow를 수행하도록 하면, 기존에 선택되어있던 그리드의 row 선택이 해제된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGdZQ/btscQxxA2Sn/WjA0t4kQC8lKlc3aBPOr7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGdZQ/btscQxxA2Sn/WjA0t4kQC8lKlc3aBPOr7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGdZQ/btscQxxA2Sn/WjA0t4kQC8lKlc3aBPOr7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGdZQ%2FbtscQxxA2Sn%2FWjA0t4kQC8lKlc3aBPOr7K%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;461&quot; height=&quot;125&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1682490051302&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;this.gridFocusOut = function(obj:nexacro.Grid,e:nexacro.KillFocusEventInfo){
        if(typeof(obj) == &quot;object&quot;) {
            obj.selectRow(-1);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img style=&quot;max-width: 100%; height: auto;&quot; src=&quot;https://drive.google.com/uc?export=view&amp;amp;id=1AuLr7TsGNFGtvsWilsMSKlSR53idIcEx&quot; /&gt;&lt;/p&gt;</description>
      <category>Programming/nexacro</category>
      <category>넥사크로</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/189</guid>
      <comments>https://infjin.tistory.com/189#entry189comment</comments>
      <pubDate>Wed, 26 Apr 2023 15:23:50 +0900</pubDate>
    </item>
    <item>
      <title>[Oracle]DB 인덱스 정의, 특징, 주의사항</title>
      <link>https://infjin.tistory.com/187</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블에 저장되어있는 데이터를 스캔할 때 &lt;b&gt;table full scan&lt;/b&gt;을 수행하면 &lt;b&gt;O(n)&lt;/b&gt;만큼의 시간복잡도를 가진다. 저장된 데이터가 많아질 수록 검색속도가 느려지게 되는데, 이 때 &lt;b&gt;인덱스&lt;/b&gt;를 이용하면 검색속도를 향상시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덱스 생성&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 자동으로 생성되는 인덱스와 사용자가 수동으로 생성하는 인덱스가 있다. 대부분의 RDBMS에서는 테이블 정의에서 PRIMARY KEY 또는 UNIQUE 제약 조건을 정의하면 인덱스가 자동으로 생성되는데 이를 &lt;b&gt;고유 인덱스&lt;/b&gt;라고 하며, 사용자가 직접 생성한 인덱스를 &lt;b&gt;비고유 인덱스&lt;/b&gt;라고한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생성쿼리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1677305751579&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE [UNIQUE][BITMAP]INDEX 인덱스이름 ON 테이블 (column[, column]...)&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;인덱스가 기반으로 하는 열의 값이 고유해야 함을 나타내려면 UNIQUE를 지정하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 행을 별도로 인덱스화하지 않고 각 구분 키에 대한 비트맵을 사용하여 인덱스가 생성되도록 하려면 BITMAP을 지정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 생성된 &lt;b&gt;인덱스는 수정할 수 없으므로&lt;/b&gt; 인덱스를 변경하려면 삭제 후 다시 생성해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 인덱스만으로 처리하기 힘든 대용량 데이터베이스 에서는 두 개 이상의 컬럼을 하나의 인덱스에 지정해서 생성하기도 하는데 이를 &lt;b&gt;결합인덱스&lt;/b&gt;라고 한다. &lt;a href=&quot;http://www.gurubee.net/lecture/2229&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(결합인덱스 지정 순서에 관한 글 참조)&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MeJ7g/btr1lvrnT4l/soBLQwHkKkP7KEHto7B3KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MeJ7g/btr1lvrnT4l/soBLQwHkKkP7KEHto7B3KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MeJ7g/btr1lvrnT4l/soBLQwHkKkP7KEHto7B3KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMeJ7g%2Fbtr1lvrnT4l%2FsoBLQwHkKkP7KEHto7B3KK%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;785&quot; height=&quot;355&quot; data-origin-width=&quot;1517&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;*이해를 돕기위한 예시이며 실제 인덱스는 행열 형태가 아닌 B-Tree 또는 B+Tree 형태로 저장되어있다.&lt;/i&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;b&gt;①인덱스에서 데이터를 찾은 다음&lt;/b&gt; &lt;b&gt;&amp;rarr; ②테이블에 접근해서 데이터를 가져온다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 기본적으로 정렬되어 있기 때문에 B-tree 혹은 B+tree와 같은 탐색기법으로 O(logN)의 속도로 탐색 가능하며, 인덱스에 저장된 &lt;u&gt;물리적주소를 이용해 테이블에 접근하므로 table full scan이 이루어지지 않는다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덱스 선정기준&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 많이 생성한다고 반드시 좋은 것(쿼리속도 향상)은 아니다. DML 작업이 커밋되면 변경사항을 인덱스에도 반영해야하는데, 테이블과 연관된 인덱스가 많을 수록 관련 인덱스를 모두 갱신해야 함으로 서버의 부담이 증가한다. 따라서 다음과 같은 경우에만 인덱스를 생성해야한다.&lt;/p&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;1) 열(Column)에 많은 NULL 값이 포함된 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- NULL 값을 제외하고 검색해야하는 경우 인덱스를 사용하면 검색 속도를 향상시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 열(Column)에 광범위한 값이 포함된 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인덱스 컬럼에 다양한 값이 있는 경우 인덱스를 사용하면 검색 속도를 향상시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) WHERE절 혹은 JOIN 조건에 자주 사용되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 테이블이 크고 대부분의 쿼리가 테이블에서&lt;span style=&quot;color: #ee2323;&quot;&gt; 2~4%&lt;/span&gt; 미만의 행을 검색할 것으로 예상되는경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- &amp;nbsp;Index range scan이 table full scan 보다 느려지는 조회 건수 지점을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인덱스&amp;nbsp;손익분기점&lt;/b&gt;&lt;/span&gt; 이라&amp;nbsp;하는데, 테이블&amp;nbsp;전체&amp;nbsp;데이터양의&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;10&amp;nbsp;~&amp;nbsp;15%&lt;/b&gt;&lt;/span&gt; 이상을 출력하게 되면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;오히려&lt;span&gt; &lt;/span&gt;&lt;/span&gt;table full scan이 효율적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) ORDER BY 절에 자주 사용되는 경우 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 인덱스는 기본적으로 정렬되어있어서 order by를 수행할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;* 손익분기점 2~4%는 공식문서상의 수치이며&amp;nbsp; 디스크 I/O, 메모리 I/O 비용 간의 비용 차이를 고려하여 결정되므로 환경에 따라 다르다.&lt;/b&gt;&amp;nbsp;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 다음과같은 경우에는 인덱스를 생성 또는 지정하지 말아야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 열(Column)이 조건절에서 자주 사용되지 않는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 테이블이 작거나 대부분의 query가 테이블에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;2%~4%&lt;/span&gt; 이상의 행을 검색할 것으로 예상되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 테이블이 자주 갱신되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- SELECT는 빨라질 수 있지만 인덱스를 INSERT하는 경우 위치탐색이 필요하며 UPDATE되는 경우에도 변경사항을 인덱스에 반영해주어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 인덱스화된 열이 표현식의 일부로 참조되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주의사항&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리상에서 다음과 같이 인덱스를 이용하면 인덱스를 활용했음에도 속도가 느릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1)&amp;nbsp;인덱스&amp;nbsp;컬럼&amp;nbsp;가공&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스의 기본 원리는 키 값(Key Value)을 사용하여 레코드를 신속하게 찾는 것이므로 WHERE 절에 사용된 열과 인덱스의 첫 번째&amp;nbsp;열이&amp;nbsp;일치해야&amp;nbsp;효과적인&amp;nbsp;인덱스&amp;nbsp;스캔(Index&amp;nbsp;Scan)이&amp;nbsp;이루어진다.&lt;br /&gt;&lt;b&gt;SUBSTR(인덱스,&amp;nbsp;1,&amp;nbsp;4)&amp;nbsp;=&amp;nbsp;&amp;lsquo;2023&amp;rsquo;&amp;nbsp; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;▶&amp;nbsp;LIKE&amp;nbsp;인덱스||&amp;rsquo;%&amp;rsquo;&amp;nbsp;&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;2)&amp;nbsp;묵시적&amp;nbsp;형&amp;nbsp;변환&amp;nbsp;이용&amp;nbsp;(인덱스가&amp;nbsp;DATE형&amp;nbsp;일&amp;nbsp;경우&amp;nbsp;예시) &lt;br /&gt;&lt;b&gt;인덱스&amp;nbsp;=&amp;nbsp;'20230331&amp;rsquo;&amp;nbsp; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;▶&amp;nbsp;인덱스&amp;nbsp;=&amp;nbsp;TO_DATE('20230301',&amp;nbsp;'YYYYMMDD&amp;rsquo;)&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;3)&amp;nbsp;인덱스&amp;nbsp;컬럼&amp;nbsp;부정형&amp;nbsp;비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;부정형&amp;nbsp;비교는&amp;nbsp;인덱스를&amp;nbsp;탐색하는&amp;nbsp;데&amp;nbsp;필요한&amp;nbsp;키를&amp;nbsp;정의하지&amp;nbsp;못하기&amp;nbsp;때문에&amp;nbsp;인덱스를&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;없게&amp;nbsp;된다. &lt;br /&gt;&lt;b&gt;인덱스&amp;nbsp;!=&amp;nbsp;'10&amp;rsquo;&amp;nbsp; &lt;/b&gt;&lt;br /&gt;&lt;b&gt;▶ 인덱스 IN ('20', '30&amp;rsquo;)&amp;nbsp;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;4)&amp;nbsp;LIKE&amp;nbsp;%가&amp;nbsp;앞에&amp;nbsp;위치할&amp;nbsp;경우&amp;nbsp; &lt;br /&gt;-&amp;nbsp;full&amp;nbsp;scan이&amp;nbsp;수행됨 &lt;br /&gt;&lt;br /&gt;5)&amp;nbsp;OR&amp;nbsp;조건&amp;nbsp;사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle Database 11g: SQL Fundamentals&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=uO8tL0okg7Q&amp;amp;ab_channel=SQL%EC%A0%84%EB%AC%B8%EA%B0%80%EC%A0%95%EB%AF%B8%EB%82%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인덱스를 타면 왜 빨라지는지 아니? - SQL전문가&amp;nbsp;정미나&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science/Database</category>
      <category>CS</category>
      <category>데이터베이스</category>
      <author>감성적인 개발자</author>
      <guid isPermaLink="true">https://infjin.tistory.com/187</guid>
      <comments>https://infjin.tistory.com/187#entry187comment</comments>
      <pubDate>Sat, 25 Feb 2023 15:43:07 +0900</pubDate>
    </item>
  </channel>
</rss>