수백만 개의 행이있는 테이블이 있다고 가정 해 보겠습니다. 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 크기입니다.
이 답변 도 참조하십시오 .