Подтвердить что ты не робот

Java 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..., чтобы получить счетчик и управлять этим числом, чтобы увидеть, есть ли еще одна строка, но я бы хотел этого избежать.

4b9b3361

Ответ 1

Это плохая идея. Такой подход требует, чтобы соединение было открыто все время до тех пор, пока последняя строка не будет прочитана, а за пределами уровня DAO вы никогда не узнаете, когда это произойдет, и вы также, похоже, оставите открытый набор результатов и рискуете утечками ресурсов и сбоями приложений в случае время соединения. Вы не хотите этого.

Обычная практика JDBC заключается в том, что вы получаете Connection, Statement и ResultSet в кратчайшей возможной области. Обычная практика также заключается в том, что вы сопоставляете несколько строк с List или, возможно, с Map и угадываете, что у них do есть 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 вы можете найти эту статью.

Ответ 2

Вы можете выйти из этого рассола, выполнив поиск в 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

ResultSet имеет метод isLast(), который может удовлетворить ваши потребности. JavaDoc говорит, что он довольно дорог, хотя он должен читать дальше. Есть хороший шанс, что он кэширует значение внешнего вида, как другие предлагают попробовать.

Ответ 4

Это не очень плохая идея в тех случаях, когда она вам нужна, просто вам это не нужно.

Если вам нужно сделать что-то вроде, скажем, потоковой передачи всей вашей базы данных... вы можете предварительно выбрать следующую строку - если из-за неудачной выборки ваш hasNext является ложным.

Вот что я использовал:

/**
 * @author Ian Pojman <[email protected]>
 */
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

Вы можете попробовать следующее:

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.

Ответ 6

Есть несколько вещей, которые вы могли бы сделать в зависимости от того, что вы хотите для своего класса A. Если основной вариант использования - пройти через каждый результат, то, возможно, лучше всего предварительно загрузить все объекты Entity и выбросить ResultSet.

Если вы не хотите этого делать, вы можете использовать следующий() и предыдущий() метод ResultSet

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

       if(next) {

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

Вам нужно быть осторожным, чтобы убедиться, что вы сейчас читаете в ResultSet, но если ваш класс Entity является правильным POJO (или, по крайней мере, правильно отключен от ResultSet, тогда это должен быть прекрасный подход.

Ответ 7

Один из вариантов - ResultSetIterator из проекта Apache DBUtils.

BalusC справедливо указывает на различные проблемы при этом. Вы должны быть очень осторожны, чтобы правильно обрабатывать жизненный цикл соединения/результатов. К счастью, проект DBUtils также имеет решения для безопасной работы с наборами результатов.

Если решение BalusC нецелесообразно для вас (например, вы обрабатываете большие массивы данных, которые не могут вместить в память), вы можете сделать снимок.

Ответ 8

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

Ответ 9

entity.next возвращает false, если больше нет строк, поэтому вы можете просто получить это возвращаемое значение и установить переменную-член для сохранения отслеживать статус hasNext().

Но для выполнения этой работы вам также понадобится какой-то метод init, который читает первый объект и кэширует его в классе. Затем при вызове вам нужно вернуть ранее кэшированное значение и кешировать следующее значение и т.д.

Ответ 10

Вы можете использовать ResultSetIterator, просто поместите свой ResultSet в конструктор.

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

Ответ 11

Я согласен с 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, что является правильным. Но вам не обязательно заполнять коллекцию, если память беспокоит.

Надеюсь, что это поможет.

Ответ 12

Итераторы проблематичны для прохождения ResultSets по указанным выше причинам, но поведение, подобное Итератору, со всей необходимой семантикой для обработки ошибок и закрытия ресурсов доступно с реактивными последовательностями (Observables) в RxJava. Обсерватории похожи на итераторы, но включают понятия подписки и их отмены и обработки ошибок.

В проекте rxjava-jdbc реализованы реализации Observables для операций jdbc, включая обходы ResultSets с правильным закрытием ресурсов, обработкой ошибок и возможностью отмените обход по необходимости (отмените подписку).

Ответ 13

Здесь мой итератор, который обертывает ResultSet. Строки возвращаются в виде карты. Надеюсь, вы найдете это полезным. Стратегия заключается в том, что я всегда выдвигаю один элемент заранее.

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

Похоже, вы застряли между тем, чтобы обеспечить неэффективную реализацию hasNext или выбросить исключение, заявив, что вы не поддерживаете операцию.

К сожалению, есть моменты, когда вы реализуете интерфейс, и вам не нужны все члены. В этом случае я предлагаю вам исключить исключение из этого члена, которого вы не сможете или не можете поддерживать и документировать этот член в своем типе в качестве неподдерживаемой операции.

Ответ 15

Ожидаете ли вы, что большинство данных в вашем наборе результатов действительно будут использоваться? Если это так, предварительно кэшируйте его. Это довольно тривиально, например, Spring

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

Настройтесь на свой вкус.

Ответ 16

Я думаю, что там достаточно осуждать, почему это действительно плохая идея использовать ResultSet в Iterator (короче говоря, ResultSet поддерживает активное соединение с БД и не закрывает его. ASAP может привести к проблемам).

Но в другой ситуации, если вы получаете 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());
}

Ответ 17

Это можно сделать следующим образом:

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