Я создаю приложение для обслуживания данных из базы данных PostgreSQL через REST API (с Spring MVC) и PWA (с Vaadin).
В базе данных PostgreSQL хранятся файлы размером до 2 ГБ с использованием Большие объекты (я не контролирую это);драйвер JDBC обеспечивает потоковый доступ к их двоичному содержимому через Blob#getBinaryStream
, поэтому данные не должны считываться целиком в память.
Единственное требование состоит в том, что поток из большого двоичного объекта должениспользовать в той же транзакции, в противном случае драйвер JDBC сгенерирует.
Проблема в том, что даже если я получу поток в методе транзакционного репозитория, Spring MVC и Vaadin StreamResource
будут использовать его вне транзакциипоэтому драйвер JDBC выбрасывает.
Например, если
public interface SomeRepository extends JpaRepository<SomeEntity, Long> {
@Transactional(readOnly = true)
default InputStream getStream() {
return findById(1).getBlob().getBinaryStream();
}
}
, этот метод Spring MVC завершится с ошибкой
@RestController
public class SomeController {
private final SomeRepository repository;
@GetMapping
public ResponseEntity getStream() {
var stream = repository.getStream();
var resource = new InputStreamResource(stream);
return new ResponseEntity(resource, HttpStatus.OK);
}
}
, и то же самое для этого Vaadin StreamResource
public class SomeView extends VerticalLayout {
public SomeView(SomeRepository repository) {
var resource = new StreamResource("x", repository::getStream);
var anchor = new Anchor(resource, "Download");
add(anchor);
}
}
с тем же исключением:
org.postgresql.util.PSQLException: ERROR: invalid large-object descriptor: 0
, что означает, что транзакция уже закрыта при чтении потока.
Я вижу два возможных решения этой проблемы:
- держать транзакцию открытой во время загрузки;
- записывать поток на диск во время транзакции и затем передавать файл с диска во время загрузки.
Решение 1 является антишаблоном и угрозой безопасности: длительность транзакции остается на руках клиента, и медленный читатель или злоумышленник могут заблокировать доступ к данным.
Решение 2 создаетогромная задержка между запросом клиента и ответом сервера, так как поток сначала читается из базы данных и записывается на диск.
Одной из идей может быть начало чтения с диска, пока файл записывается с данными.из базы данных, так что передача начинается немедленно, но длительность транзакции будет отделена от загрузки клиента;но я не знаю, какие побочные эффекты это может иметь.
Как мне достичь цели обслуживания больших объектов PostgreSQL безопасным и производительным способом?