Stream closeable resource with Spring MVC

前端 未结 1 649
半阙折子戏
半阙折子戏 2021-02-09 15:37

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

相关标签:
1条回答
  • 2021-02-09 16:21

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

    0 讨论(0)
提交回复
热议问题