Lockable Resource Stream Using Spring MVC

After reading this article , I want to use Spring to stream the database query results directly to the JSON response to provide persistent memory (without eagerly loading the List in memory).

As in the article with Hibernate, I put together a greetingRepository object that returns a stream of database content based on JdbcTemplate . In this implementation, I create an iterator based on the requested ResultSet , and I return the stream as follows:

 return StreamSupport.stream(spliterator(), false).onClose(() -> { log.info("Closing ResultSetIterator stream"); JdbcUtils.closeResultSet(resultSet); }); 

i.e. with the onClose() method, guaranteeing that the underlying ResultSet will be closed if the stream is declared in the try-with-resources construct:

 try(Stream<Greeting> stream = greetingRepository.stream()) { // operate on the stream } // ResultSet underlying the stream will be guaranteed to be closed 

But, as in the article, I want this stream to be consumed by a custom object adapter (extended MappingJackson2HttpMessageConverter defined in the article). If we take try-with-resources aside, it can be done as follows:

 @RequestMapping(method = GET) Stream<GreetingResource> stream() { return greetingRepository.stream().map(GreetingResource::new); } 

However, as comments at the bottom of this article, it does not care about closing down core resources.

In the context of Spring MVC, how can I completely pass the JSON response from the database and still guarantee the closure of the ResultSet ? Could you give a specific solution?

+5
source share
1 answer

You can create a construct to defer query execution during serialization. This construct will start and complete the transaction programmatically.

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

What can be used as follows:

 @RequestMapping(method = GET) TransactionalStreamable<GreetingResource> stream() { return new TransactionalStreamable(platformTransactionManager , () -> greetingRepository.stream().map(GreetingResource::new)); } 

All work will be done when Jackson serializes the object. This may or may not have problems with error handling (for example, using controller recommendations).

0
source

Source: https://habr.com/ru/post/1214555/


All Articles