Implementing a Spliterator for a JDBC ResultSet

Java 1.8 comes with the Streams API, which is a great feature for writing readable code. I recently needed to stream over a JDBC ResultSet, which requires a custom Spliterator.

According to the Java documentation a Spliterator is:

An object for traversing and partitioning elements of a source. The source of elements covered by a Spliterator could be, for example, an array, a Collection, an IO channel, or a generator function.

ResultSetSpliterator

The easiest way of implementing a custom Spliterator is to derive from an AbstractSpliterator, which already implements most of the Spliterator interface. The intent of the ResultSetSpliterator<TEntity> iterator is to iterate over a given ResultSet and map the current result to an Entity with a SqlMapper.

// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package de.bytefish.sqlmapper.iterator;

import de.bytefish.sqlmapper.SqlMapper;
import de.bytefish.sqlmapper.result.SqlMappingResult;

import java.sql.ResultSet;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;

public class ResultSetSpliterator<TEntity> extends Spliterators.AbstractSpliterator<SqlMappingResult<TEntity>> {

    private final ResultSet resultSet;
    private final SqlMapper<TEntity> sqlMapper;

    public ResultSetSpliterator(final SqlMapper<TEntity> sqlMapper, final ResultSet resultSet) {
        super(Long.MAX_VALUE,Spliterator.ORDERED);

        this.sqlMapper = sqlMapper;
        this.resultSet = resultSet;
    }

    @Override
    public boolean tryAdvance(Consumer<? super SqlMappingResult<TEntity>> action) {
        if (next()) {
            action.accept(sqlMapper.toEntity(resultSet));
            return true;
        } else {
            return false;
        }
    }

    private boolean next() {
        try {
            return resultSet.next();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Using the ResultSetSpliterator

The StreamSupport class provides low-level utility methods for creating and manipulating streams. The StreamSupport.stream method makes it possible to create a new sequential or parallel Stream from a Spliterator.

In the example I am also creating the SqlMapper, which is not in the scope of this article, but it gives you a hint how to create the stream.

@Test
public void testToEntityStream() throws Exception {
    // Number of persons to insert:
    int numPersons = 10000;
    // Insert the given number of persons:
    insert(numPersons);
    // Get all row of the Table:
    ResultSet resultSet = selectAll();
    // Create a SqlMapper, which maps between a ResultSet row and a Person entity:
    SqlMapper<Person> sqlMapper = new SqlMapper<>(() -> new Person(), new PersonMap());
    // Create the Stream using the StreamSupport class:
    Stream<SqlMappingResult<Person>> stream = StreamSupport.stream(new ResultSetSpliterator<>(sqlMapper, resultSet), false);
    // Collect the Results as a List:
    List<SqlMappingResult<Person>> result = stream.collect(Collectors.toList());
    // Assert the results:
    Assert.assertEquals(numPersons, result.size());
}

How to contribute

One of the easiest ways to contribute is to participate in discussions. You can also contribute by submitting pull requests.

General feedback and discussions?

Do you have questions or feedback on this article? Please create an issue on the GitHub issue tracker.

Something is wrong or missing?

There may be something wrong or missing in this article. If you want to help fixing it, then please make a Pull Request to this file on GitHub.