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 方法,因为 ResultSet 本身没有定义 hasNext?我正在考虑进行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,Statement并且ResultSet在尽可能的范围内。通常的做法是将多行映射到 aList或 aMap中,然后猜猜是什么,它们确实有一个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;
}

并如下使用它:

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

不要像最初想要的那样在 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

在您需要它的情况下,这并不是一个非常糟糕的主意,只是您经常不需要它。

如果你确实需要做一些事情,比如说,流式传输你的整个数据库......你可以预取下一行 - 如果提取失败,你的 hasNext 是假的。

这是我使用的:

/**
 * @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

我同意 BalusC。允许 Iterator 从您的 DAO 方法中逃脱将使关闭任何 Connection 资源变得困难。您将被迫了解 DAO 之外的连接生命周期,这会导致繁琐的代码和潜在的连接泄漏。

但是,我使用的一种选择是将函数或过程类型传递给 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 中,这是正确的。但是,如果内存是一个问题,您不一定需要填写 Collection。

希望这可以帮助。

于 2012-06-12T21:11:01.633 回答
3

一种选择是 来自 Apache DBUtils 项目 的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 的迭代器。这些行以 Map 的形式返回。我希望你会发现它有帮助。策略是我总是提前带一个元素。

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

由于上述原因,迭代器在遍历 ResultSets 时存在问题,但具有处理错误和关闭资源所需的所有语义的类似迭代器的行为可通过RxJava中的反应序列(Observables)获得。Observables 类似于迭代器,但包括订阅及其取消和错误处理的概念。

rxjava-jdbc项目实现了用于 jdbc 操作的 Observables,包括遍历 ResultSets 并正确关闭资源、错误处理以及根据需要取消遍历(取消订阅)的能力。

于 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 回答