JPA 배치 처리
수백만 건의 데이터를 배치 처리해야 하는 상황이라 가정해보자.
일반적인 방식으로 엔티티를 계속 조회하면 영속성 컨텍스트에 아주 많은 엔티티가 쌓이면서 메모리 부족 오류가 발생한다. 따라서 이런 배치 처리는 적절한 단위로 영속성 컨텍스트를 초기화 해야한다.
또한, 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();
JPA 수정 배치
수정 배치 처리는 2가지 방법을 주로 사용한다.
- 페이징 처리 : 데이터베이스 페이징 기능을 사용한다.
- 커서(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 커서(CURSOR) 배치 처리
JPA는 JDBC 커서를 지원하지 않기 때문에 하이버네이트 세션을 사용해야 한다.
하이버네이트는 scroll이라는 이름으로 JDBC 커서를 지원한다.
EntityManager em = entityManagerFactory.createEntityManager();
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();
Session을 활용해서 2차 캐시 기능을 무시하고 SQL을 조회해 ScrollableResults로 반환받는다.
next() 함수로 엔티티를 하나씩 조회하면서 100번째마다 세션을 플러시하고 초기화한다.
하이버네이트 무상태 세션 사용
하이버네이트는 무상태 세션이라는 특별한 기능을 제공한다.
이름 그대로 무상태 세션은 영속성 컨텍스트를 만들지 않고 심지어 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); // 직접 update를 호출해야 한다.
}
tx.commit();
session.close();