配置自定义 HibernateItemWriter 时获取“必须提供 HibernateOperations 或 SessionFactory”

Posted

技术标签:

【中文标题】配置自定义 HibernateItemWriter 时获取“必须提供 HibernateOperations 或 SessionFactory”【英文标题】:Getting "Either HibernateOperations or SessionFactory must be provided" when configuring custom HibernateItemWriter 【发布时间】:2017-09-13 18:22:58 【问题描述】:

所以我正在尝试构建一个使用 Hibernate 写入 mysql 数据库的应用程序,并且有一段时间试图让它工作。简而言之,它没有看到会话,即使我已经配置并自动装配了一个。这是我的代码明智的。

首先是该应用的配置:

@EnableBatchProcessing
@SpringBootApplication(scanBasePackages="com.lcbo")
@EnableIntegration
public class BatchConfig 

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory stepBuilderFactory;

@Bean
public Job processLCBOInventory(@Qualifier("getCurrentLCBODataStep") final Step getCurrentLCBODataStep,
                                @Qualifier("getLCBOStoreDataStep") final Step getLCBOStoreDataStep,
                                final JobExecutionListenerConfig jelcListener
                                ) 
    return jobBuilderFactory
            .get("processLCBOInventory")
            .incrementer(new RunIdIncrementer())
            .start(getCurrentLCBODataStep)
            .next(getLCBOStoreDataStep)
            .listener(jelcListener)
            .build();



@Bean
public Step getLCBOStoreDataStep(final LCBOStoreReader lcboStoreReader,
                                 final LCBOStoreProcessor lcboStoreProcessor,
                                 final LCBOStoreWriter lcboStoreWriter,
                                 final ExecutionContextPromotionListener listener) throws Exception 

    return stepBuilderFactory
            .get("getLCBOStoreDataStep")
            .<LCBOStore, LCBOStore>chunk(inventoryTrackerProperties.getDefaults().getChunkSize())
            .reader(lcboStoreReader.read())
            .processor(lcboStoreProcessor)
            .writer(lcboStoreWriter)
            .listener(listener)
            .build();

那么,作者:

@Component
public class LCBOStoreWriter extends HibernateItemWriter<LCBOStore> 

        @Autowired
private LCBOStoreService lcboStoreService;

@Autowired
private DatabaseConfig dbConfigy;

private static final Logger log = LoggerFactory.getLogger(LCBOStoreWriter.class);

@Override
public void write(List<? extends LCBOStore> lcboStoreItems) 

    for (LCBOStore lcboStoreItem : lcboStoreItems) 
        lcboStoreService.addNewStore(lcboStoreItem);
    


以及 DatabaseConfig 文件内容:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.lcbo.domain")
public class DatabaseConfig 

    @Autowired
    private LCBOInventoryTrackerProperties properties;

    @Bean
    public EntityManagerFactory entityManagerFactory() throws SQLException 

        final LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setJpaDialect(new HibernateJpaDialect());
        factoryBean.setJpaProperties(getHibernateProperties());
        factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        factoryBean.setPackagesToScan("com.lcbo.domain");
        factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        factoryBean.setPersistenceUnitName("persistenceUnit");
        factoryBean.afterPropertiesSet();


        return factoryBean.getObject();
    

    @Bean(destroyMethod = "")
    public DataSource dataSource() throws SQLException 
        BasicDataSource dataSource = new BasicDataSource();

        dataSource.setDriverClassName(properties.getDb().getDriver());
        dataSource.setUrl(properties.getDb().getUrl());
        dataSource.setUsername(properties.getDb().getUsername());
        dataSource.setPassword(properties.getDb().getPassword());

        return dataSource;
    

甚至尝试过 SessionFactory 本身,像这样

public LocalSessionFactoryBean getSessionFactory() throws SQLException 
    LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource(dataSource());
    sessionFactory.setPackagesToScan(new String[]  "com.bytestree.model" );
    sessionFactory.setHibernateProperties(getHibernateProperties());

    return sessionFactory;

但没有骰子。我还尝试将 SessionFactory 实例注入 HibernateItemWriter 附带的 setSessionFactory 中,同样没有骰子。

我知道我有什么问题,问题是它期望 SessionFactory 被放置在哪里以被应用程序重新配置,或者我是否已将这个编写器配置在任何接近正确的地方?

编辑:根据要求,运行此代码时在 intellj 中显示的堆栈跟踪。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'LCBOInventoryWriter' defined in file [C:\workspace\LCBOInventoryTracker\target\classes\com\lcbo\writer\LCBOInventoryWriter.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Either HibernateOperations or SessionFactory must be provided
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866) ~[spring-context-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) ~[spring-context-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) [spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370) [spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) [spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162) [spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151) [spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at com.lcbo.config.LCBOBatchConfig.main(LCBOBatchConfig.java:157) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_102]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_102]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_102]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_102]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) [idea_rt.jar:na]
Caused by: java.lang.IllegalStateException: Either HibernateOperations or SessionFactory must be provided
    at org.springframework.util.Assert.state(Assert.java:392) ~[spring-core-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.batch.item.database.HibernateItemWriter.afterPropertiesSet(HibernateItemWriter.java:93) ~[spring-batch-infrastructure-3.0.7.RELEASE.jar:3.0.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    ... 20 common frames omitted

【问题讨论】:

作者的SessionFactory设置在哪里? 你能显示完整的堆栈跟踪/错误吗?你如何设置春季批次?我注意到在您的LCBOStoreWriter.write() 中,您将 Session 转换为 SessionFactory。那是行不通的!尝试使用 SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class); 设置 HibernateItemWriter 的 SessionFactory HibernateItemWriter 需要在afterPropertiesSet 方法中检查的明确连接的sessionFactory。另外恕我直言,您的应用程序存在缺陷,因为您不需要SessionFactoryEntityMangerFactory。你为什么还要使用HibernateItemWriter?您应该改用JpaItemWriter,因为您不需要现在的破解/解决方法。 @MichaelMinella 我在编写器中使用 this.setSessionFactory(databaseConfig.getSessionFactory()) 设置 SessionFactory 的地方无效。 只需使用一个普通的JpaItemWriter 你不需要扩展它,这是你的全部问题,你的扩展正在破坏作者的正常工作。 【参考方案1】:

由于 HibernateItemWriter 是第三方依赖项,我认为将 LCBOStoreWriter 设置为@Bean 而不是@Component 可能会更好,并在那里设置sessionFactory

从 LCBOStoreWriter 中删除 @Component 并将以下内容添加到 BatchConfig

@Bean
public LCBOStoreWriter lCBOStoreWriter() 
    LCBOStoreWriter wr = new LCBOStoreWriter();
    wr.setSessionFactory(sessionFactory())
    return wr;

如果没有自动装配,您可能需要设置 lcboStoreService 和 dbConfigy。或者,您可以通过其他方式注入它们,我不记得所有方式...

@Bean
public SessionFactory sessionFactory() throws SQLException 
    LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource(dataSource());
    sessionFactory.setPackagesToScan(new String[]  "com.bytestree.model" );
    sessionFactory.setHibernateProperties(getHibernateProperties());
    return sessionFactory.getObject();

【讨论】:

所以自动装配 dbConfig,修改 sessionFactory 以适应您的示例,修改 Step 配置并将 lCBOStoreWriter 添加到 Batch Config,但遗憾的是仍然是完全相同的错误。但是,尝试了以下方法:wr.setSessionFactory(dbConfig.entityManagerFactory().unwrap(‌​SessionFactory.class‌​));并将错误替换为“包含在没有活动事务的情况下无效”错误 你能调试 lCBOStoreWriter() 和 getLCBOStoreDataStep() 并验证 sessionfactory 设置为 not 吗?实际上,我同意@m-deinum。为什么要扩展 HibernateItemWriter?您可能应该将其设置为带有会话工厂的 bean。处理器应该简单地返回休眠实体,编写器应该自动编写它们,而不需要服务。 您看过本指南吗? spring.io/guides/gs/batch-processing 这是一个很好的说明点,您可以将其扩展为与 hibernate 一起使用。在这里,您将使用 application.properties 设置数据源,而不是在 config 中设置它。 Spring Boot 将为您提供数据源。然后我认为你可以自动装配 sessionfactory / entityManager 告诉它是怎么回事:) 我找到了各种各样的解决方案。我已经在下面发布了我的答案。【参考方案2】:

因此,从我收到的反馈来看,正如我所担心的那样,我创建了一个 frakenstein 解决方案,该解决方案是从多个在线来源拼凑而成的。在大量的帮助和反复试验的帮助下,我通过以下方法解决了这个问题。

首先,这是作者现在的样子。

@Component
public class LCBOStoreWriter implements ItemWriter<LCBOStore> 

    @Autowired
    private DatabaseConfig dbConfig;

    @Override
    public void write(List<? extends LCBOStore> items) throws Exception 

        JpaItemWriter wr = new JpaItemWriter();

        wr.setEntityManagerFactory(dbConfig.entityManagerFactory());
        wr.write(items);

    

编辑:我扩展编写器主要是因为将功能分离到自己的类中。我发现这些东西会随着项目的进行而扩展,所以在开始时将它们分开。另外我喜欢它的外观。话虽如此,这不是必需的,就像@M. Deinum 指出可以使事情变得比需要的更复杂。除非您知道您需要使用自定义编写器扩展功能,或者有像我这样的愚蠢的东西,您发现分离看起来更好的代码明智,请使用 @qtips 创建的内容,然后在您的 Step 配置中为您的编写器添加该方法。

然后是 LCBOStore,它是 java 对象和 Hibernate/JPA 注释的组合。

@Entity
@Table(name = "store")
public class LCBOStore 

    @Id
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    @Column(name = "addressLineOne", length = 255)
    private String addressLineOne;

    @Column(name = "addressLineTwo", length = 255)
    private String addressLineTwo;

    @Column(name = "city", length = 255)
    private String city;

    @Column(name = "postalCode", length = 10)
    private String postalCode;

    @Column(name = "latitude", length = 255)
    private String latitude;

    @Column(name = "longitude", length = 255)
    private String longitude;

    @Column(name = "updatedAt", length = 255)
    private String updatedAt; //Convert to Date

//getters and setters


自动装配的 entityManagerFactory 类:

@Bean
public EntityManagerFactory entityManagerFactory() throws SQLException 

    final LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
    factoryBean.setDataSource(dataSource());
    factoryBean.setJpaDialect(new HibernateJpaDialect());
    factoryBean.setJpaProperties(getHibernateProperties());
    factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
    factoryBean.setPackagesToScan("com.lcbo.batch.domain");
   factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
    factoryBean.setPersistenceUnitName("persistenceUnit");
    factoryBean.afterPropertiesSet();


    return factoryBean.getObject();

以及步骤的配置

@Bean
public Step getLCBOStoreDataStep(final LCBOStoreReader lcboStoreReader,
                                 final LCBOStoreProcessor lcboStoreProcessor,
                                 final LCBOStoreWriter lcboStoreWriter,
                                 final ExecutionContextPromotionListener listener) throws Exception 

    return stepBuilderFactory
            .get("getLCBOStoreDataStep")
            .<LCBOStore, LCBOStore>chunk(inventoryTrackerProperties.getDefaults().getChunkSize())
            .reader(lcboStoreReader.read())
            .processor(lcboStoreProcessor)
            .writer(lcboStoreWriter)
            .listener(listener)
            .build();

我要感谢 qtips 和 M. Deinum 在这方面的帮助。非常感谢!

【讨论】:

这仍然是一个弗兰肯斯坦解决方案,您不需要那个包装器......它除了复杂性之外没有增加任何东西。这也让我担心你的其他读者和处理器。 我认为你设法让它运行真是太好了!第一次让 bean 运行起来可能是一个挑战。正如@M.Deinum 建议的那样,尝试在不扩展休眠编写器的情况下使其工作,您将开始拥有spring batch ;) @M.Deinum 我相信我了解您来自哪里,并且已经为答案添加了一个附录,除非您知道您需要扩展您的编写器/处理器(公关很愚蠢并且就像那个样式一样),只需将其放在您配置步骤的配置样式中即可。 如果你扩展/包装东西,至少知道你在做什么。有些编写器实现了更多接口,而您的包装方式(仅使用ItemWriter)可能会破坏这些编写器(或其他组件)的正常运行。因此,我强烈建议不要在没有正确理解 Spring Batch 的详细信息的情况下扩展默认值,因为通常会中断。

以上是关于配置自定义 HibernateItemWriter 时获取“必须提供 HibernateOperations 或 SessionFactory”的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot的自定义配置

Toolbar自定义样式配置及用法

AS配置Git自定义快捷命令

0404-Ribbon通过代码自定义配置使用配置文件自定义Ribbon Client

如何在Spring容器中加载自定义的配置文件

Logstash-自定义模板配置中文分词