[Spring Batch] 배치 애플리케이션 경험하기 2 본문

백앤드 개발일지/스프링부트

[Spring Batch] 배치 애플리케이션 경험하기 2

giron 2021. 8. 31. 15:57
728x90

이번에는 JPA 기본을 중심으로 Batch 처리에 대해서 조금 다뤄보려고 한다.

 

저번 시간에 얘기 했던 것처럼 수백만 건의 배치 처리할 때,

  • 일반적인 방식으로 엔티티를 계속 조회하면, 영속성 컨텍스트에 많은 엔티티가 쌓이면서 메모리 부족 오류가 발생한다.
  • 따라서 이러한 배치 처리를 적절한 단위로 영속성 컨텍스트를 초기화해야 한다. 
  • 또한 2차 캐시를 사용하고 있다면 2차 캐시에 엔티티를 보관하지 않도록 주의해야 한다.

[JPA 등록 배치]

  • 수만 건 이상의 엔티티를 한 번에 등록할 때 주의할 점은 영속성 컨텍스트에 엔티티가 계속 쌓이지 않도록 일정 단위마다 영속성 컨텍스트의 엔티티를 데이터베이스에 플러시하고 영속성 컨텍스트를 초기화해야 한다.
    EntityManager em = entityManagerFactory.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
    
        for(int i=0; i<100000; ++i){
            Product product = new Product("item"+ i, 10000);
            em.persist(product);
            
            //100건마다 플러시와 영속성 컨텍스트 초기화
            if(i%100 == 0){
                em.flush();
                em.clear();
            }
        }
        tx.commit();
        em.close();​
  • 위 예제는 엔티티를 100건 저장 할 때마다 플러시를 호출하고 영속성 컨텍스트를 초기화한다.

 

[JPA 수정 배치]

  • 배치 처리는 아주 많은 데이터를 조회해서 수정한다. 이때 수많은 데이터들을 한 번에 메모리에 올려둘 수 없어서 가지 방법을 주로 이용한다고 한다.
    • 페이징 처리: 데이터베이스 페이징 기능을 사용한다.
    • 커서(CURSOR): 데이터베이스가 지원하는 커서 기능을 사용한다.
  • [JPA 페이징 배치 처리]

    • EntityManager em = entityManagerFactory.createEntityManager();
      EntityTransaction tx = em.getTransaction();
      
      tx.begin(); 
      int pageSize = 100;
      
      for(int i=0; i<10; ++i){ 
      	List<Product> resultList = em.createQuery("select p from Product p", Product.class) 
                          .setFirstResult(i*pageSize)
                          .setMaxResults(pageSize)
                          .getResultList(); 
      
      	//비즈니스 로직 실행 
          for(Product product : resultList){ 
              product.setPrice(product.getPrice + 100); 
          } 
          em.flush();
          em.clear(); 
      } 
      tx.commit();
      em.close();
    •  한 번에 100건씩 페이징 쿼리로 조회하면서 상품의 가격을 100원씩 증가한다. 그리고 페이지 단위마다 영속성 컨텍스트를 플러시하고 초기화한다.다음으로 커서를 사용하는 방법을 알아보자! 그전에 저번 표에서도 보였지만 JPA는 JDBC 커서를 지원하지 않는다. 따라서 커서를 사용하려면 하이버네이트 세션을 사용해야 한다.,
  • [하이버네이트 scroll]
    • 하이버네이트는  scroll이라는 이름으로 JDBC 커서를 지원한다
    • EntityTransaction tx = em.getTransaction();
          Session session = em.unwrap(Session.class);
          tx.begin();
          ScrollableResults scroll = session.createQuery("select p from Product p")
                  .setCacheMode(CacheMode.IGNORE) // 2차 캐시 기능을 끈다.
                  .scroll(ScrollMode.FORWARD_ONLY);
          
          int count = 0;
          while(scroll.next()){
              Product p = (Product) scroll.get(0);
              p.setPrice(p.getPrice() + 100);
              
              count++;
              if(count % 100 == 0){
                  session.flush();    //플러시
                  session.clear();    //영속성 컨텍스트 초기화
              }
          }
          tx.commit();
          session.close();
      scroll은 하이버네이트 전용 기능이므로 먼저 em.unwrap() 메소드를 통해 하이버네이트 세션을 구한다.
    • 다음으로 쿼리를 조회하면서 scroll() 메소드로 ScrollableResults 객체를 반환받는다. 이 객체의 next() 메소드를 호출하면 엔티티를 하나씩 조회할 수 있다.
  • [하이버네이트 무상태 세션 사용]
    • 하이버네이트는 무상태 세션이라는 특별한 기능을 제공한다.
    • 무상태 세션은 영속성 컨텍스트를 만들지 않고 심지어 2차 캐시도 사용하지 않는다.
    • 즉, 무상태 세션은 영속성 컨텍스트가 없다. 그리고 엔티티를 수정하려면 무상태 세션이 제공하는
    • update() 메소드를 직접 호출해야 한다
    • SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
          StatelessSession session = sessionFactory.openStatelessSession();
          Transaction tx = session.beginTransaction();
          ScrollableResults scroll = session.createQuery("select p from Product p").scroll();
          
          while(scroll.next()){
              Product p = (Product) scroll.get(0);
              p.setPrice(p.getPrice() + 100);
              session.update(p); // w직접 update를 호출해야 한다.
          }
          tx.commit();
          session.close();
      하이버네이트 무상태 세션은 영속성 컨텍스트가 없기 때문에 영속성 컨텍스트를 플러시 하거나 초기화하지 않아도 된다. 대신 엔티티를 수정할 때 update()메소드를 직접 호출해야 한다.

Reference

  • 자바 ORM 표준 JPA프로그래밍 -김영한-
728x90
Comments