代码之家  ›  专栏  ›  技术社区  ›  Jordan Messina

由结果集支持的Java迭代器

  •  27
  • Jordan Messina  · 技术社区  · 15 年前

    我有一个类,它实现了以结果集作为数据成员的迭代器。基本上,类如下所示:

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

    如何检查结果集是否有另一行,以便创建有效的hasNext方法,因为结果集本身没有hasNext定义?我在想怎么做 SELECT COUNT(*) FROM... 查询以获取计数并管理该数字,以查看是否还有其他行,但我希望避免这种情况。

    17 回复  |  直到 8 年前
        1
  •  35
  •   BalusC    9 年前

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

    通常的JDBC实践是 Connection , Statement ResultSet 最短的 可能的范围。通常的做法是将多行映射到 List 或者也许 Map 你猜怎么着,他们 有一个 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层之外传递昂贵的DB资源。对于普通JDBC实践和DAO模式的更多基本示例,您可能会发现 this article 有用的。

        2
  •  38
  •   rsp    15 年前

    您可以通过在 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;
        }
        ...
    }
    
        3
  •  5
  •   mrvisser    15 年前

    结果集有一个“islast()”方法,可以满足您的需要。这个 JavaDoc 说它很贵,因为它必须提前阅读。它很有可能像其他人建议的那样缓存前瞻性值。

        4
  •  3
  •   Ian Pojman    13 年前

    在你需要它的情况下,这不是一个很坏的主意,只是你经常不需要它。

    如果您确实需要这样做,比如说,流式处理整个数据库……您可以预先获取下一行-如果获取失败,则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
  •   NobodyMan    13 年前

    一种选择是 ResultSetIterator 从Apache DBUTILS项目。

    俾路支正确地指出了在这方面的各种问题。你需要 非常 注意正确处理连接/结果集生命周期。幸运的是,dbutils项目也有 solutions 用于安全地使用结果集。

    如果Balusc的解决方案对您来说不切实际(例如,您正在处理无法全部装入内存的大型数据集),那么您可能需要对其进行一次尝试。

        6
  •  3
  •   Venkata Raju    10 年前
    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;
        }
    }
    
        7
  •  2
  •   ComSubVie    15 年前

    您可以尝试以下操作:

    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
  •   Dunderklumpen    15 年前

    根据您想要的类A,您可以做一些事情。如果主要用例是遍历每个结果,那么最好是预加载所有实体对象并丢弃结果集。

    但是,如果不想这样做,可以使用resultset的next()和previous()方法

    public boolean hasNext(){
           boolean next = entities.next();
    
           if(next) {
    
               //reset the cursor back to its previous position
               entities.previous();
           }
    }
    

    您必须小心确保当前没有从resultset中读取数据,但是,如果您的实体类是一个正确的pojo(或者至少与resultset正确断开连接),那么这应该是一种很好的方法。

        9
  •  2
  •   taftster    12 年前

    我同意俾路支。允许迭代器从DAO方法中逸出会使关闭任何连接资源变得困难。您将被迫了解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中,这是正确的。但是,如果考虑到内存问题,则不一定需要填充集合。

    希望这有帮助。

        10
  •  1
  •   Justin Ethier    15 年前

    entities.next 如果没有更多的行,则返回false,因此您只需获取该返回值并设置一个成员变量来跟踪hasNext()的状态。

    但要实现这一点,您还必须使用某种in it方法来读取第一个实体并将其缓存到类中。然后在调用next时,需要返回以前缓存的值并缓存下一个值等…

        11
  •  1
  •   B. Broto    13 年前

    你可以使用 ResultSetIterator ,只需将结果集放入构造函数。

    ResultSet rs = ...    
    ResultSetIterator = new ResultSetIterator(rs); 
    
        12
  •  1
  •   Dave Moten    11 年前

    由于上述原因,迭代器对于遍历结果集是有问题的,但是具有处理错误和关闭资源所需的所有语义的类似迭代器的行为在 RxJava .可观测数据类似于迭代器,但包括订阅的概念、订阅的取消和错误处理。

    项目 rxjava-jdbc 具有JDBC操作的可观察性实现,包括适当关闭资源的结果集遍历、错误处理和根据需要取消遍历的能力(取消订阅)。

        13
  •  1
  •   Giannis    8 年前

    这是包装结果集的迭代器。以映射的形式返回行。我希望你会发现它有帮助。策略是我总是提前带一个元素。

    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);
            }
        }
    }
    
        14
  •  0
  •   Dan    15 年前

    您是否希望实际使用结果集中的大部分数据?如果是,请预先缓存它。用弹簧很简单

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

    适应你的口味。

        15
  •  0
  •   ADTC Adam Rosenfield    12 年前

    我认为有足够的理由谴责为什么在迭代器中使用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());
    }
    
        16
  •  0
  •   Art GlenPeterson    10 年前

    可以这样做:

    public boolean hasNext() {
        ...
        return !entities.isLast();
        ...
    }
    
        17
  •  -1
  •   Andrew Hare    15 年前

    听起来您陷入了提供效率低下的 hasNext 或者抛出一个声明您不支持该操作的异常。

    不幸的是,有时实现接口时并不需要所有成员。在这种情况下,我建议您在该成员中抛出一个您将不支持或无法支持的异常,并将您类型上的该成员记录为不支持的操作。