由ResultSet支持的Java Iterator

时间:2009-12-08 21:37:06

标签: java sql jdbc iterator resultset

我有一个实现Iterator的类,ResultSet作为数据成员。基本上这个类看起来像这样:

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...查询以获取计数并管理该数字以查看是否有另一行,但我想避免这种情况。

17 个答案:

答案 0 :(得分:39)

你可以通过在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;
    }
    ...
}

答案 1 :(得分:36)

这是一个坏主意。这种方法要求连接一直打开直到读取最后一行,并且在DAO层之外你永远不知道它何时会发生,并且你似乎也保持结果集开放并且风险资源泄漏和应用程序崩溃的情况下连接超时。你不想拥有它。

正常的JDBC实践是您在最短范围内获得ConnectionStatementResultSet。通常的做法是,您将多行映射到ListMap,然后猜测他们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模式的更多基本示例,您可能会发现this article很有用。

答案 2 :(得分:5)

ResultSet有一个'isLast()'方法,可能适合您的需要。 JavaDoc表示它非常昂贵,因为它必须提前阅读。很有可能像其他人建议的那样缓存前瞻性价值。

答案 3 :(得分:3)

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

答案 4 :(得分: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);
            }
        }
    };
}

答案 5 :(得分: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中,这是正确的。但是,如果记忆是一个问题,你不一定要填写收藏品。

希望这有帮助。

答案 6 :(得分:3)

一个选项是Apache DBUtils项目中的ResultSetIterator

BalusC正确地指出了这样做的各种担忧。您需要非常小心地正确处理连接/结果集生命周期。幸运的是,DBUtils项目还具有solutions,可以安全地使用结果集。

如果BalusC的解决方案对您来说不切实际(例如,您处理的大数据集不能完全适合内存),您可能需要尝试一下。

答案 7 :(得分: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。

答案 8 :(得分:2)

根据您对A类的要求,您可以执行以下几项操作。如果主要用例是通过每个结果,则可能最好预先加载所有Entity对象并丢弃ResultSet。 / p>

如果您不想这样做,可以使用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正确断开连接,那么这应该是一个很好的方法。

答案 9 :(得分:1)

这是我的包装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);
        }
    }
}

答案 10 :(得分:1)

由于上面提到的原因,迭代器在遍历ResultSet时存在问题,但是RxJava中的反应序列(Observables)可以使用具有处理错误和关闭资源所需的所有语义的迭代器行为。 Observable类似于迭代器,但包括订阅及其取消和错误处理的概念。

项目rxjava-jdbc具有针对jdbc操作的Observable实现,包括对ResultSets的遍历以及正确的资源关闭,错误处理以及根据需要取消遍历的能力(取消订阅)。

答案 11 :(得分:1)

您可以使用ResultSetIterator,只需将ResultSet放在构造函数中。

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

答案 12 :(得分:1)

如果没有更多行,

entities.next将返回false,因此您可以获取该返回值并设置成员变量以跟踪hasNext()的状态。

但是为了完成这项工作,你还必须有某种init方法来读取第一个实体并将其缓存在类中。然后在调用next时,您需要返回先前缓存的值并缓存下一个值,等等......

答案 13 :(得分:0)

我认为有足够的解密为什么在Iterator中使用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());
}

答案 14 :(得分:0)

您是否期望实际使用结果集中的大部分数据?如果是这样,请预先缓存它。使用例如Spring

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

根据您的口味进行调整。

答案 15 :(得分:0)

可以这样做:

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

答案 16 :(得分:-1)

听起来好像你在提供hasNext的低效实现或者抛出一个声明你不支持该操作的异常。

不幸的是,有时您实现了一个接口,而您不需要所有成员。在这种情况下,我建议你在该成员中抛出一个例外,你不会或不能支持并将你的类型成员记录为不受支持的操作。