Сохранение в базу данных в потоковом конвейере - PullRequest
8 голосов
/ 24 января 2020

Согласно документации на веб-сайте Oracle :

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

Включает ли это сохранение элементов потока в базу данных?

Представьте себе следующий (псевдо) код:

public SavedCar saveCar(Car car) {
  SavedCar savedCar = this.getDb().save(car);
  return savedCar;
}

public List<SavedCars> saveCars(List<Car> cars) {
  return cars.stream()
           .map(this::saveCar)
           .collect(Collectors.toList());
}

Каковы противоположные нежелательные эффекты к этой реализации:

public SavedCar saveCar(Car car) {
  SavedCar savedCar = this.getDb().save(car);
  return savedCar;
}

public List<SavedCars> saveCars(List<Car> cars) {
  List<SavedCars> savedCars = new ArrayList<>();
  for (Cat car : cars) {
    savedCars.add(this.saveCar(car));
  }
  return savedCars.
}

Ответы [ 2 ]

7 голосов
/ 24 января 2020

Абсолютно простой пример:

cars.stream()
    .map(this:saveCar)
    .count()

В этом случае, начиная с java -9 и выше, map не будет выполняться; поскольку вам вообще не нужно знать count.

Есть и другие многочисленные случаи, когда побочные эффекты могут причинить вам много боли; при определенных условиях.

4 голосов
/ 28 января 2020

Согласно документации на веб-сайте Oracle [...]

Эта ссылка для Java 8. Возможно, вы захотите прочитать документацию по Java 9 (вышла в 2017 году) и более поздние версии, так как они более явные в этом отношении. В частности:

Реализация потока допускает значительную свободу действий при оптимизации вычисления результата. Например, потоковая реализация может свободно исключать операции (или целые этапы) из потокового конвейера - и, следовательно, разрешать вызов поведенческих параметров - если она может доказать, что это не повлияет на результат вычисления. Это означает, что побочные эффекты поведенческих параметров не всегда могут выполняться и на них не следует полагаться, если не указано иное (например, в операциях терминала forEach и forEachOrdered). (Для конкретного c примера такой оптимизации см. Примечание API, документированное для операции count(). Подробнее см. В разделе побочные эффекты потока пакет документации.)

Источник: Java 9's Javado c для интерфейса Stream .

А также обновленная версия do c, которую вы цитировали:

Побочные эффекты

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

  • видимость этих побочных эффектов для других потоков;
  • то, что различные операции над "одним и тем же" элементом в одном и том же потоковом конвейере выполняются в одном и том же потоке; и
  • , что поведенческие параметры всегда вызываются, поскольку реализация потока может свободно исключать операции (или целые этапы) из конвейера потока, если она может доказать, что это не повлияет на результат вычисления.

Порядок побочных эффектов может быть удивительным. Даже когда конвейер ограничен для получения результата, который согласуется с порядком встречи с источником потока (например, IntStream.range(0,5).parallel().map(x -> x*2).toArray() должен произвести [0, 2, 4, 6, 8]), никаких гарантий относительно порядка, в котором применяется функция отображения, не дается. для отдельных элементов или в каком потоке любой поведенческий параметр выполняется для данного элемента.

Исключение побочных эффектов также может вызывать удивление. За исключением операций терминала forEach и forEachOrdered, побочные эффекты поведенческих параметров могут не всегда выполняться, когда реализация потока может оптимизировать выполнение поведенческих параметров, не влияя на результат вычисления , (Для конкретного примера c см. Примечание API, документированное для операции count.)

Источник: Java 9 Javado c для java.util.stream package .

Весь акцент мой.

Как видите, в официальной документации current более подробно рассматриваются вопросы, которые вы можете столкнуться, если вы решите использовать побочные эффекты в ваших потоковых операциях. Также очень ясно, что forEach и forEachOrdered являются единственными терминальными операциями, где выполнение побочных эффектов гарантировано (учтите, что проблемы с безопасностью потоков все еще существуют, как показывают официальные примеры).


При этом, учитывая ваш указанный c код и только указанный код:

public List<SavedCars> saveCars(List<Car> cars) {
  return cars.stream()
           .map(this::saveCar)
           .collect(Collectors.toList());
}

Я не вижу проблем, связанных с потоками, с указанным кодом как есть.

  • Шаг .map() будет выполнен, потому что .collect() (операция изменчивое сокращение , которую советник c рекомендует вместо таких вещей, как .forEach(list::add)) полагается на .map() и, поскольку это (то есть Вывод saveCar() s отличается от его ввода, поток не может "доказать, что [eliding] , это не повлияет на результат вычисления" .
  • Это не parallelStream(), поэтому он не должен вызывать проблем параллелизма, которых раньше не было (конечно, если кто-то добавил .parallel() позже, тогда могут возникнуть проблемы - например, если кто-то решит распараллелить for l oop путем запуска новых потоков для внутренних вычислений).

Это не означает, что в этом примере используется код Good Code ™. Последовательность .stream.map(::someSideEffect()).collect() как способ выполнения операций с побочными эффектами для каждого элемента в коллекции может выглядеть более простой / короткой / элегантной? чем его for аналог, и иногда это может быть. Однако, как сказали вам Юджин, Хольгер и некоторые другие, есть более эффективные способы подойти к этому.
В качестве быстрой мысли: стоимость запуска Stream против итерации простого for не будет незначительной, если у вас нет лот элементов, и если у вас есть лот элементов, то вы: a) вероятно, не хотите создавать новый доступ к БД для каждого, поэтому saveAll(List items) API будет лучше; и б), вероятно, не хотят испытывать снижение производительности при последовательной обработке лота элементов, поэтому вам придется использовать распараллеливание, и тогда возникает целый новый набор проблем.

...