不使用数据库持久化元数据的Spring Batch?

65

我想创建一个spring-batch作业,但我希望在没有任何数据库持久化的情况下运行它。不幸的是,Spring Batch需要以某种方式将作业周期的metadata写入数据库,这迫使我提供至少一种带有事务管理器和实体管理器的数据库。

有没有办法防止元数据并独立于事务管理器和数据库运行?

更新:

ERROR org.springframework.batch.core.job.AbstractJob: Encountered fatal error executing job
java.lang.NullPointerException
    at org.springframework.batch.core.repository.dao.MapJobExecutionDao.synchronizeStatus(MapJobExecutionDao.java:158) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.repository.support.SimpleJobRepository.update(SimpleJobRepository.java:161) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[?:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[?:1.7.0_51]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) ~[spring-tx-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at com.sun.proxy.$Proxy134.update(Unknown Source) ~[?:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[?:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[?:1.7.0_51]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) ~[spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at com.sun.proxy.$Proxy134.update(Unknown Source) ~[?:?]
    at org.springframework.batch.core.job.AbstractJob.updateStatus(AbstractJob.java:416) ~[spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:299) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:135) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) [spring-core-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:128) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.7.0_51]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) ~[?:1.7.0_51]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.7.0_51]
    at java.lang.reflect.Method.invoke(Method.java:606) ~[?:1.7.0_51]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) [spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) [spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) [spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127) [spring-batch-core-3.0.1.RELEASE.jar:3.0.1.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) [spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) [spring-aop-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at com.sun.proxy.$Proxy50.run(Unknown Source) [?:?]

你能分享一下你的工作和工作存储库配置吗?(假设你正在使用用户3218114提到的MapJobRepository)? - Michael Minella
10个回答

66

只需创建一个没有数据源的配置即可用于批处理配置:

@Configuration
@EnableAutoConfiguration
@EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {

    @Override
    public void setDataSource(DataSource dataSource) {
        // override to do not set datasource even if a datasource exist.
        // initialize will use a Map based JobRepository (instead of database)
    }

}
它将使用基于内存映射的实现初始化JobRepository和JobExplorer。 https://github.com/spring-projects/spring-batch/blob/342d27bc1ed83312bdcd9c0cb30510f4c469e47d/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/DefaultBatchConfigurer.java#L84 并且即使使用Spring Boot自动配置的生产数据源,您也可以使用它。

我正在使用数据源来提取数据并使用数据源进行转换,Spring Batch使用此数据源来存储其表格。现在我可以使用我的数据源,但是Spring Batch的表格没有被使用。谢谢。 - AntuanSoft
1
你可以在应用程序属性中使用 spring.batch.initializer.enabled=false 来避免重新创建 Spring Batch 表。 - AntuanSoft
1
很好的解决方案,谢谢你,也感谢@AntuanSoft。 - v.ladynev
对我来说最好的答案。 - Faraz
但是这个配置正在等待数据源。我想完全不使用数据源工作。 - gstackoverflow
3
不幸的是,这不仅禁用了Spring Batch的持久性,而且还禁用了整个项目的持久性。 - Sepultura

31

我想在没有任何数据库持久化的情况下运行它。

您可以使用MapJobRepositoryFactoryBeanResourcelessTransactionManager

示例配置:

<bean id="transactionManager"
    class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />

<bean id="jobRepository"
    class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
    <property name="transactionManager" ref="transactionManager" />
</bean>

<bean id="jobLauncher"
    class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
</bean>

对于Spring 4.X,基于注解的配置如下:

@Bean
public PlatformTransactionManager getTransactionManager() {
    return new ResourcelessTransactionManager();
}

@Bean
public JobRepository getJobRepo() {
    return new MapJobRepositoryFactoryBean(getTransactionManager()).getObject();
}

1
我应该分享完整的配置文件给你吗? - Braj
1
也许这会有所帮助,那将非常友善。我的配置看起来完全像这个:https://github.com/lhinze/spring-batch-retrytemplate-example/blob/master/src/main/java/example/ExampleConfiguration.java 主要问题似乎是在 MapJobExecutionDao.class 中的 getJobExecution() 方法返回 null,因为 executionsById 映射为空。我可能忘记做任何初始化了吗? - membersound
我回来了,将您的示例配置迁移到了基于Spring 4注释的配置。我进行了编辑,现在它按预期工作! - membersound
3
尝试过这个,但在使用spring-boot和spring-batch时无效。 - Majky
以上配置是在2014年8月发布的,仅适用于核心Spring框架。我不确定它是否适用于Spring Boot。 - Braj
显示剩余3条评论

18

在修改了@Braj的回答后,我的工作配置如下:

@Bean
public ResourcelessTransactionManager transactionManager() {
    return new ResourcelessTransactionManager();
}

@Bean
public JobRepository jobRepository(ResourcelessTransactionManager transactionManager) throws Exception {
    MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean = new MapJobRepositoryFactoryBean(transactionManager);
    mapJobRepositoryFactoryBean.setTransactionManager(transactionManager);
    return mapJobRepositoryFactoryBean.getObject();
}

@Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
    SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
    simpleJobLauncher.setJobRepository(jobRepository);
    return simpleJobLauncher;
}

对我来说,某种程度上基于xml的配置可以工作,但基于注释的配置却不能。 - Sabir Khan
你确定你复制这些 @Bean 定义的类被注解为 @Configuration 并且被 Spring Boot 所识别吗?请查看启动日志以确认。 - Cleankod
只要我明确地排除数据源bean,它就能正常工作,就像我在这个问题中所回答的那样。 - Sabir Khan
谢谢 @Sabir Khan,@SpringBootApplication(exclude={DataSource.class,DataSourceAutoConfiguration.class}) 对我很有帮助。 - Kishor K
1
Cleankod的配置导致我在使用Spring Boot 1.5.2进行批处理时抛出了“org.springframework.transaction.TransactionSuspensionNotSupportedException”异常。 - oschlueter
@Cleankod- 如何使用JobParameters设置作业 - Jeff Cook

14

由于解决方案不再起作用,我回到了自己的问题。从spring-batch-1.5.3开始使用如下:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
...
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new ResourcelessTransactionManager();
    }
}

1
谢谢。我不需要声明transactionManager,只需使用exclude就可以正常工作。 - Rakesh
2
这是在假设应用程序没有使用其他数据库的情况下吗? - Abhi
1
如果您的批处理使用JPA存储库将数据保存到数据库中,但出现错误,请注意此问题。 - DanzerZoneJS
谢谢兄弟,因为我没有使用任何数据库,所以这很有用。 - Shridutt Kothari

9
如果您不想将任务的元数据存储在数据库中,配置如下所示:
@Configuration
public class InMemoryJobRepositoryConfiguration {
@Bean
public ResourcelessTransactionManager transactionManager() {
    return new ResourcelessTransactionManager();
}

@Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(ResourcelessTransactionManager transactionManager)
        throws Exception {
    MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean(transactionManager);
    factory.afterPropertiesSet();
    return factory;
}

@Bean
public JobRepository jobRepository(MapJobRepositoryFactoryBean repositoryFactory) throws Exception {
    return repositoryFactory.getObject();
}

@Bean
public JobExplorer jobExplorer(MapJobRepositoryFactoryBean repositoryFactory) {
    return new SimpleJobExplorer(repositoryFactory.getJobInstanceDao(), repositoryFactory.getJobExecutionDao(),
            repositoryFactory.getStepExecutionDao(), repositoryFactory.getExecutionContextDao());
}

@Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
    SimpleJobLauncher launcher = new SimpleJobLauncher();
    launcher.setJobRepository(jobRepository);
    return launcher;
}
}

如果工作需要读写业务数据到数据库,且数据源配置如下:
@Autowired
private DataSource dataSource;

Spring Batch通过使用数据源来创建内部模式(BATCH_*表和序列)。为了防止这种情况发生,请在application.properties中设置标志spring.batch.initializer.enabled=false


spring.batch.initializer.enabled=false is now spring.batch.jdbc.initialize-schema=never - albertocavalcante

7

我尝试了上面所有的解决方案,但是在我的情况下并不适用,因为我的Spring Batch作业具有高级功能,例如多线程。它需要一个数据库。所以这就是我解决问题的方法:

@Configuration
public class FakeBatchConfig implements BatchConfigurer {

  PlatformTransactionManager transactionManager;
  JobRepository jobRepository;
  JobLauncher jobLauncher;
  JobExplorer jobExplorer;

  @Override
  public JobRepository getJobRepository() {
    return jobRepository;
  }

  @Override
  public PlatformTransactionManager getTransactionManager() {
    return transactionManager;
  }

  @Override
  public JobLauncher getJobLauncher() {
    return jobLauncher;
  }

  @Override
  public JobExplorer getJobExplorer() {
    return jobExplorer;
  }

  @PostConstruct
  void initialize() throws Exception {

    if (this.transactionManager == null) {
      this.transactionManager = new ResourcelessTransactionManager();
    }

    MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(this.transactionManager);
    jobRepositoryFactory.afterPropertiesSet();
    this.jobRepository = jobRepositoryFactory.getObject();

    MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(jobRepositoryFactory);
    jobExplorerFactory.afterPropertiesSet();
    this.jobExplorer = jobExplorerFactory.getObject();
    this.jobLauncher = createJobLauncher();
  }

  private JobLauncher createJobLauncher() throws Exception {
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setJobRepository(jobRepository);
    jobLauncher.afterPropertiesSet();
    return jobLauncher;
  }

}

感谢Jeff进行编辑。我的原始帖子是用Groovy编写的,而不是Java。 - Tracy Xia
@Qing Xia,谢谢。您能指导一下我们如何在这里传递JobParameters吗? - Jeff Cook
当我使用相同的解决方案时,使用Spring Data Repository.save()会遇到问题.. 它无法持久化对象! - Ghassen
@Ghassen,我遇到了保存不起作用的问题。你有什么指针可以告诉我如何解决吗? - user2800089
@Ghassen 我也遇到了同样的问题。批处理无法将数据保存到数据库中。 - DanzerZoneJS

6

除了@Braj-s的回答,我还需要排除批处理自动配置: @SpringBootApplication(exclude = {BatchAutoConfiguration.class})

否则它不能正常工作。 这包括Spring Boot应用程序。


2

在阅读所有提供的答案后,我成功了。我使用了membersound和Aure77解决方案的混合体。我发现不需要重写setDataSource方法。DefaultBatchConfigurer自动处理PlatformTransactionManager,不需要DataSource。请注意,我正在使用Spring boot 2.0.3.RELEASE版本。以下是我使用的方法:

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableBatchProcessing
public class DemoApplication extends DefaultBatchConfigurer {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    protected Tasklet tasklet() {

        return new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution contribution, ChunkContext context) {
                return RepeatStatus.FINISHED;
            }
        };
    }

    @Bean
    public Job job() throws Exception {
        return this.jobs.get("job").start(step1()).build();
    }

    @Bean
    protected Step step1() throws Exception {
        return this.steps.get("step1").tasklet(tasklet()).build();
    }

    public static void main(String[] args) throws Exception {
        //System.exit(SpringApplication.exit(SpringApplication.run(DemoApplication.class, args)));
        SpringApplication.run(DemoApplication.class, args);
    }
}

2

最简单的方法是将嵌入式数据库添加到类路径中。当然,在生产环境中不推荐使用,但如果您想用它来学习并节省时间,则可以使用。

在pom.xml中添加嵌入式H2数据库依赖项:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

这就是全部。


1
是的,这绝对是最简单的!谢谢。 - ben3000
请注意,MapJobRepositoryFactoryBean 将在 Spring Batch v5 中被移除。因此,本帖中的大部分答案将不再正确。Spring Batch 维护人员已经表达了只支持嵌入式数据库的意愿:https://docs.spring.io/spring-batch/docs/current/reference/html/job.html#inMemoryRepository - Michael Hoffman
一些支持文件,与Michael Hoffman提到的更改有关:https://github.com/spring-projects/spring-batch/issues/3834 - Simeon Leyzerzon

0

可以进行以下更改,以在不创建与Spring Batch相关的元数据数据库结构的情况下使用Spring Batch。

这对我有用:

  1. 添加类型为ResourcelessTransactionManager的bean,并配置MapJobRepositoryFactoryBean
  2. 使用配置的bean MapJobRepositoryFactoryBean 创建一个SimpleJoblauncher bean。
  3. @SpringBootApplication下面添加注释@EnableBatchProcessing
  4. 要覆盖默认值,必须在application.properties中添加spring.main.allow-bean-definition-overriding=true

我只想使用Spring Batch提供的调度器、ItemReader和ItemWriter等功能,但不想在数据库表中维护执行状态,因为我所需求的是一个简单且不重要的需求。如果您有复杂的企业级需求,使用所有Spring Batch表将会很有帮助,我也鼓励这样做。 - rv.comm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接