After having read this article, I wish to use Spring to stream database query results directly to a JSON response to ensure constant-memory usage (no greedy loading of a L
You could create a construct to defer the query execution at the serialization time. This construct will start and end the transaction programmaticaly.
public class TransactionalStreamable<T> {
private final PlatformTransactionManager platformTransactionManager;
private final Callable<Stream<T>> callable;
public TransactionalStreamable(PlatformTransactionManager platformTransactionManager, Callable<Stream<T>> callable) {
this.platformTransactionManager = platformTransactionManager;
this.callable = callable;
}
public Stream stream() {
TransactionTemplate txTemplate = new TransactionTemplate(platformTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.setReadOnly(true);
TransactionStatus transaction = platformTransactionManager.getTransaction(txTemplate);
try {
return callable.call().onClose(() -> {
platformTransactionManager.commit(transaction);
});
} catch (Exception e) {
platformTransactionManager.rollback(transaction);
throw new RuntimeException(e);
}
}
public void forEach(Consumer<T> c) {
try (Stream<T> s = stream()){
s.forEach(c);
}
}
}
Using a dedicated json serializer:
JsonSerializer<?> transactionalStreamableSer = new StdSerializer<TransactionalStreamable<?>>(TransactionalStreamable.class, true) {
@Override
public void serialize(TransactionalStreamable<?> streamable, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartArray();
streamable.forEach((CheckedConsumer) e -> {
provider.findValueSerializer(e.getClass(), null).serialize(e, jgen, provider);
});
jgen.writeEndArray();
}
};
Which could be used like this:
@RequestMapping(method = GET)
TransactionalStreamable<GreetingResource> stream() {
return new TransactionalStreamable(platformTransactionManager , () -> greetingRepository.stream().map(GreetingResource::new));
}
All the work will be done when jackson will serialize the object. It may be or not an issue regarding the error handling (eg. using controller advice).