문제

데이터베이스에서 많은 양의 데이터를 내보내야합니다. 다음은 내 데이터를 나타내는 클래스입니다.

public class Product{
...

    @OneToMany
    @JoinColumn(name = "product_id")
    @Cascade({SAVE_UPDATE, DELETE_ORPHAN})
    List<ProductHtmlSource> htmlSources = new ArrayList<ProductHtmlSource>();

... }

ProductHtmlSource - 내부에 큰 문자열이 포함되어 있으며 실제로 내보내기가 필요합니다.

내보낸 데이터의 크기는 JVM 메모리보다 크기 때문에 청크로 내 데이터를 읽고 있습니다. 이와 같이:

final int batchSize = 1000;      
for (int i = 0; i < 50; i++) {
  ScrollableResults iterator = getProductIterator(batchSize * i, batchSize * (i + 1));
  while (iterator.getScrollableResults().next()) {
     Product product = (Product) iterator.getScrollableResults().get(0); 
     List<String> htmls = product.getHtmlSources();
     <some processing>
  }

}

코드 getProductIterator :

public ScrollableResults getProductIterator(int offset, int limit) {
        Session session = getSession(true);
        session.setCacheMode(CacheMode.IGNORE);
        ScrollableResults iterator = session
                .createCriteria(Product.class)
                .add(Restrictions.eq("status", Product.Status.DONE))
                .setFirstResult(offset)
                .setMaxResults(limit)
                .scroll(ScrollMode.FORWARD_ONLY);
        session.flush();
        session.clear();

        return iterator;
    }

문제는 각 데이터 청크를 읽은 후 세션을 지우는데도 불구하고 Product 물체가 어딘가에 축적되며 제외한 예외가 발생합니다. 문제는 코드의 처리 블록에 있지 않아도 메모리 오류가 발생하지 않습니다. 배치 크기는 또한 1000 개의 물체가 쉽게 메모리에 앉아 있기 때문에 문제가되지 않습니다.

프로파일 러는 물체가 축적된다는 것을 보여 주었다 org.hibernate.engine.StatefulPersistenceContext 수업.

스택 트레이스 :

Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:99)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:518)
    at java.lang.StringBuffer.append(StringBuffer.java:307)
    at org.hibernate.type.TextType.get(TextType.java:41)
    at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163)
    at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:154)
    at org.hibernate.type.AbstractType.hydrate(AbstractType.java:81)
    at org.hibernate.persister.entity.AbstractEntityPersister.hydrate(AbstractEntityPersister.java:2101)
    at org.hibernate.loader.Loader.loadFromResultSet(Loader.java:1380)
    at org.hibernate.loader.Loader.instanceNotYetLoaded(Loader.java:1308)
    at org.hibernate.loader.Loader.getRow(Loader.java:1206)
    at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:580)
    at org.hibernate.loader.Loader.doQuery(Loader.java:701)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
    at org.hibernate.loader.Loader.loadCollection(Loader.java:1994)
    at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:36)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:565)
    at org.hibernate.event.def.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:63)
    at org.hibernate.impl.SessionImpl.initializeCollection(SessionImpl.java:1716)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:344)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
    at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:109)
    at org.hibernate.collection.PersistentBag.size(PersistentBag.java:225)
    **at com.rivalwatch.plum.model.Product.getHtmlSource(Product.java:76)
    at com.rivalwatch.plum.model.Product.getHtmlSourceText(Product.java:80)
    at com.rivalwatch.plum.readers.AbstractDataReader.getData(AbstractDataReader.java:64)**
도움이 되었습니까?

해결책

시작 및 종료 행 번호로 getProductiterator ()를 호출하는 것처럼 보이며 GetProductiterator ()는 시작 행과 행 계수를 기대하고 있습니다. "상한"이 높아짐에 따라 더 큰 덩어리에서 데이터를 읽고 있습니다. 나는 당신이 getProductiterator ()의 두 번째 인수로 배치 크기를 전달한다고 생각합니다.

다른 팁

직접적인 답변이 아니라 이러한 종류의 데이터 조작을 위해 상태가없는 인터페이스.

Keithl이 옳습니다 - 당신은 계속 증가하는 한도를 통과합니다. 그러나 그것을 그렇게 깨는 것은 어쨌든 의미가 없습니다. 스크롤 커서의 요점은 한 번에 한 번 씩 행을 처리하므로 덩어리로 분해 할 필요가 없다는 것입니다. 페치 크기는 더 많은 메모리를 사용하는 비용으로 데이터베이스로의 여행을 줄입니다. 일반적인 패턴은 다음과 같습니다.

Query q = session.createCriteria(... no offset or limit ...);
q.setCacheMode(CacheMode.IGNORE); // prevent query or second level caching
q.setFetchSize(1000);  // experiment with this to optimize performance vs. memory
ScrollableResults iterator = query.scroll(ScrollMode.FORWARD_ONLY);
while (iterator.next()) {
  Product p = (Product)iterator.get();
  ...
  session.evict(p);  // required to keep objects from accumulating in the session
}

즉, 오류는 gethtmlSources이므로 문제는 세션/커서/스크롤 문제와 완전히 관련이 없을 수 있습니다. 그 HTML 문자열이 거대하고 전체 시간을 참조하고 있다면, 당신은 만족스러운 메모리가 부족할 수 있습니다.

BTW, 나는 scrollberesults에서 getScrollaberesults 메소드가 보이지 않습니다.

바보 같은 것처럼 보일 위험이 있습니다 - 당신은 이것을 다른 방식으로하는 것을 고려 했습니까?

개인적으로 나는 데이터베이스에서 "멀리 떨어진"배치 처리를 피할 것입니다. 어떤 데이터베이스를 사용하고 있는지 모르겠지만 일반적으로 데이터베이스를 데이터베이스에서 효율적으로 끌어 당기고 나가는 길에 간단한 조작이 포함되어 있어도 파일로 효율적으로 끌어 당기는 메커니즘이 있습니다. 저장된 절차, 특정 수출 유틸리티. 데이터베이스 공급 업체에서 사용할 수있는 다른 것을 조사하십시오.

예외 스택 트레이스를 게시 할 수 있습니까? GC에 적합한 JVM 옵션을 전달하여 해결할 수 있습니다.

나는 이것이 관련이 있다고 생각한다 - Java StringBuilder 큰 오버 헤드.

스택 트레이스에서 매우 큰 문자열이 생성되고 예외가 발생합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top