Frage

Ich habe eine Klasse, die Iterator mit einem Ergebnis 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 Ergebnis eine andere Zeile hat, damit ich eine gültige HasNext -Methode erstellen kann, da das Ergebnis kein HasNext selbst definiert ist? Ich dachte SELECT COUNT(*) FROM... Fragen Sie die Zählung und die Verwaltung dieser Nummer, um zu sehen, ob es eine andere Zeile gibt, aber ich möchte dies vermeiden.

War es hilfreich?

Lösung

Das ist eine schlechte Idee. Dieser Ansatz erfordert, dass die Verbindung die ganze Zeit bis zur letzten Zeile geöffnet ist, und außerhalb der DAO -Ebene wissen Sie nie, wann sie passieren wird Die Verbindungszeiten. Das willst du nicht.

Die normale JDBC -Praxis ist, dass Sie erwerben Connection, Statement und ResultSet in dem kürzeste möglicher Umfang. Die normale Praxis ist auch, dass Sie mehrere Zeilen in a abbilden List oder vielleicht a Map und raten Sie mal, sie tun einen haben Iterator.

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 unten:

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

Geben Sie nicht teure DB -Ressourcen außerhalb der DAO -Schicht wie Sie anfangs vor. Für grundlegende Beispiele für normale JDBC -Praktiken und das DAO -Muster, das Sie möglicherweise finden, finden Sie Dieser Artikel nützlich.

Andere Tipps

Sie können aus dieser Gurke herauskommen, indem Sie einen Look-Shead in der ausführen hasNext() Und wenn Sie sich daran erinnern, dass Sie sich umgesehen haben, um zu viele Platten zu konsumieren, so etwas wie:

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

Das Ergebnis verfügt über eine "iSlast ()" -Methode, die Ihren Anforderungen entspricht. Das Javadoc Sagt, es ist jedoch ziemlich teuer, da es im Voraus lesen muss. Es besteht eine gute Chance, dass es den Look-Shead-Wert zwischen den anderen zwischengespeichert wird.

Es ist keine wirklich schlechte Idee in den Fällen, in denen Sie es brauchen, es ist nur so, dass Sie es oft nicht brauchen.

Wenn Sie so etwas wie beispielsweise Ihre gesamte Datenbank streamen müssen ... Sie könnten die nächste Zeile vorab reichen - wenn der Abruf fehlschlägt, ist Ihr HasNext falsch.

Hier ist, was ich verwendet habe:

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

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

Eine Option ist die Ergebnissetiterator Aus dem Apache DButils -Projekt.

Balusc weist zu Recht auf die verschiedenen Bedenken hin. Du musst sein sehr Achten Sie darauf, den Verbindungs-/Ergebnis -Lebenszyklus ordnungsgemäß zu behandeln. Glücklicherweise hat das DBUTILS -Projekt auch Lösungen Für sichere Arbeiten mit Ergebnissen.

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 möglicherweise eine Aufnahme machen.

Ich stimme Balusc zu. Wenn ein Iterator aus Ihrer DAO -Methode entkommen kann, wird es schwierig, Verbindungsressourcen zu schließen. Sie werden gezwungen sein, den Verbindungslebenszyklus außerhalb Ihres DAO zu kennen, was zu umständlichen Code und potenziellen Verbindungslecks führt.

Eine Wahl, die ich verwendet habe, besteht jedoch darin, einen Funktions- oder Verfahrenstyp an die DAO -Methode zu übergeben. Geben Sie im Grunde eine Art Callback -Schnittstelle über, die jede Zeile in Ihrem Ergebnissatz einnimmt.

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 ordnungsgemäß ist. Sie müssen jedoch nicht unbedingt eine Sammlung füllen, wenn Speicher ein Problem darstellt.

Hoffe das hilft.

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

Sie könnten Folgendes ausprobieren:

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 vor, und HasNext () gibt true zurück, wenn die zwischengespeicherte Entität gültig ist, andernfalls gibt sie false zurück.

Es gibt ein paar Dinge, die Sie tun können, je nachdem, was Sie von Ihrer Klasse A.

Wenn Sie dies jedoch nicht wollen, können Sie die nächste () und vorherige () -Methode des Ergebnissets verwenden

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

       if(next) {

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

Sie müssen darauf achten, sicherzustellen, dass Sie derzeit nicht ab dem Ergebnis lesen. Wenn Ihre Entitätsklasse jedoch ein ordnungsgemäßes Pojo ist (oder zumindest von Ergebnis ordnungsgemäß getrennt ist, sollte dies ein guter Ansatz sein.

Entitäten.Next Gibt false zurück, wenn es keine Zeilen mehr gibt, sodass Sie diesen Rückgabewert einfach erhalten und eine Mitgliedsvariable festlegen können, um den Status für HasNext () zu verfolgen.

Aber um diese Arbeit zu machen, müssen Sie auch eine Art Init -Methode haben, mit der die erste Entität liest und sie in der Klasse zwischengespeichert. Wenn Sie dann als nächstes anrufen, müssten Sie den zuvor zwischengespeicherten Wert zurückgeben und den nächsten Wert zwischenspeichern usw.

Sie können verwenden Ergebnissetiterator, Setzen Sie einfach Ihr Ergebnis in den Konstruktor ein.

ResultSet rs = ...    
ResultSetIterator = new ResultSetIterator(rs); 

Iteratoren sind problematisch für das Durchqueren von Ergebnissen aus den oben genannten Gründen, aber Iterator -ähnliches Verhalten mit allen erforderlichen Semantik zum Umgang mit Fehlern und Schließungsressourcen sind mit reaktiven Sequenzen (Observables) in verfügbar Rxjava. Observables sind wie Iteratoren, enthalten jedoch die Vorstellungen von Abonnements sowie deren Stornierungen und Fehlerbehelden.

Das Projekt rxjava-jdbc Hat Implementierungen von Observablen für JDBC -Operationen, einschließlich Durchqueren von Ergebnissen mit ordnungsgemäßem Ressourcenverschluss, Fehlerbehandlung und der Fähigkeit, den Durchgang nach Bedarf zu stornieren (Abbestellen).

Hier ist mein Iterator, der ein Ergebnisset einbringt. Die Zeilen werden in Form einer Karte zurückgegeben. Ich hoffe, Sie werden es hilfreich finden. Die Strategie ist, dass ich immer ein Element im Voraus bringe.

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

Erwarten Sie, dass die meisten Daten in Ihrem Ergebnissatz tatsächlich verwendet werden? Wenn ja, vorbeugen Sie es vor. Es ist ziemlich trivial mit z. B. Frühling

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

Passen Sie sich an Ihren Geschmack an.

Ich denke, es gibt genügend Entschlüsse darüber, warum es eine wirklich schlechte Idee ist, das Ergebnis in einem Iterator zu verwenden (kurz gesagt, Ergebnis hält eine aktive Verbindung zu DB und kann nicht so schnell wie möglich geschlossen werden).

Aber in einer anderen Situation, wenn Sie Ergebnis (RS) erhalten und über die Elemente iterieren, aber Sie wollten auch etwas vor der Iteration wie dieser tun:

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

Es kann so gemacht werden:

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

Es hört sich so an, als würden Sie zwischen einer ineffizienten Implementierung von einer ineffizienten Implementierung festhalten hasNext oder eine Ausnahme ausgeben, die besagt, dass Sie die Operation nicht unterstützen.

Leider gibt es Zeiten, in denen Sie eine Schnittstelle implementieren und nicht alle Mitglieder benötigen. In diesem Fall würde ich vorschlagen, dass Sie eine Ausnahme in das Mitglied geben, das Sie nicht unterstützen und nicht das Mitglied in Ihrem Typ als nicht unterstützten Operation dokumentieren und nicht dokumentieren können.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top