webentwicklung-frage-antwort-db.com.de

Java-Iterator wird durch ein ResultSet unterstützt

Ich habe eine Klasse, die Iterator mit einem ResultSet als Datenmitglied implementiert. Im Wesentlichen sieht die Klasse so aus:

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?
    }
    ...
}

Wie kann ich überprüfen, ob das ResultSet eine andere Zeile hat, sodass ich eine gültige hasNext-Methode erstellen kann, da ResultSet selbst keinen hasNext definiert hat? Ich dachte daran, die Funktion SELECT COUNT(*) FROM... zu verwenden, um die Anzahl zu ermitteln, und diese Zahl zu verwalten, um zu sehen, ob es eine weitere Zeile gibt, aber ich möchte das vermeiden.

27
Jordan Messina

Das ist eine schlechte Idee. Dieser Ansatz erfordert, dass die Verbindung die ganze Zeit geöffnet ist, bis die letzte Zeile gelesen wird. Außerhalb der DAO-Schicht wissen Sie nie, wann sie stattfinden wird, und Sie scheinen auch das Resultset offen zu lassen, wodurch Ressourcenlecks und Anwendungsabstürze im Fall auftreten die Verbindung läuft ab. Das willst du nicht haben. 

Die übliche JDBC-Praxis ist, dass Sie Connection, Statement und ResultSet im kürzesten Bereich erwerben. Die übliche Praxis ist auch, dass Sie mehrere Zeilen in eine List oder möglicherweise eine Map abbilden und raten, was do eine Iterator hat.

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;
}

Und benutze es wie folgt:

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

Übergeben Sie keine teuren DB-Ressourcen außerhalb der DAO-Schicht, wie Sie es anfangs tun wollten. Für einfachere Beispiele für normale JDBC-Praktiken und das DAO-Muster finden Sie diesen Artikel /.

35
BalusC

Sie können aus dieser Pickle herauskommen, indem Sie in hasNext() eine Vorausschau ausführen und daran denken, dass Sie eine Suche durchgeführt haben, um zu vermeiden, dass zu viele Datensätze verwendet werden.

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

ResultSet verfügt über eine 'isLast ()' - Methode, die möglicherweise Ihren Anforderungen entspricht. Das JavaDoc sagt, dass es ziemlich teuer ist, da es vorauslesen muss. Es besteht eine gute Chance, dass der Look-Ahead-Wert zwischengespeichert wird, wie die anderen es vorschlagen.

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

Es ist keine wirklich schlechte Idee, wenn Sie es brauchen, es ist nur so, dass Sie es oft nicht brauchen.

Wenn Sie etwa die gesamte Datenbank streamen müssen, können Sie die nächste Zeile vorher abrufen - wenn der Abruf fehlschlägt, ist Ihr hasNext false.

Folgendes habe ich verwendet:

/**
 * @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();
    }
}

dann:

    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

Eine Option ist der ResultSetIterator aus dem Apache DBUtils-Projekt. 

BalusC weist zu Recht auf die verschiedenen Bedenken hin. Sie müssen very vorsichtig sein, um den Verbindungszyklus/Resultset-Lebenszyklus ordnungsgemäß abzuwickeln. Glücklicherweise verfügt das DBUtils-Projekt über Lösungen , um mit Resultsets sicher zu arbeiten. 

Wenn die Lösung von BalusC für Sie unpraktisch ist (z. B. verarbeiten Sie große Datensätze, die nicht alle in den Speicher passen), möchten Sie vielleicht einen Versuch machen.

3
NobodyMan

Sie könnten folgendes versuchen:

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;
    }
}

Dieser Code speichert die nächste Entität zwischen, und hasNext () gibt true zurück, wenn die zwischengespeicherte Entität gültig ist. Andernfalls wird false zurückgegeben.

2
ComSubVie

Es gibt einige Dinge, die Sie je nach Wunsch Ihrer Klasse A tun können. Wenn der Hauptanwendungsfall darin besteht, jedes einzelne Ergebnis durchzugehen, empfiehlt es sich, alle Entity-Objekte vorab zu laden und das ResultSet wegzuwerfen.

Wenn Sie dies jedoch nicht möchten, können Sie die next () - und previous () - Methode von ResultSet verwenden

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

       if(next) {

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

Sie müssen vorsichtig sein, um sicherzustellen, dass Sie nicht gerade aus dem ResultSet lesen. Wenn Ihre Entity-Klasse jedoch ein richtiger POJO ist (oder zumindest ordnungsgemäß von ResultSet getrennt ist, sollte dies ein guter Ansatz sein.

2
Dunderklumpen

Ich stimme mit BalusC überein. Wenn Sie einem Iterator erlauben, aus Ihrer DAO-Methode zu entkommen, wird es schwierig, Verbindungsressourcen zu schließen. Sie werden gezwungen, den Verbindungslebenszyklus außerhalb Ihres DAOs zu kennen, was zu umständlichem Code und potenziellen Verbindungslecks führt.

Eine von mir gewählte Möglichkeit besteht jedoch darin, einen Funktions- oder Prozedurentyp an die DAO-Methode zu übergeben. Übergeben Sie im Grunde eine Art Callback-Schnittstelle, die jede Zeile in Ihrer Ergebnismenge aufnehmen wird.

Zum Beispiel vielleicht so etwas:

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

Auf diese Weise behalten Sie Ihre Datenbankverbindungsressourcen in Ihrem DAO, was richtig ist. Sie müssen jedoch nicht unbedingt eine Collection füllen, wenn Speicher ein Problem ist.

Hoffe das hilft.

2
taftster

Sie können ResultSetIterator verwenden. Fügen Sie einfach Ihr ResultSet im Konstruktor ein.

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

Iteratoren sind für das Durchlaufen von ResultSets aus den oben genannten Gründen problematisch, aber ein Iterator-ähnliches Verhalten mit allen erforderlichen Semantiken für das Behandeln von Fehlern und das Schließen von Ressourcen ist mit reaktiven Sequenzen (Observables) in RxJava verfügbar. Observables sind wie Iteratoren, beinhalten jedoch die Begriffe Abonnements und deren Abbruch und Fehlerbehandlung. 

Das Projekt rxjava-jdbc verfügt über Implementierungen von Observables für jdbc-Vorgänge, einschließlich Durchlaufen von ResultSets mit ordnungsgemäßem Schließen von Ressourcen, Fehlerbehandlung und der Möglichkeit, den Durchlauf nach Bedarf abzubrechen (Abbestellen).

1
Dave Moten

entity.next gibt false zurück, wenn keine weiteren Zeilen vorhanden sind. Sie können also einfach diesen Rückgabewert abrufen und eine Membervariable festlegen, um den Status von hasNext () zu verfolgen. 

Um dies zu erreichen, müssten Sie jedoch auch eine Art init-Methode haben, die die erste Entität liest und in der Klasse zwischenspeichert. Beim nächsten Aufruf müssten Sie dann den zuvor zwischengespeicherten Wert zurückgeben und den nächsten Wert zwischenspeichern usw.

1
Justin Ethier

Hier ist mein Iterator, der ein ResultSet einhüllt. Die Zeilen werden in Form einer Map zurückgegeben. Ich hoffe, Sie finden es hilfreich. Die Strategie ist, dass ich immer ein Element im Voraus mitbringe.

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

Ich denke, es gibt genug Gründe, warum es eine wirklich schlechte Idee ist, ResultSet in einem Iterator zu verwenden (kurz gesagt, ResultSet unterhält eine aktive Verbindung zur DB, und wenn es nicht geschlossen wird, kann dies zu Problemen führen).

Aber in einer anderen Situation, wenn Sie ResultSet (rs) erhalten und über die Elemente iterieren möchten, aber Sie wollten auch etwas vor der Iteration machen:

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
}

Sie können den gleichen Effekt erzielen, indem Sie stattdessen so schreiben:

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

Es kann so gemacht werden:

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

Erwarten Sie, dass die meisten Daten in Ihrer Ergebnismenge tatsächlich verwendet werden? Wenn ja, zwischenspeichern. Es ist ziemlich trivial, wenn man zB Frühling benutzt

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

Passen Sie nach Ihrem Geschmack an.

0
Dan