2015-03-03 154 views
5

閱讀this article後,我希望使用Spring將數據庫查詢結果直接傳輸到JSON響應,以確保內存使用量恆定(不會在內存中加載List)。使用Spring MVC流可關閉資源

與Hibernate的文章中所做的類似,我組裝了一個greetingRepository對象,它返回基於JdbcTemplate的數據庫內容流。在該實施中,我創建了查詢的ResultSet一個迭代器,我返回流如下:

return StreamSupport.stream(spliterator(), false).onClose(() -> { 
    log.info("Closing ResultSetIterator stream"); 
    JdbcUtils.closeResultSet(resultSet); 
}); 
onClose()方法保證,如果物流在 try-with-resources構建聲明底層 ResultSet將被關閉

即:

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

但正如在文章中,我想此流通過一個自定義對象映射器(本文中所定義的增強的MappingJackson2HttpMessageConverter)被消耗。如果我們把try-with-resources需要一邊,如下這是可行的:

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

但是作爲同事評論在這個文章的底部,這並不需要關閉底層資源的照顧。

在Spring MVC的上下文中,我怎樣才能從數據庫一路流入JSON響應,並仍然保證ResultSet將被關閉?你能提供一個具體的例子解決方案?

+2

我不認爲你的問題是資源泄漏:Spring將肯定會提交事務並釋放連接,並且傳遞性地關閉你的結果集。但我期待着相反的問題:你如何管理Connection存在於視圖層?我依賴於'OpenSessionInViewInterceptor',它是Hibernate特有的。 – 2015-03-03 12:13:41

+0

沒錯,在我的測試場景中,我忘記使用事務了,我無法再使用視圖層(另外我使用MySQL,所以我運氣不佳)。我得出結論認爲,流式傳輸到視圖層是非常誘人的,因爲它可能是高效且優雅的,但在實踐中仍然很難使用。 – 2015-03-04 18:27:13

+0

不幸的是,仍然沒有足夠的支持。這是一個狂野的前沿。不過,我確實希望它能夠迎頭趕上,因爲Java架構迄今在這個部門非常缺乏。 – 2015-03-04 18:32:39

回答

0

您可以創建一個結構,以便在序列化時推遲查詢執行。這個構造將開始並結束交易程序。

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

採用專用的JSON序列:

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

哪位能像這樣被使用:

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

所有的工作都將完成時傑克遜將序列化對象。它可能是或不是關於錯誤處理的問題(例如使用控制器建議)。