Как динамически определить несколько заданий в Spring Batch?
У меня есть приложение, которое использует Spring Batch для определения заданного количества заданий, которые в настоящее время все определены в XML.
Со временем мы добавляем больше заданий, что требует обновления XML, однако эти задания всегда основаны на одном и том же родительском элементе и могут быть легко определены с помощью простого запроса SQL.
Поэтому я пытался переключиться на использование некоторой комбинации конфигурации XML и конфигурации на основе Java, но быстро запутался.
Несмотря на то, что у нас много рабочих мест, каждое определение работы подпадает под одну из двух категорий. Все задания наследуются от одного или другого родительского задания и фактически идентичны, кроме того, что имеют разные имена. Имя задания используется в процессе для выбора различных данных из базы данных.
Я придумал некоторый код, похожий на следующий, но столкнулся с проблемами, заставляя его работать.
Полный отказ от ответственности, что я также не совсем уверен, что я делаю это правильно. Подробнее об этом в секунду; во-первых, код:
@Configuration
@EnableBatchProcessing
public class DynamicJobConfigurer extends DefaultBatchConfigurer implements InitializingBean {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private JobRegistry jobRegistry;
@Autowired
private DataSource dataSource;
@Autowired
private CustomJobDefinitionService customJobDefinitionService;
private Flow injectedFlow1;
private Flow injectedFlow2;
public void setupJobs() throws DuplicateJobException {
List<JobDefinition> jobDefinitions = customJobDefinitionService.getAllJobDefinitions();
for (JobDefinition jobDefinition : jobDefinitions) {
Job job = null;
if (jobDefinition.getType() == 1) {
job = jobBuilderFactory.get(jobDefinition.getName())
.start(injectedFlow1).build()
.build();
} else if (jobDefinition.getType() == 2) {
job = jobBuilderFactory.get(jobDefinition.getName())
.start(injectedFlow2).build()
.build();
}
if (job != null) {
jobRegistry.register(new ReferenceJobFactory(job));
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
setupJobs();
}
public void setInjectedFlow1(Flow injectedFlow1) {
this.injectedFlow1 = injectedFlow1;
}
public void setInjectedFlow2(Flow injectedFlow2) {
this.injectedFlow2 = injectedFlow2;
}
}
У меня есть потоки, которые вводятся, определены в XML, очень похоже на это:
<batch:flow id="injectedFlow1">
<batch:step id="InjectedFlow1.Step1" next="InjectedFlow1.Step2">
<batch:flow parent="InjectedFlow.Step1" />
</batch:step>
<batch:step id="InjectedFlow1.Step2">
<batch:flow parent="InjectedFlow.Step2" />
</batch:step>
</batch:flow>
Так что, как вы можете видеть, я эффективно setupJobs()
метод (который предназначен для динамического создания этих определений заданий) из afterPropertiesSet()
метод InitializingBean
, Я не уверен, что это правильно. Он работает, но я не уверен, есть ли другая точка входа, которая лучше предназначена для этой цели. Также я не уверен, какой смысл @Configuration
аннотация должна быть честной.
Проблема, с которой я сейчас сталкиваюсь, заключается в том, как только я позвоню register()
от JobRegistry
, бросает следующее IllegalStateException
:
Чтобы использовать BatchConfigurer по умолчанию, контекст должен содержать не более одного источника данных, найдено 2.
Примечание. На самом деле в моем проекте определены два источника данных. Первый - это bean-компонент dataSource по умолчанию, который подключается к базе данных, которую использует Spring Batch. Второй источник данных - это внешняя база данных, и этот второй содержит всю информацию, необходимую для определения моего списка заданий. Но основной использует имя по умолчанию "dataSource", так что я не совсем уверен, как еще я могу сказать ему использовать его.
2 ответа
Прежде всего - я не рекомендую использовать комбинацию XML, а также конфигурацию Java. Используйте только один, предпочтительно Java, так как это не слишком большая задача для преобразования XML-конфигурации в Java-конфигурацию. (Если у вас нет очень веских причин для этого - то, что вы не объяснили)
Я не использовал Spring Batch в одиночку, так как я всегда использовал его с Spring Boot, и у меня есть проект, в котором я определил несколько заданий, и он всегда работал хорошо для аналогичного кода, который вы показали.
Для вашей проблемы есть несколько ответов на SO, например, это или это, которые в основном пытаются сказать, что вам нужно написать свой собственный BatchConfigurer, а не полагаться на стандартный.
Теперь пришло к решению с помощью Spring Boot
С помощью Spring Boot вы должны попытаться разделить определения заданий и выполнения заданий. Сначала вы должны попытаться просто определить задания и инициализировать контекст Spring без включения заданий (spring.batch.job.enabled=false
)
В основном методе Spring Boot, когда вы запускаете приложение с чем-то вроде - SpringApplication.run(Application.class, args);
...ты получишь ApplicationContext ctx
Теперь вы можете получить соответствующие бины из этого контекста и запускать определенные задания, получая имена из свойств или командной строки и т. Д. И используя JobLauncher.run(...)
метод.
Вы можете отослать мой ответ, если хотите заказать выполнение работы. Вы также можете писать планировщики заданий, используя Java.
Суть в том, что вы разделяете свои задачи построения / конфигурации бинов и задачи выполнения заданий.
Вызов
Сохранение нескольких заданий в одном проекте может оказаться сложной задачей, если вы пытаетесь использовать разные настройки для каждого задания, как application.properties
Файл зависит от среды и не зависит от задания, т.е. свойства весенней загрузки будут применяться ко всем заданиям.
В моем конкретном случае решение было на самом деле устранить @Configuration
а также @EnableBatchProcessing
аннотации из моего класса выше. Что-то из этого заставило его попытаться использовать DefaultBatchConfigurer, который завершается ошибкой, когда у вас определено более одного источника данных (даже если вы четко определили их с "dataSource" в качестве основного и некоторым другим именем для дополнительного).
@Configuration
Класс, в частности, не был необходим, потому что все, что он действительно делает, - это позволяет вашему классу автоматически создаваться без определения его как компонента в контексте приложения. Но так как я делал это в любом случае, это было излишним.
Один из недостатков удаления @EnableBatchProcessing
было то, что я больше не мог автоматически связывать bean-компонент JobBuilderFactory. Так что вместо этого я просто должен был сделать это:
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.afterPropertiesSet();
jobRepository = factory.getObject();
jobBuilderFactory = new JobBuilderFactory(jobRepository);
Тогда, кажется, я уже на правильном пути, используя jobRegistry.register(...)
определить мою работу. Так что, по сути, как только я удалил эти аннотации, все начало работать Однако я собираюсь отметить ответ Сабира как правильный, потому что он помог мне.