I have a query related to spring scheduler.
I have implemented scheduler as below which just prints current timestamp every 5 seconds. This used to work perfectly fine when the project was on springboot version 3.6.3. On springboot version update to 3.7.1 this implementation randomly stops after first trigger.
I'm trying to add a handle to start scheduler when it stops. Have tried with postProcessBeforeDestruction and postProcessAfterInitialization methods, which is not helping.
Any help would be much appreciated.
Scheduler class is as below
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
#Configuration
#EnableScheduling
public class Test implements SchedulingConfigurer {
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(1);
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(() -> {
System.out.println(":::Inside Runnable Method:::");
ExecutorService e = Executors.newCachedThreadPool();
e.submit(new BatchRunner(1L));
e.shutdown();
}, triggerContext -> {
System.out.println(":::Inside Trigger:::");
Calendar nextExecutionTime = new GregorianCalendar();
Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
String schedulerTime = "5";
nextExecutionTime.add(Calendar.SECOND, null != schedulerTime ? Integer.parseInt(schedulerTime) : 0);
return nextExecutionTime.getTime();
});
}
private class BatchRunner implements Runnable {
private long id;
public BatchRunner(long id) {
this.id = id;
}
#Override
public void run() {
System.out.println();
System.out.println("::::TIME:::: "+ new Date());
}
}
}
Logs
:::Inside Runnable Method:::
:::Inside Trigger:::
::::TIME:::: Thu Sep 15 17:04:09
:::Inside Runnable Method:::
:::Inside Trigger:::
::::TIME:::: Thu Sep 15 17:04:14
Related
I have an issue with detecting adding a new row to the table. I want to trigger a method from some service (Spring boot) when somebody executes an insert query on the database (Postgres)
Somebody told me I can use #Scheduled annotation and check if something was added using a repository. I have to make some changes instantly (by using another method). The scheduled method should run every 5 seconds to do this instantly. Of course, this is a really bad idea because it will kill the database someday and it's not efficient.
How can I do this better?
You can write concrete implementer of org.hibernate.integrator.spi.Integrator. and give it to hibernate.integrator_provider
From ServiceRegistry we can get EventListenerRegistry and then append listener of type EventType.POST_INSERT. More events here.
Main Reference Hibernate Integrator Ref
As per the query, I have also added how to call the service method from the listener class.
Here is how I have done it:
package com.example.samplejdbctemplatecall;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.jpa.boot.spi.IntegratorProvider;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
#RequestMapping(path = "/entity-listener")
#RestController
public class SampleLogController {
private final SampleLogRepository sampleLogRepository;
private final SampleLogEntries sampleLogEntiries;
#Autowired
public SampleLogController(SampleLogRepository sampleLogRepository, SampleLogEntries sampleLogEntiries) {
this.sampleLogRepository = sampleLogRepository;
this.sampleLogEntiries = sampleLogEntiries;
}
// This is usually post method but for test purpose creating new log with uuid random and inserting
#GetMapping(path = "insert")
public SampleLog insertNewEntry() {
final String uuid = UUID.randomUUID().toString();
final SampleLog sampleLog = new SampleLog();
sampleLog.setMessage(uuid);
return sampleLogRepository.save(sampleLog);
}
#GetMapping(path = "list-recent-inserts")
public Map<Long, String> entries() {
return sampleLogEntiries.data();
}
}
#Slf4j
#Component
class HibernateConfig implements HibernatePropertiesCustomizer {
private final JpaEventListenerIntegrator jpaEventListenerIntegrator;
#Autowired
HibernateConfig(JpaEventListenerIntegrator jpaEventListenerIntegrator) {
this.jpaEventListenerIntegrator = jpaEventListenerIntegrator;
}
#Override
public void customize(Map<String, Object> hibernateProperties) {
log.warn("Called hibernate configuration");
hibernateProperties.put("hibernate.integrator_provider",
(IntegratorProvider) () -> Collections.singletonList(jpaEventListenerIntegrator));
}
}
#Configuration
class SampleConfiguration {
#Bean
SampleLogEntries sampleEntries() {
return new SampleLogEntries();
}
}
class SampleLogEntries {
private final ConcurrentMap<Long, String> map = new ConcurrentHashMap<>();
public void add(SampleLog sampleLog) {
this.map.put(sampleLog.getId(), sampleLog.getMessage());
}
public Map<Long, String> data() {
return Collections.unmodifiableMap(this.map);
}
}
#Repository
interface SampleLogRepository extends CrudRepository<SampleLog, Long> {
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
class SampleLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String message;
}
#Service
#Slf4j
class JpaEventListenerIntegrator implements Integrator, PostInsertEventListener {
private final SampleLogEntries sampleLogEntiries;
#Autowired
JpaEventListenerIntegrator(SampleLogEntries sampleLogEntiries) {
this.sampleLogEntiries = sampleLogEntiries;
}
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry
.appendListeners(EventType.POST_INSERT, this);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
EventListenerGroup<PostInsertEventListener> eventListenerGroup = eventListenerRegistry
.getEventListenerGroup(EventType.POST_INSERT);
log.info("listener attached were: " + eventListenerGroup.getClass().getSimpleName());
log.error("disintegrate : " + getClass().getCanonicalName());
eventListenerGroup.clearListeners();
}
#Override
public void onPostInsert(PostInsertEvent event) {
log.info("Inserted : " + event.getEntity());
final Object entity = event.getEntity();
if (entity instanceof SampleLog) {
sampleLogEntiries.add((SampleLog) entity);
}
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
The answer from #silentsudo is the best one if you only ever use JPA to update the table in question, and if you only have one process updating it.
The issue is that since you are being notified via the JPA interceptor, you won't be notified of any updates that happen outside of your JAP repository.
If you need these other notifications, then you can use Postgres' LISTEN/NOTIFY without polling by using an alternate postgresql JDBC driver, pgjdbc-ng, which implements async notifications.
With this method, you create a trigger in the database to send the notification, so you will be notified of other's updates as well. See https://www.openmakesoftware.com/postgresql-listen-notify-events-example
i have to compose 2 processors as following :
processor 1 implement the itemProcessor Interface with itemProcessor<A,B> (transforming data).
processor 2 implement the itemProcessor Interface with itemProcessor<B,B>.(treat transformed data).
the CompositeItemProcessor<I, O> requires the delegates to be in the same type , moreover when passing it to the Step the step is already configure with fixed types <A,B>.
how i could chain these processors with different types and assign it to the step processor ?
You need to declare your step as well as your composite processor with <A, B>. Here is a quick example:
import java.util.Arrays;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.CompositeItemProcessor;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableBatchProcessing
public class MyJobConfiguration {
#Bean
public ItemReader<A> itemReader() {
return new ListItemReader<>(Arrays.asList(new A("a1"), new A("a2")));
}
#Bean
public ItemProcessor<A, B> itemProcessor1() {
return item -> new B(item.name);
}
#Bean
public ItemProcessor<B, B> itemProcessor2() {
return item -> item; // TODO process item as needed
}
#Bean
public ItemProcessor<A, B> compositeItemProcessor() {
CompositeItemProcessor<A, B> compositeItemProcessor = new CompositeItemProcessor<>();
compositeItemProcessor.setDelegates(Arrays.asList(itemProcessor1(), itemProcessor2()));
return compositeItemProcessor;
}
#Bean
public ItemWriter<B> itemWriter() {
return items -> {
for (B item : items) {
System.out.println("item = " + item.name);
}
};
}
#Bean
public Job job(JobBuilderFactory jobs, StepBuilderFactory steps) {
return jobs.get("job")
.start(steps.get("step")
.<A, B>chunk(2)
.reader(itemReader())
.processor(compositeItemProcessor())
.writer(itemWriter())
.build())
.build();
}
public static void main(String[] args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(MyJobConfiguration.class);
JobLauncher jobLauncher = context.getBean(JobLauncher.class);
Job job = context.getBean(Job.class);
jobLauncher.run(job, new JobParameters());
}
class A {
String name;
public A(String name) { this.name = name; }
}
class B {
String name;
public B(String name) { this.name = name; }
}
}
We were using Spring Boot 2.1.6.RELEASE.
after that we updated spring version to 2.2.2.RELEASE. When we change the version we noticed our quartz jobs not working. We have multiple jobs and we configured them like below. After some reasearch i found some differences between in QuartzAutoConfiguration class.
How can i inject my triggers in spring 2.2.2.RELEASE. Is there any easy way? I dont want to write to many triggers and trigger details.
MyConfig
import io.rkpc.commons.util.ApplicationReflectionUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
#Slf4j
#Configuration
#ConfigurationProperties(prefix = "quartz")
#Profile("quartz")
#Data
public class JobConfig {
private List<Job> jobs;
#Bean
public JobDetail[] jobDetail() throws SchedulerConfigException {
Set<Class<QuartzJobBean>> subClasses = ApplicationReflectionUtil.getSubClasses(QuartzJobBean.class, "io.rkpc");
List<JobDetail> jobDetails = new ArrayList<>();
for (Class<QuartzJobBean> quartzJobBeanClass : subClasses) {
Job job = getJob(quartzJobBeanClass.getSimpleName());
if (job.isEnabled()) {
JobDetail jobDetail = JobBuilder.newJob(quartzJobBeanClass)
.withIdentity(quartzJobBeanClass.getSimpleName())
.storeDurably()
.build();
jobDetails.add(jobDetail);
}
}
return jobDetails.toArray(new JobDetail[0]);
}
#Bean
public Trigger[] jobATrigger(JobDetail[] jobADetails) throws SchedulerConfigException {
List<Trigger> triggers = new ArrayList<>();
for (JobDetail jobDetail : jobADetails) {
Job job = getJob(jobDetail.getKey().getName());
CronTrigger trigger = TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity(jobDetail.getKey().getName().concat("Trigger"))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
.build();
triggers.add(trigger);
}
return triggers.toArray(new Trigger[0]);
}
private Job getJob(String name) throws SchedulerConfigException {
List<Job> filteredJobs = jobs.stream().filter(job -> job.getName().equals(name)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(filteredJobs) || filteredJobs.size() > 1) {
log.error("{} is not configured", name);
throw new SchedulerConfigException("Job is not configured");
}
return filteredJobs.get(0);
}
#Data
public static class Job {
private String name;
private String cron;
private boolean enabled;
}
}
QuartzAutoConfiguration.java Spring version 2.1.6 github url ; https://github.com/spring-projects/spring-boot/blob/v2.1.6.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java
QuartzAutoConfiguration.java Spring version 2.2.2 github url https://github.com/spring-projects/spring-boot/blob/v2.2.2.RELEASE/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java
Main difference i notice is ; in 2.1.6 version Quartz AutoConfiguration was "Trigger" array but 2.2.2 doesn't have "Trigger" array.
Spring has always some magic :)
import io.rkpc.commons.util.ApplicationReflectionUtil;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.validation.constraints.NotNull;
import java.text.ParseException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
#Slf4j
#Configuration
#ConfigurationProperties(prefix = "quartz")
#Profile("quartz")
#Data
#AutoConfigureBefore({QuartzAutoConfiguration.class})
#RequiredArgsConstructor(onConstructor = #__({#Autowired, #NotNull}))
public class JobConfig {
private final List<Job> jobs;
private final DefaultListableBeanFactory beanFactory;
#PostConstruct
public void init() throws SchedulerConfigException, ParseException {
Set<Class<QuartzJobBean>> subClasses = ApplicationReflectionUtil.getSubClasses(QuartzJobBean.class, "io.rkpc");
for (Class<QuartzJobBean> quartzJobBeanClass : subClasses) {
Job job = getJob(quartzJobBeanClass.getSimpleName(), jobs);
if (job.isEnabled()) {
JobDetailImpl jobDetail = (JobDetailImpl) JobBuilder.newJob(quartzJobBeanClass)
.withIdentity(quartzJobBeanClass.getSimpleName())
.storeDurably()
.build();
CronTriggerImpl trigger = (CronTriggerImpl) TriggerBuilder.newTrigger().forJob(jobDetail)
.withIdentity(jobDetail.getKey().getName().concat("Trigger"))
.withSchedule(CronScheduleBuilder.cronSchedule(job.getCron()))
.build();
GenericBeanDefinition jobBeanDefinition = new GenericBeanDefinition();
jobBeanDefinition.setBeanClass(JobDetailImpl.class);
jobBeanDefinition.getPropertyValues().addPropertyValue("jobClass", quartzJobBeanClass);
jobBeanDefinition.getPropertyValues().addPropertyValue("key", jobDetail.getKey());
jobBeanDefinition.getPropertyValues().addPropertyValue("durability", jobDetail.isDurable());
beanFactory.registerBeanDefinition(quartzJobBeanClass.getSimpleName(), jobBeanDefinition);
GenericBeanDefinition triggerBeanDefinition = new GenericBeanDefinition();
triggerBeanDefinition.setBeanClass(CronTriggerImpl.class);
triggerBeanDefinition.getPropertyValues().addPropertyValue("jobKey", trigger.getJobKey());
triggerBeanDefinition.getPropertyValues().addPropertyValue("key", trigger.getKey());
triggerBeanDefinition.getPropertyValues().addPropertyValue("cronExpression", new CronExpression(trigger.getCronExpression()));
beanFactory.registerBeanDefinition(trigger.getName(), triggerBeanDefinition);
}
}
}
public Job getJob(String name, List<Job> jobs) throws SchedulerConfigException {
List<Job> filteredJobs = jobs.stream().filter(job -> job.getName().equals(name)).collect(Collectors.toList());
if (CollectionUtils.isEmpty(filteredJobs) || filteredJobs.size() > 1) {
log.error("{} is not configured", name);
throw new SchedulerConfigException("Job is not configured");
}
return filteredJobs.get(0);
}
#Data
public static class Job {
private String name;
private String cron;
private boolean enabled;
}
}
You are exposing a single Trigger[] bean rather than multiple Trigger beans. You should define one bean per Trigger. You should also do the same for each JobDetail. This was working by accident with Spring Boot 2.1.x as you were relying on the auto-configuration using ObjectProvider<Trigger[]>. The intent of the auto-configuration was to consume all Trigger beans, with the application context turning those beans into an array before injecting them.
If you don't want to define multiple Trigger and JobDetail beans, you may be better configuring the SchedulerFactoryBean yourself rather than relying on the auto-configuration.
I am trying to configure inbound and outbound adaptors as provided in the spring batch remote partitioning samples for Manager and worker beans. Having difficulty since they are configured in context of AMQPConnectionFactory.
However when I follow spring integration samples, there is no class which can provide Connection Factory. Help appreciated.
Below is sample code:-
import com.microsoft.azure.spring.integration.core.DefaultMessageHandler;
import com.microsoft.azure.spring.integration.core.api.CheckpointConfig;
import com.microsoft.azure.spring.integration.core.api.CheckpointMode;
import com.microsoft.azure.spring.integration.servicebus.inbound.ServiceBusQueueInboundChannelAdapter;
import com.microsoft.azure.spring.integration.servicebus.queue.ServiceBusQueueOperation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.integration.partition.RemotePartitioningManagerStepBuilderFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.concurrent.ListenableFutureCallback;
#Configuration
#IntegrationComponentScan
public class ManagerConfiguration {
private static final int GRID_SIZE = 3;
private static final String REQUEST_QUEUE_NAME = "digital.intg.batch.cm.request";
private static final String REPLY_QUEUE_NAME = "digital.intg.batch.cm.reply";
private static final String MANAGER_INPUT_CHANNEL = "manager.input";
private static final String MANGER_OUTPUT_CHANNEL = "manager.output";
private static final Log LOGGER = LogFactory.getLog(ManagerConfiguration.class);
private final JobBuilderFactory jobBuilderFactory;
private final RemotePartitioningManagerStepBuilderFactory managerStepBuilderFactory;
public ManagerConfiguration(JobBuilderFactory jobBuilderFactory,
RemotePartitioningManagerStepBuilderFactory managerStepBuilderFactory
) {
this.jobBuilderFactory = jobBuilderFactory;
this.managerStepBuilderFactory = managerStepBuilderFactory;
}
/*
* Configure outbound flow (requests going to workers)
*/
#Bean( name = MANGER_OUTPUT_CHANNEL )
public DirectChannel managerRequests() {
return new DirectChannel();
}
/*
* Configure inbound flow (replies coming from workers)
*/
#Bean( name = MANAGER_INPUT_CHANNEL )
public DirectChannel managerReplies() {
return new DirectChannel();
}
#Bean
public ServiceBusQueueInboundChannelAdapter managerQueueMessageChannelAdapter(
#Qualifier( MANAGER_INPUT_CHANNEL ) MessageChannel inputChannel, ServiceBusQueueOperation queueOperation) {
queueOperation.setCheckpointConfig(CheckpointConfig.builder().checkpointMode(CheckpointMode.MANUAL).build());
ServiceBusQueueInboundChannelAdapter adapter = new ServiceBusQueueInboundChannelAdapter(REPLY_QUEUE_NAME,
queueOperation);
adapter.setOutputChannel(inputChannel);
return adapter;
}
#Bean
#ServiceActivator( inputChannel = MANGER_OUTPUT_CHANNEL )
public MessageHandler managerQueueMessageSender(ServiceBusQueueOperation queueOperation) {
DefaultMessageHandler handler = new DefaultMessageHandler(REQUEST_QUEUE_NAME, queueOperation);
handler.setSendCallback(new ListenableFutureCallback<Void>() {
#Override
public void onSuccess(Void result) {
LOGGER.info("Manager Request Message was sent successfully.");
}
#Override
public void onFailure(Throwable ex) {
LOGGER.info("There was an error sending request message to worker.");
}
});
return handler;
}
#Bean
public IntegrationFlow managerOutboundFlow(MessageHandler managerQueueMessageSender) {
return IntegrationFlows
.from(managerRequests())
.handle(managerQueueMessageSender)
.get();
}
#Bean
public IntegrationFlow managerInboundFlow(ServiceBusQueueInboundChannelAdapter managerQueueMessageChannelAdapter) {
return IntegrationFlows
.from(managerQueueMessageChannelAdapter)
.channel(managerReplies())
.get();
}
/*
* Configure the manager step
*/
#Bean
public Step managerStep() {
return this.managerStepBuilderFactory.get("managerStep")
.partitioner("workerStep", new BasicPartitioner())
.gridSize(GRID_SIZE)
.outputChannel(managerRequests())
.inputChannel(managerReplies())
//.aggregator()
.build();
}
#Bean
public Job remotePartitioningJob() {
return this.jobBuilderFactory.get("remotePartitioningJob")
.start(managerStep())
.build();
}
}
The sample uses ActiveMQ because it is easily embeddable in a JVM for our tests and samples. But you can use any other broker that you want.
?? what should I inject here?
You should inject any dependency required by the queueMessageChannelAdapter handler:
.handle(queueMessageChannelAdapter)
I have written EntityListener using eclipseLink's "DescriptorEventAdapter". I tried almost all variations whatever present online BUT the entity which I am saving from my listener is not getting saved. I suspect something fishy is going on with transaction but didn't get the root cause. Here is the code :
package com.db;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;
import javax.transaction.Transactional;
import javax.transaction.Transactional.TxType;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.jpa.JpaEntityManager;
import org.eclipse.persistence.queries.InsertObjectQuery;
import org.eclipse.persistence.queries.UpdateObjectQuery;
import org.eclipse.persistence.sessions.changesets.DirectToFieldChangeRecord;
import org.eclipse.persistence.sessions.changesets.ObjectChangeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class NotificationMessageListener extends DescriptorEventAdapter {
public static Logger logger = LoggerFactory.getLogger(NotificationMessageListener.class);
private static final String targetColumn = "STATUS";
//private static AuditRepository auditRepo;
#Autowired
private StatusAuditRepository statusAuditRepo;
#Autowired
private RuleResultAuditRepository ruleResultRepo;
#Autowired
private EntityManagerFactory factory;
JpaEntityManager entityManager = null;
#PostConstruct
public void init() {
try {
entityManager = (JpaEntityManager) factory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(NotificationMessage.class);
// Add this class as a listener to the class descriptor
desc.getEventManager().addListener(this);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
/*#Autowired
public void setAuditRepo(AuditRepository auditRepo) {
NotificationMessageListener.auditRepo = auditRepo;
}*/
#Transactional(value = TxType.REQUIRES_NEW)
#Override
public void postInsert(DescriptorEvent event) {
logger.info("post insert is called ");
//NotificationMessage notificationMsg = (NotificationMessage) ((InsertObjectQuery) event.getQuery()).getObject();
//entityManager.getTransaction().begin();
NotificationStatusAudit statusAudit = new NotificationStatusAudit();
statusAudit.setInsertionTime(new Date());
//statusAudit.setNewVal(notificationMsg.getStatus());
statusAudit.setNewVal("abc");
statusAudit.setOldval("asdf");
statusAudit.setTargetColumnName("from listner");
//statusAudit.setTargetRecordId(notificationMsg.getId());
statusAudit.setTargetRecordId(123L);
statusAudit = statusAuditRepo.save(statusAudit);
//entityManager.getTransaction().commit();
//logger.info("Number of records "+statusAuditRepo.count());
//auditRuleResult(notificationMsg.getMessageCorrelationId() , true);
}
#Override
public void postUpdate(DescriptorEvent event) {
ObjectChangeSet objectChanges = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
DirectToFieldChangeRecord statusChanges = (DirectToFieldChangeRecord) objectChanges
.getChangesForAttributeNamed("status");
if (statusChanges != null && !statusChanges.getNewValue().equals(statusChanges.getOldValue())) {
NotificationStatusAudit statusAudit = new NotificationStatusAudit();
statusAudit.setInsertionTime(new Date());
statusAudit.setNewVal("abc");
statusAudit.setOldval("asdf");
statusAudit.setTargetColumnName(targetColumn);
statusAudit.setTargetRecordId((Long) objectChanges.getId());
statusAudit = statusAuditRepo.save(statusAudit);
}
}
}
Here all I have to do is save the record in another (Audit) table when data is getting inserted in one table. My application is spring boot app and am using eclipseLink for persistent. I had to manually register my entity-listener in "PostConstruct" because if it is registered using #EntityListner annotation , spring-data-repos were not getting autowired. Here are my questions :
1) Using EntityListener for my requirement is good approach or should I use direct "save" operations ?
2) I debugged the EntityListener code and method is not initiated a new Transaction even after adding Requires_new. I can see method is not being called $proxy (spring-proxy). I don't understand why ?
I am not sure about what you are doing in your #PostConstruct init() method... but I suspect you should be configuring this DescriptorEventAdapter using EclipseLink's DescriptorCustomizer. Here is an example:
public class MessageEventListener extends DescriptorEventAdapter implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) {
descriptor.getEventManager().addListener(this);
}
#Override
public void postUpdate(DescriptorEvent event) {
ObjectChangeSet objectChanges = ((UpdateObjectQuery) event.getQuery()).getObjectChangeSet();
//More business logic...
}
}
#Entity
#Customizer(MessageEventListener.class)
public class Message {
#Id private long id;
private String content;
}