Lưu vào cơ sở dữ liệu trong đường ống truyền phát


8

Theo tài liệu trên trang web của Oracle :

Nói chung, các tác dụng phụ trong các tham số hành vi đối với các hoạt động truyền phát, nói chung, không được khuyến khích, vì chúng thường có thể dẫn đến việc vô tình vi phạm yêu cầu không trạng thái, cũng như các nguy cơ an toàn luồng khác.

Điều này có bao gồm lưu các yếu tố của luồng vào cơ sở dữ liệu không?

Hãy tưởng tượng mã (giả) sau đây:

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

Các tác dụng không mong muốn trái ngược với thực hiện này là gì:

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

1
vâng , điều này là xấu và trong những điều kiện nhất định, bạn sẽ bị đau.
Eugene

Làm sao vậy Sự khác biệt với việc viết này như là một for-loop thông thường là gì?
Tiêu đề

Điều này là hiển nhiên, nếu bạn sử dụng, parallelStreambạn chắc chắn sẽ mất bối cảnh giao dịch.
Glains

Một nghi ngờ xung quanh việc thiết kế mã này - Tại sao một phương thức ghi vào cơ sở dữ liệu của bạn trả về và mô hình cập nhật? Điều đó có thể không được tách ra? Tôi có nghĩa là ánh xạ các đối tượng cơ sở dữ liệu đến một số đối tượng khác trong một giai đoạn và viết nó vào cơ sở dữ liệu trong một giai đoạn khác.
Naman

4
Các tài liệu nói rằng tác dụng phụ là nói chung, không khuyến khích . Sau đó, bạn đang hỏi về những gì về ví dụ cụ thể này, nhưng khi bạn nhận được phản hồi chú ý đến các vấn đề của ví dụ cụ thể, bạn nói rằng nhưng đây chỉ là một ví dụ. Vì vậy, nếu câu hỏi của bạn không phải là về ví dụ cụ thể này, câu hỏi thực sự của bạn là gì? Bạn có thực sự mong đợi tài liệu chính thức sẽ đưa ra tuyên bố cho từng trường hợp sử dụng giả định, khi nó đã đưa ra tuyên bố chung?
Holger

Câu trả lời:


4

Theo tài liệu trên trang web của Oracle [...]

Liên kết đó dành cho Java 8. Bạn có thể muốn đọc tài liệu cho Java 9 (xuất hiện vào năm 2017) và các phiên bản mới hơn, vì chúng rõ ràng hơn về vấn đề này. Đặc biệt:

Việc triển khai luồng được cho phép có vĩ độ đáng kể trong việc tối ưu hóa tính toán của kết quả. Ví dụ, việc triển khai luồng là miễn phí đối với các hoạt động (hoặc toàn bộ các giai đoạn) từ một đường truyền luồng - và do đó, yêu cầu gọi các tham số hành vi - nếu nó có thể chứng minh rằng nó sẽ không ảnh hưởng đến kết quả tính toán. Điều này có nghĩa là các tác dụng phụ của các tham số hành vi có thể không phải luôn luôn được thực thi và không nên dựa vào, trừ khi có quy định khác (chẳng hạn như bởi các hoạt động của thiết bị đầu cuối forEachforEachOrdered). (Để biết ví dụ cụ thể về tối ưu hóa như vậy, hãy xem ghi chú API được ghi lại trong count()hoạt động. Để biết thêm chi tiết, hãy xem phần tác dụng phụ của tài liệu gói luồng.)

Nguồn: Javadoc của Java 9 cho Streamgiao diện .

Và cũng là phiên bản cập nhật của tài liệu bạn trích dẫn:

Phản ứng phụ

Nhìn chung, các tác dụng phụ trong các tham số hành vi đối với các hoạt động truyền phát, không được khuyến khích, vì chúng thường có thể dẫn đến việc vô tình vi phạm yêu cầu không trạng thái, cũng như các nguy cơ về an toàn luồng khác.
Nếu các tham số hành vi có tác dụng phụ, trừ khi được nêu rõ ràng, không có đảm bảo nào về :

  • khả năng hiển thị của các tác dụng phụ cho các chủ đề khác;
  • rằng các hoạt động khác nhau trên phần tử "giống nhau" trong cùng một đường truyền luồng được thực thi trong cùng một luồng; và
  • rằng các tham số hành vi luôn luôn được gọi, vì việc triển khai luồng là miễn phí để tách các hoạt động (hoặc toàn bộ các giai đoạn) khỏi đường ống dẫn nếu nó có thể chứng minh rằng nó sẽ không ảnh hưởng đến kết quả tính toán.

Thứ tự của các tác dụng phụ có thể gây ngạc nhiên. Ngay cả khi một đường ống bị hạn chế tạo ra kết quả phù hợp với thứ tự bắt gặp của nguồn phát (ví dụ: IntStream.range(0,5).parallel().map(x -> x*2).toArray()phải tạo ra [0, 2, 4, 6, 8]), không có đảm bảo nào được thực hiện theo thứ tự mà hàm ánh xạ được áp dụng cho các phần tử riêng lẻ hoặc trong luồng nào bất kỳ tham số hành vi nào được thực thi cho một phần tử đã cho.

Các tác dụng phụ của tác dụng phụ cũng có thể gây ngạc nhiên. Ngoại trừ các hoạt động đầu cuối forEachforEachOrdered , tác dụng phụ của các tham số hành vi có thể không luôn luôn được thực thi khi thực hiện luồng có thể tối ưu hóa việc thực hiện các tham số hành vi mà không ảnh hưởng đến kết quả tính toán. (Để biết ví dụ cụ thể, hãy xem ghi chú API được ghi lại trong counthoạt động.)

Nguồn: Javadoc của Java 9 cho java.util.streamgói .

Tất cả nhấn mạnh của tôi.

Như bạn có thể thấy, tài liệu chính thức hiện tại sẽ đi sâu vào chi tiết hơn về các vấn đề mà bạn có thể gặp phải nếu bạn quyết định sử dụng các tác dụng phụ trong hoạt động truyền phát của mình. Nó cũng rất rõ ràng forEachforEachOrderedlà hoạt động đầu cuối duy nhất mà việc thực hiện các tác dụng phụ được đảm bảo (lưu ý bạn, vấn đề an toàn luồng vẫn được áp dụng, như các ví dụ chính thức cho thấy).


Điều đó đang được nói, và liên quan đến mã cụ thể của bạn và chỉ nói mã:

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

Tôi thấy không có vấn đề nào liên quan đến Streams với mã đã nói.

  • Các .map()bước sẽ được thực hiện bởi vì .collect()(một giảm có thể thay đổi hoạt động, đó là những gì các doc chính thức đề nghị thay vì những thứ như .forEach(list::add)) dựa trên .map()'đầu ra s, và vì điều này (ví dụ saveCar()' s) đầu ra là khác nhau hơn so với đầu vào của nó, con suối không thể "chứng minh rằng [eliding] nó sẽ không ảnh hưởng đến kết quả tính toán " .
  • Nó không phải là parallelStream()vì vậy nó không nên đưa ra bất kỳ vấn đề tương tranh nào không tồn tại trước đó (tất nhiên, nếu ai đó thêm vào .parallel()sau đó thì các vấn đề có thể phát sinh giống như nếu ai đó quyết định song song hóa một forvòng lặp bằng cách kích hoạt các luồng mới cho các tính toán bên trong ).

Điều đó không có nghĩa là mã trong ví dụ đó là Good Code ™. Trình tự .stream.map(::someSideEffect()).collect()như một cách thực hiện các hoạt động hiệu ứng phụ cho mọi mục trong bộ sưu tập có thể trông đơn giản / ngắn / thanh lịch hơn? hơn forđối tác của nó , và đôi khi nó có thể được. Tuy nhiên, như Eugene, Holger và một số người khác nói với bạn, có nhiều cách tốt hơn để tiếp cận điều này.
Như một suy nghĩ nhanh: chi phí để khởi động Streamso với lặp lại đơn giản forlà không đáng kể trừ khi bạn có nhiều vật phẩm và nếu bạn có nhiều vật phẩm thì bạn: a) có thể không muốn truy cập DB mới đối với mỗi người, do đó, một saveAll(List items)API sẽ tốt hơn; và b) có lẽ không muốn đạt hiệu suất xử lý nhiều các mục theo tuần tự, vì vậy bạn sẽ kết thúc bằng cách sử dụng song song và sau đó một loạt các vấn đề mới phát sinh.


1
Hãy xem, đây là câu trả lời tôi đã tìm kiếm. Một lời giải thích tốt đẹp với các liên kết đến tài liệu xác nhận hành vi.
Tiêu đề

7

Ví dụ dễ nhất tuyệt đối là:

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

Trong trường hợp này, từ java-9 trở lên, mapsẽ không được thực thi; vì bạn không cần nó để biết counttất cả.

Có nhiều trường hợp khác mà tác dụng phụ sẽ khiến bạn đau đớn; dưới một số điều kiện nhất định.


1
Tôi nghĩ count()vẫn sẽ được thực thi, nhưng việc triển khai có thể bỏ qua các bước trung gian nếu nó có thể tạo ra kết quả từ nguồn (nhưng có rất nhiều ifs cấp thực hiện )
ernest_k

2
@Titulum đó là một câu hỏi hoàn toàn khác nhau và phụ thuộc vào việc thực hiện; nhưng có, những điều như vậy nên được thực hiện trong hoạt động của thiết bị đầu cuối, giống như một tùy chỉnh Collector.
Eugene

2
@Titulum điều này sẽ không được ghi lại, bất cứ nơi nào. Đây là các chi tiết thực hiện; nhưng nếu bạn làm theo tài liệu (như phần tác dụng phụ của bạn) - bạn sẽ không quan tâm đến chúng phải không?
Eugene

2
Các tính năng của @Titulum Java 8 không biến Java thành ngôn ngữ chức năng và Luồng, v.v. không phải là một công cụ thay thế , chúng là một công cụ bổ sung. Lưu mọi thứ vào cơ sở dữ liệu là một tác dụng phụ rất lớn , vì vậy bạn đang cố gắng đánh bóng thứ gì đó không phù hợp chỉ vì bạn thích ý tưởng lập trình chức năng. Bạn có thể muốn xem Clojure nếu bạn muốn đi tất cả các chức năng.
Kayaman

1
FWIW, điều có thể làm cho hiệu ứng phụ này trở nên tồi tệ hơn là nó thực sự sẽ có worktrên các phiên bản Java 8 cũ nhưng không phải trên Java 9 trở lên. Tối ưu hóa cụ thể đó cho các luồng có kích thước đã được giới thiệu trong JDK-8067969
Stefan Zobel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.