使用 Java Streams 和 Spring Boot 的 RESTful Web 服务

Posted

技术标签:

【中文标题】使用 Java Streams 和 Spring Boot 的 RESTful Web 服务【英文标题】:RESTful webservice using Java Streams and Spring Boot 【发布时间】:2020-07-08 15:10:07 【问题描述】:

我尝试实现一个 RESTful WebService,它能够直接从数据库中流式传输数百万条记录。 我正在使用 SpringBoot 2.2.5、Hibernate 5 和 PostgreSQL 11

根据这篇文章: https://www.airpair.com/java/posts/spring-streams-memory-efficiency

需要一步将标志“allowResultAccessAfterCompletion”设置为真。 但是如何在 Spring Boot 中做到这一点?

到目前为止,我的应用程序中没有任何 SessionFactory、EntityManagerFactory、Datasource 等配置。一切都由 SpringBoot 自动配置。

如果我在下面添加建议的配置,应用程序将无法启动,因为缺少 SessionFactory。

@Configuration
@EnableTransactionManagement
public class DataConfig 

  @Autowired @Bean
  public PlatformTransactionManager txManager(SessionFactory sf) 
    HibernateTransactionManager mgr = new HibernateTransactionManager(sf);
    mgr.setAllowResultAccessAfterCompletion(true);
    return mgr;
  
  ... (the rest of your data config, including the LocalSessionFactoryBean) ...

如果我通过从 EntityManagerFactory 解包来提供 SessionFactory bean,我会得到另一个异常:

通过字段 'entityManagerFactory' 表达的不满足的依赖关系;嵌套异常是 org.springframework.beans.factory.BeanCurrentlyInCreationException:创建名为“getSessionFactory”的 bean 时出错:当前正在创建请求的 bean:是否存在无法解析的循环引用?

有人对我的设置有有效的配置吗?

这个标志不能简单地由 application.properties 中的一些配置值设置吗?

谢谢!

【问题讨论】:

你的 pom.xml 中是否添加了 spring-boot-starter-data-jpa maven 依赖项?还在application.properties/application.yml 中添加适当的密钥(驱动程序类名称、用户名、密码、url) 【参考方案1】:

首先,您需要确定您的项目是否需要使用 Hibernate 或 Spring JPA。使用框架而不是反对它。今天大多数人更喜欢使用 jpa 类而不是 hibernate 类。 由于您使用的是 springboot ,因此最好的方法是使用框架并使用 spring-boot-starter-data-jpa ,它将在启动时自动配置所有必要的 bean。在这种情况下,您可以提供自己的自定义 bean 来根据需要覆盖参数。

在您在问题中提供的示例代码中,您直接使用 Hibernate 类,因此您必须手动创建所有必要的 bean,因为除非您禁用自动配置可能会导致您出现循环依赖问题。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.sample.spring.repository")
@PropertySource("classpath:database.properties")

public class DataConfig 

    private final String PROPERTY_DRIVER = "driver";
    private final String PROPERTY_URL = "url";
    private final String PROPERTY_USERNAME = "user";
    private final String PROPERTY_PASSWORD = "password";
    private final String PROPERTY_SHOW_SQL = "hibernate.show_sql";
    private final String PROPERTY_DIALECT = "hibernate.dialect";

    @Autowired
    Environment environment;

    @Bean
    LocalContainerEntityManagerFactoryBean entityManagerFactory() 
        LocalContainerEntityManagerFactoryBean lfb = new LocalContainerEntityManagerFactoryBean();
        lfb.setDataSource(dataSource());
        lfb.setPersistenceProviderClass(HibernatePersistence.class);
        lfb.setPackagesToScan("com.sample.spring");
        lfb.setJpaProperties(hibernateProps());
        return lfb;
    

    @Bean
    DataSource dataSource() 
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setUrl(environment.getProperty(PROPERTY_URL));
        ds.setUsername(environment.getProperty(PROPERTY_USERNAME));
        ds.setPassword(environment.getProperty(PROPERTY_PASSWORD));
        ds.setDriverClassName(environment.getProperty(PROPERTY_DRIVER));
        return ds;
    

    Properties hibernateProps() 
        Properties properties = new Properties();
        properties.setProperty(PROPERTY_DIALECT, environment.getProperty(PROPERTY_DIALECT));
        properties.setProperty(PROPERTY_SHOW_SQL, environment.getProperty(PROPERTY_SHOW_SQL));
        return properties;
    

    @Bean
    JpaTransactionManager transactionManager() 
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    


 

【讨论】:

嗨,我使用的是 spring-boot-starter-data-jpa 并且不直接使用任何 Hibernate 类。使用 Spring Boot 自动配置一切运行良好。我不想禁用它。问题是,我必须将 HibernateTransactionManager 类的属性“allowResultAccessAfterCompletion”设置为 true。【参考方案2】:

您也可以很好地提供自己的 SessionFactory,在这种情况下,spring boot 不会为您创建另一个。以下是Bootstrapping Hibernate 5 with Spring article的摘录,请根据您的需要随意调整

@Configuration
@EnableTransactionManagement
public class HibernateConf 
 
    @Bean
    public LocalSessionFactoryBean sessionFactory() 
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(
          "com.baeldung.hibernate.bootstrap.model" );
        sessionFactory.setHibernateProperties(hibernateProperties());
        return sessionFactory;
    
 
    @Bean
    public DataSource dataSource() 
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
        dataSource.setUsername("sa");
        dataSource.setPassword("sa");
 
        return dataSource;
    
 
    @Bean
    public PlatformTransactionManager hibernateTransactionManager() 
        HibernateTransactionManager transactionManager
          = new HibernateTransactionManager();
        transactionManager.setAllowResultAccessAfterCompletion(true);
        transactionManager.setSessionFactory(sessionFactory().getObject());
        return transactionManager;
    
 
    private final Properties hibernateProperties() 
        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty(
          "hibernate.hbm2ddl.auto", "create-drop");
        hibernateProperties.setProperty(
          "hibernate.dialect", "org.hibernate.dialect.H2Dialect");
 
        return hibernateProperties;
    

如果您知道 Spring Boot 已经在其自己的生命周期中创建了 HibernateTransactionManager,则另一种方法是使用 BeanPostProcessor。以下是这个 BeanPostProcessor 的轮廓

public class HTMPostProcessor implements BeanPostProcessor 
   public Object postProcessBeforeInitialization(Object bean, String beanName) 
      throws BeansException 
      if (bean instanceof HibernateTransactionManager) 
         ((HibernateTransactionManager)bean).setAllowResultAccessAfterCompletion(true);
      
      return bean;  // you can return any other object as well
   
   public Object postProcessAfterInitialization(Object bean, String beanName) 
      throws BeansException 
      return bean;  // you can return any other object as well
   

希望这会有所帮助!

【讨论】:

嗨,尝试了使用 BeanPostProcessor 的方法。但是,如果我使用 JPA,似乎我没有 HibernateTransactionManger。 JpaTransactionManager 已被实例化,但我无法设置所需的标志。

以上是关于使用 Java Streams 和 Spring Boot 的 RESTful Web 服务的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Streams 没有在消息中设置 kafka 键?

Spring Boot Reactive Streams

在Spring 5中调试Reactive Streams

使用 JDK8 和 lambda (java.util.stream.Streams.zip) 压缩流

带有Spring Cloud Stream的Kafka Streams进程中的Serd错误

如何使用 Java Streams API 添加和更新地图条目