<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hye-log</title>
    <link>https://hye-log.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 26 Aug 2026 10:41:13 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>iihye_</managingEditor>
    <image>
      <title>hye-log</title>
      <url>https://tistory1.daumcdn.net/tistory/5180404/attach/ac5cc2b322b645b7bbcd1a89f904ecf7</url>
      <link>https://hye-log.tistory.com</link>
    </image>
    <item>
      <title>[React/Kendo UI] Grid 반응형 열 컬럼 구현 with Playwright (Kendo Grid Responsive Columns)</title>
      <link>https://hye-log.tistory.com/entry/ReactKendo-UI-Grid-%EB%B0%98%EC%9D%91%ED%98%95-%EC%97%B4-%EC%BB%AC%EB%9F%BC-%EA%B5%AC%ED%98%84-with-Playwright-Kendo-Grid-Responsive-Columns</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1577&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpubKa/dJMcag0DLr5/gBAbnWircNq3eJKsYJ5Er1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpubKa/dJMcag0DLr5/gBAbnWircNq3eJKsYJ5Er1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpubKa/dJMcag0DLr5/gBAbnWircNq3eJKsYJ5Er1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpubKa%2FdJMcag0DLr5%2FgBAbnWircNq3eJKsYJ5Er1%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;1577&quot; height=&quot;832&quot; data-origin-width=&quot;1577&quot; data-origin-height=&quot;832&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;최근 프로젝트에서 Kendo UI를 사용하여 프론트엔드를 구현하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kendo UI는 Telerik에서 제공하는 UI 컴포넌트 라이브러리로 다양한 컴포넌트가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그 중에서 Grid 컴포넌트는 페이징, 정렬, 필터링 등 유용한 기능을 제공해서 목록조회를 구현할 때 사용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://www.telerik.com/kendo-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.telerik.com/kendo-ui&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1785391054942&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JavaScript UI Components - Build Better Apps Faster - Progress Kendo UI&quot; data-og-description=&quot;Quickly build eye-catching web apps using Kendo UI's ultimate collection of JavaScript UI components with libraries for jQuery, Angular, React, and Vue.&quot; data-og-host=&quot;www.telerik.com&quot; data-og-source-url=&quot;https://www.telerik.com/kendo-ui&quot; data-og-url=&quot;https://www.telerik.com/kendo-ui&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rN8nV/dJMb8RR6fyV/si0K27BAFx4Eduxkkh6uG1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/kpHiO/dJMb8YXZ8DO/SyxkyA2RRgu63l868dikf0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cEbc8s/dJMb8ZvPJIe/tTVsjfOgYDJUKEFkp9P7XK/img.png?width=800&amp;amp;height=784&amp;amp;face=0_0_800_784&quot;&gt;&lt;a href=&quot;https://www.telerik.com/kendo-ui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.telerik.com/kendo-ui&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rN8nV/dJMb8RR6fyV/si0K27BAFx4Eduxkkh6uG1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/kpHiO/dJMb8YXZ8DO/SyxkyA2RRgu63l868dikf0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cEbc8s/dJMb8ZvPJIe/tTVsjfOgYDJUKEFkp9P7XK/img.png?width=800&amp;amp;height=784&amp;amp;face=0_0_800_784');&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;JavaScript UI Components - Build Better Apps Faster - Progress Kendo UI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Quickly build eye-catching web apps using Kendo UI's ultimate collection of JavaScript UI components with libraries for jQuery, Angular, React, and Vue.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.telerik.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제가 되는 부분은 &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 Light';&quot;&gt;모든 컬럼에 width를 지정하면, 화면이 넓어져도 컬럼이 늘어나지 않아 Grid에 빈 공간이 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;한 컬럼을 제외하고 width를 지정하면, 화면이 넓어질 때 한 컬럼이 반응형으로 늘어나지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;화면이 좁아지면 컬럼이 사라지는 문제가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785391308552&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;GridColumn field=&quot;columnNm&quot; width=&quot;300px&quot;/&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 Light';&quot;&gt;Kendo Grid의 스크롤 구조는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1785391368634&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Page
 └─ Box
     └─ Grid
         ├─ Header
         └─ Content&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;단순히 Grid에 minWidth를 적용하면 Grid 내부가 부모보다 넓어지면 브라우저 전체에 가로 스크롤이 생기게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;내가 원하는건 브라우저 전체 크기에 맞게 Grid가 보여야 하기 때문에 Grid 내부에 스크롤이 생겨야 한다.&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_1785391486390&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Box
    sx={{
        width: '100%',
        '&amp;amp; .k-grid': {
            width: '100%',
        },
    }}
&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일단 부모 컨테이너를 100%를 고정하여 페이지가 늘어나지 않도록 고정한다.&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_1785391525502&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sx={{
    '&amp;amp; .k-grid-header-table, &amp;amp; .k-grid-table': {
        minWidth: '{고정 컬럼 width 합 + 가변 컬럼 width}',
    },
}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Grid는 고정 컬럼 width 합 + 가변 컬럼 width로 정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예를 들어서 4개의 컬럼이 있다고 가정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;번호(80px), 이름(120px), 건축물명(최소 300px), 작성날짜(200px) 이면,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;고정 컬럼(번호, 이름, 작성날짜) 400px + 가변 컬럼(건축물명)) 300px = 700px 을 minWidth로 잡아준다.&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 Light';&quot;&gt;그 결과 Grid 내부에 스크롤이 존재하면서, 가변 컬럼에 대해서 minWidth를 유지하게 되었다.&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 Light';&quot;&gt;이 부분은 다른 목록조회 화면에서도 발생했던 문제이기 때문에,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재사용 가능하도록 컴포넌트로 분리해서 사용할 수 있게 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;프로젝트 내 어떤 파일에서든 KendoContainer 컴포넌트를 사용하면 Grid 반응형 열 컬럼 기능을 사용할 수 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 과정을 해결하기 위해 minWidth, className-headerClassName, css 여러 방법을 1시간 정도 시도했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Grid 레이아웃이 의도하는 대로 동작하는지 자동으로 검증하고 코드를 수정하기 위해서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;b&gt;Playwright&lt;/b&gt;를 이용했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Light';&quot;&gt;Playwright는 브라우저 자동화 및 End-to-End 테스트 프레임워크로,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Light';&quot;&gt;실제 브라우저를 실행해 사용자의 동작을 자동으로 수행하고, 의도한 대로 동작하는지 검증할 수 있는 도구이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://playwright.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://playwright.dev/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1785392093903&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Fast and reliable end-to-end testing for modern web apps | Playwright&quot; data-og-description=&quot;Web automation and testing for apps, scripts, and AI agents&quot; data-og-host=&quot;playwright.dev&quot; data-og-source-url=&quot;https://playwright.dev/&quot; data-og-url=&quot;https://playwright.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhJM4F/dJMb9gxzFkF/fKRJjNPrYFxTIgvC5Pn9SK/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/lDj7a/dJMb9iIUYni/p7C23p7lwHEkcO3eeBGkvk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/RVCpb/dJMb9kUg3oG/luNl9Apj2LXxKI8S24VXy1/img.png?width=2500&amp;amp;height=1988&amp;amp;face=0_0_2500_1988&quot;&gt;&lt;a href=&quot;https://playwright.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://playwright.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhJM4F/dJMb9gxzFkF/fKRJjNPrYFxTIgvC5Pn9SK/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/lDj7a/dJMb9iIUYni/p7C23p7lwHEkcO3eeBGkvk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/RVCpb/dJMb9kUg3oG/luNl9Apj2LXxKI8S24VXy1/img.png?width=2500&amp;amp;height=1988&amp;amp;face=0_0_2500_1988');&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;Fast and reliable end-to-end testing for modern web apps | Playwright&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Web automation and testing for apps, scripts, and AI agents&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;playwright.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Playwright가 문제를 분석하고 해결한 과정은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kendo Grid 구조 분석 -&amp;gt; CSS 수정 -&amp;gt; minWidth 테스트 -&amp;gt; 스크롤 테스트 -&amp;gt; 검증 -&amp;gt; 코드 확정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사실 인간이 수행하는 문제 과정과 다를 바가 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다만 테스트 과정에서 단순히 &quot;잘 보인다&quot;가 아니라 실제 스크롤 동작과 열 너비를 검증하면서 정확한 테스트를 진행할 수 있었다.&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 Light';&quot;&gt;토큰은 총 input에 40~60k, output에 4~7k, 총 50~65k 정도 사용되었는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;코드 수정 - 테스트 - 결과 확인 - 수정 - 테스트 - 결과 확인 - ... 이 과정을 반복하면 토큰이 많이 사용되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만 처음부터 모든 제약 조건을 한 번에 전달하고, 최종 격롸만 요청했기 때문에 토큰을 절약해서 사용할 수 있었다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/React</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/308</guid>
      <comments>https://hye-log.tistory.com/entry/ReactKendo-UI-Grid-%EB%B0%98%EC%9D%91%ED%98%95-%EC%97%B4-%EC%BB%AC%EB%9F%BC-%EA%B5%AC%ED%98%84-with-Playwright-Kendo-Grid-Responsive-Columns#entry308comment</comments>
      <pubDate>Thu, 30 Jul 2026 15:16:51 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 미디어쿼리(Media Query) vs 유저 에이전트(User Agent) 모바일 디바이스 감지하기</title>
      <link>https://hye-log.tistory.com/entry/JavaScript-%EB%AF%B8%EB%94%94%EC%96%B4%EC%BF%BC%EB%A6%ACMedia-Query-vs-%EC%9C%A0%EC%A0%80-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8User-Agent-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4-%EA%B0%90%EC%A7%80%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2025년 11월 4일 오후 05_45_39.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU4w6c/dJMcaaKmsdu/KhEV5sYNw3kUGUXzi5qIw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU4w6c/dJMcaaKmsdu/KhEV5sYNw3kUGUXzi5qIw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU4w6c/dJMcaaKmsdu/KhEV5sYNw3kUGUXzi5qIw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU4w6c%2FdJMcaaKmsdu%2FKhEV5sYNw3kUGUXzi5qIw0%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;557&quot; height=&quot;371&quot; data-filename=&quot;ChatGPT Image 2025년 11월 4일 오후 05_45_39.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;최근에 서비스 중인 웹 페이지를 모바일 디바이스에 맞게 변형하는 작업을 진행했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존 메인 페이지를 모바일 환경에 다 담지 못하므로, 모바일용 메인 페이지를 만들고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;게시판도 모바일 디바이스에서 유연하게 사용할 수 있도록 UI를 전면 수정했다.&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 Light';&quot;&gt;전반적인 디자인은 웹 디자이너께서 해주셨지만 이를 적용하는 건 나의 몫이었다 &lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;웹 화면과 모바일 화면을 구분하기 위해서 사용한 방법은 &lt;b&gt;미디어 쿼리(Media Query)&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 Light';&quot;&gt;미디어 쿼리는 &lt;b&gt;CSS에서 화면의 크기나 기기의 특성에 따라 다른 스타일을 적용할 수 있게 해주는 기능&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 Light';&quot;&gt;즉, 화면의 크기에 따라 달라지는 &quot;&lt;b&gt;반응형 웹(responsive web)&lt;/b&gt;&quot;을 만들 수 있다.&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 Light';&quot;&gt;미디어 쿼리는 &lt;b&gt;@media&lt;/b&gt; 으로 CSS에 작성하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762244803269&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@media (조건) {
  /* 조건을 만족할 때 적용될 CSS */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일반적으로 &lt;b&gt;모바일은 768px 이하, 데스크톱은 1024px 이하&lt;/b&gt;로 구분하기 때문에 다음과 같이 작성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762244920155&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 기본 스타일 (데스크톱용) */
body {
  font-size: 18px;
  background: white;
}

/* 화면 폭이 768px 이하일 때 (모바일용) */
@media (max-width: 768px) {
  body {
    font-size: 16px;
    background: #f0f0f0;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CSS를 이렇게 작성했으면 JS에서도 화면의 크기를 판단하는 기준을 만들어줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모바일 상태를 판단하는 mobileContext 파일을 만들어서 &lt;b&gt;window.innerWidth&lt;/b&gt;를 기준으로 화면의 크기를 가져와서 판단했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762244847045&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from 'react'

const mobileContext = () =&amp;gt; {
  // 화면 크기 상태
  const [isMobile, setIsMobile] = useState(false)

  // 화면 크기 추적
  useEffect(() =&amp;gt; {
    const handleResize = () =&amp;gt; {
      setIsMobile(window.innerWidth &amp;lt;= 768)
    }

    handleResize() // 초기값 설정
    window.addEventListener('resize', handleResize)

    // 컴포넌트 언마운트 시 이벤트 리스너 제거
    return () =&amp;gt; window.removeEventListener('resize', handleResize)
  }, [])

  return isMobile
}

export default mobileContext&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 Light';&quot;&gt;이와 같이 적용했더니 문제점이 발생했다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;웹에서 테스트 할 때는 화면의 크기를 줄이면서, &quot;이 쯤이면 모바일에서도 보이겠지&quot; 생각했는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제 배포 후 웹 화면으로 접속했을 때에는 아래와 같이 잘려 보이는 것이었다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;미디어 쿼리는 화면의 크기에 따라 요소가 바뀌는 반응형 웹에 적합한 방식이지만,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;내가 원했던 건 &quot;&lt;b&gt;모바일 디바이스에서는 모바일 화면만 보인다&lt;/b&gt;&quot; 이기 때문에 다른 방법을 찾아야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;873&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y9Dd4/dJMcajUP10k/uy08RcdpvD6oDDxnn1JXj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y9Dd4/dJMcajUP10k/uy08RcdpvD6oDDxnn1JXj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y9Dd4/dJMcajUP10k/uy08RcdpvD6oDDxnn1JXj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy9Dd4%2FdJMcajUP10k%2Fuy08RcdpvD6oDDxnn1JXj1%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;577&quot; height=&quot;412&quot; data-origin-width=&quot;1223&quot; data-origin-height=&quot;873&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;클라이언트가 서버에 자신을 알리기 위해 보내는 문자열 정보인 &lt;b&gt;유저 에이전트(User Agent)&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;font-family: 'Noto Sans Light';&quot;&gt;우리가 브라우저에 접속하면 클라이언트는 아래와 같이 유저 에이전트를 서버에 보낸다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762245353491&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/121.0.0.0 Safari/537.36&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그럼 서버는 이 정보를 보고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; &quot;아, 사용자는 Windows 10 64bit &lt;b&gt;운영체제&lt;/b&gt; 환경에서 Chrome 121 &lt;b&gt;브라우저&lt;/b&gt;로 Webkit 기반의 &lt;b&gt;엔진&lt;/b&gt;을 사용하구나!&quot; 라고 이해할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;유저 에이전트를 통해 어떤 운영체제로 접속하는지 확인할 수 있기 때문에 안드로이드, 아이폰 등 모바일 디바이스를 감지할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;유저 에이전트는 &lt;b&gt;navigator.userAgent&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 Light';&quot;&gt;이 속성은 읽기 전용으로 현재 브라우저의 정보를 문자열로 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762245749688&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(navigator.userAgent);&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 Light';&quot;&gt;innerWidth 대신에 &lt;b&gt;userAgent&lt;/b&gt; 속성을 사용해서 모바일 디바이스를 감지한 코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1762245786662&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect, useState } from 'react'

function detectMobileDevice(agent: string) {
  const mobileRegex = [
    /Android/i,
    /iPhone/i,
    /iPad/i,
    /iPod/i,
    /BlackBerry/i,
    /Windows Phone/i,
  ]

  return mobileRegex.some((mobile) =&amp;gt; agent.match(mobile))
}

const mobileContext = () =&amp;gt; {
  // 화면 크기 상태
  const [isMobile, setIsMobile] = useState(false)

  // 화면 크기 추적
  useEffect(() =&amp;gt; {
    setIsMobile(detectMobileDevice(navigator.userAgent))
  }, [])

  return isMobile
}

export default mobileContext&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&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 Light';&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://imkh.dev/js-detect-mobile-device&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://imkh.dev/js-detect-mobile-device&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/JavaScript</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/306</guid>
      <comments>https://hye-log.tistory.com/entry/JavaScript-%EB%AF%B8%EB%94%94%EC%96%B4%EC%BF%BC%EB%A6%ACMedia-Query-vs-%EC%9C%A0%EC%A0%80-%EC%97%90%EC%9D%B4%EC%A0%84%ED%8A%B8User-Agent-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4-%EA%B0%90%EC%A7%80%ED%95%98%EA%B8%B0#entry306comment</comments>
      <pubDate>Tue, 4 Nov 2025 17:52:56 +0900</pubDate>
    </item>
    <item>
      <title>[QueryDSL] BooleanExpression과 BooleanBuilder의 모든 것</title>
      <link>https://hye-log.tistory.com/entry/QueryDSL-BooleanExpression%EA%B3%BC-BooleanBuilder%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2025년 10월 31일 오후 04_46_19.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyzZD4/dJMcaklTcTu/OPtrLOIT22lySeX2BHIYH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyzZD4/dJMcaklTcTu/OPtrLOIT22lySeX2BHIYH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyzZD4/dJMcaklTcTu/OPtrLOIT22lySeX2BHIYH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyzZD4%2FdJMcaklTcTu%2FOPtrLOIT22lySeX2BHIYH0%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;452&quot; height=&quot;301&quot; data-filename=&quot;ChatGPT Image 2025년 10월 31일 오후 04_46_19.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&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';&quot;&gt;JPA, QueryDSL을 사용하는 프로젝트에서 BooleanExpression을 이용해서 where절을 자주 쓰곤 했는데,&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';&quot;&gt;최근에 소스코드 보안점검을 해보니 '&lt;b&gt;널(Null) 포인터 역참조가 발생할 수 있다&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';&quot;&gt;BooleanExpression에서 널 포인터 역참조를 피하려면 어떻게 해야하는지,&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';&quot;&gt;BooleanExpression과 BooleanBuilder는 어떤 차이가 있고 어떤 상황에서 써야하는지 알아보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;BooleanExpression&lt;/b&gt;과 &lt;b&gt;BooleanBuilder&lt;/b&gt;는 QueryDSL에서 &lt;b&gt;where절의 조건&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사실 query문에 &lt;b&gt;where문을 직접 입력&lt;/b&gt;해도 된다!&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761895146231&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;query.where(qUser.age.goe(18));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를 들면 나이(age)는 입력했지만, 이름(name)은 입력 안할 수도 있고, 활성화 여부(active)는 전체일 수도 있다.&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';&quot;&gt;query.where(...) 안에 직접 쓰면 if 문으로 계속 쿼리를 만들어야 한다.&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';&quot;&gt;따라서 &lt;b&gt;동적으로 where절을 만들어서&lt;/b&gt; 재사용성, 가독성, 유지보수성을 향상시키기 위해 조건 표현식을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761895174296&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BooleanBuilder where = new BooleanBuilder();

if (age != null) {
  where.and(qUser.age.goe(age));
}

if (StringUtils.hasText(name)) {
  where.and(qUser.name.contains(name));
}

if (!&quot;all&quot;.equals(status)) {
  where.and(qUser.status.eq(status));
}

query.where(where);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;null 역참조가 발생할 수 있는 코드를 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761894945039&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BooleanExpression whereClause = Expressions.TRUE;
/**
	whereClause에 붙은 수많은 조건들...
**/

/** srchClause만 따로 작성하는 이유는 검색 관련 where절만 따로 관리하려고 **/
BooleanExpression srchClause= null;

if (StringUtils.hasText(srchData)) {
  srchClause= qDatas.title.eq(srchData);
}

if (srchClause!= null) {
  whereClause = whereClause.and(srchClause);
}&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;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제점 1. Expressions.TRUE&lt;/span&gt;&lt;/b&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';&quot;&gt;일반적으로 BooleanExpression에서 Expressions.TRUE를 쓰면 항상 true인 조건으로 시작하기 때문에,&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';&quot;&gt;where절에 초기값이 null이 아니라는 점에서 &lt;b&gt;안전한 시작점&lt;/b&gt;을 만들어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 코드와 같이 whereClause.and(...) 메소드에 srchClause와 같이 또 다른 BooleanExpression이 들어간다면?&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';&quot;&gt;srchClause에서 null 참조 에러가 발생하게 된다.&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';&quot;&gt;실제 BooleanExpressions.and()의 내부에서는 다음과 같이 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761895507801&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public BooleanExpression and(Predicate right) {
    return Expressions.booleanOperation(Ops.AND, this, right);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;right가 null이면 내부에서 null 참조 에러가 발생하기 때문에, null 참조 에러가 발생하게 되는 것이다.&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';&quot;&gt;문제점 2. BooleanExpressions은 불변이다.&lt;/span&gt;&lt;/b&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';&quot;&gt;BooleanExpression은 불변이라 and()가 호출될 때마다 새로운 객체를 만든다.&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';&quot;&gt;즉, and()를 호출할 때마다 기존 식이 변경되는 것이 아니라 &lt;b&gt;새로운 객체&lt;/b&gt;를 만드는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761895613932&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BooleanExpression where = qUser.active.isTrue();
where = where.and(qUser.age.goe(18));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 코드에서도 qUser.active.isTrue() 에서 BooleanExpression A가 생성되고,&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';&quot;&gt;A.and(qUser.age.goe(18)) 에서 BooleanExpresion B가 생성된다.&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';&quot;&gt;결과적으로 where은 BooleanExpression B를 가리키게 되고, A는 사라지지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 든 궁금증이 BooleanExpression으로 수십 개의 조건을 달면 메모리 문제가 발생할 수도 있을까?&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';&quot;&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';&quot;&gt;반복 루프로 수천~수만 개가 생성될 경우 GC에 부하를 일으킬 수 있고,&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';&quot;&gt;static 저장과 같이 장시간으로 누적되면 메모리 누수가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;null 역참조가 발생할 수 있는 코드를&lt;b&gt; BooleanBuilder&lt;/b&gt;를 사용하여 수정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761896044208&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BooleanBuilder whereClause = new BooleanBuilder();

if (StringUtils.hasText(srchData)) {
    whereClause.and(qDatas.title.eq(srchData));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;whereClause를 BooleanBuilder 생성자를 이용하여 만들어주고,&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';&quot;&gt;BooleanExpression을 사용하는 것처럼 .and()를 호출하면서 조건을 붙여주면 된다.&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';&quot;&gt;BooleanBuilder는 &lt;b&gt;가변 객체&lt;/b&gt;라서 null 체크가 필요 없다.&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';&quot;&gt;즉, and()를 호출하더라도 객체 내부의 상태가 변화하기 때문에 null 체크를 신경쓰지 않아도 된다.&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';&quot;&gt;null 체크를 하지 않아도 되기 때문에 조건이 많아도 코드를 간결하게 작성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그럼 null 체크를 하지 않아도 되는 &lt;b&gt;BooleanBuilder를 쓰는게 항상 좋을까?&lt;/b&gt;&lt;/span&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';&quot;&gt;그건 아니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;BooleanExpression&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';&quot;&gt;특정 BooleanExpression을 다른 곳에서 재사용하고 싶을 때 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761896365504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BooleanExpression activeUsers = qUser.status.eq(&quot;ACTIVE&quot;);
BooleanExpression adminUsers = qUser.role.eq(&quot;ADMIN&quot;);

query.where(activeUsers.and(adminUsers)); // 바로 조합 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 같은 상황의 경우 고정 조건을 만들어서 and 하기 때문에 null 발생 우려가 없다.&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';&quot;&gt;&lt;b&gt;BooleanExpression&lt;/b&gt;을 사용하기 애매한 상황에서 BooleanBuilder를 사용한다.&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';&quot;&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';&quot;&gt;검색 화면, 필터링 조건처럼 여러 조건을 순차적으로 누적해야 할 때 유용하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1761896505557&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BooleanBuilder builder = new BooleanBuilder();
if (hasText(title)) builder.and(qDatas.title.eq(title));
if (hasText(status)) builder.and(qDatas.status.eq(status));
if (hasText(owner)) builder.and(qDatas.owner.eq(owner));

query.where(builder);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn5kEh/dJMcajUORm0/Qt5wtGD1JnsoeFCxrXox1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn5kEh/dJMcajUORm0/Qt5wtGD1JnsoeFCxrXox1k/img.png&quot; data-alt=&quot;http://querydsl.com/static/querydsl/3.6.3/reference/ko-KR/html_single/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn5kEh/dJMcajUORm0/Qt5wtGD1JnsoeFCxrXox1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn5kEh%2FdJMcajUORm0%2FQt5wtGD1JnsoeFCxrXox1k%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;719&quot; height=&quot;330&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://querydsl.com/static/querydsl/3.6.3/reference/ko-KR/html_single/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QueryDSL 공식 문서에서는 복합 조건식을 작성할 때 BooleanBuilder 클래스를 사용하라고 권장하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 상황에 따라 달라질 수 있지 않을까?!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때에 따라 적절한 표현식을 사용하고, 어떤 특징이 있는지 알고 사용하는 것도 중요하다!&lt;/p&gt;</description>
      <category>Development/Java</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/305</guid>
      <comments>https://hye-log.tistory.com/entry/QueryDSL-BooleanExpression%EA%B3%BC-BooleanBuilder%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83#entry305comment</comments>
      <pubDate>Fri, 31 Oct 2025 16:47:45 +0900</pubDate>
    </item>
    <item>
      <title>[한글] 1분 만에 한글 메일 머지로 문서 자동화 하는 방법</title>
      <link>https://hye-log.tistory.com/entry/%ED%95%9C%EA%B8%80-1%EB%B6%84-%EB%A7%8C%EC%97%90-%ED%95%9C%EA%B8%80-%EB%A9%94%EC%9D%BC-%EB%A8%B8%EC%A7%80%EB%A1%9C-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQEN7H/btsQKzLUiwo/EZ31w0TY40fkk1bCZDUvyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQEN7H/btsQKzLUiwo/EZ31w0TY40fkk1bCZDUvyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQEN7H/btsQKzLUiwo/EZ31w0TY40fkk1bCZDUvyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQEN7H%2FbtsQKzLUiwo%2FEZ31w0TY40fkk1bCZDUvyk%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;1022&quot; height=&quot;399&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이런 문서에 여러 개의 고유번호, 이름을 입력해서 문서를 만들어야 한다면?&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;물론 하나씩 입력할 수 있지만...&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;우리에게 시간은 중요하니까!&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;메일 머지 기능을 사용해서 1분 만에 입력해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/elHA3A/btsQMa5p0ye/YgFY4f1GmuKwJsM62UL451/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/elHA3A/btsQMa5p0ye/YgFY4f1GmuKwJsM62UL451/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/elHA3A/btsQMa5p0ye/YgFY4f1GmuKwJsM62UL451/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FelHA3A%2FbtsQMa5p0ye%2FYgFY4f1GmuKwJsM62UL451%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;432&quot; height=&quot;175&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;사용하는 기능은 도구 &amp;gt; 메일 머지 에 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;여기에 두 가지 메뉴가 있는데,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;일단 메일 머지 표시 달기를 클릭하여 메일 머지를 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpyP8U/btsQM77UUKL/e6IgwNvDKl7oviPaBszJh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpyP8U/btsQM77UUKL/e6IgwNvDKl7oviPaBszJh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpyP8U/btsQM77UUKL/e6IgwNvDKl7oviPaBszJh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpyP8U%2FbtsQM77UUKL%2Fe6IgwNvDKl7oviPaBszJh0%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;394&quot; height=&quot;267&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;주소록 필드 선택하기에서 사용하는 게 있다면 골라도 되고,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O47bx/btsQMK6c5XN/I8E7BqA30LwwilBP8fCnh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O47bx/btsQMK6c5XN/I8E7BqA30LwwilBP8fCnh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O47bx/btsQMK6c5XN/I8E7BqA30LwwilBP8fCnh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO47bx%2FbtsQMK6c5XN%2FI8E7BqA30LwwilBP8fCnh1%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;395&quot; height=&quot;270&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;필드 만들기 탭에서 새로 만들어도 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;내가 원하는 필드가 없기 때문에 직접 만들어서 사용했다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;원하는 필드를 만든 후 선택하여 넣기를 누르면 된다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRyeja/btsQLdVT7gZ/bpzjou9NVOwuakk7xYClCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRyeja/btsQLdVT7gZ/bpzjou9NVOwuakk7xYClCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRyeja/btsQLdVT7gZ/bpzjou9NVOwuakk7xYClCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRyeja%2FbtsQLdVT7gZ%2Fbpzjou9NVOwuakk7xYClCK%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;1027&quot; height=&quot;399&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;399&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 이렇게 중괄호 두 개 안에 필드 이름이 적혀 있고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;겉은 흰색 모양의 괄호가 감싸고 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 위치에 내가 원하는 값들이 들어갈거다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이제 메일 머지를 만들어보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMyvB3/btsQM9dAqtQ/kjmSekPM8fCbKUo18CUeeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMyvB3/btsQM9dAqtQ/kjmSekPM8fCbKUo18CUeeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMyvB3/btsQM9dAqtQ/kjmSekPM8fCbKUo18CUeeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMyvB3%2FbtsQM9dAqtQ%2FkjmSekPM8fCbKUo18CUeeK%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;428&quot; height=&quot;171&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;도구 &amp;gt; 메일 머지 &amp;gt; 메일 머지 만들기를 눌러 메일 머지를 만든다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzMMaz/btsQKCPpyH7/6mKtDf722ZPpMCpxwrJKE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzMMaz/btsQKCPpyH7/6mKtDf722ZPpMCpxwrJKE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzMMaz/btsQKCPpyH7/6mKtDf722ZPpMCpxwrJKE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzMMaz%2FbtsQKCPpyH7%2F6mKtDf722ZPpMCpxwrJKE1%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;455&quot; height=&quot;328&quot; data-origin-width=&quot;455&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 자료 종류에 여러 가지가 있지만, 그나마 친숙한 네 번째 엑셀 파일을 가지고 만들거다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;출력 방향도 바로 인쇄할 수 있지만 확인차 파일로 저장한 뒤에 출력해보자.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjbVUI/btsQLg50Epi/wvkdPmsokk7X1C8Hcw16o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjbVUI/btsQLg50Epi/wvkdPmsokk7X1C8Hcw16o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjbVUI/btsQLg50Epi/wvkdPmsokk7X1C8Hcw16o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjbVUI%2FbtsQLg50Epi%2FwvkdPmsokk7X1C8Hcw16o0%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;248&quot; height=&quot;181&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1행에 필드명을 입력하고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2행부터 해당 필드에 들어갈 값을 적어 저장해준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이 파일을 자료 종류 &amp;gt; 한셀/엑셀 파일에서 선택해준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;출력 방향 &amp;gt; 파일 &amp;gt; 파일 이름도 저장하고 싶은 위치를 고른 후 파일 이름을 입력해준다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHD6ef/btsQNDlcU5D/6NUrufmOIiIJ5AYkG36TpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHD6ef/btsQNDlcU5D/6NUrufmOIiIJ5AYkG36TpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHD6ef/btsQNDlcU5D/6NUrufmOIiIJ5AYkG36TpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHD6ef%2FbtsQNDlcU5D%2F6NUrufmOIiIJ5AYkG36TpK%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;418&quot; height=&quot;167&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 이렇게 시트를 선택할 수 있고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF63gG/btsQLdatYLQ/4KBlRWrj9CsYh86kDOcLhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF63gG/btsQLdatYLQ/4KBlRWrj9CsYh86kDOcLhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF63gG/btsQLdatYLQ/4KBlRWrj9CsYh86kDOcLhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF63gG%2FbtsQLdatYLQ%2F4KBlRWrj9CsYh86kDOcLhk%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;396&quot; height=&quot;316&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;메일 머지를 할 목록을 선택할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;shift 키를 눌러 여러 개를 연속적으로 선택할 수 있고,&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;ctrl 키를 눌러 단일 항목을 여러 개 선택할 수도 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chSDK9/btsQL6aStTS/GQFSlNyhSTFAmJy3ZSOpFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chSDK9/btsQL6aStTS/GQFSlNyhSTFAmJy3ZSOpFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chSDK9/btsQL6aStTS/GQFSlNyhSTFAmJy3ZSOpFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchSDK9%2FbtsQL6aStTS%2FGQFSlNyhSTFAmJy3ZSOpFK%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;1024&quot; height=&quot;393&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;선택 버튼을 눌러 저장된 파일을 확인하면 다음과 같이 입력된 것을 볼 수 있다.&lt;/p&gt;</description>
      <category>Tools/Document</category>
      <category>메일머지</category>
      <category>문서자동화</category>
      <category>엑셀</category>
      <category>한글</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/304</guid>
      <comments>https://hye-log.tistory.com/entry/%ED%95%9C%EA%B8%80-1%EB%B6%84-%EB%A7%8C%EC%97%90-%ED%95%9C%EA%B8%80-%EB%A9%94%EC%9D%BC-%EB%A8%B8%EC%A7%80%EB%A1%9C-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95#entry304comment</comments>
      <pubDate>Wed, 24 Sep 2025 12:49:34 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] img 비율 유지하면서 크기 줄이기(!important)</title>
      <link>https://hye-log.tistory.com/entry/CSS-img-%EB%B9%84%EC%9C%A8-%EC%9C%A0%EC%A7%80%ED%95%98%EB%A9%B4%EC%84%9C-%ED%81%AC%EA%B8%B0-%EC%A4%84%EC%9D%B4%EA%B8%B0important</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존 웹페이지의 일부를 반응형으로 만드는 작업을 하고 있다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;s&gt;(이거까지 하게 될 줄 몰랐지)&lt;/s&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아무튼!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CSS는 디자이너 분이 해주셔서 내가 건들일 없다고 생각했는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;테스트 페이지로 구조만 잡아주시고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제 소스코드에 적용하는 건 내가 하다보니까&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CSS를 수정하는 경우가 굉..장히... 많다(!!!)&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 Light';&quot;&gt;게시판에 이미지를 업로드 한 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&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;style3&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;840&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRiU5O/btsQJCVod7W/pBOKkdvi3xWOXOqtpMMEE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRiU5O/btsQJCVod7W/pBOKkdvi3xWOXOqtpMMEE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRiU5O/btsQJCVod7W/pBOKkdvi3xWOXOqtpMMEE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRiU5O%2FbtsQJCVod7W%2FpBOKkdvi3xWOXOqtpMMEE1%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;1143&quot; height=&quot;840&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가로 길이를 줄이면 반응형으로 모바일 화면으로 전환되는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이미지 크기는 변하지 않다보니 이렇게 튀어나오는 현상이 발생했다.&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;830&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czZmLX/btsQK5CmOoB/K3vbxkpI4Y0AYKFhtCeh21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czZmLX/btsQK5CmOoB/K3vbxkpI4Y0AYKFhtCeh21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czZmLX/btsQK5CmOoB/K3vbxkpI4Y0AYKFhtCeh21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczZmLX%2FbtsQK5CmOoB%2FK3vbxkpI4Y0AYKFhtCeh21%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;1151&quot; height=&quot;830&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;830&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1758531042124&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.table-scrollable img {
    max-width: 100%;
    display: block;   
}&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 Light'; color: #333333; text-align: start;&quot;&gt;튀어나온 이미지를 해결하기 위해서 &lt;b&gt;max-width&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 Light';&quot;&gt;max-width를 100%로 조절하여 이미지가 부모 요소의 너비를 초과하지 않도록 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서는 가장 바깥에 있는 table width에 맞춰서 width가 설정된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하지만..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;내가 원한 건 &lt;u&gt;기존 이미지의 비율을 유지하면서 사이즈를 줄이는 것&lt;/u&gt;이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그래서 이 비율을 유지하면서 어떻게 width를 줄일까 고민하다가,&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1149&quot; data-origin-height=&quot;831&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OvHgF/btsQHZX2Qi6/xRSUOSCB8RD1U9rVlDiGVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OvHgF/btsQHZX2Qi6/xRSUOSCB8RD1U9rVlDiGVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OvHgF/btsQHZX2Qi6/xRSUOSCB8RD1U9rVlDiGVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOvHgF%2FbtsQHZX2Qi6%2FxRSUOSCB8RD1U9rVlDiGVK%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;1149&quot; height=&quot;831&quot; data-origin-width=&quot;1149&quot; data-origin-height=&quot;831&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1758531060506&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.table-scrollable img {
    max-width: 100%;   
    height: auto !important;
    display: block;   
}&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 Light';&quot;&gt;height에 &lt;b&gt;!important&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 Light';&quot;&gt;!important는 다른 CSS에서 height 값을 적용했더라도 &lt;u&gt;스타일 규칙을 우선적으로 적용&lt;/u&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 Light';&quot;&gt;max-width를 설정하면 이미지의 너비가 부모 요소의 너비를 넘지 않도록 제한한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이미지의 비율을 유지하려면 높이도 자동적으로 비례적으로 조정되어야 하는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;height: auto를 우선적으로 적용시키면서 &lt;u&gt;자동으로 높이가 설정&lt;/u&gt;되도록 하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;!important로 이미지의 비율을 유지하면서 크기를 조절해보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;창의 크기가 늘어나면 이미지도 비율에 맞게 커지고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;창의 크기가 줄어들면 이미지도 비율에 맞게 작아진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다만 !important는 다른 스타일 규칙보다 우선적으로 적용되는 강제성이 있기 때문에 &lt;u&gt;유지보수성&lt;/u&gt;이 떨어질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;따라서 &lt;u&gt;꼭 필요한 경우에만&lt;/u&gt; 사용하는 것이 좋다!&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/CSS</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/303</guid>
      <comments>https://hye-log.tistory.com/entry/CSS-img-%EB%B9%84%EC%9C%A8-%EC%9C%A0%EC%A7%80%ED%95%98%EB%A9%B4%EC%84%9C-%ED%81%AC%EA%B8%B0-%EC%A4%84%EC%9D%B4%EA%B8%B0important#entry303comment</comments>
      <pubDate>Tue, 23 Sep 2025 16:00:10 +0900</pubDate>
    </item>
    <item>
      <title>[QueryDSL] Expressions.dateTemplate로 SQL 구문 만들기</title>
      <link>https://hye-log.tistory.com/entry/QueryDSL-ExpressionsdateTemplate%EB%A1%9C-SQL-%EA%B5%AC%EB%AC%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;QueryDSL로 날짜별 생성된 데이터를 세는 쿼리를 구현했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그런데 내가 생각한 것과 데이터 개수가 다르게 나오는 것이다...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;s&gt;(왜그럴까)&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. 문제 상황&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758011661484&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Tuple results = queryFactory.select(qStatsTable.regDt, qStatsTable.count()) 
                            .from(qStatsTable) 
                            .orderBy(qStatsTable.regDt.desc()) 
                            .groupBy(qStatsTable.regDt) 
                            .limit(1) 
                            .fetchOne();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;stats_table에서 reg_dt를 기준으로 그룹화하여 내림차순으로 정렬한 뒤 select 하는 QueryDSL이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;reg_dt는 등록일자를 의미하는 컬럼인데 내가 생각한 결과는 &lt;u&gt;날짜별로&lt;/u&gt; count 개수를 세는 것이다.&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_1758011710421&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT reg_dt, COUNT(*) 
FROM stats_table 
GROUP BY reg_dt 
ORDER BY reg_dt DESC 
LIMIT 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;QueryDSL을 쿼리문으로 바꾼 뒤 실행한 결과는 다음과 같았다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bruiTP/btsQBiavtOI/Ky2l2EQe76vCQ5aAJIyn7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bruiTP/btsQBiavtOI/Ky2l2EQe76vCQ5aAJIyn7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bruiTP/btsQBiavtOI/Ky2l2EQe76vCQ5aAJIyn7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbruiTP%2FbtsQBiavtOI%2FKy2l2EQe76vCQ5aAJIyn7k%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;481&quot; height=&quot;154&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 결과가 나온 이유는 reg_dt는 &lt;b&gt;TimeStamp&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 Light';&quot;&gt;&lt;u&gt;초단위 시간까지 일치&lt;/u&gt;하는 reg_dt끼리 그룹화되어버린 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;내가 원하는 결과는 reg_dt가 &lt;u&gt;일 단위까지&lt;/u&gt; 끊어서 그룹화 하는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 해결 방법&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 Light';&quot;&gt;그래서 수정한 쿼리문은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757926608271&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT DATE(reg_dt) AS reg_dt, COUNT(*) 
FROM stats_table 
GROUP BY DATE(reg_dt) 
ORDER BY reg_dt DESC 
LIMIT 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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;reg_dt를 &lt;b&gt;DATE&lt;/b&gt;로 감싸면 TimeStamp였던 형식을 Date 형식으로 변환하면서,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;내가 원하는 일 단위 기준의 데이터 개수를 얻게 되었다!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0gmXx/btsQyqawNCj/FclpxhEpqp9UYVZSbslUk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0gmXx/btsQyqawNCj/FclpxhEpqp9UYVZSbslUk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0gmXx/btsQyqawNCj/FclpxhEpqp9UYVZSbslUk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0gmXx%2FbtsQyqawNCj%2FFclpxhEpqp9UYVZSbslUk0%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;483&quot; height=&quot;152&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;152&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 Light';&quot;&gt;자 이렇게 쿼리문으로 어떻게 짜야 하는지 감을 잡았으니 이제 &lt;b&gt;QueryDSL&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;font-family: 'Noto Sans Light';&quot;&gt;다양한 방법이 있곘지만 내가 사용하고 있는 데이터베이스는 Cubrid(큐브리드)이기 때문에,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(큐브리드가 지원할 수 있는) Expressions.dateTemplate을 이용하여 SQL 표현식을 작성할 수 있다.&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 Light';&quot;&gt;&lt;b&gt;Expressions.dateTemplate&lt;/b&gt;는 QueryDSL에서 날짜 형식을 지정하여 SQL 구문을 작성할 때 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기본적인 형식은 이렇게 생겼다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758011868084&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.dateTemplate(Date.class, &quot;DATE({0})&quot;, qEntity.dateField)&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) &lt;b&gt;타입(Date.class)&lt;/b&gt; : 반환될 타입을 지정한다. TimeStamp -&amp;gt; Date 변환이 필요하면 Date.class를, TimeStamp -&amp;gt; String 변환이 필요하면 String.class를 입력하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) &lt;b&gt;패턴(&quot;DATE({0})&quot;)&lt;/b&gt; : 날짜 형식을 지정하는 템플릿 문자열이다. DATE 함수를 통해 {0}에는 qEntity.dateField가 들어간다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) &lt;b&gt;값(qEntity.dateField)&lt;/b&gt;&amp;nbsp;: 템플릿에서 사용될 값들을 전달한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이렇게 하면 시간 부분은 제외한 날짜만 추출할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예를 들어, &lt;u&gt;2025-09-15 12:30:45&lt;/u&gt; 에서 &lt;u&gt;2025-09-15&lt;/u&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 Light';&quot;&gt;날짜 형식을 &lt;u&gt;DATE_FORMAT&lt;/u&gt;을 이용하여 직접 지정할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757926745598&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.dateTemplate(Date.class, &quot;DATE_FORMAT({0}, {1})&quot;, qEntity.dateField, &quot;yyyy-MM-dd&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Query문으로 돌아가는 걸 확인했으니 QueryDSL에 적용시켜보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758011761743&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Tuple results = queryFactory.select(qStatsTable.regDt, qStatsTable.count())
                    .from(qStatsTable)
                    .groupBy(Expressions.dateTemplate(String.class, &quot;DATE({0})&quot;, qStatsTable.regDt))
                    .orderBy(qStatsTable.regDt.desc())
                    .limit(1)
                    .fetchOne();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;select 조건은 유지한채로 groupBy 문을 바꿔주었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기존에 qStatsTable.regDt 기준에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;qStatsTable.regDt을 &lt;/span&gt;DATE 형식으로 변환한 형태로 바꿔주었다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. 주요 Expressions 종류&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 Light';&quot;&gt;&lt;b&gt;1) Expressions.constant() &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 Light';&quot;&gt;&lt;span&gt;- 상수 값을 쿼리에서 사용할 때 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012897616&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.constant(&quot;Hello&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span&gt;2) Expressioins.template()&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span&gt;- 템플릿 문자열을 이용하여 표현식을 생성한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012893498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.template(String.class, &quot;CONCAT({0}, {1})&quot;, qEntity.firstName, qEntity.lastName)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span&gt;3) Expressions.dateTemplate()&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span&gt;- 날짜 형식을 지정할 때 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012884590&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.dateTemplate(Date.class, &quot;DATE_FORMAT({0}, {1})&quot;, qEntity.dateField, &quot;yyyy-MM-dd&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span&gt;4) Expressions.booleanTemplate()&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span&gt;- Boolean 타입을 처리할 수 있는 템플릿 표현식을 생성한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012878184&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.booleanTemplate(&quot;{0} = {1}&quot;, qEntity.status, 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span&gt;5) Expressions.numberTemplate()&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span&gt;- 숫자 타입 표현식을 생성할 때 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012927090&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.numberTemplate(Integer.class, &quot;{0} + {1}&quot;, qEntity.num1, qEntity.num2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6) Expressions.stringTemplate()&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 문자열 타입 표현식을 생성할 때 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012957817&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.stringTemplate(&quot;CONCAT({0}, {1})&quot;, qEntity.firstName, qEntity.lastName)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;7) Expressions.path()&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Path 객체를 생성할 때 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012966315&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.path(String.class, &quot;name&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8) Expressions.predicate() &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Predicate 객체를 생성할 때 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758012996113&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Expressions.predicate(Ops.EQ, qEntity.status, 1)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;저장할 때 Timestamp로 저장했기 때문에 groupBy도 초 단위로 그룹화하는 것을 놓쳤었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Expressions을 활용해서 다양한 조건을 가지고 있는 변수를 다룰 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/Java</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/302</guid>
      <comments>https://hye-log.tistory.com/entry/QueryDSL-ExpressionsdateTemplate%EB%A1%9C-SQL-%EA%B5%AC%EB%AC%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry302comment</comments>
      <pubDate>Mon, 22 Sep 2025 09:20:56 +0900</pubDate>
    </item>
    <item>
      <title>[Apache POI] Iterator로 읽은 빈 셀 CellType.BLANK로 예외처리하기</title>
      <link>https://hye-log.tistory.com/entry/Apache-POI-Iterator%EB%A1%9C-%EC%9D%BD%EC%9D%80-%EB%B9%88-%EC%85%80-CellTypeBLANK%EB%A1%9C-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Apache POI를 이용해서 엑셀 파일을 읽어오는 기능을 구현했는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기능 개발을 할 때는 되다가 테스트를 하니 ResponseDto에서 값을 가져오지 못하는 에러가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Dto에서 디버깅을 해보니 값은 들어있어서 xlsx에서 row를 iterator로 읽는 과정에서 문제가 있다고 판단했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 문제 상황&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757997124581&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try (InputStream inputStream = file.getInputStream()) {
      Workbook workbook = new XSSFWorkbook(inputStream);
      Sheet sheet = workbook.getSheetAt(0);
      Iterator&amp;lt;Row&amp;gt; rowIterator = sheet.iterator();
	 
      rowIterator.next(); // head 읽기

      while (rowIterator.hasNext()) { // body 읽기
        Row row = rowIterator.next();
        /** 비즈니스 로직 **/
        ResponseDto.toDto(row)
      }

      workbook.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 코드는 xlsx 파일에서 한 줄씩 읽어 비즈니스 로직을 수행한 후 Dto로 변환하는 코드이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서 문제가 된 부분은 바로 &lt;b&gt;rowIterator.next()&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 Light';&quot;&gt;이 부분은 head row를 제외한 body row를 반복문으로 읽는 부분이다.&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 Light';&quot;&gt;내가 읽은 xslx 파일은 다음과 같이 생겼다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XOETd/btsQyv3QNhr/buMbZ2NspksZeWxnfOgGP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XOETd/btsQyv3QNhr/buMbZ2NspksZeWxnfOgGP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XOETd/btsQyv3QNhr/buMbZ2NspksZeWxnfOgGP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXOETd%2FbtsQyv3QNhr%2FbuMbZ2NspksZeWxnfOgGP1%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;716&quot; height=&quot;77&quot; data-origin-width=&quot;716&quot; data-origin-height=&quot;77&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 Light';&quot;&gt;rowIterator.next() 부분에 디버깅해보니 다음과 같았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일단 첫 번째 줄은 정상적으로 읽었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757997385922&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;xml-fragment r=&quot;2&quot; spans=&quot;1:6&quot; x14ac:dyDescent=&quot;0.3&quot; xmlns:r=&quot;http://schemas.openxmlformats.org/officeDocument/2006/relationships&quot; xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; xmlns:x14ac=&quot;http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac&quot; xmlns:main=&quot;http://schemas.openxmlformats.org/spreadsheetml/2006/main&quot;&amp;gt;
  &amp;lt;main:c r=&quot;A2&quot; s=&quot;2&quot; t=&quot;s&quot;&amp;gt;
    &amp;lt;main:v&amp;gt;6&amp;lt;/main:v&amp;gt;
  &amp;lt;/main:c&amp;gt;
  &amp;lt;main:c r=&quot;B2&quot; s=&quot;2&quot; t=&quot;s&quot;&amp;gt;
    &amp;lt;main:v&amp;gt;7&amp;lt;/main:v&amp;gt;
  &amp;lt;/main:c&amp;gt;
  &amp;lt;main:c r=&quot;C2&quot; s=&quot;2&quot; t=&quot;s&quot;&amp;gt;
    &amp;lt;main:v&amp;gt;8&amp;lt;/main:v&amp;gt;
  &amp;lt;/main:c&amp;gt;
  &amp;lt;main:c r=&quot;D2&quot; s=&quot;2&quot; t=&quot;s&quot;&amp;gt;
    &amp;lt;main:v&amp;gt;9&amp;lt;/main:v&amp;gt;
  &amp;lt;/main:c&amp;gt;
  &amp;lt;main:c r=&quot;E2&quot; s=&quot;2&quot; t=&quot;s&quot;&amp;gt;
    &amp;lt;main:v&amp;gt;10&amp;lt;/main:v&amp;gt;
  &amp;lt;/main:c&amp;gt;
  &amp;lt;main:c r=&quot;F2&quot; s=&quot;2&quot; t=&quot;s&quot;&amp;gt;
    &amp;lt;main:v&amp;gt;11&amp;lt;/main:v&amp;gt;
  &amp;lt;/main:c&amp;gt;
&amp;lt;/xml-fragment&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1757997394157&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{0=데이터값1, 1=데이터값2, 2=데이터값3, 3=데이터값4, 4=데이터값5, 5=데이터값6}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5개의 데이터값을 정상적으로 읽어왔다.&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 Light';&quot;&gt;다음 rowIterator.next()를 실행했을 때 빈 셀이므로 읽지 말아야 하는데,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757997445591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;xml-fragment r=&quot;3&quot; spans=&quot;1:6&quot; x14ac:dyDescent=&quot;0.3&quot; xmlns:r=&quot;http://schemas.openxmlformats.org/officeDocument/2006/relationships&quot; xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; xmlns:x14ac=&quot;http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac&quot; xmlns:main=&quot;http://schemas.openxmlformats.org/spreadsheetml/2006/main&quot;&amp;gt;
  &amp;lt;main:c r=&quot;A3&quot; s=&quot;2&quot;/&amp;gt;
  &amp;lt;main:c r=&quot;B3&quot; s=&quot;2&quot;/&amp;gt;
  &amp;lt;main:c r=&quot;C3&quot; s=&quot;2&quot;/&amp;gt;
  &amp;lt;main:c r=&quot;D3&quot; s=&quot;2&quot;/&amp;gt;
  &amp;lt;main:c r=&quot;E3&quot; s=&quot;2&quot;/&amp;gt;
&amp;lt;/xml-fragment&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1757997451268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{0=, 1=, 2=, 3=, 4=}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다음 비어있는 row도 읽어버리는 것이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Iterator는 row를 읽어버렸으니 비즈니스 로직을 수행하고 dto로 변환하는 작업까지 들어가버린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러니 dto에서는 빈 값만 받아 값을 가져오지 못하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 원인 분석&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정확한 오류 원인을 파악하기 위해서 몇 가지 엑셀 파일로 실험을 해보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RI9WC/btsQBUPzNzV/EpsaVHzOEi4X5W5o6BpqBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RI9WC/btsQBUPzNzV/EpsaVHzOEi4X5W5o6BpqBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RI9WC/btsQBUPzNzV/EpsaVHzOEi4X5W5o6BpqBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRI9WC%2FbtsQBUPzNzV%2FEpsaVHzOEi4X5W5o6BpqBK%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;1730&quot; height=&quot;429&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;일단 이 기능은 간단하게 설명하자면 &lt;u&gt;엑셀 파일을 다운 받아 -&amp;gt; head5에 값을 입력한 뒤 -&amp;gt; 다시 업로드&lt;/u&gt; 하는 기능이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJj3It/btsQD35L8Wf/gJ2qETpj9YxOVKBW8Vk55k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJj3It/btsQD35L8Wf/gJ2qETpj9YxOVKBW8Vk55k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJj3It/btsQD35L8Wf/gJ2qETpj9YxOVKBW8Vk55k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJj3It%2FbtsQD35L8Wf%2FgJ2qETpj9YxOVKBW8Vk55k%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;746&quot; height=&quot;243&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;첫 번째 파일은 &lt;u&gt;여러 row&lt;/u&gt;가 있는 엑셀 파일을 다운 받아 &lt;u&gt;임의의 row를 지우고&lt;/u&gt; 데이터 값을 입력한 뒤 한 줄만 남기고 업로드하고,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JBq9r/btsQBSEfFht/npLeHNbgx6EcpsNDpMrAq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JBq9r/btsQBSEfFht/npLeHNbgx6EcpsNDpMrAq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JBq9r/btsQBSEfFht/npLeHNbgx6EcpsNDpMrAq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJBq9r%2FbtsQBSEfFht%2FnpLeHNbgx6EcpsNDpMrAq1%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;761&quot; height=&quot;116&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 번째 파일은 &lt;u&gt;하나의 row&lt;/u&gt;가 있는 엑셀 파일을 다운 받아 데이터 값을 입력한 뒤 &lt;u&gt;그대로&lt;/u&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 Light';&quot;&gt;결과는?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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 Light';&quot;&gt;두 번째 파일은 에러가 발생하지 않았다!&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 파일의 차이점이라고 한다면 &lt;u&gt;사용자가 빈 셀에도 값을 입력한 적이 있는지&lt;/u&gt; 여부이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;첫 번째 파일은 &lt;b&gt;임의로 지운 row에서 빈 값을 읽어&lt;/b&gt; dto로 넘기니 에러가 발생하는 것이고,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO3M0i/btsQEpOe4tI/gUjDqd1YvyGLmi7XVG3nqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO3M0i/btsQEpOe4tI/gUjDqd1YvyGLmi7XVG3nqK/img.png&quot; data-alt=&quot;첫 번째 파일 - 데이터값이 있었으나 delete로 삭제하여 셀 속성값 등이 남아 있는 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO3M0i/btsQEpOe4tI/gUjDqd1YvyGLmi7XVG3nqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO3M0i%2FbtsQEpOe4tI%2FgUjDqd1YvyGLmi7XVG3nqK%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;996&quot; height=&quot;195&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;첫 번째 파일 - 데이터값이 있었으나 delete로 삭제하여 셀 속성값 등이 남아 있는 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;두 번째 파일은 &lt;b&gt;하나의 row 외에 나머지 셀에 값을 입력한 적이 없으니&lt;/b&gt; 한 줄만 읽고 동작을 끝내는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmKmZ4/btsQCvhIdZC/23wkerUBCS3X1zGU0wRInK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmKmZ4/btsQCvhIdZC/23wkerUBCS3X1zGU0wRInK/img.png&quot; data-alt=&quot;두 번째 파일 - 처음부터 데이터값을 입력하지 않은 깨끗한 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmKmZ4/btsQCvhIdZC/23wkerUBCS3X1zGU0wRInK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmKmZ4%2FbtsQCvhIdZC%2F23wkerUBCS3X1zGU0wRInK%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;996&quot; height=&quot;207&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두 번째 파일 - 처음부터 데이터값을 입력하지 않은 깨끗한 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;데이터 값을 삭제한다고 해도 &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 Light';&quot;&gt;iterator로 읽을 때 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;값이 남아 있다고 판단하여 계속 읽게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;엑셀에서 파일을 편집할 때 &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 Light';&quot;&gt;사용자 입장에서는 &quot;&lt;u&gt;데이터만 없으면 셀을 안 읽는거 아니야?&lt;/u&gt;&quot; 라고 생각할 수 있기 때문에,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;번거롭게 할 필요가 없다고 생각하여 이 부분에 &lt;b&gt;예외 처리를 추가&lt;/b&gt;하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (5).png&quot; data-origin-width=&quot;213&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mln8q/btsQA3FgOOa/cCWersG29NtlqQKC3XzVcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mln8q/btsQA3FgOOa/cCWersG29NtlqQKC3XzVcK/img.png&quot; data-alt=&quot;이 삭제 기능을 쓰면 셀 값을 완전히 지울 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mln8q/btsQA3FgOOa/cCWersG29NtlqQKC3XzVcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmln8q%2FbtsQA3FgOOa%2FcCWersG29NtlqQKC3XzVcK%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;213&quot; height=&quot;308&quot; data-filename=&quot;image (5).png&quot; data-origin-width=&quot;213&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이 삭제 기능을 쓰면 셀 값을 완전히 지울 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 해결 방법&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;첫 번째로 생각한 방법은 iterator로 빈 셀 값을 읽으니 value를 string으로 바꾸어 &lt;b&gt;빈 문자열&lt;/b&gt;인지 판단하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757997984511&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (rowIterator.hasNext()) {
    Row row = rowIterator.next();

    // 셀 값을 string으로 바꾸어 빈 셀 판
    if (row.getCell(0).toString().equals(&quot;&quot;))
      break;

    /** 비즈니스 로직 **/
    ResponseDto.toDto(row)
  }&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 Light';&quot;&gt;row.getCell(0)이 반환하는 Cell 객체에 대해 문자열을 바꾸어 빈 문자열인지 판단한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그러나 빈 셀의 경우 &lt;u&gt;&quot;[Empty]&quot; 또는 &quot;null&quot;&lt;/u&gt; 과 같은 결과를 반환할 수 있기 때문에 정확하게 체크하지 못할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또한, 문자열이 아니라 &lt;u&gt;숫자나 날짜 형식의 셀&lt;/u&gt;이었다면 원하지 않는 형태로 변환될 수도 있고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;toString() 을 호출하는 것 자체가 &lt;u&gt;불필요한 연산&lt;/u&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 Light';&quot;&gt;두 번째로 찾아본 방법은 셀의 값을 완전히 삭제하지 않는 경우,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;iterator를 통해 셀을 읽긴 하니까&lt;b&gt; CellType&lt;/b&gt;을 이용하여 빈 셀인지 판단하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757998250412&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while (rowIterator.hasNext()) {
    Row row = rowIterator.next();

    // CellType.BLANK를 이용하여 빈 셀 판단
    if (row.getCell(0).getCellType() == CellType.BLANK)
      break;

    /** 비즈니스 로직 **/
    ResponseDto.toDto(row)
  }&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 Light';&quot;&gt;row.getCell()은 Cell의 타입을 반환하기 때문에 이 속성을 이용하여 빈 셀인지 판단할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CellType에는 5가지 종류가 있다.&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 Light';&quot;&gt;- CellType.BLANK : 빈 셀&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 Light';&quot;&gt;- CellType.STRING : 문자열&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 Light';&quot;&gt;- CellType.NUMERIC : 숫자&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 Light';&quot;&gt;- CellType.BOOLEAN : 불리언(true, false)&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 Light';&quot;&gt;- CellType.ERROR : 오류(예. #DIV/0!, #VALUE!, #REF!)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문자열, 숫자, 날짜 등 어떤 데이터 타입이 오든 &lt;u&gt;CellType&lt;/u&gt;을 기준으로 판단하기 때문에 정확하게 판단할 수 있고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;toString()을 사용했을 때 발생할 수 있는 &lt;u&gt;NullPointerException&lt;/u&gt;의 가능성도 줄여준다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;셀에 값이 없어도 보이지 않는 속성값으로 인하여 에러가 발생할 수 있다는 사실을 알게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발을 할 때 가장 중요하게 생각하는 건 '사용자가 기능을 편하게 사용할 수 있는가?' 이므로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;발생할 수 있는 가능성에 대해 예외 처리를 하는 것도 개발 능력이라고 생각한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예외 처리 방법도 사용자에게 에러 상황이라고 알려줄지, 예외 처리를 통해 정상적으로 서비스할지 선택할 수 있는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이런 상황은 사용자가 굳이(?) 알 필요 없이 처리하면 되는 상황이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;충분히 가능할 수 있는 상황을 서비스 배포 전에 발견하고 처리할 수 있어서 다행이다!&lt;/span&gt;&lt;/p&gt;</description>
      <category>Development/Java</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/301</guid>
      <comments>https://hye-log.tistory.com/entry/Apache-POI-Iterator%EB%A1%9C-%EC%9D%BD%EC%9D%80-%EB%B9%88-%EC%85%80-CellTypeBLANK%EB%A1%9C-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0#entry301comment</comments>
      <pubDate>Tue, 16 Sep 2025 14:20:24 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka]헷갈리는 토픽(topic), 이벤트(event), 파티션(partition), 컨슈머(consumer) 정리하기 - 택배 물류 창고로 비유하기</title>
      <link>https://hye-log.tistory.com/entry/Kafka%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%ED%86%A0%ED%94%BDtopic-%EC%9D%B4%EB%B2%A4%ED%8A%B8event-%ED%8C%8C%ED%8B%B0%EC%85%98partition-%EC%BB%A8%EC%8A%88%EB%A8%B8consumer-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0-%ED%83%9D%EB%B0%B0-%EB%AC%BC%EB%A5%98-%EC%B0%BD%EA%B3%A0%EB%A1%9C-%EB%B9%84%EC%9C%A0%ED%95%98%EA%B8%B0</link>
      <description>&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;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qwADy/btsQqubG3HK/ccvDK1whXKzyLZnycSMjS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qwADy/btsQqubG3HK/ccvDK1whXKzyLZnycSMjS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qwADy/btsQqubG3HK/ccvDK1whXKzyLZnycSMjS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqwADy%2FbtsQqubG3HK%2FccvDK1whXKzyLZnycSMjS1%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;572&quot; height=&quot;572&quot; data-filename=&quot;물류 창고 다이어그램.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카는 토픽을 기준으로 이벤트를 저장하고 전달하는데,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;토픽은 여러 파티션으로 나뉘어져 있어서 여러 컨슈머가 병렬로 읽을 수 있어 처리 속도가 빨라!&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카는&amp;nbsp;이벤트를&amp;nbsp;안전하게&amp;nbsp;전달해서&amp;nbsp;여러&amp;nbsp;서비스가&amp;nbsp;구독하여&amp;nbsp;처리하게&amp;nbsp;해줘. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서&amp;nbsp;&lt;u&gt;이벤트를&amp;nbsp;분류하고&amp;nbsp;저장하는&amp;nbsp;단위&lt;/u&gt;를&amp;nbsp;&lt;b&gt;토픽&lt;/b&gt;이라고&amp;nbsp;하는데, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;물리적으로&amp;nbsp;여러&amp;nbsp;파티션에&amp;nbsp;나뉘어&amp;nbsp;저장할&amp;nbsp;수&amp;nbsp;있어서&amp;nbsp;병렬&amp;nbsp;처리가&amp;nbsp;가능해. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;u&gt;토픽에&amp;nbsp;담긴&amp;nbsp;개별&amp;nbsp;메시지&lt;/u&gt;를&amp;nbsp;&lt;b&gt;이벤트&lt;/b&gt;라고&amp;nbsp;불러. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카의&amp;nbsp;주요&amp;nbsp;개념인&amp;nbsp;토픽,&amp;nbsp;이벤트,&amp;nbsp;파티션을&amp;nbsp;택배에&amp;nbsp;비유해보자. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;모든&amp;nbsp;택배가&amp;nbsp;모이는&amp;nbsp;물류창고가&amp;nbsp;있을거야. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이&amp;nbsp;물류창고에는&amp;nbsp;여러&amp;nbsp;컨베이어&amp;nbsp;벨트가&amp;nbsp;있고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이&amp;nbsp;컨베이어&amp;nbsp;벨트로&amp;nbsp;여러&amp;nbsp;택배&amp;nbsp;상자들이&amp;nbsp;있어. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서&amp;nbsp;&lt;b&gt;물류창고&amp;nbsp;=&amp;nbsp;토픽,&amp;nbsp;컨베이어&amp;nbsp;벨트&amp;nbsp;=&amp;nbsp;파티션,&amp;nbsp;택배&amp;nbsp;상자&amp;nbsp;=&amp;nbsp;이벤트&lt;/b&gt;로&amp;nbsp;비유할&amp;nbsp;수&amp;nbsp;있어. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약&amp;nbsp;전국의&amp;nbsp;택배를&amp;nbsp;하나의&amp;nbsp;컨베이어&amp;nbsp;벨트로&amp;nbsp;분류한다면&amp;nbsp;어떻게&amp;nbsp;될까? &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;하나의&amp;nbsp;컨베이어&amp;nbsp;벨트에&amp;nbsp;택배&amp;nbsp;상자가&amp;nbsp;계속&amp;nbsp;쌓이겠지? &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면&amp;nbsp;컨베이어&amp;nbsp;벨트를&amp;nbsp;허브&amp;nbsp;별로&amp;nbsp;나눈다면&amp;nbsp;병목&amp;nbsp;현상을&amp;nbsp;해결할&amp;nbsp;수&amp;nbsp;있을꺼야. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카도&amp;nbsp;마찬가지로&amp;nbsp;이벤트가&amp;nbsp;계속&amp;nbsp;들어오는데&amp;nbsp;하나의&amp;nbsp;파티션으로&amp;nbsp;처리하면&amp;nbsp;느리게&amp;nbsp;돼. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여러&amp;nbsp;&lt;b&gt;파티션&lt;/b&gt;으로&amp;nbsp;나누면&amp;nbsp;각&amp;nbsp;특성에&amp;nbsp;따라&amp;nbsp;이벤트를&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;되는거야. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각&amp;nbsp;파티션마다&amp;nbsp;각&amp;nbsp;컨슈머가&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있어서&amp;nbsp;병렬&amp;nbsp;처리가&amp;nbsp;가능하게&amp;nbsp;돼. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각 파티션은 같은 토픽의 이벤트를 전달하고, 각 파티션은 하나의 &lt;b&gt;컨슈머&lt;/b&gt;만 접근할 수 있어.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어떤 이벤트가 어떤 파티션에 들어갈지는 프로듀서의 파티셔닝 전략에 따라 달라져서,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파티션마다 담기는 메시지 범위가 달라질 수 있어.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;u&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약&amp;nbsp;특정&amp;nbsp;파티션을&amp;nbsp;담당하는&amp;nbsp;컨슈머의&amp;nbsp;트래픽에&amp;nbsp;과부하가&amp;nbsp;걸리게&amp;nbsp;된다면? &lt;/span&gt;&lt;/u&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨슈머를 추가해서 파티션 분배를 다시 조정하거나,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;동적 키 매핑 / 해시 분산을 통해 리파티셔닝(re-partitioning)을 통해 새로운 토픽으로 쪼갤 수 있어 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또는 핫 방 분리 전략이라고 해서 특정 키만 별도의 토픽/파티션을 만들어서 별도의 컨슈머가 처리하게 만드는 것도 가능해!&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra/Kafka</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/300</guid>
      <comments>https://hye-log.tistory.com/entry/Kafka%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%ED%86%A0%ED%94%BDtopic-%EC%9D%B4%EB%B2%A4%ED%8A%B8event-%ED%8C%8C%ED%8B%B0%EC%85%98partition-%EC%BB%A8%EC%8A%88%EB%A8%B8consumer-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0-%ED%83%9D%EB%B0%B0-%EB%AC%BC%EB%A5%98-%EC%B0%BD%EA%B3%A0%EB%A1%9C-%EB%B9%84%EC%9C%A0%ED%95%98%EA%B8%B0#entry300comment</comments>
      <pubDate>Mon, 8 Sep 2025 17:52:09 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka]카프카(Apache Kafka)가 왜 필요한걸까? - 커피 주문으로 비유하기</title>
      <link>https://hye-log.tistory.com/entry/Kafka%EC%B9%B4%ED%94%84%EC%B9%B4Apache-Kafka%EA%B0%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B1%B8%EA%B9%8C-%EC%BB%A4%ED%94%BC-%EC%A3%BC%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B9%84%EC%9C%A0%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2025년 8월 26일 오후 02_55_33.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAkGGd/btsP5Hi8ghg/vrTEwEVEWQOQFZRJ6hKX8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAkGGd/btsP5Hi8ghg/vrTEwEVEWQOQFZRJ6hKX8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAkGGd/btsP5Hi8ghg/vrTEwEVEWQOQFZRJ6hKX8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAkGGd%2FbtsP5Hi8ghg%2FvrTEwEVEWQOQFZRJ6hKX8K%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;621&quot; height=&quot;414&quot; data-filename=&quot;ChatGPT Image 2025년 8월 26일 오후 02_55_33.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카는 서비스 간 이벤트를 느슨하게 연결해서,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;엄청&amp;nbsp;많은&amp;nbsp;트래픽을&amp;nbsp;안전하게&amp;nbsp;비동기로&amp;nbsp;처리하면서&amp;nbsp;확장&amp;nbsp;가능한&amp;nbsp;메시지&amp;nbsp;플랫폼이야.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우리가&amp;nbsp;키오스크에서&amp;nbsp;커피를&amp;nbsp;주문한다고&amp;nbsp;가정해보자. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그럼&amp;nbsp;커피를&amp;nbsp;주문함으로&amp;nbsp;인해, &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1.&amp;nbsp;(저장)&amp;nbsp;서버에&amp;nbsp;커피&amp;nbsp;주문이&amp;nbsp;저장된다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2.&amp;nbsp;(주방)&amp;nbsp;주문&amp;nbsp;내역을&amp;nbsp;주방으로&amp;nbsp;전송한다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3.&amp;nbsp;(재고)&amp;nbsp;커피&amp;nbsp;원두,&amp;nbsp;우유&amp;nbsp;등&amp;nbsp;재고를&amp;nbsp;차감한다. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;저장,&amp;nbsp;주문,&amp;nbsp;재고&amp;nbsp;세&amp;nbsp;가지&amp;nbsp;일이&amp;nbsp;동시에&amp;nbsp;일어나야해. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;만약&amp;nbsp;커피&amp;nbsp;주문이&amp;nbsp;엄청&amp;nbsp;많이&amp;nbsp;몰려온다면&amp;nbsp;어떻게&amp;nbsp;될까? &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;커피&amp;nbsp;주문과&amp;nbsp;동시에&amp;nbsp;저장,&amp;nbsp;주문,&amp;nbsp;재고&amp;nbsp;처리를&amp;nbsp;한&amp;nbsp;번에&amp;nbsp;하다보면 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;서버에&amp;nbsp;주문이&amp;nbsp;저장되는&amp;nbsp;데&amp;nbsp;시간이&amp;nbsp;걸려&amp;nbsp;주문&amp;nbsp;기록이&amp;nbsp;누락되거나, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주방으로&amp;nbsp;주문이&amp;nbsp;전달되는데&amp;nbsp;지연이&amp;nbsp;생기거나 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;재고&amp;nbsp;차감이&amp;nbsp;중복되어&amp;nbsp;마이너스&amp;nbsp;재고가&amp;nbsp;발생할&amp;nbsp;수&amp;nbsp;있어. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한&amp;nbsp;문제를&amp;nbsp;해결하기&amp;nbsp;위해서 &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각&amp;nbsp;작업을&amp;nbsp;독립적이고&amp;nbsp;효율적으로&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있어야&amp;nbsp;해. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서&amp;nbsp;&quot;&lt;b&gt;카프카(Apache&amp;nbsp;Kafka)&lt;/b&gt;&quot;가&amp;nbsp;등장하게&amp;nbsp;된거야. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카는&amp;nbsp;각&amp;nbsp;작업을&amp;nbsp;&lt;b&gt;비동기적&lt;/b&gt;으로&amp;nbsp;처리하고, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각&amp;nbsp;서비스가&amp;nbsp;&lt;b&gt;독립적&lt;/b&gt;으로&amp;nbsp;메시지를&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;커피&amp;nbsp;주문을&amp;nbsp;카프카에&amp;nbsp;비유해보자면, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주문이&amp;nbsp;들어오면&amp;nbsp;카프카가&amp;nbsp;주문&amp;nbsp;이벤트를&amp;nbsp;받아. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카는&amp;nbsp;주문&amp;nbsp;저장,&amp;nbsp;주방&amp;nbsp;전달,&amp;nbsp;재고&amp;nbsp;차감&amp;nbsp;등&amp;nbsp;여러&amp;nbsp;시스템이&amp;nbsp;서로&amp;nbsp;영향을&amp;nbsp;주지&amp;nbsp;않도록&amp;nbsp;독립적으로&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;도와줘. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;각&amp;nbsp;시스템이&amp;nbsp;카프카로부터&amp;nbsp;메시지를&amp;nbsp;수신하여&amp;nbsp;처리하므로, &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;대량의&amp;nbsp;주문이&amp;nbsp;몰려도&amp;nbsp;시스템은&amp;nbsp;병목&amp;nbsp;현상&amp;nbsp;없이&amp;nbsp;원활하게&amp;nbsp;작업을&amp;nbsp;분산시킬&amp;nbsp;수&amp;nbsp;있어. &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&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;/span&gt;&lt;/p&gt;</description>
      <category>Infra/Kafka</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/299</guid>
      <comments>https://hye-log.tistory.com/entry/Kafka%EC%B9%B4%ED%94%84%EC%B9%B4Apache-Kafka%EA%B0%80-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B1%B8%EA%B9%8C-%EC%BB%A4%ED%94%BC-%EC%A3%BC%EB%AC%B8%EC%9C%BC%EB%A1%9C-%EB%B9%84%EC%9C%A0%ED%95%98%EA%B8%B0#entry299comment</comments>
      <pubDate>Tue, 26 Aug 2025 14:56:24 +0900</pubDate>
    </item>
    <item>
      <title>[React Bits] React에 애니메이션을 추가하고 싶다면, React Bits!</title>
      <link>https://hye-log.tistory.com/entry/React-Bits-React%EC%97%90-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%84-%EC%B6%94%EA%B0%80%ED%95%98%EA%B3%A0-%EC%8B%B6%EB%8B%A4%EB%A9%B4-React-Bits</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1863&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLaDs/btsPXSyxOtc/9cG8PI3k33TLeHbIRVEqQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLaDs/btsPXSyxOtc/9cG8PI3k33TLeHbIRVEqQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLaDs/btsPXSyxOtc/9cG8PI3k33TLeHbIRVEqQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLaDs%2FbtsPXSyxOtc%2F9cG8PI3k33TLeHbIRVEqQ0%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;1863&quot; height=&quot;836&quot; data-origin-width=&quot;1863&quot; data-origin-height=&quot;836&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 Light';&quot;&gt;React 관련 애니메이션 문서를 찾아보다가&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;애니메이션, 컴포넌트, 배경 등 여러 React 컴포넌트를 제공해주고 있는 라이브러리를 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이미 Github 21.9K star를 받은 레포지토리인데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;요즘 React를 쓴다면 &quot;우와&quot; 할법한 애니메이션들이 가득하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;실제로 컴포넌트를 체험해볼 수도 있다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용 방법도 간단하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;npm에서 설치를 먼저 해주고,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755675368000&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install gsap&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 Light';&quot;&gt;사이트에서 체험해본 원하는 컴포넌트를 가져와서 쓰면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1755675390072&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SplitText from &quot;./SplitText&quot;;

&amp;lt;SplitText
  text=&quot;Hello, you!&quot;
  delay={100}
  duration={0.6}
/&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 Light';&quot;&gt;delay, duration 같은 props를 맘대로 조절할 수 있어서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;커스터마이징에 자유롭다는 게 큰 장점인거 같다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://reactbits.dev/&quot;&gt;https://reactbits.dev/&lt;/a&gt; &lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755675203595&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React Bits&quot; data-og-description=&quot;An open source collection of high quality, animated, interactive &amp;amp; fully customizable React components for building stunning, memorable user interfaces.&quot; data-og-host=&quot;reactbits.dev&quot; data-og-source-url=&quot;https://reactbits.dev/&quot; data-og-url=&quot;https://reactbits.dev&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/brXm3x/hyZzKDyGuO/YzbDKxK1t9MofJ7E0PtKF1/img.jpg?width=1691&amp;amp;height=952&amp;amp;face=0_0_1691_952,https://scrap.kakaocdn.net/dn/AR9c7/hyZydlLElh/38ceGRg9Wy5UnHncohStN0/img.jpg?width=1691&amp;amp;height=952&amp;amp;face=0_0_1691_952&quot;&gt;&lt;a href=&quot;https://reactbits.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://reactbits.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/brXm3x/hyZzKDyGuO/YzbDKxK1t9MofJ7E0PtKF1/img.jpg?width=1691&amp;amp;height=952&amp;amp;face=0_0_1691_952,https://scrap.kakaocdn.net/dn/AR9c7/hyZydlLElh/38ceGRg9Wy5UnHncohStN0/img.jpg?width=1691&amp;amp;height=952&amp;amp;face=0_0_1691_952');&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;React Bits&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An open source collection of high quality, animated, interactive &amp;amp; fully customizable React components for building stunning, memorable user interfaces.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;reactbits.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://github.com/DavidHDev/react-bits&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/DavidHDev/react-bits&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1755675425359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - DavidHDev/react-bits: An open source collection of animated, interactive &amp;amp; fully customizable React components for buil&quot; data-og-description=&quot;An open source collection of animated, interactive &amp;amp; fully customizable React components for building stunning, memorable websites. - DavidHDev/react-bits&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/DavidHDev/react-bits&quot; data-og-url=&quot;https://github.com/DavidHDev/react-bits&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/EU6oz/hyZCX9nQGr/khZ6sKoJaYxA5smhcD9tsK/img.png?width=1281&amp;amp;height=641&amp;amp;face=0_0_1281_641,https://scrap.kakaocdn.net/dn/uiyYS/hyZC8iK7rt/9SUWmKvk5kKQ4dxSPGsqf0/img.png?width=1281&amp;amp;height=641&amp;amp;face=0_0_1281_641&quot;&gt;&lt;a href=&quot;https://github.com/DavidHDev/react-bits&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/DavidHDev/react-bits&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/EU6oz/hyZCX9nQGr/khZ6sKoJaYxA5smhcD9tsK/img.png?width=1281&amp;amp;height=641&amp;amp;face=0_0_1281_641,https://scrap.kakaocdn.net/dn/uiyYS/hyZC8iK7rt/9SUWmKvk5kKQ4dxSPGsqf0/img.png?width=1281&amp;amp;height=641&amp;amp;face=0_0_1281_641');&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;GitHub - DavidHDev/react-bits: An open source collection of animated, interactive &amp;amp; fully customizable React components for buil&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An open source collection of animated, interactive &amp;amp; fully customizable React components for building stunning, memorable websites. - DavidHDev/react-bits&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Development/React</category>
      <author>iihye_</author>
      <guid isPermaLink="true">https://hye-log.tistory.com/298</guid>
      <comments>https://hye-log.tistory.com/entry/React-Bits-React%EC%97%90-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98%EC%9D%84-%EC%B6%94%EA%B0%80%ED%95%98%EA%B3%A0-%EC%8B%B6%EB%8B%A4%EB%A9%B4-React-Bits#entry298comment</comments>
      <pubDate>Wed, 20 Aug 2025 16:37:41 +0900</pubDate>
    </item>
  </channel>
</rss>