<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>subinto의 개발노트</title>
    <link>https://subinto.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 14 May 2026 00:44:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>꿈꾸는법사</managingEditor>
    <item>
      <title>java.sql.SQLRecoverableException: 소켓에서 읽을 데이터가 없습니다</title>
      <link>https://subinto.tistory.com/334</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;br /&gt;java.sql.SQLRecoverableException: 소켓에서 읽을 데이터가 없습니다 &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CMAREngineNIO.prepareForUnmarshall(T4CMAREngineNIO.java:804) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CMAREngineNIO.unmarshalUB1(T4CMAREngineNIO.java:449) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:410) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:269) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:655) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:270) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:91) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] &amp;emsp;&amp;emsp;at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:970) ~[ojdbc10-19.7.0.0.jar:19.7.0.0.0] ...&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;mapper 에서 쿼리를 호출할때 발생함. 어쩔때는 되고 어쩔때는 안되고... CLOB데이터로 인해 발생한 오류.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/오류</category>
      <category>clob</category>
      <category>error</category>
      <category>SQLRecoverableException</category>
      <category>에러</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/334</guid>
      <comments>https://subinto.tistory.com/334#entry334comment</comments>
      <pubDate>Mon, 6 Apr 2026 16:33:26 +0900</pubDate>
    </item>
    <item>
      <title>운영되었던 과거 사이트의 흔적을 찾을 때 추천!!!!</title>
      <link>https://subinto.tistory.com/333</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;과거에 홈페이지를 만들어 운영했었는데 소스도 흔적도 없어졌는데 아래 사이트에서 그 흔적을 찾았네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2002년도면 지금으로부터 23년 거의 24년전인데... 너무 반가운(?)마음에 블로그에 흔적(?)을 남겨요. ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://web.archive.org/&quot;&gt;https://web.archive.org/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1766032399157&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;Wayback Machine&quot; data-og-description=&quot;Ask the publishers to restore access to 500,000+ books.&quot; data-og-host=&quot;web.archive.org&quot; data-og-source-url=&quot;https://web.archive.org/&quot; data-og-url=&quot;https://web.archive.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://web.archive.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://web.archive.org/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Wayback Machine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ask the publishers to restore access to 500,000+ books.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;web.archive.org&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;a href=&quot;http://subinto.wo.to&quot;&gt;http://subinto.wo.to&lt;/a&gt; 였는데 검색하니 아래와 같이 나오네요. ㅎㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1259&quot; data-origin-height=&quot;626&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6MKQj/dJMcaiu4Y6I/s8JJxBcZsjtXyKa2WptSx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6MKQj/dJMcaiu4Y6I/s8JJxBcZsjtXyKa2WptSx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6MKQj/dJMcaiu4Y6I/s8JJxBcZsjtXyKa2WptSx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6MKQj%2FdJMcaiu4Y6I%2Fs8JJxBcZsjtXyKa2WptSx0%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;1259&quot; height=&quot;626&quot; data-origin-width=&quot;1259&quot; data-origin-height=&quot;626&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>문서 및 기타/유용한 팁</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/333</guid>
      <comments>https://subinto.tistory.com/333#entry333comment</comments>
      <pubDate>Thu, 18 Dec 2025 13:36:24 +0900</pubDate>
    </item>
    <item>
      <title>서버에서 List&amp;lt;Map&amp;gt;형태로 받아온 데이터 json 형태 배열로 바꾸기</title>
      <link>https://subinto.tistory.com/332</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 List&amp;lt;Map&amp;gt;형태로 화면이 로드될때 받아온 데이터를 스크립트에서 여러군데에서 사용한다면 스크립트 변수에 담아서 사용하면 편리하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&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;let&amp;nbsp;_actionPairs&amp;nbsp;=&amp;nbsp;null; &lt;br /&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;let _actionObj = null;&lt;br /&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;let _actionJsonArr = [];&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;c:forEach items=&quot;${actionList}&quot; var=&quot;item&quot; varStatus=&quot;status&quot;&amp;gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; _actionPairs = &quot;${item}&quot;.slice(1, -1).split(', '); &lt;br /&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;_actionObj&amp;nbsp;=&amp;nbsp;{}; &lt;br /&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;_actionPairs.forEach(pair&amp;nbsp;=&amp;gt;&amp;nbsp;{ &lt;br /&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;const&amp;nbsp;[key,&amp;nbsp;value]&amp;nbsp;=&amp;nbsp;pair.split('='); &lt;br /&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;_actionObj[key.trim()]&amp;nbsp;=&amp;nbsp;value.trim(); &lt;br /&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;}); &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; _actionJsonArr.push(_actionObj);&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;/c:forEach&amp;gt;&lt;/blockquote&gt;</description>
      <category>개발/Javascript</category>
      <category>JSON</category>
      <category>List&amp;lt;Map&amp;gt;</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/332</guid>
      <comments>https://subinto.tistory.com/332#entry332comment</comments>
      <pubDate>Tue, 12 Aug 2025 08:17:23 +0900</pubDate>
    </item>
    <item>
      <title>핸들러(Handler) 사용 가이드</title>
      <link>https://subinto.tistory.com/331</link>
      <description>&lt;p&gt;자바와 스프링 프레임워크를 공부하다 보면 &amp;#39;핸들러(Handler)&amp;#39;라는 용어를 자주 접하게 됩니다. 이름만 들어서는 무슨 일을 하는지 감이 잘 오지 않을 수 있는데요. 핸들러는 특정 요청이나 이벤트를 전담해서 처리하는 역할을 하는 객체를 의미합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;1. 핸들러, 왜 사용할까요?&lt;/h3&gt;
&lt;p&gt;핸들러의 가장 큰 목적은 *관심사 분리(Separation of Concerns)*입니다. 복잡한 로직을 하나의 거대한 클래스에 몰아넣지 않고, 기능별로 나누어 관리하는 것이죠. 예를 들어, 웹 요청을 처리할 때 로그인, 결제, 상품 조회 등 각기 다른 요청들을 하나의 클래스가 모두 처리한다면 코드가 매우 복잡해지고, 수정이 필요할 때마다 전체 코드를 분석해야 하는 어려움이 있습니다.&lt;/p&gt;
&lt;p&gt;핸들러를 사용하면 다음과 같은 장점을 얻을 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;높은 확장성&lt;/strong&gt;: 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 새로운 핸들러만 만들면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쉬운 유지보수&lt;/strong&gt;: 특정 기능의 로직을 수정할 때 해당 핸들러만 보면 되므로 다른 코드에 영향을 줄 가능성이 줄어듭니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;명확한 책임&lt;/strong&gt;: 각 핸들러는 자신의 역할에만 집중하므로 코드를 이해하기 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2. 스프링에서의 핸들러 (컨트롤러)&lt;/h3&gt;
&lt;p&gt;스프링 프레임워크에서 가장 흔히 접하는 핸들러는 바로 **컨트롤러(Controller)**입니다. &lt;code&gt;@Controller&lt;/code&gt; 어노테이션이 붙은 클래스가 바로 핸들러의 역할을 수행합니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;예시 코드:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Controller
public class MyController {

    @GetMapping(&amp;quot;/hello&amp;quot;)
    public String sayHello() {
        // &amp;quot;hello&amp;quot;라는 요청이 들어오면 이 메소드가 처리합니다.
        return &amp;quot;hello&amp;quot;; // hello.html 같은 뷰를 반환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 &lt;code&gt;sayHello()&lt;/code&gt; 메소드는 &lt;code&gt;/hello&lt;/code&gt;라는 URL 요청을 전담해서 처리하는 &lt;strong&gt;핸들러 메소드&lt;/strong&gt;입니다. 스프링은 &lt;code&gt;@GetMapping&lt;/code&gt;과 같은 어노테이션을 보고 어떤 요청을 어떤 핸들러 메소드에 연결할지 자동으로 매핑해줍니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;3. 전략 패턴을 활용한 핸들러&lt;/h3&gt;
&lt;p&gt;컨트롤러 외에도, 여러 핸들러를 만들고 상황에 따라 적절한 핸들러를 선택하여 실행하는 &lt;strong&gt;전략 패턴(Strategy Pattern)&lt;/strong&gt; 방식의 핸들러 사용법도 매우 중요합니다. 이것이 바로 질문하신 이미지에서 설명하는 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;단계별 구현 방법:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;핸들러 인터페이스 정의&lt;/strong&gt;: 모든 핸들러가 공통으로 가져야 할 메소드를 정의합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public interface MyHandler {
    void handle(String data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;핸들러 구현 클래스 생성&lt;/strong&gt;: 인터페이스를 구현하는 여러 클래스를 만들고, &lt;code&gt;@Component&lt;/code&gt; 어노테이션으로 이름을 부여하여 스프링 컨테이너에 등록합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component(&amp;quot;typeA&amp;quot;)
public class TypeAHandler implements MyHandler {
    @Override
    public void handle(String data) {
        System.out.println(&amp;quot;타입 A 핸들러가 데이터를 처리합니다: &amp;quot; + data);
    }
}

@Component(&amp;quot;typeB&amp;quot;)
public class TypeBHandler implements MyHandler {
    @Override
    public void handle(String data) {
        System.out.println(&amp;quot;타입 B 핸들러가 데이터를 처리합니다: &amp;quot; + data);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;핸들러를 관리하는 클래스&lt;/strong&gt;: 모든 핸들러를 주입받아 보관하고, 요청에 따라 적절한 핸들러를 찾아 실행하는 클래스를 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Service
public class HandlerService {

    private final Map&amp;lt;String, MyHandler&amp;gt; handlers;

    // 생성자를 통해 스프링 컨테이너의 모든 핸들러를 자동으로 주입받습니다.
    public HandlerService(Map&amp;lt;String, MyHandler&amp;gt; handlers) {
        this.handlers = handlers;
    }

    public void executeHandler(String type, String data) {
        MyHandler handler = handlers.get(type); // 요청 타입에 맞는 핸들러를 찾습니다.
        if (handler != null) {
            handler.handle(data); // 찾은 핸들러의 메소드를 실행합니다.
        } else {
            // 핸들러가 없을 경우의 처리 로직
            System.out.println(&amp;quot;해당 타입의 핸들러를 찾을 수 없습니다.&amp;quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;사용&lt;/strong&gt;: 컨트롤러에서 &lt;code&gt;HandlerService&lt;/code&gt;를 주입받아 사용자의 요청에 따라 핸들러를 호출합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@RestController
public class MyApiController {

    private final HandlerService handlerService;

    public MyApiController(HandlerService handlerService) {
        this.handlerService = handlerService;
    }

    @PostMapping(&amp;quot;/process&amp;quot;)
    public String processData(@RequestParam String type, @RequestBody String data) {
        handlerService.executeHandler(type, data);
        return &amp;quot;ok&amp;quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4. 정리: 언제 핸들러를 사용해야 할까요?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;요청의 종류가 다양할 때&lt;/strong&gt;: 웹 요청, 메시지 큐 이벤트 등 처리해야 할 요청의 종류가 많고 각각의 로직이 다를 때&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장 가능성이 높을 때&lt;/strong&gt;: 앞으로 새로운 유형의 요청이 추가될 가능성이 높을 때&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유지보수가 중요할 때&lt;/strong&gt;: 복잡한 로직을 여러 사람이 함께 개발하고 관리해야 할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;핸들러를 잘 활용하면 깔끔하고 효율적인 코드를 작성할 수 있습니다. 처음에는 조금 복잡해 보일 수 있지만, 이러한 구조를 이해하고 나면 유지보수와 확장이 훨씬 쉬운 애플리케이션을 만들 수 있을 거예요.&lt;/p&gt;</description>
      <category>개발/JAVA</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/331</guid>
      <comments>https://subinto.tistory.com/331#entry331comment</comments>
      <pubDate>Fri, 8 Aug 2025 14:14:53 +0900</pubDate>
    </item>
    <item>
      <title>여러 엑셀파일을 하나의 파일로 통합하기</title>
      <link>https://subinto.tistory.com/330</link>
      <description>&lt;pre id=&quot;code_1747012122718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;

import java.io.*;
import java.util.*;

public class ExcelMerger {

    private Workbook mergedWorkbook;

    public ExcelMerger() {
        mergedWorkbook = new XSSFWorkbook(); // 항상 결과는 .xlsx로 만듦
    }

    public void mergeExcelFiles(List&amp;lt;File&amp;gt; excelFiles) throws Exception {
        for (File file : excelFiles) {
            try (InputStream is = new FileInputStream(file)) {
                Workbook srcWorkbook = WorkbookFactory.create(is);
                for (int i = 0; i &amp;lt; srcWorkbook.getNumberOfSheets(); i++) {
                    Sheet srcSheet = srcWorkbook.getSheetAt(i);
                    String newSheetName = getUniqueSheetName(srcSheet.getSheetName());
                    Sheet destSheet = mergedWorkbook.createSheet(newSheetName);

                    copySheet(srcSheet, destSheet);
                }
            }
        }
    }

    private String getUniqueSheetName(String baseName) {
        String name = baseName;
        int counter = 1;
        while (mergedWorkbook.getSheet(name) != null) {
            name = baseName + &quot;_&quot; + counter++;
        }
        return name;
    }

    private void copySheet(Sheet srcSheet, Sheet destSheet) {
        Map&amp;lt;Integer, CellStyle&amp;gt; styleMap = new HashMap&amp;lt;&amp;gt;();

        for (int rowNum = 0; rowNum &amp;lt;= srcSheet.getLastRowNum(); rowNum++) {
            Row srcRow = srcSheet.getRow(rowNum);
            if (srcRow == null) continue;
            
            // 열 너비 복사
            Row firstRow = srcSheet.getRow(0);
            if (firstRow != null) {
                for (int col = 0; col &amp;lt;= firstRow.getLastCellNum(); col++) {
                    destSheet.setColumnWidth(col, srcSheet.getColumnWidth(col));
                }
            }
            
            Row destRow = destSheet.createRow(rowNum);
            destRow.setHeight(srcRow.getHeight());

            for (int colNum = 0; colNum &amp;lt; srcRow.getLastCellNum(); colNum++) {
                Cell srcCell = srcRow.getCell(colNum);
                if (srcCell == null) continue;

                Cell destCell = destRow.createCell(colNum);
                copyCell(srcCell, destCell, styleMap);
            }
        }

        // 복사 병합 영역
        for (int i = 0; i &amp;lt; srcSheet.getNumMergedRegions(); i++) {
            CellRangeAddress merged = srcSheet.getMergedRegion(i);
            destSheet.addMergedRegion(merged);
        }

        // 복사 이미지 (XSSF만 해당)
        if (srcSheet instanceof XSSFSheet &amp;amp;&amp;amp; destSheet instanceof XSSFSheet) {
            try {
                copyPictures((XSSFSheet) srcSheet, (XSSFSheet) destSheet, (XSSFWorkbook) mergedWorkbook);
            } catch (IOException e) {
                System.err.println(&quot;이미지 복사 중 오류: &quot; + e.getMessage());
            }
        }
    }

    private void copyCell(Cell srcCell, Cell destCell, Map&amp;lt;Integer, CellStyle&amp;gt; styleMap) {
        Workbook destWb = destCell.getSheet().getWorkbook();
        CellStyle newStyle = null;

        // 스타일 복사 (단, XSSFCellStyle일 때만)
        if (srcCell.getCellStyle() != null &amp;amp;&amp;amp;
            srcCell.getCellStyle().getClass().equals(destWb.createCellStyle().getClass())) {

            int styleHash = srcCell.getCellStyle().hashCode();
            newStyle = styleMap.computeIfAbsent(styleHash, k -&amp;gt; {
                CellStyle cloned = destWb.createCellStyle();
                cloned.cloneStyleFrom(srcCell.getCellStyle());
                return cloned;
            });
            destCell.setCellStyle(newStyle);
        } else {
            // 서로 다른 스타일 클래스(HSSF &amp;harr; XSSF)이므로 새 기본 스타일 사용
            destCell.setCellStyle(destWb.createCellStyle());
        }

        // 값 복사
        switch (srcCell.getCellType()) {
            case STRING:
                destCell.setCellValue(srcCell.getStringCellValue());
                break;
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(srcCell)) {
                    destCell.setCellValue(srcCell.getDateCellValue());
                } else {
                    destCell.setCellValue(srcCell.getNumericCellValue());
                }
                break;
            case BOOLEAN:
                destCell.setCellValue(srcCell.getBooleanCellValue());
                break;
            case FORMULA:
                try {
                    FormulaEvaluator evaluator = srcCell.getSheet().getWorkbook()
                            .getCreationHelper().createFormulaEvaluator();
                    CellValue evaluatedValue = evaluator.evaluate(srcCell);
                    if (evaluatedValue != null) {
                        switch (evaluatedValue.getCellType()) {
                            case NUMERIC:
                                destCell.setCellValue(evaluatedValue.getNumberValue());
                                break;
                            case STRING:
                                destCell.setCellValue(evaluatedValue.getStringValue());
                                break;
                            case BOOLEAN:
                                destCell.setCellValue(evaluatedValue.getBooleanValue());
                                break;
                            case ERROR:
                                destCell.setCellErrorValue(evaluatedValue.getErrorValue());
                                break;
                            case BLANK:
                                destCell.setBlank();
                                break;
                        }
                    }
                } catch (Exception e) {
                    destCell.setBlank(); // 안전하게 처리
                }
                break;
            case ERROR:
                destCell.setCellErrorValue(srcCell.getErrorCellValue());
                break;
            case BLANK:
                destCell.setBlank();
                break;
        }
    }


    private void copyPictures(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFWorkbook destWorkbook) throws IOException {
        XSSFDrawing srcDrawing = srcSheet.getDrawingPatriarch();
        if (srcDrawing == null) return;

        XSSFDrawing destDrawing = destSheet.createDrawingPatriarch();

        for (XSSFShape shape : srcDrawing.getShapes()) {
            if (shape instanceof XSSFPicture) {
                XSSFPicture srcPicture = (XSSFPicture) shape;
                XSSFPictureData picData = srcPicture.getPictureData();

                int pictureIndex = destWorkbook.addPicture(picData.getData(), picData.getPictureType());

                XSSFClientAnchor anchor = (XSSFClientAnchor) srcPicture.getAnchor();
                XSSFClientAnchor newAnchor = new XSSFClientAnchor(
                    anchor.getDx1(), anchor.getDy1(), anchor.getDx2(), anchor.getDy2(),
                    anchor.getCol1(), anchor.getRow1(), anchor.getCol2(), anchor.getRow2()
                );

                destDrawing.createPicture(newAnchor, pictureIndex);
            }
        }
    }

    public void saveMergedFile(String outputPath) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            mergedWorkbook.write(fos);
        }
    }
    
    public static void main(String[] args) throws Exception {
        ExcelMerger merger = new ExcelMerger();

        merger.mergeExcelFiles(Arrays.asList(
            new File(&quot;C:\\Users\\subinto\\Downloads\\AAA.xlsx&quot;),
            new File(&quot;C:\\Users\\subinto\\Downloads\\BBB.xls&quot;),
            new File(&quot;C:\\Users\\subinto\\Downloads\\CCC.xlsx&quot;)
        ));

        merger.saveMergedFile(&quot;C:\\Users\\subinto\\Downloads\\merged_result.xlsx&quot;);
        System.out.println(&quot;############### end&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/JAVA</category>
      <category>POI</category>
      <category>엑셀</category>
      <category>엑셀 통합</category>
      <category>여러 엑셀 파일</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/330</guid>
      <comments>https://subinto.tistory.com/330#entry330comment</comments>
      <pubDate>Mon, 12 May 2025 10:09:39 +0900</pubDate>
    </item>
    <item>
      <title>Java에서 String변수가 String[]에 포함되어 있는지 확인</title>
      <link>https://subinto.tistory.com/329</link>
      <description>&lt;p&gt;배열 &lt;code&gt;aa&lt;/code&gt; 안에 문자열 &lt;code&gt;bb&lt;/code&gt; 값이 포함되어 있는지 확인하려면 &lt;code&gt;Arrays.asList(aa).contains(bb)&lt;/code&gt; 또는 &lt;code&gt;Stream API&lt;/code&gt;를 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;1. &lt;code&gt;Arrays.asList().contains()&lt;/code&gt; 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] aa = {&amp;quot;AA&amp;quot;, &amp;quot;BB&amp;quot;, &amp;quot;CC&amp;quot;};
        String bb = &amp;quot;BB&amp;quot;;

        boolean exists = Arrays.asList(aa).contains(bb);
        System.out.println(&amp;quot;포함 여부: &amp;quot; + exists); // true 출력
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방법은 간단하지만 내부적으로 &lt;code&gt;List&lt;/code&gt; 변환이 필요합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;2. &lt;code&gt;Stream API&lt;/code&gt; 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        String[] aa = {&amp;quot;AA&amp;quot;, &amp;quot;BB&amp;quot;, &amp;quot;CC&amp;quot;};
        String bb = &amp;quot;BB&amp;quot;;

        boolean exists = Stream.of(aa).anyMatch(s -&amp;gt; s.equals(bb));
        System.out.println(&amp;quot;포함 여부: &amp;quot; + exists); // true 출력
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방법은 &lt;code&gt;Stream&lt;/code&gt;을 사용하여 한 줄로 해결할 수 있습니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;3. &lt;code&gt;for&lt;/code&gt; 루프 사용 (전통적인 방법)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Main {
    public static void main(String[] args) {
        String[] aa = {&amp;quot;AA&amp;quot;, &amp;quot;BB&amp;quot;, &amp;quot;CC&amp;quot;};
        String bb = &amp;quot;BB&amp;quot;;

        boolean exists = false;
        for (String s : aa) {
            if (s.equals(bb)) {
                exists = true;
                break;
            }
        }
        System.out.println(&amp;quot;포함 여부: &amp;quot; + exists); // true 출력
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방법은 가장 기본적인 방법으로, 작은 배열에서는 성능 차이가 크지 않지만, 큰 배열에서는 &lt;code&gt;Set&lt;/code&gt;을 사용하는 것이 효율적일 수 있습니다.&lt;/p&gt;
&lt;p&gt;필요에 따라 적절한 방법을 선택하세요!  &lt;/p&gt;</description>
      <category>개발/JAVA</category>
      <category>string 찾기</category>
      <category>string[]</category>
      <category>배열</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/329</guid>
      <comments>https://subinto.tistory.com/329#entry329comment</comments>
      <pubDate>Tue, 1 Apr 2025 09:56:09 +0900</pubDate>
    </item>
    <item>
      <title>input 의 값이 너무 길때 tooltip로 표시하기</title>
      <link>https://subinto.tistory.com/328</link>
      <description>&lt;h3&gt;  코드:&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Input Tooltip&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
        body {
            font-family: Arial, sans-serif;
            height: 200vh; /* 스크롤 테스트를 위해 높이 추가 */
            padding: 20px;
        }
        .tooltip {
            position: absolute;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 8px;
            border-radius: 5px;
            font-size: 14px;
            max-width: 300px;
            word-wrap: break-word;
            display: none;
            z-index: 1000;
        }
        input {
            width: 300px;
            padding: 5px;
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;

    &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;inputField&amp;quot; value=&amp;quot;이것은 너무 길어서 input 창에 다 안보이는 값입니다. 여기에 마우스를 올려보세요.&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;tooltip&amp;quot; class=&amp;quot;tooltip&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;

    &amp;lt;script&amp;gt;
        const inputField = document.getElementById(&amp;quot;inputField&amp;quot;);
        const tooltip = document.getElementById(&amp;quot;tooltip&amp;quot;);

        inputField.addEventListener(&amp;quot;mouseover&amp;quot;, function() {
            tooltip.textContent = inputField.value;
            tooltip.style.display = &amp;quot;block&amp;quot;;
        });

        inputField.addEventListener(&amp;quot;mousemove&amp;quot;, function(event) {
            // pageX, pageY를 사용하여 스크롤 영향을 받지 않도록 조정
            tooltip.style.top = (event.pageY + 10) + &amp;quot;px&amp;quot;;
            tooltip.style.left = (event.pageX + 10) + &amp;quot;px&amp;quot;;
        });

        inputField.addEventListener(&amp;quot;mouseout&amp;quot;, function() {
            tooltip.style.display = &amp;quot;none&amp;quot;;
        });
    &amp;lt;/script&amp;gt;

&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>개발/Javascript</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/328</guid>
      <comments>https://subinto.tistory.com/328#entry328comment</comments>
      <pubDate>Thu, 20 Mar 2025 08:10:21 +0900</pubDate>
    </item>
    <item>
      <title>jsp:include후 callback 함수 호출하는 방법</title>
      <link>https://subinto.tistory.com/327</link>
      <description>&lt;p&gt;&lt;code&gt;&amp;lt;jsp:include&amp;gt;&lt;/code&gt;는 JSP 페이지에서 다른 JSP를 포함할 때 사용하는 태그이지만, 이 방식은 &lt;strong&gt;동기적으로 동작&lt;/strong&gt;하며, 포함된 페이지가 모두 처리된 후에야 다음 코드가 실행됩니다. 즉, &lt;strong&gt;callback 함수 같은 개념이 직접적으로 존재하지 않습니다&lt;/strong&gt;.  &lt;/p&gt;
&lt;p&gt;하지만, 아래와 같은 방식으로 원하는 동작을 구현할 수 있습니다.  &lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;✅ &lt;strong&gt;해결 방법&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;1️⃣ &lt;strong&gt;JSP 내부에서 직접 함수 호출 (동기적)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;jsp:include&amp;gt;&lt;/code&gt;가 실행된 후 JSP의 Java 코드에서 특정 함수를 호출하는 방법입니다.  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsp&quot;&gt;&amp;lt;jsp:include page=&amp;quot;includedPage.jsp&amp;quot; /&amp;gt;
&amp;lt;% afterIncludeCallback(); %&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;  &lt;code&gt;afterIncludeCallback()&lt;/code&gt; 함수는 includedPage.jsp가 실행된 후 호출됨&lt;/strong&gt;  &lt;/p&gt;
&lt;p&gt;하지만, 이 방식은 JSP가 렌더링될 때만 동작하며 &lt;strong&gt;동적인 클라이언트 측 로직을 수행할 수 없습니다&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;2️⃣ &lt;strong&gt;JavaScript를 활용한 비동기 처리 (AJAX 방식)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;만약 &lt;strong&gt;callback 개념처럼 포함된 JSP가 실행된 후 특정 동작을 수행하고 싶다면&lt;/strong&gt;, AJAX를 이용하는 것이 효과적입니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;✅ AJAX + Servlet 방식&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsp&quot;&gt;&amp;lt;script&amp;gt;
  function loadIncludedPage() {
    fetch(&amp;#39;includedPage.jsp&amp;#39;) // includedPage.jsp를 비동기 로드
      .then(response =&amp;gt; response.text())
      .then(html =&amp;gt; {
        document.getElementById(&amp;#39;includedContent&amp;#39;).innerHTML = html;
        afterIncludeCallback(); // 포함된 페이지 로딩 후 실행할 함수
      });
  }

  function afterIncludeCallback() {
    alert(&amp;quot;포함된 JSP 로딩 완료!&amp;quot;);
  }

  window.onload = loadIncludedPage; // 페이지 로드 후 실행
&amp;lt;/script&amp;gt;

&amp;lt;div id=&amp;quot;includedContent&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방식은 &lt;strong&gt;실제 클라이언트에서 비동기로 JSP 파일을 로드하고, 로드 완료 후 콜백 함수 실행&lt;/strong&gt;이 가능합니다.&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;3️⃣ &lt;strong&gt;Servlet을 활용한 동적 데이터 처리&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;만약 &lt;code&gt;&amp;lt;jsp:include&amp;gt;&lt;/code&gt;가 데이터 처리를 위해 사용된다면, &lt;strong&gt;Servlet을 호출하여 데이터를 받아온 후 특정 동작을 수행하는 방법도 가능합니다&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;  &lt;code&gt;includedPage.jsp&lt;/code&gt; 대신 &lt;code&gt;IncludedServlet.java&lt;/code&gt; 사용&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@WebServlet(&amp;quot;/IncludedServlet&amp;quot;)
public class IncludedServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType(&amp;quot;text/html&amp;quot;);
        PrintWriter out = response.getWriter();
        out.println(&amp;quot;&amp;lt;p&amp;gt;포함된 Servlet 내용&amp;lt;/p&amp;gt;&amp;quot;);
        out.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;  JSP에서 AJAX로 Servlet 호출&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsp&quot;&gt;&amp;lt;script&amp;gt;
  function loadIncludedServlet() {
    fetch(&amp;#39;IncludedServlet&amp;#39;)
      .then(response =&amp;gt; response.text())
      .then(data =&amp;gt; {
        document.getElementById(&amp;#39;includedContent&amp;#39;).innerHTML = data;
        afterIncludeCallback();
      });
  }

  function afterIncludeCallback() {
    console.log(&amp;quot;Servlet 호출 후 실행할 코드&amp;quot;);
  }

  window.onload = loadIncludedServlet;
&amp;lt;/script&amp;gt;

&amp;lt;div id=&amp;quot;includedContent&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  &lt;strong&gt;결론&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;1️⃣ 동기적 실행이 필요하면?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;  &lt;code&gt;&amp;lt;jsp:include page=&amp;quot;includedPage.jsp&amp;quot; /&amp;gt;&lt;/code&gt; 이후에 바로 Java 코드 실행  &lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;2️⃣ 비동기적 실행 (callback 기능)을 원하면?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;  AJAX + JavaScript를 활용하여 &lt;code&gt;fetch()&lt;/code&gt;로 JSP or Servlet을 호출 후 callback 실행  &lt;/p&gt;
&lt;p&gt;이렇게 하면 &lt;code&gt;&amp;lt;jsp:include&amp;gt;&lt;/code&gt;의 단점을 보완하면서 &lt;strong&gt;비동기적인 callback 로직을 구현&lt;/strong&gt;할 수 있습니다.  &lt;/p&gt;</description>
      <category>개발/기타</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/327</guid>
      <comments>https://subinto.tistory.com/327#entry327comment</comments>
      <pubDate>Tue, 18 Mar 2025 11:14:55 +0900</pubDate>
    </item>
    <item>
      <title>CORS 정책 차단 오류 해결 방법</title>
      <link>https://subinto.tistory.com/326</link>
      <description>&lt;h3&gt;  CORS 정책 차단 오류 해결 방법&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;오류 메시지:&lt;/strong&gt;&lt;br&gt;❌ &lt;code&gt;&amp;quot;has been blocked by CORS policy: No &amp;#39;Access-Control-Allow-Origin&amp;#39; header is present on the requested resource.&amp;quot;&lt;/code&gt;  &lt;/p&gt;
&lt;p&gt;이 오류는 &lt;strong&gt;다른 도메인(origin)에서 API 요청을 보낼 때, 서버가 이를 허용하지 않아서 발생&lt;/strong&gt;하는 문제입니다. 웹 브라우저는 보안상의 이유로 다른 출처(Origin)에서 온 요청을 차단합니다.  &lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;✅ &lt;strong&gt;해결 방법&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;&lt;strong&gt;1️⃣ 서버에서 CORS 허용하기 (권장)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;요청을 보내는 서버에서 &lt;strong&gt;CORS 헤더를 추가&lt;/strong&gt;해야 합니다.  &lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;  Node.js (Express 서버)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const cors = require(&amp;#39;cors&amp;#39;);
const express = require(&amp;#39;express&amp;#39;);
const app = express();

app.use(cors()); // 모든 도메인에서 요청 허용

// 특정 도메인만 허용
app.use(cors({
  origin: &amp;#39;https://your-allowed-domain.com&amp;#39;
}));

app.listen(3000, () =&amp;gt; console.log(&amp;#39;Server running on port 3000&amp;#39;));&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;  Apache 서버&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;.htaccess&lt;/code&gt; 파일 수정:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot;&gt;Header set Access-Control-Allow-Origin &amp;quot;*&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;*&lt;/code&gt; 대신 특정 도메인만 허용 가능: &lt;code&gt;&amp;quot;https://your-allowed-domain.com&amp;quot;&lt;/code&gt;)&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;2️⃣ 프록시 서버 사용하기&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;백엔드 서버를 직접 수정할 수 없을 경우, &lt;strong&gt;프록시 서버를 사용&lt;/strong&gt;하면 해결할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;  프록시 설정 (React/Vue 등)&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;  &lt;code&gt;package.json&lt;/code&gt;에 프록시 설정 추가:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&amp;quot;proxy&amp;quot;: &amp;quot;https://your-api-server.com&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;3️⃣ 브라우저에서 CORS 무시하기 (임시 해결)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;개발 중이라면, CORS 정책을 무시하는 방법도 있습니다.&lt;br&gt;&lt;strong&gt;⚠️ 보안상 안전하지 않으므로, 배포 환경에서는 사용하면 안 됩니다!&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;  크롬 브라우저에서 CORS 비활성화 실행 (Windows)&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;chrome.exe --disable-web-security --user-data-dir=&amp;quot;C:\chrome-dev&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;strong&gt;  크롬 확장 프로그램 사용&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/moesif-origin-cors-change/&quot;&gt;Moesif CORS Chrome Extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;&lt;strong&gt;  결론&lt;/strong&gt;&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;서버에서 CORS 허용 설정 (가장 좋은 방법)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프록시 서버 사용&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개발 중이라면 브라우저 설정 변경 (배포용 아님)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>개발/오류</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/326</guid>
      <comments>https://subinto.tistory.com/326#entry326comment</comments>
      <pubDate>Tue, 18 Mar 2025 11:12:54 +0900</pubDate>
    </item>
    <item>
      <title>poi에서 A파일 sheet에 B파일 sheet 복사하기</title>
      <link>https://subinto.tistory.com/325</link>
      <description>&lt;h2&gt;&lt;strong&gt;1. 필요한 라이브러리&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Apache POI 라이브러리를 사용해야 해. 아래 의존성을 추가해 줘.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maven 프로젝트라면 &lt;code&gt;pom.xml&lt;/code&gt;에 추가:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;!-- Apache POI --&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.poi&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;poi-ooxml&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;5.2.3&amp;lt;/version&amp;gt; &amp;lt;!-- 최신 버전 확인 필요 --&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Gradle 프로젝트라면 &lt;code&gt;build.gradle&lt;/code&gt;에 추가:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;dependencies {
    implementation &amp;#39;org.apache.poi:poi-ooxml:5.2.3&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;2. Java 코드 구현&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;파일 A.xlsx, B.xlsx을 엶&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B.xlsx의 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트를 읽어옴&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A.xlsx의 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트를 지우고 B.xlsx의 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트를 복사&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A.xlsx를 저장&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;

public class ExcelSheetReplace {
    public static void main(String[] args) {
        String fileA = &amp;quot;A.xlsx&amp;quot;;  // 대상 파일
        String fileB = &amp;quot;B.xlsx&amp;quot;;  // 소스 파일
        String sheetName = &amp;quot;GOOD&amp;quot;;  // 덮어쓸 시트 이름

        try (FileInputStream fisA = new FileInputStream(fileA);
             FileInputStream fisB = new FileInputStream(fileB);
             XSSFWorkbook workbookA = new XSSFWorkbook(fisA);
             XSSFWorkbook workbookB = new XSSFWorkbook(fisB)) {

            // B.xlsx에서 &amp;quot;GOOD&amp;quot; 시트를 가져옴
            XSSFSheet sheetB = workbookB.getSheet(sheetName);
            if (sheetB == null) {
                System.out.println(&amp;quot;B.xlsx에 &amp;#39;&amp;quot; + sheetName + &amp;quot;&amp;#39; 시트가 없습니다.&amp;quot;);
                return;
            }

            // A.xlsx에서 &amp;quot;GOOD&amp;quot; 시트 삭제 (기존 시트가 존재하면 제거)
            int sheetIndexA = workbookA.getSheetIndex(sheetName);
            if (sheetIndexA != -1) {
                workbookA.removeSheetAt(sheetIndexA);
            }

            // A.xlsx에 새로운 &amp;quot;GOOD&amp;quot; 시트 생성
            XSSFSheet sheetA = workbookA.createSheet(sheetName);

            // B.xlsx의 &amp;quot;GOOD&amp;quot; 시트 내용을 A.xlsx의 &amp;quot;GOOD&amp;quot; 시트로 복사
            copySheet(sheetB, sheetA, workbookA);

            // 변경된 A.xlsx 저장
            try (FileOutputStream fosA = new FileOutputStream(fileA)) {
                workbookA.write(fosA);
            }

            System.out.println(&amp;quot;B.xlsx의 &amp;#39;GOOD&amp;#39; 시트가 A.xlsx의 &amp;#39;GOOD&amp;#39; 시트로 성공적으로 덮어씌워졌습니다.&amp;quot;);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 시트 복사 함수
    private static void copySheet(XSSFSheet sourceSheet, XSSFSheet targetSheet, XSSFWorkbook targetWorkbook) {
        for (int rowIndex = 0; rowIndex &amp;lt;= sourceSheet.getLastRowNum(); rowIndex++) {
            Row sourceRow = sourceSheet.getRow(rowIndex);
            Row targetRow = targetSheet.createRow(rowIndex);

            if (sourceRow != null) {
                for (int colIndex = 0; colIndex &amp;lt; sourceRow.getLastCellNum(); colIndex++) {
                    Cell sourceCell = sourceRow.getCell(colIndex);
                    Cell targetCell = targetRow.createCell(colIndex);

                    if (sourceCell != null) {
                        copyCell(sourceCell, targetCell, targetWorkbook);
                    }
                }
            }
        }
    }

    // 셀 복사 함수
    private static void copyCell(Cell sourceCell, Cell targetCell, XSSFWorkbook workbook) {
        targetCell.setCellType(sourceCell.getCellType());

        switch (sourceCell.getCellType()) {
            case STRING:
                targetCell.setCellValue(sourceCell.getStringCellValue());
                break;
            case NUMERIC:
                targetCell.setCellValue(sourceCell.getNumericCellValue());
                break;
            case BOOLEAN:
                targetCell.setCellValue(sourceCell.getBooleanCellValue());
                break;
            case FORMULA:
                targetCell.setCellFormula(sourceCell.getCellFormula());
                break;
            case BLANK:
                targetCell.setBlank();
                break;
            default:
                break;
        }

        // 스타일 복사
        CellStyle newCellStyle = workbook.createCellStyle();
        newCellStyle.cloneStyleFrom(sourceCell.getCellStyle());
        targetCell.setCellStyle(newCellStyle);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;3. 코드 설명&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;파일 읽기&lt;/strong&gt;: &lt;code&gt;FileInputStream&lt;/code&gt;으로 &lt;code&gt;A.xlsx&lt;/code&gt;, &lt;code&gt;B.xlsx&lt;/code&gt;을 연다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시트 삭제&lt;/strong&gt;: A.xlsx에 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트가 있다면 삭제한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;새 시트 생성&lt;/strong&gt;: &lt;code&gt;A.xlsx&lt;/code&gt;에 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트를 새로 만든다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시트 복사&lt;/strong&gt;: &lt;code&gt;copySheet()&lt;/code&gt; 함수를 사용해 &lt;code&gt;B.xlsx&lt;/code&gt;의 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 내용을 &lt;code&gt;A.xlsx&lt;/code&gt;로 복사한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;파일 저장&lt;/strong&gt;: &lt;code&gt;FileOutputStream&lt;/code&gt;을 사용해 변경된 &lt;code&gt;A.xlsx&lt;/code&gt;을 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;&lt;strong&gt;4. 실행 결과&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A.xlsx의 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트가 B.xlsx의 &lt;code&gt;&amp;quot;GOOD&amp;quot;&lt;/code&gt; 시트 내용으로 덮어씌워진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;  주의사항&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A.xlsx와 B.xlsx가 존재해야 한다.&lt;/li&gt;
&lt;li&gt;두 파일이 열려 있으면 저장이 실패할 수 있으므로, Excel에서 닫아둬야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이제 실행하면 원하는 동작이 수행될 거야!  &lt;/p&gt;</description>
      <category>개발/JAVA</category>
      <category>Java</category>
      <category>POI</category>
      <category>sheet copy</category>
      <author>꿈꾸는법사</author>
      <guid isPermaLink="true">https://subinto.tistory.com/325</guid>
      <comments>https://subinto.tistory.com/325#entry325comment</comments>
      <pubDate>Tue, 4 Mar 2025 13:24:10 +0900</pubDate>
    </item>
  </channel>
</rss>