Статьи

Пятничные вкусности Java 8: потоки SQL ResultSet

В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему. Мы пару раз писали о приятных вкусностях Java 8 , и теперь мы чувствуем, что пришло время начать новую серию блогов,…

Ява 8 Пятница

Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .

Java 8 Goodie: SQL ResultSet Streams

Да, тема SQL должна быть рассмотрена снова. Даже если на прошлой неделе мы пообещали статью о параллелизме , есть один очень важный аспект лямбда-кода в Java 8 и совместимость с «устаревшими» API, о которых нам нужно поговорить, во-первых.

Проверенные исключения

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

Давайте вспомним, как исключения IOException вызывали проблемы при обходе файловой системы . Если вы не напишите свою собственную утилиту, вам придется прибегнуть к этой красоте:

01
02
03
04
05
06
07
08
09
10
Arrays.stream(dir.listFiles()).forEach(file -> {
    try {
        System.out.println(file.getCanonicalPath());
    }
    catch (IOException e) {
        throw new RuntimeException(e);
    }
 
    // Ouch, my fingers hurt! All this typing!
});

Мы считаем, что можно с уверенностью сказать:

Java 8 и проверенные исключения не совпадают.

Обходной путь должен написать свой собственный CheckedConsumer который оборачивает проверенное исключение. Такой потребитель будет многократно использоваться, но … Вы думали обо всех других FunctionalInterfaces ? В пакете java.util.function их довольно много:

jOOλ — Исправление лямбды в Java 8

jool-логотип-черный При написании этой серии блогов по Java 8 мы постоянно сталкивались с необходимостью заключать проверенные исключения в лямбда-выражения . И что мы, гики, делаем, когда часто сталкиваемся с проблемой? Мы это исправим! И мы создали jOOλ (также jOOL, jOO-Lambda) , лицензированный по ASL 2.0 , в котором мы почти полностью продублировали каждый FunctionalInterface , доступный в JDK для поддержки проверенных исключений. Вот как бы вы использовали jOOλ в приведенном выше примере:

1
2
3
4
5
6
7
Arrays.stream(dir.listFiles()).forEach(
    Unchecked.consumer(file -> {
        // Throw all sorts of checked exceptions
        // here, we don't care...
        System.out.println(file.getCanonicalPath());
    })
);

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

01
02
03
04
05
06
07
08
09
10
11
Arrays.stream(dir.listFiles())
      .forEach(Unchecked.consumer(
 
    file -> {
        System.out.println(file.getCanonicalPath());
    },
    e -> {
        log.info("Log stuff here", e);
        throw new MyRuntimeException(e);
    }
);

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

1
2
3
4
5
6
7
Arrays.stream(dir.listFiles())
      .forEach(Unchecked.consumer(
    file -> {
        System.out.println(file.getCanonicalPath());
    },
    myExceptionHandler
);

jOOλ — Предоставление потоков результатов JDBC ResultSet

К сожалению, большинство усилий в Java 8 Streams API было сделано в области правильной реализации распараллеливаемых потоков. Хотя это очень полезно для тех из нас, кто на самом деле занимается параллельными вычислениями, для большинства других лучшая интеграция с устаревшими API-интерфейсами была бы лучше. Одним из API, который серьезно заслуживает некоторого поднятия, является JDBC, и мы уже писали об этом раньше . С помощью jOOλ теперь вы можете генерировать потоки непосредственно из ResultSets или даже из PreparedStatements . Вот как вы готовитесь:

01
02
03
04
05
06
07
08
09
10
Class.forName("org.h2.Driver");
try (Connection c = getConnection()) {
    String sql = "select schema_name, is_default " +
                 "from information_schema.schemata " +
                 "order by schema_name";
 
    try (PreparedStatement stmt = c.prepareStatement(sql)) {
        // code here
    }
}

Теперь все, что вам нужно сделать при использовании jOOλ, — это поток ваших PreparedStatements как таковой:

1
2
3
4
5
6
7
SQL.stream(stmt, Unchecked.function(rs ->
    new SQLGoodies.Schema(
        rs.getString("SCHEMA_NAME"),
        rs.getBoolean("IS_DEFAULT")
    )
))
.forEach(System.out::println);

Где SQLGoodies.Schema это просто обычный POJO. Вот некоторые из сигнатур метода stream() :

01
02
03
04
05
06
07
08
09
10
public static <T> Stream<T> stream(
    PreparedStatement stmt,
    Function<ResultSet, T> rowFunction
);
 
public static <T> Stream<T> stream(
    PreparedStatement stmt,
    Function<ResultSet, T> rowFunction,
    Consumer<? super SQLException> exceptionHandler
);

Другие также доступны.

Это здорово, не правда ли?

JDBC ResultSets должен быть Java 8 Streams.

Жаль, что приведенный выше код не попал в JDK 8, поскольку это было бы шансом наконец-то значительно улучшить API JDBC. Другая, похожая попытка улучшить ситуацию была предпринята Джулианом Эксенбергером.

Java 8 альтернативы написания SQL

Мы также опубликовали несколько альтернатив jOOλ, используя Java 8 с SQL здесь: http://www.jooq.org/java-8-and-sql

Вывод

В то время как лямбда-выражения в Java 8 потрясающие, новый API Streams довольно неполон. При реализации вышесказанного нам пришлось реализовать наш собственный ResultSetIterator и написать весь этот беспорядок, чтобы обернуть итератор в Stream :

1
2
3
4
5
6
7
8
9
StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(
        new ResultSetIterator<>(
            supplier,
            rowFunction,
            exceptionTranslator
        ), 0
    ), false
);

И вообще не нужно писать Iterator , если бы мы только могли генерировать конечные потоки :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// Unfortunately, this method doesn't exist
Stream.generate(
    // Supplier, generating new POJOs
    () -> {
        rs.next();
        return new SQLGoodies.Schema(
            rs.getString("SCHEMA_NAME"),
            rs.getBoolean("IS_DEFAULT")
        );
    },
 
    // Predicate, terminating the Stream
    () -> { !rs.isLast(); }
);

Хотя jOOλ является приемлемым промежуточным решением, и ребята из Guava , вероятно, уже разрабатывают, как исправить свою библиотеку, на самом деле очень плохо, что в Java 8 нет такой функциональности утилиты.

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

Подробнее о Java 8

А пока взгляните на удивительную страницу ресурсов Java 8 от Eugen Paraschiv.

Ссылка: Java 8 Пятница, вкусности: SQL ResultSet Streams от нашего партнера по JCG Лукаса Эдера в блоге JAVA, SQL и AND JOOQ .