3

Oracle、Spring、Hibernate、および JPA を使用しています。メモリ不足を心配することなく、任意の数の DB 結果を反復処理したいと考えています。

取得したすべてのオブジェクトを保持せずに、クエリ結果をスクロールしようとしています。

まず、ネイティブ クエリを作成します。

    Query q = getEm().createNativeQuery(sql,reportRowType);
    q.setHint("org.hibernate.fetchSize",1000);
    q.setHint("org.hibernate.cacheable",false);

次に、クエリを実行し、結果を処理するオートワイヤー オブジェクトのメソッドを呼び出します。テストのために、私は結果を完全に無視し、それらを繰り返します。

    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    public <T extends ResultRow> long run(EntityManager em, Query q) {
        ScrollableResults sr = q.unwrap(org.hibernate.Query.class)
            .setReadOnly(true)
            .setFetchSize(1000)
            .setCacheable(false)
            .setCacheMode(CacheMode.IGNORE)
            .scroll(ScrollMode.FORWARD_ONLY);
        try {
            while(sr.next()) {
                T obj = (T)sr.get(0);
                em.detach(obj);

                // do something with the row here
            }
        } finally {
            if(sr!=null)
                sr.close();
        }
    }

上記のコードを使用すると、最終的にメモリ不足になることがわかりました (私のテストでは約 150 万件の結果が得られました)。Query オブジェクトは何らかの形でオブジェクトを保持しています。

ページを介してクエリを実行しても (q.setFirstResult と q.setMaxResults を使用)、実際にはオブジェクトが保持されることがわかりました。

これを行う唯一の方法は、まったく新しい Query オブジェクトを作成し、次に setFirstResult と setMaxResults を使用して結果 1 ~ 10000、次に 10001 ~ 20000 などを取得することです。

休止状態の StatelessSession について読んだことがありますが、それを機能させるにはかなり複雑に見えます。クエリのすべての結果を保持せずにjpaクエリを実行する方法は本当にありませんか?

4

2 に答える 2

2

最終的に、休止状態のステートレス セッションの使用方法を理解しました。これは移植可能ではありませんが、休止状態を使用している場合は、このようなものが機能します。

import javax.persistence.EntityManager;

import java.util.Map;

import org.apache.log4j.Logger;
import org.hibernate.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class StatelessQueryRunner
{
    /** Executes specified native sql in a stateless session. The consumer is given each row as it's received. */
    @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
    public <T> long run(EntityManager em, String sql, Class<T> resultRowType, Map<String,Object> params, int fetchSize, Integer firstResult, Integer maxResults, QueryResultConsumer<T> consumer) {
        int totalResults = 0;
        Session hibernateSession = em.unwrap(Session.class);
        StatelessSession statelessSession = hibernateSession.getSessionFactory().openStatelessSession();
        try {
            // create the query for the stateless session.
            SQLQuery q = statelessSession.createSQLQuery(sql);
            q.addEntity(resultRowType);

            q.setFetchSize(1000);
            JpaQueryWrapper qw = new JpaQueryWrapper(q);
            if(params!=null) {
                for(Map.Entry<String,Object> entry : params.entrySet()) {
                    qw.setParameter(entry.getKey(),entry.getValue());
                }
            }

            if(firstResult!=null)
                q.setFirstResult(firstResult);
            if(maxResults!=null)
                q.setMaxResults(maxResults);


            ScrollableResults sr = q
                .setReadOnly(true)
                .setFetchSize(fetchSize)
                .setCacheable(false)
                .scroll(ScrollMode.FORWARD_ONLY);
            try {
                while(sr.next()) {
                    T obj = (T)sr.get(0);
                    em.detach(obj);
                    consumer.consume(obj);

                    ++totalResults;
//                    if(totalResults % 100000 == 0)
//                        Logger.getLogger(getClass()).debug("totalResults="+ totalResults);
                }
            } finally {
                if(sr!=null)
                    sr.close();
            }

            return totalResults;

        } finally {
            statelessSession.close();
        }
    }

    /** You can't use annotations to start transactions when inside a stateless session, so if you want to do anything you'll need to call this method to run code in a separate session which uses the Session object to start a transaction and save things. */
    public <T> void runInSession(EntityManager em, SessionRunnable<T> action) {
        Session hibernateSession = em.unwrap(Session.class);
        Session session = hibernateSession.getSessionFactory().openSession();
        try {
            action.run(session);
        } finally {
            session.close();
        }
    }


    public interface SessionRunnable<T>
    {
        public void run(Session session);
    }
}


import javax.persistence.*;
import java.util.*;

import org.hibernate.SQLQuery;

public class JpaQueryWrapper
    implements javax.persistence.Query
{
    private SQLQuery q;

    public JpaQueryWrapper(SQLQuery q) {
        this.q = q;
    }

    @Override
    public List getResultList() {throw new UnsupportedOperationException();}
    @Override
    public Object getSingleResult() {throw new UnsupportedOperationException();}
    @Override
    public int executeUpdate() {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setMaxResults(int maxResult) {throw new UnsupportedOperationException();}
    @Override
    public int getMaxResults() {throw new UnsupportedOperationException();}
    @Override
    public Query setFirstResult(int startPosition) {throw new UnsupportedOperationException();}
    @Override
    public int getFirstResult() {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setHint(String hintName, Object value) {throw new UnsupportedOperationException();}
    @Override
    public Map<String, Object> getHints() {throw new UnsupportedOperationException();}
    @Override
    public <T> javax.persistence.Query setParameter(Parameter<T> param, T value) {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setParameter(String name, Object value) {
        if(value instanceof Enum) {
            q.setParameter(name,((Enum) value).name());
        } else {
            q.setParameter(name,value);
        }
        return this;
    }
    @Override
    public javax.persistence.Query setParameter(String name, Calendar value, TemporalType temporalType) {return setParameter(name,(Date)(value==null?null:value.getTime()),temporalType);}
    @Override
    public javax.persistence.Query setParameter(String name, Date value, TemporalType temporalType) {
        if(temporalType==TemporalType.DATE)
            q.setDate(name,value);
        else if(temporalType==TemporalType.TIME)
            q.setTime(name,value);
        else if(temporalType==TemporalType.TIMESTAMP)
            q.setTimestamp(name,value);
        else
            throw new UnsupportedOperationException();
        return this;
    }
    @Override
    public javax.persistence.Query setParameter(int position, Object value) {q.setParameter(position,value); return this;}
    @Override
    public javax.persistence.Query setParameter(int position, Calendar value, TemporalType temporalType) {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setParameter(int position, Date value, TemporalType temporalType) {throw new UnsupportedOperationException();}
    @Override
    public Set<Parameter<?>> getParameters() {throw new UnsupportedOperationException();}
    @Override
    public Parameter<?> getParameter(String name) {throw new UnsupportedOperationException();}
    @Override
    public <T> Parameter<T> getParameter(String name, Class<T> type) {throw new UnsupportedOperationException();}
    @Override
    public Parameter<?> getParameter(int position) {throw new UnsupportedOperationException();}
    @Override
    public <T> Parameter<T> getParameter(int position, Class<T> type) {throw new UnsupportedOperationException();}
    @Override
    public boolean isBound(Parameter<?> param) {throw new UnsupportedOperationException();}
    @Override
    public <T> T getParameterValue(Parameter<T> param) {throw new UnsupportedOperationException();}
    @Override
    public Object getParameterValue(String name) {throw new UnsupportedOperationException();}
    @Override
    public Object getParameterValue(int position) {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setFlushMode(FlushModeType flushMode) {throw new UnsupportedOperationException();}
    @Override
    public FlushModeType getFlushMode() {throw new UnsupportedOperationException();}
    @Override
    public javax.persistence.Query setLockMode(LockModeType lockMode) {throw new UnsupportedOperationException();}
    @Override
    public LockModeType getLockMode() {throw new UnsupportedOperationException();}
    @Override
    public <T> T unwrap(Class <T> cls) {throw new UnsupportedOperationException();}
}


public interface QueryResultConsumer<T>
{
    public void consume(T obj);
}
于 2014-06-25T00:29:55.920 に答える
1

私が知る限り、JPAでは、あなたが説明したように、それを行う唯一の方法は複数のクエリを使用することです。反対に、それが唯一の使用されているテクニックであることは確かです (使用されている場合)。つまり、ページごとに DB クエリを使用して、すべての行がフェッチされるか、ページ分割されます。

もちろん、この機能がもっと頻繁に必要な場合は、コンストラクターで pageSize と QueryProvider を期待する特別な Iterator を作成できます。

于 2013-10-01T07:22:34.533 に答える