Jedną opcją byłoby użycie alternatywnych klientów SQL, którzy nie są całkowicie blokowani. Niektóre przykłady obejmują: https://github.com/mauricio/postgresql-async lub https://github.com/finagle/roc. Oczywiście żaden z tych sterowników nie jest oficjalnie wspierany przez dostawców baz danych. Ponadto funkcjonalność jest znacznie mniej atrakcyjna w porównaniu do dojrzałych abstrakcji opartych na JDBC, takich jak Hibernate lub jOOQ.
Alternatywny pomysł przyszedł mi ze świata Scala. Chodzi o to, aby wywoływanie blokujących wywołań w izolowanym wątku ThreadPool nie mieszać blokowania i nie blokowania połączeń. Pozwoli nam to kontrolować ogólną liczbę wątków i pozwoli CPU obsługiwać zadania nieblokujące w kontekście wykonania głównego z pewnymi potencjalnymi optymalizacjami. Zakładając, że mamy implementację opartą na JDBC, taką jak Spring Data JPA, która faktycznie blokuje, możemy sprawić, aby jej wykonanie było asynchroniczne i wysyłane do dedykowanej puli wątków.
@RestController
public class HomeController {
private final MeasurementRepository repository;
private final Scheduler scheduler;
public HomeController(MeasurementRepository repository, @Qualifier("jdbcScheduler") Scheduler scheduler) {
this.repository = repository;
this.scheduler = scheduler;
}
@GetMapping(value = "/v1/measurements")
public Flux<Measurement> getMeasurements() {
return Mono.fromCallable(() -> repository.findByFromDateGreaterThanEqual(new Date(1486980000L))).publishOn(scheduler);
}
}
Nasz harmonogram dla JDBC powinien zostać skonfigurowany przy użyciu dedykowanej puli wątków z liczbą elementów równą liczbie połączeń.
@Configuration
public class SchedulerConfiguration {
private final Integer connectionPoolSize;
public SchedulerConfiguration(@Value("${spring.datasource.maximum-pool-size}") Integer connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
@Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(Executors.newFixedThreadPool(connectionPoolSize));
}
}
Istnieją jednak trudności z tym podejściem. Głównym z nich jest zarządzanie transakcjami. W JDBC transakcje są możliwe tylko w ramach pojedynczego java.sql.Connection. Aby wykonać kilka operacji w jednej transakcji, muszą one udostępnić połączenie. Jeśli chcemy dokonać pewnych obliczeń między nimi, musimy zachować połączenie. Nie jest to zbyt skuteczne, ponieważ ograniczamy liczbę połączeń bezczynności podczas wykonywania obliczeń pośrednich.
Ta koncepcja asynchronicznego opakowania JDBC nie jest nowa i jest już zaimplementowana w bibliotece Scala Slick 3. Wreszcie, nieblokująca JDBC może pojawić się na mapie drogowej Java. Jak ogłoszono na JavaOne we wrześniu 2016 r., Możliwe, że zobaczymy to w Javie 10.
kod JDBC natury synchronicznym nie ma żadnych reaktywnych JDBC kierowcy tam (i wątpliwości nie zawsze będzie). Tak więc dostęp do takiej bazy danych nie ma większego sensu. –
Nie jestem zaznajomiony z flux, ale wiem, że możesz użyć Java 8 Stream jako typ zwracania w Spring Data JPA. Możesz zwrócić 'Stream'. Nie jestem pewien, czy ten komentarz pomaga, czy nie :) –
burcakulug
To dobry początek, ale nie jest tak asynchroniczny, więc wywołujący blokuje się podczas trwania operacji JDBC, która przerywa paraligmat nieblokowania Webflux. – NeilS