34

ResultSetをデータメンバーとしてIteratorを実装するクラスがあります。基本的に、クラスは次のようになります。

public class A implements Iterator{
    private ResultSet entities;
    ...
    public Object next(){
        entities.next();
        return new Entity(entities.getString...etc....)
    }

    public boolean hasNext(){
        //what to do?
    }
    ...
}

ResultSetにはhasNextが定義されていないため、有効なhasNextメソッドを作成できるように、ResultSetに別の行があるかどうかを確認するにはどうすればよいですか?クエリを実行してカウントを取得し、その数を管理して別の行があるかどうかを確認することを考えていSELECT COUNT(*) FROM...ましたが、これは避けたいと思います。

4

17 に答える 17

40

hasNext()で先読みを実行し、次のように大量のレコードを消費しないようにルックアップを実行したことを思い出して、このピクルスから抜け出すことができます。

public class A implements Iterator{
    private ResultSet entities;
    private boolean didNext = false;
    private boolean hasNext = false;
    ...
    public Object next(){
        if (!didNext) {
            entities.next();
        }
        didNext = false;
        return new Entity(entities.getString...etc....)
    }

    public boolean hasNext(){
        if (!didNext) {
            hasNext = entities.next();
            didNext = true;
        }
        return hasNext;
    }
    ...
}
于 2009-12-08T21:48:55.290 に答える
40

これは悪い考えです。このアプローチでは、最後の行が読み取られるまで接続が常に開いている必要があり、DAOレイヤーの外側では、いつ発生するかわからないため、結果セットを開いたままにしておくと、リソースリークやアプリケーションのクラッシュのリスクがあります。接続がタイムアウトします。あなたはそれを持ちたくない。

通常のJDBCの方法では、を取得しConnection、可能な限り最短のスコープで取得します。通常の方法では、複数の行をaまたは多分aにマップし、何を推測しますか。それらに。があります。StatementResultSetListMapIterator

public List<Data> list() throws SQLException {
    List<Data> list = new ArrayList<Data>();

    try (
        Connection connection = database.getConnection();
        Statement statement = connection.createStatement("SELECT id, name, value FROM data");
        ResultSet resultSet = statement.executeQuery();
    ) {
        while (resultSet.next()) {
            list.add(map(resultSet));
        }
    }

    return list;
}

private Data map(ResultSet resultSet) throws SQLException {
    Data data = new Data(); 
    data.setId(resultSet.getLong("id"));
    data.setName(resultSet.getString("name"));
    data.setValue(resultSet.getInteger("value"));
    return data;
}

そしてそれを以下のように使用します:

List<Data> list = dataDAO.list(); 
int count = list.size(); // Easy as that.
Iterator<Data> iterator = list.iterator(); // There is your Iterator.

最初にやりたかったように、高価なDBリソースをDAOレイヤーの外部に渡さないでください。通常のJDBCプラクティスとDAOパターンのより基本的な例については、この記事が役立つ場合があります。

于 2009-12-08T22:18:41.370 に答える
5

ResultSetには、ニーズに合う可能性のある'isLast()'メソッドがあります。JavaDocは、先読みする必要があるため、かなり高価であると述べています。他の人が試してみることを提案しているように、先読み値をキャッシュしている可能性が高いです。

于 2009-12-08T21:56:58.583 に答える
4
public class A implements Iterator<Entity>
{
    private final ResultSet entities;

    // Not required if ResultSet.isLast() is supported
    private boolean hasNextChecked, hasNext;

    . . .

    public boolean hasNext()
    {
        if (hasNextChecked)
           return hasNext;
        hasNext = entities.next();
        hasNextChecked = true;
        return hasNext;

        // You may also use !ResultSet.isLast()
        // but support for this method is optional 
    }

    public Entity next()
    {
        if (!hasNext())
           throw new NoSuchElementException();

        Entity entity = new Entity(entities.getString...etc....)

        // Not required if ResultSet.isLast() is supported
        hasNextChecked = false;

        return entity;
    }
}
于 2015-05-27T18:44:38.253 に答える
3

BalusCに同意します。イテレータをDAOメソッドからエスケープできるようにすると、接続リソースを閉じることが難しくなります。DAOの外部の接続ライフサイクルについて知ることを余儀なくされ、面倒なコードと潜在的な接続リークにつながります。

ただし、私が使用した1つの選択肢は、関数またはプロシージャの型をDAOメソッドに渡すことです。基本的に、結果セットの各行を取得するある種のコールバックインターフェイスを渡します。

たとえば、次のようなものかもしれません。

public class MyDao {

    public void iterateResults(Procedure<ResultSet> proc, Object... params)
           throws Exception {

        Connection c = getConnection();
        try {
            Statement s = c.createStatement(query);
            ResultSet rs = s.executeQuery();
            while (rs.next()) {
                proc.execute(rs);
            }

        } finally {
            // close other resources too
            c.close();
        }
    }

}


public interface Procedure<T> {
   void execute(T t) throws Exception;
}


public class ResultSetOutputStreamProcedure implements Procedure<ResultSet> {
    private final OutputStream outputStream;
    public ResultSetOutputStreamProcedure(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    @Override
    public void execute(ResultSet rs) throws SQLException {
        MyBean bean = getMyBeanFromResultSet(rs);
        writeMyBeanToOutputStream(bean);
    }    
}

このようにして、データベース接続リソースをDAO内に保持します。これは適切です。ただし、メモリが問題になる場合は、必ずしもコレクションを埋める必要はありません。

お役に立てれば。

于 2012-06-12T21:11:01.633 に答える
3

あなたがそれを必要とする場合、それは本当に悪い考えではありません、それはあなたがしばしばそれを必要としないということだけです。

たとえば、データベース全体をストリーミングするなどの操作が必要な場合は、次の行をプリフェッチできます。フェッチが失敗した場合、hasNextはfalseになります。

これが私が使用したものです:

/**
 * @author Ian Pojman <pojman@gmail.com>
 */
public abstract class LookaheadIterator<T> implements Iterator<T> {
    /** The predetermined "next" object retrieved from the wrapped iterator, can be null. */
    protected T next;

    /**
     * Implement the hasNext policy of this iterator.
     * Returns true of the getNext() policy returns a new item.
     */
    public boolean hasNext()
    {
        if (next != null)
        {
            return true;
        }

        // we havent done it already, so go find the next thing...
        if (!doesHaveNext())
        {
            return false;
        }

        return getNext();
    }

    /** by default we can return true, since our logic does not rely on hasNext() - it prefetches the next */
    protected boolean doesHaveNext() {
        return true;
    }

    /**
     * Fetch the next item
     * @return false if the next item is null. 
     */
    protected boolean getNext()
    {
        next = loadNext();

        return next!=null;
    }

    /**
     * Subclasses implement the 'get next item' functionality by implementing this method. Implementations return null when they have no more.
     * @return Null if there is no next.
     */
    protected abstract T loadNext();

    /**
     * Return the next item from the wrapped iterator.
     */
    public T next()
    {
        if (!hasNext())
        {
            throw new NoSuchElementException();
        }

        T result = next;

        next = null;

        return result;
    }

    /**
     * Not implemented.
     * @throws UnsupportedOperationException
     */
    public void remove()
    {
        throw new UnsupportedOperationException();
    }
}

それから:

    this.lookaheadIterator = new LookaheadIterator<T>() {
        @Override
        protected T loadNext() {
            try {
                if (!resultSet.next()) {
                    return null;
                }

                // process your result set - I use a Spring JDBC RowMapper
                return rowMapper.mapRow(resultSet, resultSet.getRow());
            } catch (SQLException e) {
                throw new IllegalStateException("Error reading from database", e);
            }
        }
    };
}
于 2011-08-23T15:27:51.713 に答える
3

ResultSetIteratorを使用できます。コンストラクターにResultSetを配置するだけです。

ResultSet rs = ...    
ResultSetIterator = new ResultSetIterator(rs); 
于 2012-01-05T10:48:22.353 に答える
3

1つのオプションは、 ApacheDBUtilsプロジェクト のResultSetIteratorです。

BalusCは、これを行う際のさまざまな懸念を正しく指摘しています。接続/結果セットのライフサイクルを適切に処理するには、細心の注意を払う必要があります。幸い、DBUtilsプロジェクトには、結果セットを安全に操作するための ソリューションもあります。

BalusCのソリューションが実用的でない場合(たとえば、すべてがメモリに収まらない大きなデータセットを処理している場合)、それを試してみることをお勧めします。

于 2012-03-22T21:14:15.267 に答える
2

次のことを試すことができます。

public class A implements Iterator {
    private ResultSet entities;
    private Entity nextEntity;
    ...
    public Object next() {
        Entity tempEntity;
        if ( !nextEntity ) {
            entities.next();
            tempEntity = new Entity( entities.getString...etc....)
        } else {
            tempEntity = nextEntity;
        }

        entities.next();
        nextEntity = new Entity( entities.getString...ext....)

        return tempEntity;
    }

    public boolean hasNext() {
        return nextEntity ? true : false;
    }
}

このコードは次のエンティティをキャッシュし、キャッシュされたエンティティが有効な場合はhasNext()がtrueを返し、そうでない場合はfalseを返します。

于 2009-12-08T21:49:09.553 に答える
2

クラスAの目的に応じて、実行できることがいくつかあります。主なユースケースがすべての結果を調べることである場合は、すべてのEntityオブジェクトをプリロードし、ResultSetを破棄するのがおそらく最善です。

ただし、それを望まない場合は、ResultSetのnext()およびprevious()メソッドを使用できます。

public boolean hasNext(){
       boolean next = entities.next();

       if(next) {

           //reset the cursor back to its previous position
           entities.previous();
       }
}

ResultSetから現在読み取っていないことを確認するように注意する必要がありますが、Entityクラスが適切なPOJOである場合(または少なくともResultSetから適切に切断されている場合、これは適切なアプローチです。

于 2009-12-08T21:49:50.107 に答える
2

ResultSetをラップするイテレータは次のとおりです。行はマップの形式で返されます。お役に立てば幸いです。戦略は、私が常に1つの要素を事前に持ってくることです。

public class ResultSetIterator implements Iterator<Map<String,Object>> {

    private ResultSet result;
    private ResultSetMetaData meta;
    private boolean hasNext;

    public ResultSetIterator( ResultSet result ) throws SQLException {
        this.result = result;
        meta = result.getMetaData();
        hasNext = result.next();
    }

    @Override
    public boolean hasNext() {
        return hasNext;
    }

    @Override
    public Map<String, Object> next() {
        if (! hasNext) {
            throw new NoSuchElementException();
        }
        try {
            Map<String,Object> next = new LinkedHashMap<>();
            for (int i = 1; i <= meta.getColumnCount(); i++) {
                String column = meta.getColumnName(i);
                Object value = result.getObject(i);
                next.put(column,value);
            }
            hasNext = result.next();
            return next;
        }
        catch (SQLException ex) {
            throw new RuntimeException(ex);
        }
    }
}
于 2016-11-15T14:03:18.863 に答える
1

これ以上行がない場合、 entities.nextはfalseを返すため、その戻り値を取得し、メンバー変数を設定して、hasNext()のステータスを追跡できます。

ただし、これを機能させるには、最初のエンティティを読み取り、それをクラスにキャッシュする、ある種のinitメソッドも必要になります。次に、nextを呼び出すときに、前にキャッシュされた値を返し、次の値をキャッシュする必要があります。

于 2009-12-08T21:40:20.967 に答える
1

イテレータは、上記の理由でResultSetをトラバースするのに問題がありますが、エラーの処理とリソースのクローズに必要なすべてのセマンティクスを備えたイテレータのような動作は、RxJavaのリアクティブシーケンス(Observables)で利用できます。オブザーバブルはイテレーターに似ていますが、サブスクリプションとそのキャンセルおよびエラー処理の概念が含まれています。

プロジェクトrxjava-jdbcには、リソースの適切なクローズ、エラー処理、および必要に応じてトラバーサルをキャンセルする機能(サブスクライブ解除)を備えたResultSetのトラバーサルを含むjdbc操作のObservablesの実装があります。

于 2014-03-14T04:46:15.720 に答える
0

結果セットのほとんどのデータが実際に使用されると思いますか?その場合は、事前にキャッシュします。たとえばSpringを使用するのは非常に簡単です

  List<Map<String,Object>> rows = jdbcTemplate.queryForList(sql);
  return rows.iterator();

好みに合わせて調整してください。

于 2009-12-08T21:46:44.243 に答える
0

イテレータでResultSetを使用することが本当に悪い考えである理由については十分な批判があると思います(つまり、ResultSetはDBへのアクティブな接続を維持し、できるだけ早く閉じないと問題が発生する可能性があります)。

しかし、別の状況では、ResultSet(rs)を取得して要素を反復処理しようとしているが、反復の前に次のようなことも実行したい場合は、次のようになります。

if (rs.hasNext()) { //This method doesn't exist
    //do something ONCE, *IF* there are elements in the RS
}
while (rs.next()) {
    //do something repeatedly for each element
}

代わりに次のように書くことで同じ効果を得ることができます:

if (rs.next()) {
    //do something ONCE, *IF* there are elements in the RS
    do {
        //do something repeatedly for each element
    } while (rs.next());
}
于 2013-02-01T07:21:17.860 に答える
0

これは次のように実行できます。

public boolean hasNext() {
    ...
    return !entities.isLast();
    ...
}
于 2014-09-24T10:28:14.967 に答える
-1

hasNext非効率的な実装を提供するか、操作をサポートしていないことを示す例外をスローする かのどちらかで立ち往生しているようです。

残念ながら、インターフェイスを実装していて、すべてのメンバーが必要なわけではない場合があります。その場合、サポートしない、またはサポートできないという例外をそのメンバーにスローし、サポートされていない操作としてそのメンバーを自分のタイプで文書化することをお勧めします。

于 2009-12-08T21:40:04.443 に答える