在默认配置下,当我们在oracle数据库上执行springbatch时会报"ORA-08177: 无法连续访问此事务处理"的错误,在网上查找资料,显示是因为springbatch的JobRepository默认的事务级别为“ISOLATION_SERIALIZABLE”,需要调整为“ISOLATION_READ_COMMITTED”才可以(具体为啥需要调整数据库事务级别还不是很清楚,需要找时间再研究清楚)
那么下一步我们自定义自己的JobRepository,通过查看官方文档([spring.io](https://docs.spring.io/spring-batch/docs/current/reference/html/job.html#configuringJobRepository))得知,自定义JobRepository需要实现一个BatchConfigurer接口的类。那么只需要实现这个类就行了。经过测试发现是不行的,显然springbatch还没有智能到这种程度,那么我们需要分析下springbatch的初始化过程。
不说查找的过程了,我直接写一下自己分析之后的看法吧。
首先springboot会去初始化org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration的配置文件:
@ConditionalOnClass(PlatformTransactionManager.class)
@ConditionalOnBean(DataSource.class)
@ConditionalOnMissingBean(BatchConfigurer.class)
@Configuration(proxyBeanMethods = false)
class BatchConfigurerConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = "entityManagerFactory")
static class JdbcBatchConfiguration {
@Bean
BasicBatchConfigurer batchConfigurer(BatchProperties properties, DataSource dataSource,
@BatchDataSource ObjectProvider<DataSource> batchDataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new BasicBatchConfigurer(properties, batchDataSource.getIfAvailable(() -> dataSource),
transactionManagerCustomizers.getIfAvailable());
}
}
以上代码执行成功之后就会初始化一个BasicBatchConfigurer的bean,继续看里面的代码,在BasicBatchConfigurer里有个加了initialize()方法:
@PostConstruct
public void initialize() {
try {
this.transactionManager = buildTransactionManager();
this.jobRepository = createJobRepository();
this.jobLauncher = createJobLauncher();
this.jobExplorer = createJobExplorer();
}
catch (Exception ex) {
throw new IllegalStateException("Unable to initialize Spring Batch", ex);
}
}
注意该方面有一个@PostConstruct注解,表示bean属性注入之后就会调用该方法。我们再看一下createJobRepository()方法:
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
PropertyMapper map = PropertyMapper.get();
map.from(this.dataSource).to(factory::setDataSource);
map.from(this::determineIsolationLevel).whenNonNull().to(factory::setIsolationLevelForCreate);
map.from(this.properties::getTablePrefix).whenHasText().to(factory::setTablePrefix);
map.from(this::getTransactionManager).to(factory::setTransactionManager);
factory.afterPropertiesSet();
return factory.getObject();
}
可以看到,当代码执行到此时,BasicBatchConfigurer这个bean的jobRepository属性已经初始化完成。下面我们再分析下这个jobRepository是如何作为一个bean注入到spring容器的。
这里我们就要看一下@interface EnableBatchProcessing这个注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(BatchConfigurationSelector.class)
public @interface EnableBatchProcessing {
继续看BatchConfigurationSelector的代码:
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class<?> annotationType = EnableBatchProcessing.class;
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
annotationType.getName(), false));
Assert.notNull(attributes, String.format("@%s is not present on importing class '%s' as expected",
annotationType.getSimpleName(), importingClassMetadata.getClassName()));
String[] imports;
if (attributes.containsKey("modular") && attributes.getBoolean("modular")) {
imports = new String[] { ModularBatchConfiguration.class.getName() };
}
else {
imports = new String[] { SimpleBatchConfiguration.class.getName() };
}
return imports;
}
有以上代码可以看出,当我们在代码中加了@EnableBatchProcessing()注解时,spring容器就会自动去加载SimpleBatchConfiguration,在该类中又有一个initialize方法:
protected void initialize() throws Exception {
if (initialized) {
return;
}
BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values());
jobRepository.set(configurer.getJobRepository());
jobLauncher.set(configurer.getJobLauncher());
transactionManager.set(configurer.getTransactionManager());
jobRegistry.set(new MapJobRegistry());
jobExplorer.set(configurer.getJobExplorer());
initialized = true;
}
再然后,spring容器在执行SimpleBatchConfiguration的jobRepository()时彻底的将该对象封装为一个bean放入到容器中。
由此,我们得出一个简单结论,一旦我们再代码中加入了@EnableBatchProcessing(),springboot就会根据BasicBatchConfigurer来初始化一个jobRepository。我们只有这么几个机会来自定义jobRepository:
(1)BatchConfigurerConfiguration有一个注解@ConditionalOnMissingBean(BatchConfigurer.class),意思就是说只有当BatchConfigurer.class的bean不存在时才会执行这个类的方法(描述不够准确),所以我们可以定义一个BatchConfigurer的bean来阻止系统默认的初始化。
(2)在BasicBatchConfigurer的createJobRepository()方法中是通过系统参数的map来定义JobRepository:
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
PropertyMapper map = PropertyMapper.get();
map.from(this.dataSource).to(factory::setDataSource);
map.from(this::determineIsolationLevel).whenNonNull().to(factory::setIsolationLevelForCreate);
map.from(this.properties::getTablePrefix).whenHasText().to(factory::setTablePrefix);
map.from(this::getTransactionManager).to(factory::setTransactionManager);
factory.afterPropertiesSet();
return factory.getObject();
}
因此,可以尝试改变map中的值来改变JobRepository的初始化(该方面目前我还没研究明白)。
还有一处一开始我也以为可以,后来发现不行,那就是在SimpleBatchConfiguration的initialize()方法中有一行这样的代码:
BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values());
一开始我以为只需要定义一个级别高的BatchConfigurer.class的bean也可以实现个性化定义JobRepository的方面,但是后来继续跟代码发现不太行。
最后给一下自己的解决方案:
第一步、复制一个BasicBatchConfigurer重命名为CustomerBatchConfig,然后根据自己的需要进行调整,比如我修改了JobRepository的事务级别:
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
PropertyMapper map = PropertyMapper.get();
map.from(this.dataSource).to(factory::setDataSource);
//map.from(this::determineIsolationLevel).whenNonNull().to(factory::setIsolationLevelForCreate);
factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED");
map.from(this.properties::getTablePrefix).whenHasText().to(factory::setTablePrefix);
map.from(this::getTransactionManager).to(factory::setTransactionManager);
factory.afterPropertiesSet();
return factory.getObject();
}
第二步、定义一个config:
@Configuration
public class CustomerConfigurerConfiguration {
@Bean
BatchConfigurer batchConfigurer(BatchProperties properties, DataSource dataSource,
@BatchDataSource ObjectProvider<DataSource> batchDataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new CustomerBatchConfig(properties, batchDataSource.getIfAvailable(() -> dataSource),
transactionManagerCustomizers.getIfAvailable());
}
}
其实这个代码大家看着应该也很眼熟,其实也是copy过来改的。
总而言之,对我来说自定义一个JobRepository还真是挺难的,改天仔细研究下方案二看看能不能方便一些