由ResultSet支持的Java Iterator
我有一个实现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...
查询以获取计数并管理该数字以查看是否有另一行但我想避免这种情况。
这是一个坏主意。 这种方法要求连接一直打开直到读取最后一行,并且在DAO层之外你永远不知道它何时会发生,并且你似乎也保持结果集开放并且风险资源泄漏和应用程序崩溃的情况下连接超时。 你不想拥有它。
正常的JDBC实践是您在尽可能短的范围内获取Connection
, Statement
和ResultSet
。 通常的做法是将多个行映射到List
或Map
然后猜测它们有一个Iterator
。
public List list() throws SQLException { List list = new ArrayList(); 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 list = dataDAO.list(); int count = list.size(); // Easy as that. Iterator iterator = list.iterator(); // There is your Iterator.
不要像您最初想要的那样在DAO层之外传递昂贵的数据库资源。 有关常规JDBC实践和DAO模式的更多基本示例,您可能会发现本文很有用。
您可以通过在hasNext()
中执行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; } ... }
ResultSet有一个’isLast()’方法,可能适合您的需要。 JavaDoc表示它非常昂贵,因为它必须提前阅读。 它很有可能像其他人建议的那样缓存前瞻性价值。
在你需要它的情况下它并不是一个非常糟糕的主意,它只是你经常不需要它。
如果你确实需要做一些事情,例如,流式传输你的整个数据库….你可以预取下一行 – 如果提取失败你的hasNext是假的。
这是我用过的:
/** * @author Ian Pojman */ public abstract class LookaheadIterator implements Iterator { /** 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() { @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); } } }; }
一个选项是Apache DBUtils项目中的ResultSetIterator 。
BalusC正确地指出了这样做的各种担忧。 您需要非常小心地正确处理连接/结果集生命周期。 幸运的是,DBUtils项目还提供了安全使用结果集的解决方案 。
如果BalusC的解决方案对您来说不切实际(例如,您正在处理不能完全适合内存的大型数据集),您可能需要尝试一下。
public class A implements Iterator { 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; } }
您可以尝试以下方法:
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。
根据您对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正确断开连接,那么这应该是一个很好的方法。
如果没有更多行, entities.next返回false,因此您可以获取该返回值并设置成员变量以跟踪hasNext()的状态。
但是为了完成这项工作,你还必须有某种init方法来读取第一个实体并将其缓存在类中。 然后在调用next时,您需要返回先前缓存的值并缓存下一个值,等等…
您可以使用ResultSetIterator ,只需将ResultSet放在构造函数中。
ResultSet rs = ... ResultSetIterator = new ResultSetIterator(rs);
我同意BalusC。 允许Iterator从DAO方法中逃脱将使关闭任何Connection资源变得困难。 您将被迫了解DAO之外的连接生命周期,这会导致繁琐的代码和潜在的连接泄漏。
但是,我使用的一个选择是将函数或过程类型传递给DAO方法。 基本上,传递某种回调接口,它将获取结果集中的每一行。
例如,可能是这样的:
public class MyDao { public void iterateResults(Procedure 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 { void execute(T t) throws Exception; } public class ResultSetOutputStreamProcedure implements Procedure { 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中,这是正确的。 但是,如果关注内存,则不一定要求您填写collections集。
希望这可以帮助。
由于上述原因,迭代器因遍历ResultSet而存在问题,但是迭代序列(Observables)在RxJava中可以使用具有处理错误和关闭资源所需的所有语义的迭代器行为。 Observable类似于迭代器,但包括订阅及其取消和error handling的概念。
项目rxjava-jdbc具有用于jdbc操作的Observable实现,包括具有适当资源关闭的ResultSet的遍历,error handling以及根据需要取消遍历的能力(取消订阅)。
这是包装ResultSet的迭代器。 行以Map的forms返回。 我希望你会发现它有用。 策略是我总是提前提出一个要素。
public class ResultSetIterator implements Iterator
您是否期望实际使用结果集中的大多数数据? 如果是这样,请预先缓存它。 使用例如Spring非常简单
List
根据您的口味调整。
我认为有足够的解密为什么在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()); }
它可以这样做:
public boolean hasNext() { ... return !entities.isLast(); ... }
这听起来像是在提供一个低效的hasNext
实现或抛出exception表明你不支持该操作之间。
不幸的是,有时候你实现了一个接口而你不需要所有的成员。 在这种情况下,我建议您在该成员中抛出一个exception,您将不会或不能支持并将该类型的成员记录为不受支持的操作。