Junit Test for Controller with parameters - spring-boot

Hi I'm trying to write Junit test for a controller but cant seem to find the proper approach can anyone please suggest how could write a junit test case for the following controller:
Contoller Class
public final class Contoller {
private static final Logger logger = LoggerFactory.getLogger(LiabilityContoller.class);
#Autowired
DemoService demoService;
#GetMapping("/report")
public ResponseEntity<String> getCollection(#RequestParam(name="cohort")String cohort){
List<Book> collection=demoService.getRecords(amount);
bookUtils.writeDataIntoCSVFile(collection, amount);
uploadReportsFilesToSftp(amount);
return new ResponseEntity<String>(" Files Are Generated", HttpStatus.OK);
}
Service class:
#Service
public class DemoService{
#Autowired
DemoRepository demoRepository;
public List<Liability> getRecords(String amount) {
List<Book> list=demoRepository.getRecordsByAmount(amount);
return list;
}
}
Repository
public interface DemoRepository extends JpaRepository<Book,Long>{
#Query(value="Select name,amount from Book where amount=:amount",
List<Book>getRecordsByAmount(String amount);
}
Utility:
public static void writeDataIntoReportsCSVFile(final List<Book> collection,final String amount,final String sftpLocalFile) {
try {
FileWriter FileWriter1 = new FileWriter(sftpLocalFile+"demo.csv");
CSVPrinter FilePrinter1 = new CSVPrinter(FileWriter1, CSVFormat.EXCEL);
printReportsHeader(FileWriter1);
for (Liability obj : collection) {
FilePrinter1.printRecord(obj.getName(),obj.getAmount());
}
FilePrinter1.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static void printReportsHeader(CSVPrinter FilePrinter1) {
try {
FilePrinter1.printRecord("NAME","AMOUNT");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
How could I test this controller what could be the best way to do so?

When you want to unit test a Spring application, the high level idea would be to write tests for the repository layer first, then write unit tests for the service, and then finally the controller layer.
Assuming that you have unit tests for DemoService, bookUtils, and uploadReportsFilesToSftp the Test for the controller would be exactly as below.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#ExtendWith(SpringExtension.class)
#WebMvcTest(Controller.class)
class ControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private DemoService jsonFileService;
#Test
void getCollection() throws Exception {
Mockito.doNothing().when(jsonFileService.getRecords(any()));
// similar stubbings here
this.mockMvc.perform(
get("/report")
.param("cohort", "testCohortValue")
).andExpect(
status().isOk()
);
}
}

Related

How to Capture ApplicatonEvent in Spring boot integration test?

The issue is that Application Event is not being captured in Spring boot test While it works fine for files listening to event in app project.
I want to capture an ApplicationEvent in Spring boot test(don't want to do Unit testing). My goal is to capture this application event and then perform few tasks in my test to verify the end-to-end functionality. Since, the event is not being captured in test case so I am not able to write integration tests.
Please let me know what is wrong with the code.
Thanks All.
package com.example.demo;
import org.springframework.context.ApplicationEvent;
public class CacheRefreshEvent extends ApplicationEvent {
private String message;
private static final long serialVersionUID = 1L;
public CacheRefreshEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
package com.example.demo;
import org.springframework.context.ApplicationEvent;
public class CacheRefreshCompleteEvent extends ApplicationEvent {
private String message;
private static final long serialVersionUID = 1L;
public CacheRefreshCompleteEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
package com.example.demo;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
#Component
public class CaptureCacheRefreshCompleteEvent implements ApplicationListener<CacheRefreshCompleteEvent> {
private ApplicationEventPublisher applicationEventPublisher;
void applicationEvent() throws InterruptedException {
applicationEventPublisher.publishEvent(new CacheRefreshEvent(this, "event triggered from SolrUtilitiesTest()"));
Thread.sleep(5000);
System.out.println("Finished execution of test.");
}
public void onApplicationEvent(CacheRefreshCompleteEvent cs) {
System.out.println("gotcha in CaptureCachedRefreshCompleteEvent");
}
public void setApplicationEventPublisher(ApplicationEventPublisher arg0) {
this.applicationEventPublisher = arg0;
}
}
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#DirtiesContext
#SpringBootTest
class DemoApplicationTests implements ApplicationEventPublisherAware, ApplicationListener<CacheRefreshCompleteEvent> {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Test
void applicationEvent() throws InterruptedException {
applicationEventPublisher.publishEvent(new CacheRefreshEvent(this, "event triggered from Springboot test"));
for(int i=0; i< 20; i ++) {
Thread.sleep(1000);
}
System.out.println("Finished execution of test.");
}
public void onApplicationEvent(CacheRefreshCompleteEvent cs) {
System.out.println("gotcha");
}
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher arg0) {
this.applicationEventPublisher = arg0;
}
}
One way could be to create a very simple listener with #TestComponent inside your test and autowire it as a #MockBean.
Proof of concept (tested with Spring Boot 2.2 and 2.1):
#SpringBootTest
public class PublishTest {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#MockBean
private Consumer consumer;
#Test
public void test() {
applicationEventPublisher.publishEvent(new TestEvent(this));
// events are synchronous by default
verify(consumer).consumeEvent(any(TestEvent.class));
}
#TestComponent
private static class Consumer {
#EventListener
public void consumeEvent(TestEvent testEvent) {
}
}
private static class TestEvent extends ApplicationEvent {
public TestEvent(Object source) {
super(source);
}
}
}

How to wait for a spring jms listener thread to finish executing in Junit test

I have a spring boot application that uses spring-JMS. Is there any way to tell the test method to wait the jms lister util it finishes executing without using latches in the actual code that will be tested?
Here is the JMS listener code:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyListener {
#Autowired
MyProcessor myProcessor;
#JmsListener(destination = "myQueue", concurrency = "1-4")
private void onMessage(Message message, QueueSession session) {
myProcessor.processMessage(message, session);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyProcessor {
public void processMessage(Message msg, QueueSession session) {
//Here I have some code.
}
}
import org.apache.activemq.command.ActiveMQTextMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.QueueSession;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class IntegrationTest {
#Autowired
private JmsTemplate JmsTemplate;
#Test
public void myTest() throws JMSException {
Message message = new ActiveMQTextMessage();
jmsTemplate.send("myQueue", session -> message);
/*
Here I have some testing code. How can I tell the application
to not execute this testing code until all JMS lister threads
finish executing.
*/
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.SocketUtils;
import javax.jms.ConnectionFactory;
#EnableJms
#Configuration
#Profile("test")
public class JmsTestConfig {
public static final String BROKER_URL =
"tcp://localhost:" + SocketUtils.findAvailableTcpPort();
#Bean
public BrokerService brokerService() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent(false);
brokerService.addConnector(BROKER_URL);
return brokerService;
}
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(BROKER_URL);
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return jmsTemplate;
}
}
Note: Is it applicable to solve this without adding testing purpose code to the implementation code (MyListener and MyProcessor).
Proxy the listener and add an advice to count down a latch; here's one I did for a KafkaListener recently...
#Test
public void test() throws Exception {
this.template.send("so50214261", "foo");
assertThat(TestConfig.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(TestConfig.received.get()).isEqualTo("foo");
}
#Configuration
public static class TestConfig {
private static final AtomicReference<String> received = new AtomicReference<>();
private static final CountDownLatch latch = new CountDownLatch(1);
#Bean
public static MethodInterceptor interceptor() {
return invocation -> {
received.set((String) invocation.getArguments()[0]);
return invocation.proceed();
};
}
#Bean
public static BeanPostProcessor listenerAdvisor() {
return new ListenerWrapper(interceptor());
}
}
public static class ListenerWrapper implements BeanPostProcessor, Ordered {
private final MethodInterceptor interceptor;
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
public ListenerWrapper(MethodInterceptor interceptor) {
this.interceptor = interceptor;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Listener) {
ProxyFactory pf = new ProxyFactory(bean);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(this.interceptor);
advisor.addMethodName("listen");
pf.addAdvisor(advisor);
return pf.getProxy();
}
return bean;
}
}
(but you should move the countDown to after the invocation proceed()).
A method annotated with #JmsListener deletes the message after it finishes, so a good option is to read the queue for existing messages and assume the queue is empty after your method is done. Here is the piece of code for counting the messages from the queue.
private int countMessages() {
return jmsTemplate.browse(queueName, new BrowserCallback<Integer>() {
#Override
public Integer doInJms(Session session, QueueBrowser browser) throws JMSException {
return Collections.list(browser.getEnumeration()).size();
}
});
}
Following is the code for testing the countMessages() method.
jmsTemplate.convertAndSend(queueName, "***MESSAGE CONTENT***");
while (countMessages() > 0) {
log.info("number of pending messages: " + countMessages());
Thread.sleep(1_000l);
}
// continue with your logic here
I've based my solution on the answer given by Gary Russell, but rather put the CountDownLatch in an Aspect, using Spring AOP (or the spring-boot-starter-aop variant).
public class TestJMSConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TestJMSConfiguration.class);
public static final CountDownLatch countDownLatch = new CountDownLatch(1);
#Component
#Aspect
public static class LatchCounterAspect {
#Pointcut("execution(public void be.infrabel.rocstdm.application.ROCSTDMMessageListener.onMessage(javax.jms.TextMessage))")
public void onMessageMethod() {};
#After(value = "onMessageMethod()")
public void countDownLatch() {
countDownLatch.countDown();
LOGGER.info("CountDownLatch called. Count now at: {}", countDownLatch.getCount());
}
}
A snippet of the test:
JmsTemplate jmsTemplate = new JmsTemplate(this.embeddedBrokerConnectionFactory);
jmsTemplate.convertAndSend("AQ.SOMEQUEUE.R", message);
TestJMSConfiguration.countDownLatch.await();
verify(this.listenerSpy).putResponseOnTargetQueueAlias(messageCaptor.capture());
RouteMessage outputMessage = messageCaptor.getValue();
The listenerSpy is a #SpyBean annotated field of the type of my MessageListener. The messageCaptor is a field of type ArgumentCaptor<MyMessageType> annotated with #Captor. Both of these are coming from mockito so you need to run/extend your test with both MockitoExtension (or -Runner) along with the SpringExtension (or -Runner).
My code puts an object on an outbound queue after processing the incoming message, hence the putResponseOnTargetQueueAlias method. The captor is to intercept that object and do my assertions accordingly. The same strategy could be applied to capture some other object in your logic.

Spring repository mvc how autowiring through interfaces and reaching specific repository implementations from a controller?

Good Morning,
I am building a web application and I chose to do it with an annotation driven spring mvc with REST Webservices (Jackson).
I am not using spring-boot because I wanted to add the libraries gradually when I needed them.
When I try to reach my specific repository with String str = ((GroupeMaterielRepository) repository).test(); i get a
java.lang.ClassCastException: com.sun.proxy.$Proxy210 cannot be cast
to pro.logikal.gestsoft.repository.GroupeMaterielRepository]
I would like to know how to access to my specific repository methods in which my HQL requests would be stored. I am trying to find a solution for days without success. The best I could do so far was accessing my CRUD methods in the generic repository implementation, but this implies to store in my repository interface every HQL method in the app, which will result as ugly.
I would like you to help me to get this code to work, keeping the logic of autowiring through interface's implementations extended by a more specific class with a controller layer and a repository layer.
Generic Controller :
package pro.logikal.gestsoft.controller;
import pro.logikal.gestsoft.repository.GenericCRUD;
public class GenericRestController<T> {
protected GenericCRUD<T> repository;
public GenericRestController(GenericCRUD<T> repository) {
this.repository = repository;
}
public GenericCRUD<T> getRepository() {
return repository;
}
}
Specific Controller :
package pro.logikal.gestsoft.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import pro.logikal.gestsoft.entity.GroupeMateriel;
import pro.logikal.gestsoft.repository.GenericCRUD;
import pro.logikal.gestsoft.repository.GroupeMaterielRepository;
#RestController
public class MaterielRESTController extends GenericRestController<GroupeMateriel> {
#Autowired
public MaterielRESTController(GenericCRUD<GroupeMateriel> repository) {
super(repository);
// TODO Auto-generated constructor stub
}
#GetMapping("/mat/groupes")
public ResponseEntity<String> getGroupes(){
String str = ((GroupeMaterielRepository) repository).test();
return new ResponseEntity<String>(str, HttpStatus.OK);
}
}
Repository Interface :
package pro.logikal.gestsoft.repository;
import java.util.List;
public interface GenericCRUD<T> {
void create(T entity);
void update(T entity);
void refresh(T entity);
void delete(Integer id);
T find (Integer id);
List<T> list();
}
Repository implementation :
package pro.logikal.gestsoft.repository;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.transaction.annotation.Transactional;
import pro.logikal.gestsoft.statics.ClientRequestUtils;
import pro.logikal.gestsoft.statics.DatabaseUtils;
#SuppressWarnings("unchecked")
#Transactional(DatabaseUtils.TM_GESTSOFT)
public class GenericCRUDImpl<T> implements GenericCRUD<T> {
#SuppressWarnings("unused")
public final Class<T> persistentClass;
#Autowired
#Qualifier(DatabaseUtils.GESTSOFT_SESSION)
public SessionFactory sessionFactory;
protected Session getCurrentSession() {
Session session = sessionFactory.getCurrentSession();
return session;
}
public GenericCRUDImpl(){
this.persistentClass= (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
#Override
public void create(final T entity) {
this.getCurrentSession().save(entity);
}
#Override
public void update(final T entity) {
this.getCurrentSession().update(entity);
}
#Override
public void refresh(final T entity) {
this.getCurrentSession().refresh(entity);
}
#Override
public void delete(Integer id) {
this.getCurrentSession().delete(this.find(id));
}
#Override
public T find(Integer id) {
// TODO Auto-generated method stub
return this.getCurrentSession().get(persistentClass, id);
}
#Override
public List<T> list() {
return this.getCurrentSession().createQuery("from "+persistentClass.getTypeName()).getResultList();
}
}
Repository associated to an entity and which is meant to contain the HQL requests for the related entities :
package pro.logikal.gestsoft.repository;
import org.springframework.stereotype.Repository;
import pro.logikal.gestsoft.entity.GroupeMateriel;
#Repository
public class GroupeMaterielRepository extends GenericCRUDImpl<GroupeMateriel> {
public String test() {
return "ok";
}
}
Just found out from where my problem came from reading [https://stackoverflow.com/a/6512431/8822802][1]
in my case #EnableAspectJAutoProxy(proxyTargetClass = true) on my config file

Spring/Mockito Junit testing Null Pointer Exception

Trying to get JUnit with mockito testing to work and getting a NPE on my test here. I'm wanting to have a verify that when createRegistrant(registrationDTO) is called, saveUpdateRegistration(Registration reg) method is called right after. An issue I find strange is that on Mockito.verify(registrationServiceImpl, Mockito.times(1)).createRegistrant(registrationDTO); it is not suggesting I can test if saveUpdateRegistration(Registration reg) method was called? IntelliJ only suggests methods that are in my interface?
My test code below:
#RunWith(MockitoJUnitRunner.class)
public class RegistrationServiceImplTest {
#InjectMocks
private RegistrationServiceImpl registrationServiceImpl;
#Mock
private RegistrationDTO registrationDTO;
#Mock
private Registration registration;
#Before
public void setup() {
registrationDTO.seteId("abc123");
registrationDTO.setTech("AWS");
registrationDTO.setFirstName("Bob");
registrationDTO.setLastName("Tom");
}
#Test
public void createRegistrant() throws Exception {
// Mockito.when(registrationServiceImpl.createRegistrant(registrationDTO)).thenReturn(registrationDTO);
Mockito.when(registrationServiceImpl.createRegistrant(registrationDTO)).thenReturn(registrationDTO);
registrationServiceImpl.createRegistrant(registrationDTO);
Mockito.verify(registrationServiceImpl, Mockito.times(1)).createRegistrant(registrationDTO);
}
Here is the code I'm testing:
package com.TechODex.service;
import com.TechODex.dto.RegistrationDTO;
import com.TechODex.model.Registration;
import com.TechODex.dao.RegistrationDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
#Service
#Transactional
public class RegistrationServiceImpl implements RegistrationService {
#Autowired
private RegistrationDAO registrationDAO;
public RegistrationDTO createRegistrant(RegistrationDTO reg) {
saveUpdateRegistration(setDTOToModel(reg));
return reg;
}
public void deleteRegistrant(Long id) { deleteUserRegistrant(id); }
public List<Registration> findAllRegistrant(String tech) { return findAllRegistrantDAO(tech);}
public List<Registration> findAllTech() { return findAllTechDAO();}
private Registration setDTOToModel(RegistrationDTO dto){
Registration registration = new Registration();
registration.setFirstName(dto.getFirstName());
registration.setLastName(dto.getLastName());
registration.seteId(dto.geteId());
registration.setTech(dto.getTech());
return registration;
}
private void saveUpdateRegistration(Registration reg){ registrationDAO.save(reg);}
}
There are several issues here.
First, you do not mock RegistrationDAO. This will lead to it being null in the class under test. Next, RegistrationService is not a mock, yet you try to use when and verifyon it. Last, RegistrationDTO is mocked, so the invocations to the setters in #Before has no effect.
Your test should look something like:
#RunWith(MockitoJUnitRunner.class)
public class RegistrationServiceImplTest {
#InjectMocks
private RegistrationServiceImpl registrationServiceImpl;
#Mock
private RegistrationDAO registrationDAO;
private RegistrationDTO registrationDTO;
private Registration registration;
#Before
public void setup() {
registration = new Registration();
registration.seteId("abc123");
registration.setTech("AWS");
registration.setFirstName("Bob");
registration.setLastName("Tom");
registrationDTO = new RegistrationDTO();
registrationDTO.seteId("abc123");
registrationDTO.setTech("AWS");
registrationDTO.setFirstName("Bob");
registrationDTO.setLastName("Tom");
}
#Test
public void createRegistrant() throws Exception {
final RegistrationDTO result = registrationServiceImpl.createRegistrant(registrationDTO);
assertSame(registrationDTO, result);
verify(registrationDAO).save(eq(registration));
}
}
given that Registration has an implemented equals-method where the fields that are set are compared. If not, you need to use an ArgumentCaptor.

Entity not getting saved in EclipseLink EntityListener

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;
}

Resources