Repository.Save() inside scheduling is not working - jpa

I have a repository.save() method called inside a scheduler. But it is not saving anything to the database.
Following is my scheduler
#Component
#Transactional
#Slf4j
public class WomConditionActionJob {
#Autowired
private Environment env;
#Autowired
private ECCRepository eCCRepository;
#Autowired
private WOCRepository wOCRepository;
#Autowired
private PSRepository pSRepository;
#Scheduled(fixedDelayString = "${wCATrigger.polling.frequency}", initialDelayString = "${wCATrigger.initial.delay}")
public void execute() {
try {
final PauseStatus pause = pSRepository.findByPSName(PSName.PAUSE);
pauseCondition(pause,threshold);
} catch (Exception e) {
log.error("Exception Occured {}", e);
}
}
private void pauseCondition(final PauseStatus pause, final Integer threshold) {
WOTCondition wotCId = workOrderConditionRepository.findById(1).get();
wotCId.setPauseStatus(pause);
wotCId.setIsUserAction(Boolean.FALSE);
workOrderConditionRepository.save(wotConditionbyId);
conditionCount.setErrorCount(0);
errorConditionCountRepository.save(conditionCount);
}
}
I trying using saveAndFlush() but that time I got Following error
[pool-2-thread-1]|ERROR|[o.s.s.s.TaskUtils$LoggingErrorHandler.handleError(96)]|Unexpected error occurred in scheduled task.
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710)

Adding this solved my issue
#Transactional(propagation=Propagation.REQUIRES_NEW)
Example
#Scheduled(cron = "0/5 * * * * *")
#Transactional(propagation=Propagation.REQUIRES_NEW)
#Override
public void scheduleJob() {
Message message = new Message();
message.setMessageId(UUID.randomUUID().toString());
message.setAccountId("accountId");
message.setSent(2L);
message.setFailed(2L);
message.setDelivered(2L);
// saves or update message_report table
messageRepository.save(message);
}

Related

Transaction is required to perform this operation (either use a transaction or extended persistence context) while persisting Entity

I am trying to develop a class that runs at specific intervals and performs some DB modifications.
the code I have managed to run at a specific interval, retrieve records from the DB, but when I want to commit changes to the DB I get the following error.
WFLYEE0110: Failed to run scheduled task: javax.persistence.TransactionRequiredException: WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
is #ApplicationScoped allowed to create transactions?
Thanks!
#ApplicationScoped
#ActivateRequestContext
public class TaskRunner {
#PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager em;
#Resource private ManagedScheduledExecutorService scheduler;
private ScheduledFuture<?> TaskRunnerScheduler;
private boolean initialized = false;
private void init(#Observes #Initialized(ApplicationScoped.class) Object init) {
if (initialized) return;
initialized = true;
try {
// Execute at startup
TaskRunner = scheduler.schedule(this::runSchedule, getSchedule());
} catch (Throwable throwable) {
}
}
#Transactional
private void runSchedule() {
//retrieve db records
//make changes and commit
//sample
//em.persist(someEntity)
}
private Trigger getSchedule() {
return new Trigger() {
#Override
public Date getNextRunTime(LastExecution lastExecutionInfo, Date taskScheduledTime) {
return Date.from(
ZonedDateTime.now().withSecond(0).withNano(0).plusHours("4").toInstant());
}
#Override
public boolean skipRun(LastExecution lastExecutionInfo, Date scheduledRunTime)
{return false;}};
}
}
Transactions are started via Interceptors.
When you call a method of a bean from inside that bean, the method-call is not intercepted and no Transaction can get started.
You need another Bean and persist in there
#RequestScoped
#Transactional(value = TxType.REQUIRES_NEW)
public class SomeOtherBean{
#PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager em;
public void doSomething(){
//retrieve db records
//make changes and commit
//sample
//em.persist(someEntity)
}
}
then you can Inject that bean in your TaskRunner
#ApplicationScoped
#ActivateRequestContext
public class TaskRunner {
#Inject
SomeOtherBean someBean;
...
private void runSchedule() {
someBean.doSomething()
}
}

How to quit a tasklet if error in Spring Batch?

I would like to quit a tasklet cleanly if I have an error on it and put and stop the batch without having to resort to a System.exit(1).
Here is my code:
/**
* execution de la tasklet
*
*/
#Override
public RepeatStatus execute(StepContribution arg0, ChunkContext arg1) throws IOException {
if (suiviFluxDao.getNbFileDateTrt(FilenameUtils.getName(resource), Utils.getDateFromStringFormatUS(dateTraitement)) > 0) {
LOGGER.info(PropertiesUtils.getLibelleExcep(Constantes.ERREUR_NB_FILE_SELECT,
new String[]{ConstantesNomsSql.TABLE_STCO_STAU_SUIVI_FLUX, FilenameUtils.getName(resource), dateTraitement, Constantes.NAME_TRT}));
System.exit(1);
} else {
SuiviFluxBO suiviFluxBO = new SuiviFluxBO();
suiviFluxBO.setDateSysteme(Utils.getDateTodayFormatUS());
suiviFluxBO.setDateTrt(Utils.getDateFromStringFormatUS(dateTraitement));
suiviFluxBO.setLibelleTrt("Batch_Java");
suiviFluxBO.setNomficTrt(FilenameUtils.getName(resource));
suiviFluxBO.setNbrrecTrt(Utils.countNbFile(resource));
suiviFluxBO.setNomtabTrt(ConstantesNomsSql.TABLE_STCO_STAU_FIC_ADH);
suiviFluxBO.setNbrlignesTrt(0);
suiviFluxDao.insertSuiviBO(suiviFluxBO);
}
// fin de l'execution
return RepeatStatus.FINISHED;
}
The tasklet implements StepExecutionListener but how to indicate in the IF that contains the error to modify the execution status so that it is in FAILED?
Thank you for your leads.
Based on above requirement , we can build a flow using spring batch FlowBuilder object .
1 . Build a Tasklet which performs required validations and sets ExitStatus based on validation result.
#Component
public class TestTasklet implements StepExecutionListener, Tasklet {
// Any additional properties if required can be added here .
#Override
public void beforeStep(StepExecution stepExecution) {
// Any logic added here will execute before executing step
}
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
// Any logic added here will execute after executing step
return null;
}
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws IOException {
if (suiviFluxDao.getNbFileDateTrt(FilenameUtils.getName(resource),
Utils.getDateFromStringFormatUS(dateTraitement)) > 0) {
LOGGER.info(PropertiesUtils.getLibelleExcep(Constantes.ERREUR_NB_FILE_SELECT,
new String[]{ConstantesNomsSql.TABLE_STCO_STAU_SUIVI_FLUX, FilenameUtils.getName(resource),
dateTraitement, Constantes.NAME_TRT}));
contribution.setExitStatus(ExitStatus.FAILED);
} else {
// any logic goes here .
contribution.setExitStatus(ExitStatus.COMPLETED);
}
return RepeatStatus.FINISHED;
}
}
2 . Below code snippet allows to configure job using flow builder :
#Configuration
public class JobConfigurations {
private StepBuilderFactory stepBuilderFactory;
private JobBuilderFactory jobBuilderFactory;
#Autowired
public JobConfigurations(StepBuilderFactory stepBuilderFactory,
JobBuilderFactory jobBuilderFactory) {
this.stepBuilderFactory = stepBuilderFactory;
this.jobBuilderFactory = jobBuilderFactory;
}
#Bean
public Job job(TestTasklet testTasklet) {
Step validationStep = stepBuilderFactory.get("validationTasklet")
.tasklet(testTasklet).build();
//create another step where you want to perform business logic
//for sake of brevity let us assume it to be businessValidationStep
//Step businessValidationStep = stepBuilderFactory.get("businessvalidationstep")
// .chunk().reader().processor().writer();
return jobBuilderFactory.get("JOB_NAME").incrementer(new RunIdIncrementer())
.start(validationStep)// start your job with validation step
.on(ExitStatus.FAILED.getExitCode()).end()// this will terminate your job cleanly
.from(validationStep)
.on(ExitStatus.COMPLETED.getExitCode())//.to("businessValidationStep")
.to(validationStep).build().build();
}
}
#lasnico37 Hope above code will solve the problem statement .

How to check if a job is still running or is finished regardless of finalization status

Working in Spring Batch (3) with Spring Boot(1.5) project. I have an end of day job "endOfDayJob" that is asynchronously execute through a web controller, in the controller i am returning the job execution id.
Below the code for configuration class. Highlight here that i am implementing BatchConfigurer interface and creating a async JobLauncer with SimpleAsyncTaskExecutor.
#Configuration
#EnableBatchProcessing
#EnableAsync
public class BatchConfiguration implements BatchConfigurer {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
private DataSource dataSource;
#Bean
public Job endOfDayJob() throws Exception {
SimpleJobBuilder simpleJobBuilder = jobBuilderFactory.get("endOfDayJob")
.incrementer(new RunIdIncrementer())
.start(init())
.next(updateInventory())
.next(generateSalesReport())
.next(cleanup())
.next(sendReport());
return simpleJobBuilder.build();
}
#Bean
public Step init() {
return stepBuilderFactory.get("initStep").tasklet(initTasklet()).build();
}
#Bean
public Step updateInventory() {
return stepBuilderFactory.get("updateInventoryStep").tasklet(updateInventoryTasklet()).build();
}
#Bean
public Step generateSalesReport() {
return stepBuilderFactory.get("generateSalesReportStep").tasklet(generateSalesReportTasklet()).build();
}
#Bean
public Step cleanup() {
return stepBuilderFactory.get("cleanupStep").tasklet(cleanupTasklet()).build();
}
#Bean
public Step sendReport() {
return stepBuilderFactory.get("sendReportStep").tasklet(sendReportTasklet()).build();
}
#Override
public JobRepository getJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(getTransactionManager());
factory.setIsolationLevelForCreate("ISOLATION_READ_COMMITTED");
factory.setTablePrefix("BATCH_");
return factory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return new DataSourceTransactionManager(dataSource);
}
#Override
public JobLauncher getJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(getJobRepository());
jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
#Override
public JobExplorer getJobExplorer() throws Exception {
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
jobExplorerFactoryBean.setDataSource(dataSource);
jobExplorerFactoryBean.setTablePrefix("BATCH_");
jobExplorerFactoryBean.afterPropertiesSet();
return jobExplorerFactoryBean.getObject();
}
}
Here the code for the web controller.
#RestController
#RequestMapping("/api/job")
public class WebController {
private static final Logger logger = LoggerFactory.getLogger(WebController.class);
#Autowired
private DataSource dataSource;
#Autowired
private BatchConfiguration batchConfiguration;
#Autowired
private JobLauncher jobLauncher;
#GetMapping("/endOfDayJob")
private Long kycrBatch(#RequestParam(value = "odate", required = true) String odate) {
logger.info("ExecutingendOfDayJob with odate = {}", odate);
if (odate == null || odate.isEmpty() || odate.trim().isEmpty()) {
return -1L;
}
JobParametersBuilder jobParametersBuilder = new JobParametersBuilder();
jobParametersBuilder.addString("odate", odate);
long jobExecutionId = -1L;
try {
Job endOfDayJob = this.batchConfiguration.endOfDayJob();
jobParametersBuilder.addDate("runtime", new Date());
jobExecutionId = jobLauncher.run(endOfDayJob, jobParametersBuilder.toJobParameters()).getId();
} catch (Exception e) {
logger.error("Error ocurred executing endOfDayJob with message: {}", e.getMessage());
return -1L;
}
return jobExecutionId;
}
}
Then i want to add new method in the controller to to know if the job ended or not. What is a possible way to check if a job is still running or is already finished regardless of finalization status??
Then i want to add new method in the controller to to know if the job ended or not.
You can inject the JobExplorer in your controller and write something like:
public boolean isRunning(long jobExecutionId) {
JobExecution jobExecution = jobExplorer.getJobExecution(jobExecutionId);
return jobExecution.isRunning();
}

Dynamic config Spring batch execution time and parameters via database configured parameters

I am a fresh spring batch user, pls help me. Here is my requirement:
I have fulfilled several spring batch jobs with different names. I want to execute these jobs with different job parameters, and I hope these parameters can be configured dynamically in database, so I can add new job execution with different job names and different parameters.
Also, I want to schedule my job execution in different time, and the crontab expression can also be configured.
Maybe the database structure is like:
id
task_name
spring_batch_job_name
cron_expression
Wonder if some guys can guide me. Very thanks!
Here is my job setting entity:
#Entity
#Table(name = "report_tasks_manager", schema = "reconciliation", catalog = "")
public class ReportTasksManager {
private int id;
private String taskDesc;
private String taskName;
// crontab expression
private String cronExpression;
// class name to execute job logic
private String methodName;
private int state;
private Integer conCurrent;
private String reserved1;
private String reserved2;
private String reserved3;
private Timestamp startTime;
private Timestamp endTime;
private Timestamp createTime;
}
I defined a class which implemented Job interface, and the execute() method in this class executes the business logic, like launching a spring batch job:
public class QuartzJobFactory implements Job {
public QuartzJobFactory() {
}
#Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("time ={" + new Date() + "}");
System.out.println("starting job build factory");
ReportTasksManager reportTasksManager = (ReportTasksManager) jobExecutionContext.getMergedJobDataMap().get("scheduleJob");
System.out.println("job name = {" + reportTasksManager.getTaskName() + "}");
}
}
For registering a cron trigger, I defined a rest controller to checkout job parameters in database and configure scheduler
#RestController
#RequestMapping(path = "test")
public class QuartzManager {
private SchedulerFactory schedulerFactory = new StdSchedulerFactory();
#Autowired
private ReportTaskManagerDAO reportTaskManagerDAO;
#GetMapping(value = "schedule")
public void scheduleJob() {
// Read settings from database
List<ReportTasksManager> quartzList = reportTaskManagerDAO.findAll();
if (quartzList.size() > 0) {
quartzList.forEach(reportTasksManager -> {
try {
configQuartz(reportTasksManager, schedulerFactory.getScheduler());
} catch (SchedulerException | ClassNotFoundException e) {
e.printStackTrace();
}
});
}
}
#SuppressWarnings("unchecked")
private void configQuartz(ReportTasksManager reportTasksManager, Scheduler scheduler) throws SchedulerException, ClassNotFoundException {
TriggerKey triggerKey = TriggerKey.triggerKey(reportTasksManager.getTaskName(), Scheduler.DEFAULT_GROUP);
// check if triggers already defined in scheduler
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (null == trigger) {
// not define——new trigger&jobDetail
JobDetail jobDetail =
JobBuilder.newJob((Class<? extends Job>) Class.forName(reportTasksManager.getMethodName()))
.withIdentity(reportTasksManager.getTaskName(), Scheduler.DEFAULT_GROUP)
.build();
jobDetail.getJobDataMap().put("scheduleJob", reportTasksManager);
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(reportTasksManager.getCronExpression());
trigger = TriggerBuilder.newTrigger()
.withIdentity(reportTasksManager.getTaskName(), Scheduler.DEFAULT_GROUP)
.withSchedule(scheduleBuilder)
.build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} else {
// already defined——update
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(reportTasksManager.getCronExpression());
trigger = trigger.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(scheduleBuilder)
.build();
scheduler.rescheduleJob(triggerKey, trigger);
}
}
}
You can use create some Util class( on #PostConstruct) which loads your job config from DB.
For example:
#Entity
public class Configuration{
#Id
private long id;
private String field;
private String value;
// getter and setter
}
#Component
public interface ConfigurationRepo extends JpaRepository<Configuration, Long> {
}
public final class ConfigurationUtil {
private ConfigurationUtil() {
}
private static List<Configuration> defaultConfiguration;
/**
* #return the defaultConfiguration
*/
public static List<Configuration> getDefaultConfiguration() {
return defaultConfiguration;
}
/**
* #param defaultConfiguration the defaultConfiguration to set
*/
public static void setDefaultConfiguration(List<Configuration> defaultConfiguration) {
ConfigurationUtil.defaultConfiguration = defaultConfiguration;
}
public static String getValueByField(String field) {
return defaultConfiguration.stream()
.filter(s -> s.getField()
.equalsIgnoreCase(field))
.findFirst()
.get()
.getValue();
}
}
#Component
public class ConfigurationContextInitializer {
#Autowired
ConfigurationRepo configurationRepo;
#PostConstruct
public void init() {
ConfigurationUtil.setDefaultConfiguration(configurationRepo.findAll());
}
}
//To access DB value
ConfigurationUtil.getValueByField("JOB_NAME"); // depends on your DB key

GWT Void remote services fail for seemingly no reason

I'm working on a GWT project and have several void remote services that seem to execute just fine, but on the client side, end up firing the onFailure() method. No exceptions are thrown anywhere, and the expected behavior is observed on the backend. I have no idea what could be going wrong. Here is the relevant code:
Interfaces and implementation...
#RemoteServiceRelativePath("DeleteSearchService")
public interface DeleteSearchService extends RemoteService {
/**
* Utility class for simplifying access to the instance of async service.
*/
public static class Util {
private static DeleteSearchServiceAsync instance;
public static DeleteSearchServiceAsync getInstance(){
if (instance == null) {
instance = GWT.create(DeleteSearchService.class);
}
return instance;
}
}
public void delete(SearchBean search);
}
public interface DeleteSearchServiceAsync {
public void delete(SearchBean bean, AsyncCallback<Void> callback);
}
public class DeleteSearchServiceImpl extends RemoteServiceServlet implements DeleteSearchService {
private static final long serialVersionUID = 1L;
#Override
public void delete(SearchBean search) {
try {
Connection conn = SQLAccess.getConnection();
String sql = "DELETE FROM `searches` WHERE `id`=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, search.getSearchId());
ps.execute();
sql = "DELETE FROM `searchsourcemap` WHERE `search-id` = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, search.getSearchId());
ps.execute();
return;
} catch (Exception e) {
// TODO Log error
e.printStackTrace();
}
}
}
Calling code...
private class DeleteListener implements ClickListener {
public void onClick(Widget sender) {
DeleteSearchServiceAsync dss = DeleteSearchService.Util.getInstance();
SearchBean bean = buildBeanFromGUI();
dss.delete(bean, new AsyncCallback<Void>(){
//#Override
public void onFailure(Throwable caught) {
// TODO log
SearchNotDeleted snd = new SearchNotDeleted();
snd.show();
}
//#Override
public void onSuccess(Void result) {
SearchDeleted sd = new SearchDeleted();
sd.show();
searchDef.getParent().removeFromParent();
}
});
}
}
I know I'm a jerk for posting like 500 lines of code but I've been staring at this since yesterday and can't figure out where I'm going wrong. Maybe a 2nd set of eyes would help...
Thanks,
brian
LGTM I'm afraid.
Are you using the hosted mode or a full-fledged browser? You can try switching and see if it helps.
Also, it might help listening to that //TODO and perform a GWT.log when onFailure is invoked.