<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기술 블로그</title>
    <link>https://hsdevelopment.tistory.com/</link>
    <description>기술 블로그</description>
    <language>ko</language>
    <pubDate>Fri, 10 Jul 2026 15:02:14 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>parkit</managingEditor>
    <item>
      <title>@Transactional 어노테이션의 역할과 의미</title>
      <link>https://hsdevelopment.tistory.com/849</link>
      <description>&lt;h1&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional 어노테이션의 역할과 의미&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional 어노테이션은 스프링 프레임워크에서 데이터베이스 트랜잭션을 관리하기 위해 사용됩니다. 트랜잭션은 &quot;모두 실행되거나 아무것도 실행되지 않아야 하는&quot; 작업 단위입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional의 핵심 기능&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;원자성(Atomicity)&lt;/b&gt;: 트랜잭션 내의 모든 작업이 성공적으로 완료되거나, 오류 발생 시 모두 롤백됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;일관성(Consistency)&lt;/b&gt;: 트랜잭션 전후로 데이터베이스는 일관된 상태를 유지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;격리성(Isolation)&lt;/b&gt;: 동시에 실행되는 트랜잭션들이 서로 영향을 주지 않습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;지속성(Durability)&lt;/b&gt;: 트랜잭션이 완료되면 그 결과는 영구적으로 저장됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작동 방식&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class CommentService {
    
    private final CommentRepository commentRepository;
    
    // 생성자 주입
    public CommentService(CommentRepository commentRepository) {
        this.commentRepository = commentRepository;
    }
    
    @Transactional
    public void deleteComment(Long commentId) {
        // 1. 댓글 조회
        CommentV2 comment = commentRepository.findById(commentId)
            .orElseThrow(() -&amp;gt; new RuntimeException(&quot;댓글을 찾을 수 없습니다&quot;));
        
        // 2. 댓글 삭제
        commentRepository.delete(comment);
        
        // 3. 부모 댓글 처리
        if (!comment.isRoot()) {
            // 부모 댓글 관련 로직
            // 이 시점에서 comment 객체는 자바 메모리에 여전히 존재함
        }
        
        // 메소드가 정상적으로 종료되면 이 시점에서 트랜잭션 commit
        // 예외 발생시 자동으로 rollback
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 생명주기&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;트랜잭션 시작&lt;/b&gt;: @Transactional 메소드가 호출되면 트랜잭션이 시작됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;작업 수행&lt;/b&gt;: 메소드 내부의 모든 데이터베이스 작업은 하나의 트랜잭션으로 묶입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;커밋 또는 롤백&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정상 종료: 메소드가 예외 없이 완료되면 트랜잭션이 커밋됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예외 발생: 메소드 내에서 예외가 발생하면 트랜잭션이 롤백됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 삭제 후에도 객체를 사용할 수 있는가?&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@Transactional
private void delete(CommentV2 comment) {
    commentRepository.delete(comment);  // 이 시점에는 DB에 DELETE SQL이 즉시 실행되지 않음
    
    if(!comment.isRoot()) {  // comment 객체는 메모리에 여전히 존재하므로 메소드 호출 가능
        // 다른 작업 수행
    }
    // 메소드 종료 시 트랜잭션 커밋, 이때 실제 DELETE SQL이 DB에 전송됨
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;JPA는 성능 최적화를 위해 &quot;쓰기 지연(write-behind)&quot; 전략을 사용합니다. delete() 메소드를 호출하면:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;영속성 컨텍스트에서 엔티티를 제거 대상으로 표시합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 SQL은 트랜잭션이 커밋될 때 실행됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자바 객체로서의 comment는 여전히 메모리에 존재하므로 속성이나 메소드를 계속 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것이 delete() 후에도 comment.isRoot()와 같은 메소드를 호출할 수 있는 이유입니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>JAVA</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/849</guid>
      <comments>https://hsdevelopment.tistory.com/849#entry849comment</comments>
      <pubDate>Wed, 23 Apr 2025 14:07:17 +0900</pubDate>
    </item>
    <item>
      <title>@Transactional과 프록시 패턴의 관계</title>
      <link>https://hsdevelopment.tistory.com/848</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional과 프록시 패턴의 관계&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional의 동작 방식을 프록시 패턴과 연결해서 설명해드리겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 패턴 기본 개념&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 패턴은 실제 객체를 대신하는 대리자(프록시) 객체를 사용하여:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실제 객체에 대한 접근을 제어하고&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;추가 기능을 제공하는 디자인 패턴입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional과 프록시 패턴의 연결점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링에서 @Transactional을 사용하면 다음과 같은 일이 발생합니다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링은 원본 객체를 감싸는 &lt;b&gt;프록시 객체&lt;/b&gt;를 생성합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 프록시는 실제 메소드 실행 전후에 &lt;b&gt;트랜잭션 관련 코드&lt;/b&gt;를 추가합니다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트는 원본 객체 대신 이 프록시와 상호작용합니다&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동작 과정 상세 설명&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프록시 생성&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너는 빈 초기화 시 @Transactional 애노테이션을 감지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 빈에 대한 프록시 객체를 생성 (JDK 동적 프록시 또는 CGLIB)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원본 빈 대신 프록시 객체를 컨테이너에 등록&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;메서드 호출 가로채기&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트가 서비스 메서드를 호출하면 먼저 프록시 객체의 메서드가 호출됨&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시는 @Transactional 속성(전파 방식, 격리 수준, 타임아웃 등)을 확인&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;트랜잭션 시작&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시는 PlatformTransactionManager를 통해 트랜잭션을 시작&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 정보를 ThreadLocal에 저장하여 동일 스레드에서 접근 가능하게 함&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;원본 메서드 실행&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시가 실제 비즈니스 로직을 수행하는 원본 객체의 메서드를 호출&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;트랜잭션 종료&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정상 실행 시: 트랜잭션 커밋&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예외 발생 시: 롤백 대상 예외인지 확인 후 롤백 (기본적으로 RuntimeException과 Error만 롤백)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 리소스 정리 (데이터베이스 연결 반환 등)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예시 코드로 보는 @Transactional 동작 방식&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 원본 서비스 클래스&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 다른 로직들...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 스프링이 내부적으로 생성하는 프록시 (개념적 코드)&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public class UserServiceProxy extends UserService {
    
    private UserService target; // 실제 UserService
    private PlatformTransactionManager transactionManager;
    
    public UserServiceProxy(UserService target, PlatformTransactionManager transactionManager) {
        this.target = target;
        this.transactionManager = transactionManager;
    }
    
    @Override
    public void createUser(User user) {
        // 트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        
        try {
            // 실제 비즈니스 로직 수행
            target.createUser(user);
            
            // 트랜잭션 커밋
            transactionManager.commit(status);
        } catch (Exception e) {
            // 예외 발생 시 롤백
            transactionManager.rollback(status);
            throw e;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 실제 사용 예시&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
public class UserController {
    
    private final UserService userService; // 실제로는 UserServiceProxy가 주입됨!
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @PostMapping(&quot;/users&quot;)
    public ResponseEntity&amp;lt;Void&amp;gt; createUser(@RequestBody User user) {
        userService.createUser(user); // 프록시의 메서드가 호출됨
        return ResponseEntity.ok().build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Transactional 동작 흐름도&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;┌─────────────────┐     1. 메서드 호출     ┌──────────────────────┐
│                 │─────────────────────▶│                      │
│   클라이언트     │                       │   @Transactional     │
│  (Controller)   │                       │     프록시 객체       │
│                 │◀─────────────────────│                      │
└─────────────────┘     8. 결과 반환       └──────────────────────┘
                                            │       ▲
                       2. 트랜잭션 시작      │       │ 7. 트랜잭션 완료
                                            ▼       │
                                         ┌──────────────────────┐
                                         │                      │
                                         │  PlatformTransaction │
                                         │      Manager         │
                                         │                      │
                                         └──────────────────────┘
                                            │       ▲
                    3. 트랜잭션 컨텍스트 생성 │       │ 6. 커밋/롤백
                                            ▼       │
         4. 메서드 호출       ┌──────────────────────┐
       ┌───────────────────▶│                      │
       │                    │   실제 서비스 객체     │
       │                    │ (UserServiceImpl)    │
       │                    │                      │
       │                    └──────────────────────┘
       │                              │
       │         5. DB 작업 실행       ▼
       │                    ┌──────────────────────┐
       │                    │                      │
       └────────────────────│      Database        │
                            │                      │
                            └──────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시 패턴을 활용한 @Transactional의 주요 흐름은 다음과 같습니다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트(Controller)는 서비스 메서드를 호출하지만, 실제로는 프록시 객체의 메서드가 호출됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시는 TransactionManager를 통해 트랜잭션을 시작합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 컨텍스트가 생성되고 현재 스레드에 바인딩됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시는 실제 서비스 객체의 메서드를 호출합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서비스 객체는 데이터베이스 작업을 수행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업이 완료되면 프록시로 제어가 돌아오고, 예외 발생 여부에 따라 커밋 또는 롤백됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TransactionManager가 트랜잭션을 완료합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프록시는 결과를 클라이언트에 반환합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>JAVA</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/848</guid>
      <comments>https://hsdevelopment.tistory.com/848#entry848comment</comments>
      <pubDate>Wed, 16 Apr 2025 13:35:52 +0900</pubDate>
    </item>
    <item>
      <title>CountDownLatch</title>
      <link>https://hsdevelopment.tistory.com/847</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CountDownLatch는 Java의 동시성 프레임워크에서 제공하는 동기화 도구로, 하나 이상의 스레드가 다른 스레드들의 작업이 완료될 때까지 기다릴 수 있게 해주는 메커니즘입니다. 이 클래스는 java.util.concurrent 패키지에 속해 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기본 개념&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;카운트다운 메커니즘&lt;/b&gt;: 초기 카운트 값에서 시작해 0에 도달할 때까지 감소&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;대기 기능&lt;/b&gt;: 카운트가 0이 될 때까지 스레드들을 차단(블로킹)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;일회성&lt;/b&gt;: 한 번 카운트가 0에 도달하면 재설정할 수 없음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주요 메서드&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;생성자&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CountDownLatch(int count): 지정된 카운트 값으로 래치 초기화&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;countDown()&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카운트를 1 감소시킴&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카운트가 0에 도달하면 대기 중인 모든 스레드를 해제&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;await()&lt;/b&gt;:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;카운트가 0이 될 때까지 현재 스레드를 차단&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오버로딩 버전:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;await(): 카운트가 0이 될 때까지 무기한 대기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;await(long timeout, TimeUnit unit): 지정된 시간 동안만 대기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예시 코드&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1744294255207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        // 3개의 작업을 기다리는 CountDownLatch 생성
        CountDownLatch latch = new CountDownLatch(3);
        
        // 3개의 스레드를 가진 스레드 풀 생성
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        System.out.println(&quot;메인 스레드: 작업 시작&quot;);
        
        for (int i = 0; i &amp;lt; 3; i++) {
            final int workerId = i;
            executor.submit(() -&amp;gt; {
                try {
                    // 작업 시뮬레이션
                    System.out.println(&quot;작업자 &quot; + workerId + &quot;: 작업 시작&quot;);
                    Thread.sleep(2000); // 작업에 2초 소요
                    System.out.println(&quot;작업자 &quot; + workerId + &quot;: 작업 완료&quot;);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    // 작업 완료 후 카운트다운
                    latch.countDown();
                    System.out.println(&quot;작업자 &quot; + workerId + &quot;: 카운트다운. 남은 카운트: &quot; + latch.getCount());
                }
            });
        }
        
        // 모든 작업이 완료될 때까지 메인 스레드 대기
        System.out.println(&quot;메인 스레드: 모든 작업 완료 대기 중...&quot;);
        latch.await();
        System.out.println(&quot;메인 스레드: 모든 작업 완료됨. 다음 단계로 진행합니다.&quot;);
        
        // 스레드 풀 종료
        executor.shutdown();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 결과 설명&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 코드를 실행하면 다음과 같은 결과가 나타납니다:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744294273706&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;메인 스레드: 작업 시작
메인 스레드: 모든 작업 완료 대기 중...
작업자 0: 작업 시작
작업자 1: 작업 시작
작업자 2: 작업 시작
작업자 0: 작업 완료
작업자 0: 카운트다운. 남은 카운트: 2
작업자 2: 작업 완료
작업자 2: 카운트다운. 남은 카운트: 1
작업자 1: 작업 완료
작업자 1: 카운트다운. 남은 카운트: 0
메인 스레드: 모든 작업 완료됨. 다음 단계로 진행합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 과정 설명:&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메인 스레드가 카운트가 3인 CountDownLatch를 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;세 개의 작업자 스레드를 생성하고 실행합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메인 스레드는 latch.await()를 호출하여 모든 작업이 완료될 때까지 대기합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 작업자 스레드는 작업을 완료한 후 latch.countDown()을 호출하여 카운트를 감소시킵니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마지막 작업자가 countDown()을 호출하면 카운트가 0이 되고, 대기 중이던 메인 스레드가 해제됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;메인 스레드는 작업 완료 메시지를 출력하고 진행을 계속합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주요 특징&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;동기화 메커니즘&lt;/b&gt;: 여러 스레드 간의 작업 완료를 동기화할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스타트 신호로 사용&lt;/b&gt;: 카운트가 1인 래치를 사용하여 여러 스레드를 동시에 시작할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;일회성 장벽&lt;/b&gt;: 한 번 카운트가 0에 도달하면 래치는 리셋할 수 없습니다. 다시 사용하려면 새 인스턴스를 생성해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;인터럽트 감지&lt;/b&gt;: await() 메서드는 인터럽트를 감지하고 InterruptedException을 발생시킬 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CountDownLatch는 병렬 처리 작업을 조율할 때, 특히 여러 작업이 완료될 때까지 기다려야 하는 상황에서 매우 유용합니다. 예를 들어, 서비스 초기화, 테스트 실행, 병렬 계산 등에 활용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/847</guid>
      <comments>https://hsdevelopment.tistory.com/847#entry847comment</comments>
      <pubDate>Thu, 10 Apr 2025 23:11:41 +0900</pubDate>
    </item>
    <item>
      <title>Executors.newFixedThreadPool()</title>
      <link>https://hsdevelopment.tistory.com/846</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Executors.newFixedThreadPool()는 Java의 동시성 프레임워크에서 제공하는 메서드로, 고정된 크기의 스레드 풀을 생성합니다. 이 메서드는 java.util.concurrent 패키지에 속한 Executors 클래스의 정적 메서드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기본 개념&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스레드 풀&lt;/b&gt;: 작업 실행을 위해 미리 생성된 스레드들의 집합&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;고정 크기&lt;/b&gt;: 생성 시 지정된 스레드 수를 유지 (최대 스레드 수 고정)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파라미터&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Executors.newFixedThreadPool() 메서드는 두 가지 오버로딩된 형태를 가집니다:&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newFixedThreadPool(int nThreads): 지정된 수의 스레드를 가진 스레드 풀 생성&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;nThreads: 풀에서 유지할 스레드 수&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;newFixedThreadPool(int nThreads, ThreadFactory threadFactory): 지정된 스레드 팩토리를 사용하여 스레드 풀 생성&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;nThreads: 풀에서 유지할 스레드 수&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;threadFactory: 새 스레드를 생성할 때 사용할 팩토리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 예시 코드 &lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1744294115312&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 3개의 스레드를 가진 고정 크기 스레드 풀 생성
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 5개의 작업 제출
        for (int i = 0; i &amp;lt; 5; i++) {
            final int taskId = i;
            executor.submit(() -&amp;gt; {
                String threadName = Thread.currentThread().getName();
                System.out.println(&quot;Task &quot; + taskId + &quot; is running on &quot; + threadName);
                
                // 작업이 2초 동안 실행되는 것 시뮬레이션
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                
                System.out.println(&quot;Task &quot; + taskId + &quot; completed&quot;);
                return null;
            });
        }
        
        // 새 작업 제출 중단
        executor.shutdown();
        
        try {
            // 모든 작업이 완료될 때까지 최대 10초 대기
            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                // 10초 후에도 완료되지 않은 작업 강제 종료
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
        }
        
        System.out.println(&quot;All tasks completed&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 결과 설명&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 코드를 실행하면 다음과 같은 결과가 나타납니다:&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1744294157351&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Task 0 is running on pool-1-thread-1
Task 1 is running on pool-1-thread-2
Task 2 is running on pool-1-thread-3
Task 0 completed
Task 1 completed
Task 3 is running on pool-1-thread-1
Task 2 completed
Task 4 is running on pool-1-thread-2
Task 3 completed
Task 4 completed
All tasks completed&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행 과정 설명:&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3개의 스레드를 가진 고정 크기 스레드 풀을 생성합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5개의 작업을 제출합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음 3개의 작업(0, 1, 2)은 즉시, 각각 다른 스레드에서 실행됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;각 작업은 2초 동안 실행됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음 3개의 작업이 완료되면, 나머지 2개의 작업(3, 4)이 가용 스레드에서 실행됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 작업이 완료된 후 &quot;All tasks completed&quot; 메시지가 출력됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주요 특징&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;스레드 재사용&lt;/b&gt;: 작업이 완료된 후 스레드는 폐기되지 않고 다음 작업을 위해 재사용됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;작업 큐&lt;/b&gt;: 제출된 모든 작업은 내부 큐에 저장되며, 가용 스레드가 있을 때 실행됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;자원 관리&lt;/b&gt;: 생성 시 지정된 수의 스레드만 유지하므로 과도한 스레드 생성을 방지합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;리소스 관리&lt;/b&gt;: shutdown()과 shutdownNow() 메서드를 통해 적절히 리소스를 정리해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 메서드는 높은 동시성이 필요한 서버 애플리케이션에서 특히 유용하며, 동시에 실행할 수 있는 작업의 수를 제한하고 싶을 때 적합합니다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JAVA</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/846</guid>
      <comments>https://hsdevelopment.tistory.com/846#entry846comment</comments>
      <pubDate>Thu, 10 Apr 2025 23:10:08 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot/MyBatis] ResultHandler를 활용한 대용량 데이터 엑셀 다운로드</title>
      <link>https://hsdevelopment.tistory.com/842</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;intelliJ로 구현하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 블로그 참고하였음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;데이터 생성은 아래 주소 참고.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(&lt;a style=&quot;color: #000000;&quot; href=&quot;https://hsdevelopment.tistory.com/841&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hsdevelopment.tistory.com/841&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;근데 데이터를 약 7만 3천 건 정도만 생성하였고, 그냥 진행하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(100만 건도 잘 되겠지..?)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트 전체 구조는 아래와 같다.(맨 아래에 git 주소와 프로젝트 전체 소스도 첨부하였음)&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;466&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pu9Cc/btszCLHsmWs/LChFrzhpIKZpkR2UvEOYQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pu9Cc/btszCLHsmWs/LChFrzhpIKZpkR2UvEOYQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pu9Cc/btszCLHsmWs/LChFrzhpIKZpkR2UvEOYQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpu9Cc%2FbtszCLHsmWs%2FLChFrzhpIKZpkR2UvEOYQ1%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;466&quot; height=&quot;940&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;940&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;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DbConfig.java&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698850032380&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.excel.exceldown.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
@MapperScan(value = &quot;com.excel.exceldown&quot;, sqlSessionFactoryRef = &quot;SqlSessionFactory&quot;)
public class DbConfig {

    @Value(&quot;${spring.datasource.mapper-locations}&quot;)
    String mPath;

    // mybatis 설정 파일을 따로 작성해서 임포트할 예정 - snake_case -&amp;gt; camelCase 를 위함
    @Value(&quot;${mybatis.config-location}&quot;)
    String mybatisConfigPath;

    @Bean(name = &quot;dataSource&quot;)
    @ConfigurationProperties(prefix = &quot;spring.datasource&quot;)
    public DataSource DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = &quot;SqlSessionFactory&quot;)
    public SqlSessionFactory SqlSessionFactory(@Qualifier(&quot;dataSource&quot;) DataSource DataSource, ApplicationContext applicationContext) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(DataSource);
        sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources(mPath));
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource(mybatisConfigPath));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name = &quot;SessionTemplate&quot;)
    public SqlSessionTemplate SqlSessionTemplate(@Qualifier(&quot;SqlSessionFactory&quot;) SqlSessionFactory firstSqlSessionFactory) {
        return new SqlSessionTemplate(firstSqlSessionFactory);
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ExcelDownloadController.java&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698850025545&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.excel.exceldown.controller;

import com.excel.exceldown.domain.PersonData;
import com.excel.exceldown.service.ExcelDownloadService;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping(&quot;/excelDownload&quot;)
@Controller
public class ExcelDownloadController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ExcelDownloadService excelDownloadService;

    @GetMapping
    public List&amp;lt;PersonData&amp;gt; excelDownload(HttpServletResponse response){

        logger.info(&quot;■■■■■■■■■■■■■■■■■■■■■■■&quot;);
        logger.info(&quot;ExcelDownload START&quot;);
        logger.info(&quot;■■■■■■■■■■■■■■■■■■■■■■■&quot;);

        return excelDownloadService.download(response);
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt; ExcelDownloadService.java&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698850017980&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.excel.exceldown.service;

import com.excel.exceldown.domain.PersonData;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.OutputStream;
import java.util.List;

@Service
public class ExcelDownloadService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    public List&amp;lt;PersonData&amp;gt; download(HttpServletResponse response) {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 메모리에 100개의 행을 유지합니다. 행의 수가 넘으면 디스크에 적습니다.
        SXSSFWorkbook wb = new SXSSFWorkbook(100);
        Sheet sheet = wb.createSheet();

        try {
            sqlSession.select(&quot;com.excel.exceldown.mapper.UserMapper.excelDownloadSelectId&quot;
                    , new ResultHandler&amp;lt;PersonData&amp;gt;() {
                @Override
                public void handleResult(ResultContext&amp;lt;? extends PersonData&amp;gt; resultContext) {
                    PersonData vo = resultContext.getResultObject();
                    Row row = sheet.createRow(resultContext.getResultCount() - 1);
                    Cell cell = null;
                    cell = row.createCell(0);
                    cell.setCellValue(vo.getId());
                    cell = row.createCell(1);
                    cell.setCellValue(vo.getName());
                    cell = row.createCell(2);
                    cell.setCellValue(vo.getAge());
                }
            });

            response.setHeader(&quot;Set-Cookie&quot;, &quot;fileDownload=true; path=/&quot;);
            response.setHeader(&quot;Content-Disposition&quot;, String.format(&quot;attachment; filename=\&quot;test.xlsx\&quot;&quot;));
            wb.write(response.getOutputStream());

        } catch(Exception e) {

            response.setHeader(&quot;Set-Cookie&quot;, &quot;fileDownload=false; path=/&quot;);
            response.setHeader(&quot;Cache-Control&quot;, &quot;no-cache, no-store, must-revalidate&quot;);
            response.setHeader(&quot;Content-Type&quot;,&quot;text/html; charset=utf-8&quot;);

            OutputStream out = null;
            try {
                out = response.getOutputStream();
                byte[] data = new String(&quot;fail..&quot;).getBytes();
                out.write(data, 0, data.length);
            } catch(Exception ignore) {
                ignore.printStackTrace();
            } finally {
                if(out != null) try { out.close(); } catch(Exception ignore) {}
            }

        } finally {
            sqlSession.close();

            // 디스크 적었던 임시파일을 제거합니다.
            wb.dispose();
            try {
                wb.close();
            } catch(Exception ignore) {

            }
        }
        return null;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PersonData.java&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698850004747&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.excel.exceldown.domain;

import lombok.Data;

@Data
public class PersonData {

    private int id;
    private String name;
    private int age;
    private String gender;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UserMapper.java&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698849996450&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.excel.exceldown.mapper;

import com.excel.exceldown.domain.PersonData;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {

    void excelDownloadSelectId();

}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UserMapper.xml&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698849980850&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;

&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot;
        &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;
&amp;lt;mapper namespace=&quot;com.excel.exceldown.mapper.UserMapper&quot;&amp;gt;
    &amp;lt;select id=&quot;excelDownloadSelectId&quot;
            parameterType=&quot;java.util.Map&quot;
            resultType=&quot;com.excel.exceldown.domain.PersonData&quot;&amp;gt;
        /* excelDownloadMapper.excelDownloadSelectId */
        select
           id
         , name
         , age
         , gender
        from person
    &amp;lt;/select&amp;gt;
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;mybatis-config.xml&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698849964677&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE configuration
        PUBLIC &quot;-//mybatis.org//DTD Config 3.0//EN&quot;
        &quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;&amp;gt;

&amp;lt;configuration&amp;gt;
    &amp;lt;settings&amp;gt;
        &amp;lt;!-- DB 조회결과 snake_case -&amp;gt; camelCase 변환 --&amp;gt;
        &amp;lt;setting name=&quot;mapUnderscoreToCamelCase&quot; value=&quot;true&quot; /&amp;gt;
    &amp;lt;/settings&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;index.html&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698849936918&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;ul&amp;gt;
    &amp;lt;button type=&quot;button&quot; onclick=&quot;location.href='/excelDownload'&quot;&amp;gt;엑셀다운로드&amp;lt;/button&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;application.yml&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698849925606&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  datasource:
    username: root
    password: 1234
    mapper-locations: classpath:/mapper/**/*.xml
    jdbc-url: jdbc:mysql://localhost:3306/tempdb?&amp;amp;serverTimezone=UTC&amp;amp;autoReconnect=true&amp;amp;allowMultiQueries=true&amp;amp;characterEncoding=UTF-8
    driver: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true
  config-location: classpath:/mybatis/mybatis-config.xml&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래에 git 주소와 프로젝트 소스를 압축한 폴더 첨부.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://github.com/parkhaeseon/excel&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/parkhaeseon/excel&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/Iosp9/btszCrJfTbr/RqA3yooQqCVkPtGh7xNt00/exceldown.zip?attach=1&amp;amp;knm=tfile.zip&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;exceldown.zip&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;0.18MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/842</guid>
      <comments>https://hsdevelopment.tistory.com/842#entry842comment</comments>
      <pubDate>Wed, 1 Nov 2023 23:48:37 +0900</pubDate>
    </item>
    <item>
      <title>임시 데이터 생성하기(더미데이터, 테이블)</title>
      <link>https://hsdevelopment.tistory.com/841</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대용량 데이터 엑셀 다운로드를 하기 위해 임시로 필요한 더미데이터를 생성하기 위해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래 2가지 방법을 이용했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. &lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.mockaroo.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mockaroo.com/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 홈페이지에서 생성하기(비회원은 최대 1000개 행까지만 INSERT 쿼리문 생성 가능)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 프로시저 활용하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;아래에서 tempdb 는 database이고, person은 테이블명이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1698849491081&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DELIMITER $$
DROP PROCEDURE IF EXISTS insertLoop$$
 
CREATE PROCEDURE insertLoop()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i &amp;lt;= 1000000 DO
        INSERT INTO tempdb.person(id, name, age, gender) VALUES (1+1000, 'PAKR', i, 'Male');
        SET i = i + 1;
    END WHILE;
END$$
DELIMITER $$&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스/MySql</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/841</guid>
      <comments>https://hsdevelopment.tistory.com/841#entry841comment</comments>
      <pubDate>Wed, 1 Nov 2023 23:39:37 +0900</pubDate>
    </item>
    <item>
      <title>쿠폰을 발급하다가 에러가 발생하면 어떻게 하나요?</title>
      <link>https://hsdevelopment.tistory.com/839</link>
      <description>&lt;pre id=&quot;code_1696337620754&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        try {
            couponRepository.save(new Coupon(userId));
        } catch (Exception e) {
            logger.error(&quot;failed to create coupon:: &quot; + userId);
            failedEventRepository.save(new FailedEvent(userId));
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Log를 남긴다.&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;1330&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hz1c2/btswP0txZeX/9mdpoYQOI03jdwMVwQb041/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hz1c2/btswP0txZeX/9mdpoYQOI03jdwMVwQb041/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hz1c2/btswP0txZeX/9mdpoYQOI03jdwMVwQb041/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHz1c2%2FbtswP0txZeX%2F9mdpoYQOI03jdwMVwQb041%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1330&quot; height=&quot;220&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쿠폰 발급 중에 오류가 발생하면, FailedEvent에 실패한 Event를 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고, Log를 통해 확인하고 FailedEvent에 쌓인 데이터를 배치 프로그램 통해 읽어서(Read), 다시 재발급하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>온라인강의/선착순이벤트</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/839</guid>
      <comments>https://hsdevelopment.tistory.com/839#entry839comment</comments>
      <pubDate>Tue, 3 Oct 2023 21:54:23 +0900</pubDate>
    </item>
    <item>
      <title>Redis Set 실습</title>
      <link>https://hsdevelopment.tistory.com/838</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;활용 : 쿠폰 1인 당 1개만 발급가능 할 때, Redis Set 활용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\study\kafka&amp;gt;&amp;nbsp;docker&amp;nbsp;ps &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CONTAINER&amp;nbsp;ID&amp;nbsp;&amp;nbsp;&amp;nbsp;NAMES &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;972184460c21&amp;nbsp;&amp;nbsp;&amp;nbsp;myredis &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\study\kafka&amp;gt;&amp;nbsp;docker&amp;nbsp;exec&amp;nbsp;-it&amp;nbsp;972184460c21&amp;nbsp;redis-cli &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;127.0.0.1:6379&amp;gt;&amp;nbsp;sadd test&amp;nbsp;1&lt;/b&gt; &lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&amp;rarr; set add key value&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(integer) 1 &lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&amp;rarr; 추가되었다면, 추가된 value의 개수를 return&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;127.0.0.1:6379&amp;gt;&amp;nbsp;sadd&amp;nbsp;test&amp;nbsp;1 &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(integer)&amp;nbsp;0 &lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;b&gt;&amp;rarr; 이미 test라는 key에 1이라는 value가 존재하므로, 0을 return&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>온라인강의/선착순이벤트</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/838</guid>
      <comments>https://hsdevelopment.tistory.com/838#entry838comment</comments>
      <pubDate>Tue, 3 Oct 2023 21:21:19 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 TEST할 때마다 해줘야 할 것(토픽 생성, 컨슈머 실행 등)</title>
      <link>https://hsdevelopment.tistory.com/837</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Redis의 Data 초기화&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS C:\Users\park7&amp;gt; docker exec -it &lt;i&gt;[ContainerId]&lt;/i&gt; redis-cli&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;127.0.0.1:6379&amp;gt;&amp;nbsp;&lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;b&gt;flushall&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;해당 프로젝트를 위한 coupon_create 토픽 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\Users\park7&amp;gt;&amp;nbsp;docker&amp;nbsp;exec&amp;nbsp;-it&amp;nbsp;kafka&amp;nbsp;kafka-topics.sh&amp;nbsp;--bootstrap-server&amp;nbsp;localhost:9092&amp;nbsp;--create&amp;nbsp;--topic&amp;nbsp;coupon_create&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;WARNING:&amp;nbsp;Due&amp;nbsp;to&amp;nbsp;limitations&amp;nbsp;in&amp;nbsp;metric&amp;nbsp;names,&amp;nbsp;topics&amp;nbsp;with&amp;nbsp;a&amp;nbsp;period&amp;nbsp;('.')&amp;nbsp;or&amp;nbsp;underscore&amp;nbsp;('_')&amp;nbsp;could&amp;nbsp;collide.&amp;nbsp;To&amp;nbsp;avoid&amp;nbsp;issues&amp;nbsp;it&amp;nbsp;is&amp;nbsp;best&amp;nbsp;to&amp;nbsp;use&amp;nbsp;either,&amp;nbsp;but&amp;nbsp;not&amp;nbsp;both.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Created&amp;nbsp;topic&amp;nbsp;coupon_create.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;토픽에 들어오는 데이터를 확인할 수 있는 컨슈머 실행&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\Users\park7&amp;gt;&amp;nbsp;docker&amp;nbsp;exec&amp;nbsp;-it&amp;nbsp;kafka&amp;nbsp;kafka-console-consumer.sh&amp;nbsp;--topic&amp;nbsp;coupon_create&amp;nbsp;--bootstrap-server&amp;nbsp;localhost:9092&amp;nbsp;--key-deserializer&amp;nbsp;&quot;org.apache.kafka.common.serialization.StringDeserializer&quot;&amp;nbsp;--value-deserializer&amp;nbsp;&quot;org.apache.kafka.common.serialization.LongDeserializer&quot;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>온라인강의/선착순이벤트</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/837</guid>
      <comments>https://hsdevelopment.tistory.com/837#entry837comment</comments>
      <pubDate>Tue, 3 Oct 2023 19:38:54 +0900</pubDate>
    </item>
    <item>
      <title>kafka 토픽 생성, 프로듀서 실행, 컨슈머 실행</title>
      <link>https://hsdevelopment.tistory.com/835</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Windows PowerShell 창 1&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;토픽 생성&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\study\kafka&amp;gt;&amp;nbsp;docker&amp;nbsp;exec&amp;nbsp;-it&amp;nbsp;kafka&amp;nbsp;kafka-topics.sh&amp;nbsp;--bootstrap-server&amp;nbsp;localhost:9092&amp;nbsp;--create&amp;nbsp;--topic&amp;nbsp;testTopic&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Created topic testTopic. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&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;color: #000000;&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;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\study\kafka&amp;gt;&amp;nbsp;docker&amp;nbsp;exec&amp;nbsp;-it&amp;nbsp;kafka&amp;nbsp;kafka-console-producer.sh&amp;nbsp;--topic&amp;nbsp;testTopic&amp;nbsp;--broker-list&amp;nbsp;0.0.0.0:9092 &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;gt;hello &amp;rarr; 창 2를 Open하고, 컨슈머 실행 명령어까지 실행 후 hello 입력하면, 창 2에서 hello가 출력됨.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Windows PowerShell 창 2&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;컨슈머 실행&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;PS&amp;nbsp;C:\Users\park7&amp;gt;&amp;nbsp;docker&amp;nbsp;exec&amp;nbsp;-it&amp;nbsp;kafka&amp;nbsp;kafka-console-consumer.sh&amp;nbsp;--topic&amp;nbsp;testTopic&amp;nbsp;--bootstrap-server&amp;nbsp;localhost:9092 &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;hello&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>온라인강의/선착순이벤트</category>
      <author>parkit</author>
      <guid isPermaLink="true">https://hsdevelopment.tistory.com/835</guid>
      <comments>https://hsdevelopment.tistory.com/835#entry835comment</comments>
      <pubDate>Tue, 3 Oct 2023 15:41:36 +0900</pubDate>
    </item>
  </channel>
</rss>