web-dev-qa-db-ja.com

Java ResultSetを基にしたイテレータ

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

29
Jordan Messina

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

通常のJDBCプラクティスでは、shortestConnectionStatementおよびResultSetを取得します。範囲。通常の方法では、複数の行をListまたはMapにマップし、それらを推測しますdoIteratorがあります。

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パターンのより基本的な例については、 この記事 が役立ちます。

36
BalusC

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;
    }
    ...
}
39
rsp

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

5
Charlie Brown
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;
    }
}
3
Venkata Raju

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

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

ここに私が使用したものがあります:

/**
 * @author Ian Pojman <[email protected]>
 */
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);
            }
        }
    };
}
3
Ian Pojman

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

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

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

3
NobodyMan

BalusCに同意します。イテレータがDAOメソッドからエスケープできるようにすると、Connectionリソースを閉じることが難しくなります。 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内に保持します。これは適切です。ただし、メモリが懸念される場合は、必ずしもコレクションを埋める必要はありません。

お役に立てれば。

3
taftster

以下を試すことができます:

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を返します。

2
ComSubVie

クラス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から適切に切断されている場合、これは素晴らしいアプローチです。

2
Dunderklumpen

ResultSetIterator を使用して、コンストラクタにResultSetを入れるだけです。

ResultSet rs = ...    
ResultSetIterator = new ResultSetIterator(rs); 
1
B. Broto

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

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

1
Dave Moten

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

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

1
Justin Ethier

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);
        }
    }
}
1
Giannis

イテレータで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());
}
0
ADTC

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

public boolean hasNext() {
    ...
    return !entities.isLast();
    ...
}
0
Art

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

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

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

0
Dan