[java] JPA : 큰 결과 세트를 반복하는 데 적합한 패턴은 무엇입니까?

수백만 개의 행이있는 테이블이 있다고 가정 해 보겠습니다. JPA를 사용하여 해당 테이블에 대해 쿼리를 반복하는 적절한 방법은 무엇입니까 ? 그래서 수백만 개의 개체 가있는 모든 메모리 내 목록이 없습니다 .

예를 들어, 테이블이 크면 다음이 폭발 할 것이라고 생각합니다.

List<Model> models = entityManager().createQuery("from Model m", Model.class).getResultList();

for (Model model : models)
{
     System.out.println(model.getId());
}

페이지 매김 (루핑 및 수동 업데이트 setFirstResult()/ setMaxResult())이 정말 최상의 솔루션입니까?

편집 : 내가 목표로하는 주요 사용 사례는 일종의 일괄 작업입니다. 실행하는 데 시간이 오래 걸리더라도 괜찮습니다. 관련된 웹 클라이언트가 없습니다. 한 번에 하나씩 (또는 작은 N) 각 행에 대해 “무언가”를 수행하면됩니다. 나는 그들 모두를 동시에 기억하는 것을 피하려고 노력하고 있습니다.



답변

Java Persistence with Hibernate의 537 페이지는를 사용하는 솔루션을 제공 ScrollableResults하지만 아쉽게도 Hibernate에만 해당됩니다.

따라서 setFirstResult/ setMaxResults및 수동 반복 을 사용하는 것이 실제로 필요한 것 같습니다 . 다음은 JPA를 사용하는 내 솔루션입니다.

private List<Model> getAllModelsIterable(int offset, int max)
{
    return entityManager.createQuery("from Model m", Model.class).setFirstResult(offset).setMaxResults(max).getResultList();
}

그런 다음 다음과 같이 사용하십시오.

private void iterateAll()
{
    int offset = 0;

    List<Model> models;
    while ((models = Model.getAllModelsIterable(offset, 100)).size() > 0)
    {
        entityManager.getTransaction().begin();
        for (Model model : models)
        {
            log.info("do something with model: " + model.getId());
        }

        entityManager.flush();
        entityManager.clear();
        em.getTransaction().commit();
        offset += models.size();
    }
}


답변

여기에 제시된 답변을 시도했지만 JBoss 5.1 + MySQL Connector / J 5.1.15 + Hibernate 3.3.2는 이들과 함께 작동하지 않았습니다. 우리는 방금 JBoss 4.x에서 JBoss 5.1로 마이그레이션 했으므로 지금은 그대로 유지 했으므로 사용할 수있는 최신 Hibernate는 3.3.2입니다.

몇 가지 추가 매개 변수를 추가하면 작업이 수행되었으며 다음과 같은 코드는 OOME없이 실행됩니다.

        StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();

        Query query = session
                .createQuery("SELECT a FROM Address a WHERE .... ORDER BY a.id");
        query.setFetchSize(Integer.valueOf(1000));
        query.setReadOnly(true);
        query.setLockMode("a", LockMode.NONE);
        ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
        while (results.next()) {
            Address addr = (Address) results.get(0);
            // Do stuff
        }
        results.close();
        session.close();

중요한 라인은 createQuery와 scroll 사이의 쿼리 매개 변수입니다. 그것들이 없으면 “scroll”호출은 모든 것을 메모리에로드하려고 시도하며 종료되지 않거나 OutOfMemoryError로 실행됩니다.


답변

직선 JPA에서는 실제로 이것을 할 수 없지만 Hibernate는 stateless 세션과 스크롤 가능한 결과 세트를 지원합니다.

우리 는 도움을 받아 수십억 개의 행을 일상적으로 처리 합니다.

다음은 문서에 대한 링크입니다. http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession


답변

솔직히 말해서 JPA를 떠나고 JDBC를 고수하는 것이 좋습니다 (하지만 확실히 JdbcTemplate지원 클래스 등을 사용). JPA (및 기타 ORM 공급자 / 사양)는로드 된 모든 항목이 첫 번째 수준 캐시에 있어야한다고 가정하므로 한 트랜잭션 내의 많은 개체에서 작동하도록 설계되지 않았습니다 (따라서 clear()JPA 에서 필요함 ).

또한 ORM (반사는 빙산의 일각 일뿐)의 오버 헤드가 너무 중요 할 수 있기 때문에 더 낮은 수준의 솔루션을 권장하고 있으며, ResultSet언급 된 것과 같은 가벼운 지원을 사용하더라도 일반을 반복하는 JdbcTemplate것이 훨씬 빠를 것입니다.

JPA는 단순히 많은 양의 엔티티에서 작업을 수행하도록 설계되지 않았습니다. 피하려면 flush()/ clear()로 놀 수 OutOfMemoryError있지만 다시 한 번 고려하십시오. 막대한 자원 소비에 대한 대가를 치르더라도 얻는 것이 거의 없습니다.


답변

EclipseLink를 사용하는 경우이 방법을 사용하여 Iterable로 결과를 얻습니다.

private static <T> Iterable<T> getResult(TypedQuery<T> query)
{
  //eclipseLink
  if(query instanceof JpaQuery) {
    JpaQuery<T> jQuery = (JpaQuery<T>) query;
    jQuery.setHint(QueryHints.RESULT_SET_TYPE, ResultSetType.ForwardOnly)
       .setHint(QueryHints.SCROLLABLE_CURSOR, true);

    final Cursor cursor = jQuery.getResultCursor();
    return new Iterable<T>()
    {     
      @SuppressWarnings("unchecked")
      @Override
      public Iterator<T> iterator()
      {
        return cursor;
      }
    }; 
   }
  return query.getResultList();  
}  

닫기 방법

static void closeCursor(Iterable<?> list)
{
  if (list.iterator() instanceof Cursor)
    {
      ((Cursor) list.iterator()).close();
    }
}


답변

수행해야하는 작업의 종류에 따라 다릅니다. 백만 개 이상의 행을 반복하는 이유는 무엇입니까? 배치 모드에서 무언가를 업데이트하고 있습니까? 모든 레코드를 클라이언트에 표시 하시겠습니까? 검색된 엔티티에 대한 통계를 계산하고 있습니까?

백만 개의 레코드를 클라이언트에 표시하려면 사용자 인터페이스를 재고하십시오. 이 경우, 해당 솔루션은 결과를 paginating 및 사용 setFirstResult()setMaxResult().

많은 양의 레코드 업데이트를 시작한 경우 업데이트를 간단하게 유지하고 Query.executeUpdate(). 선택적으로 Message-Driven Bean oa Work Manager를 사용하여 비동기 모드에서 업데이트를 실행할 수 있습니다.

검색된 엔티티에 대한 일부 통계를 계산하는 경우 JPA 사양에 정의 된 그룹화 함수를 활용할 수 있습니다.

다른 경우에는 더 구체적으로 작성해주세요. 🙂


답변

이 작업을 수행 할 “적절한”작업은 없습니다. 이것은 JPA 나 JDO 또는 다른 ORM이 수행하려는 작업이 아닙니다. 적은 수의 행을 다시 가져 오도록 구성 할 수 있으므로 스트레이트 JDBC가 최상의 대안이 될 것입니다. 시간을 지정하고 사용되는대로 플러시하므로 서버 측 커서가 존재합니다.

ORM 도구는 대량 처리를 위해 설계되지 않았습니다. 개체를 조작하고 데이터가 저장되는 RDBMS를 가능한 한 투명하게 만들려고 시도 할 수 있도록 설계되었으며 대부분 투명 부분에서 어느 정도 실패합니다. 이 규모에서는 ORM으로 수십만 개의 행 (Objects)을 처리 할 수있는 방법이 없으며, 단순하고 단순한 개체 인스턴스화 오버 헤드로 인해 적절한 시간 내에 실행되도록 할 수 없습니다.

적절한 도구를 사용하십시오. 스트레이트 JDBC 및 스토어드 프로시 저는 2011 년에 확실히 자리를 잡았습니다. 특히 이러한 ORM 프레임 워크에 비해 더 나은 작업을 수행 할 수 있습니다.

수백만 가지를 단순한 List<Integer>것으로 가져 오는 것은 당신이 어떻게 하든지 간에 그다지 효율적이지 않을 것입니다. 요청한 작업을 수행하는 올바른 방법은 간단한 SELECT id FROM table, SERVER SIDE(공급 업체에 따라 다름)로 설정 하고 커서를 그 위로 FORWARD_ONLY READ-ONLY반복하는 것입니다.

각각의 웹 서버를 호출하여 처리 할 수백만 개의 ID를 처리하는 경우 적절한 시간 내에 실행되도록 동시에 처리해야합니다. JDBC 커서를 가져 와서 한 번에 몇 개씩 ConcurrentLinkedQueue 에 배치하고 작은 스레드 풀 (# CPU / Cores + 1)을 가져 와서 처리하는 것이 ” 이미 메모리가 부족한 경우 정상 “RAM 크기입니다.

답변 도 참조하십시오 .