I have following configuration in my spring boot application. and I am throwing RuntimeException from Repository layer and Service layer to see transaction handling. When I throw RuntimeException from repository layer, the transaction is getting rolled back, however, if I throw Runtime exception from Service layer as shown below, the transaction is not getting rolled back and data is saved into the database. Could anybody help me what is wrong with the following configuration?
#Configuration
#SpringBootApplication
#EnableAsync
#EnableScheduling
#ComponentScan(basePackages = { "com.abc.xyz.api.config", Constant.BASE_PACKAGE, com.abc.c4s.util.Constant.BASE_PACKAGE })
#EnableAutoConfiguration(exclude = { JndiConnectionFactoryAutoConfiguration.class, DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
#PropertySources({
#PropertySource(value = "classpath:jdbc.properties")
})
#EnableTransactionManagement
public class ProjectManagerApplication {
public static ConfigurableApplicationContext context;
private static final Logger logger = LoggerFactory.getLogger(ProjectManagerApplication.class);
public static ConfigurableApplicationContext startServer() throws Exception {
logger.debug("Starting server");
SpringApplicationBuilder builder = new SpringApplicationBuilder(ProjectManagerApplication.class);
builder.headless(false);
context = builder.run(new String[] {});
logger.debug("Server started - " + (context != null ? context.isRunning() : "context null, starting failed"));
return context;
}
}
And, Following is my controller class configuration
#RestController
public class RWSettingsController {
#Autowired
SettingsService rwSettingsService;
#RequestMapping(value = "/api/settings", method = RequestMethod.POST)
public void createKiosk(#RequestBody SettingsDTO settingDTO) throws ABCInternalException, ABCException {
//try{
settingsService.create(settingDTO);
//}catch (Exception e){
// e.printStackTrace();
// }
}
}
This is my service class.
#Service
#Transactional//(propagation = Propagation.SUPPORTS, readOnly = true)
public class SettingsService {
#Autowired
SettingsRepository settingsRepository;
#Autowired
ModelMapper modelMapper;
private static final Logger logger = LoggerFactory.getLogger(SettingsService.class);
// #Transactional(
// propagation = Propagation.REQUIRED,
// readOnly = false)
public void create(SettingsDTO settingsDTO) throws ButterflyInternalException, ButterflyException {
try{
SettingsModel settingsModel = new SettingsModel ();
settingsModel .setActive(true);
settingsModel .setParameterDescription(settingsDTO.getParameterDescription());
settingsModel .setParameterName(settingsDTO.getParameterName());
settingsModel .setParameterValue(settingsDTO.getParameterValue());
//throw new HibernateException("");
rwsSettingsRepository.create(rwsKioskSettingsBPA);
throw new RuntimeException("");
}catch(ABCException e){
logger.error(e.getMessage(),e);
throw e;
}catch(Exception e){
logger.error(e);
throw e;
}
}
}
And my repository class is as follows
#Repository
//#Transactional//(propagation = Propagation.SUPPORTS, readOnly = true)
public class SettingsRepository {
#Autowired
#Qualifier("sessionFactory")
SessionFactory abcSessionFactory;
#Autowired
ABCUtilityRepository abcUtilityRepository;
private static final Logger logger = LoggerFactory.getLogger(SettingsRepository .class);
// #Transactional(
// propagation = Propagation.REQUIRED,
// readOnly = false)
public void create(RWSKioskSettingsBPA rwsKioskSettings) throws ABCException, ABCInternalException {
abcUtilityRepository.saveEntity(rwsKioskSettings);
//throw new RuntimeException();
}
I found what was the reason behind this behavior. Spring boot rolls back the transaction if application throw runtime exception. In my service layer, I catch the runtime exception and throw the application specific exception which is the reason why spring does not roll back the transaction. So after changing the code to throw the same runtime exception that was originally generated, application successfully rolled back the transaction.
Above code will run if I put this in the service layer
#Transactional(rollbackFor = Exception.class)
Related
Spring #Autowire field is null even though it works fine in other classes successfully.
public class SendRunner implements Runnable {
private String senderAddress;
#Autowired
private SubscriberService subscriberService;
public SendRunner(String senderAddress) {
this.senderAddress = senderAddress;
}
#Override
public void run() {
sendRequest();
}
private void sendRequest() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
Also this class is managed as a bean in the dispatcher servlet :
<bean id="SendRunner" class="sms.dating.messenger.connector.SendRunner">
</bean>
In here i'm getting a null pointer exception for subscriberService. What would be the possible reason for this? Thanks in advance.
Can you please try with below code snippet
#Configuration
public class Someclass{
#Autowired
private SubscriberService subscriberService;
Thread subscriberThread = new Thread() {
#Override
public void run() {
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("subscriberId", senderAddress);
HttpEntity<?> entity = new HttpEntity<Object>(dataMap, httpHeaders);
Subscriber subscriber = subscriberService.getSubscriberByMsisdn(senderAddress);
} catch (Exception e) {
logger.error("Error occurred while trying to send api request", e);
}
}
};
}
Can you please annotate your SendRunner class with #Component or #Service and include the SendRunner package in componentscanpackage
Your bean not in Spring Managed context, below can be the reasons.
Package sms.dating.messenger.connector not in Component scan.
You are moving out of the Spring context by creating an object with new (see below),
this way you will not get the autowired fields.
SendRunner sendRunner = new SendRunner () ,
sendRunner.sendRequest();
Just check how I implement. Hope this will help.
#RestController
public class RestRequest {
#Autowired
SendRunner sendRunner;
#RequestMapping("/api")
public void Uri() {
sendRunner.start();
}
}
SendRunner class
#Service
public class SendRunner extends Thread{
#Autowired
private SubscriberService subscriberService;
#Override
public void run() {
SendRequest();
}
private void SendRequest() {
System.out.println("Object is " + subscriberService);
String senderAddress = "address";
subscriberService.getSubscriberByMsisdn(senderAddress);
}
}
Below are the logs printed when I hit the REST api.
Object is com.example.demo.SubscriberService#40f33492
#RestController
#RequestMapping("/Api/Order")
public class OrderController {
private OrderService service;
private RefundService refundService;
#AsCustomer
#DeleteMapping(value = "/{orderID}/RefundApplication")
#Transactional(rollbackFor = RuntimeException.class)
public Map cancelRefundApplication(#SessionAttribute("user") User user,
#PathVariable("orderID") String orderID) {
Order order = service.getOrderByID(orderID);
RefundApplication application = refundService.get(orderID);
order.setState(Order.STATE_PAYED);
refundService.delete(orderID);
service.updateOrder(order);
throw new EntityNotFoundException("test");
}
...
I want transaction created in cancelRefundApplication method to be rolled back when a RuntimeException is thrown, and to be commit if no RuntimeException is thrown. But I find the transaction is not rolled back even if a RuntimeException is thrown. For test perpose, I change the code to make it always throw a EntityNotFoundException, and test it with following test method. After running the test, I check database and find refund application data is deleted, which means transaction is not rolled back and #Transactional annotation is not working.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {WebConfig.class, RootConfig.class, DataConfig.class})
#WebAppConfiguration
class OrderControllerTest {
#Autowired
OrderController controller;
#Autowired
UserService userService;
#Autowired
OrderService orderService;
#Autowired
AppWideExceptionHandler exceptionHandler;
private User customer;
private User seller;
private HashMap<String, Object> sessionAttrs;
private ResultMatcher success = jsonPath("$.code")
.value("0");
private MockMvc mockMvc;
#Test
void cancelRefundApplication() throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
String path = String.format("/Api/Order/%s%d0001/RefundApplication"
, simpleDateFormat.format(new Date()), customer.getID());
mockMvc.perform(delete(path)
.characterEncoding("UTF-8")
.sessionAttrs(sessionAttrs))
.andDo(print())
.andExpect(success);
}
...
This is DataConfig class:
#Configuration
#MapperScan("youshu.mapper")
public class DataConfig {
#Bean
public DataSource dataSource() {
// org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
PooledDataSource pds = new PooledDataSource();
pds.setDriver("com.mysql.cj.jdbc.Driver");
pds.setUsername(...);
pds.setPassword(...);
pds.setUrl("jdbc:mysql://XXXX");
return pds;
}
#Bean
public JdbcOperations jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setTypeAliasesPackage("youshu.entity");
return sessionFactory.getObject();
}
#Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
#Bean
public SqlSessionTemplate sqlSession(SqlSessionFactory factory){
return new SqlSessionTemplate(factory);
}
}
Transactions need to be enabled manually by annotating config class with #EnableTransactionManagement
Check include or not TransactionalTestExecutionListener in your test, if not add: #TestExecutionListeners(listeners = {TransactionalTestExecutionListener.class})
I am writing Kafka Consumer Unit Test, and need to Mock the Service of my KafkaConsumer for testing the Kafka Consumer independently. But, the mockObject of Service is not getting invoked, instead Spring is creating the original Service class object and calling it. Thus, my mock class object not getting called.
KafkaConsumer :
#Slf4j
#Component
#RequiredArgsConstructor (onConstructor = #__(#Autowired))
public class KafkaEventConsumer {
private final MyService requestService;
#KafkaListener (topics = "${kafka.topic:topic-name}")
public void receive(#Payload String message) throws Exception {
try {
LOGGER.debug("Received message:{} ", message);
ObjectMapper mapper = new ObjectMapper();
ForecastRequest forecastRequest = mapper.readValue(message, ForecastRequest.class);
JobDetail jobDetail = requestForecastService.refreshForecasts(forecastRequest);
if (jobDetail.getJobStatus() != JobStatus.complete) {
LOGGER.error("Failed to Refresh Forecast for ProgramId-{}, JobId-{}, JobStatus-{}",
forecastRequest.getProgramId(), jobDetail.getJobId(), jobDetail.getJobStatus());
throw new Exception("Internal Server Error");
}
} catch (Exception e) {
LOGGER.error("Failed to Refresh Forecast for Forecast Request {}", message, e);
throw e;
}
}
}
Kafka Consumer Test :
#RunWith (SpringRunner.class)
#ActiveProfiles ("kafkatest")
#SpringBootTest (classes = ForecastEventConsumerApplication.class)
#DirtiesContext
public class KafkaEventConsumerTest {
private static String TOPIC = "topic-name";
#Mock
private MyServiceImpl myServiceMock;
#InjectMocks
private KafkaEventConsumer kafkaEventConsumer;
private KafkaTemplate<String, String> template;
#Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
#ClassRule
public static final KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true,3, TOPIC);
#Before
public void setUp() throws Exception {
kafkaEventConsumer = new KafkaEventConsumer(myServiceMock);
// set up the Kafka producer properties
Map<String, Object> senderProperties = KafkaTestUtils.senderProps(embeddedKafka.getBrokersAsString());
// create a Kafka producer factory
ProducerFactory<String, String> producerFactory = new DefaultKafkaProducerFactory<String, String>(senderProperties);
// create a Kafka template
template = new KafkaTemplate<>(producerFactory);
// set the default topic to send to
template.setDefaultTopic(TOPIC);
// wait until the partitions are assigned
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
messageListenerContainer.setupMessageListener(new MessageListener<String, String>() {
#Override
public void onMessage(ConsumerRecord<String, String> record) {
try {
kafkaEventConsumer.receive(record.value());
} catch (Exception e) {
e.printStackTrace();
}
}
});
ContainerTestUtils.waitForAssignment(messageListenerContainer, embeddedKafka.getPartitionsPerTopic());
}
}
#AfterClass
public static void tearDown() throws Exception {
embeddedKafka.destroy();
}
#Test
public void testReceive() throws Exception {
String forecastRequestMessage = "{\"programId\":100011770}";
ForecastRequest forecastRequest = ForecastRequest.builder().programId(100011770L).build();
JobDetail jobDetail = JobDetail.builder().jobStatus(JobStatus.complete).build();
Mockito.when(forecastServiceMock.refreshForecasts(Matchers.any())).thenReturn(jobDetail);
template.sendDefault(forecastRequestMessage);
Thread.sleep(2000L);
// validate something
}
}
The problem is, in the above #Test method instead of calling the mocked version of MyService it is calling the original MyService implementation. Also, while debugging my code I found that overridden onMessage() is also not getting called. Please help me in finding what am I doing wrong here.
You have to stop() all the MessageListenerContainers before calling their setupMessageListener(). Then you will need to start() them back to let them to pick up a fresh listener:
protected void doStart() {
...
Object messageListener = containerProperties.getMessageListener();
Assert.state(messageListener != null, "A MessageListener is required");
Anyway that sounds like you really would like to mock only your MyService which is injected into the real KafkaEventConsumer. So, how about to consider to use that like this:
#MockBean
private MyServiceImpl myServiceMock;
And you won't need to do anything in your #Before and no need in the #InjectMocks.
The KafkaEmbedded can expose its host/port (or brokers) properties to the expected Spring Boot conventional configuration properties like this:
#BeforeClass
public static void setup() {
System.setProperty("spring.kafka.bootstrap-servers", kafkaEmbedded.getBrokersAsString());
}
https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/htmlsingle/#boot-features-testing-spring-boot-applications-mocking-beans
I had add #Transactional on the method in service layer.
#Transactional(readOnly = false)
public void add(UserFollow uf){
UserFollow db_uf = userFollowRepository.findByUserIdAndFollowUserId(uf.getUserId(), uf.getFollowUserId());
if(db_uf == null) {
userFollowRepository.save(uf);
userCountService.followInc(uf.getFollowUserId(), true);
userCountService.fansInc(uf.getUserId(), true);
throw new RuntimeException();// throw an Exception
}
}
userFollowRepository.save(uf); still save seccessful,doesn't rollback...
i enable transaction manager on the Application.
#Configuration
#ComponentScan
#EnableAutoConfiguration
#EnableJpaRepositories
#EnableTransactionManagement
public class Application {
#Bean
public AppConfig appConfig() {
return new AppConfig();
}
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
i move #Transactional to Control layer, it works, the code:
#Transactional
#RequestMapping(value="following", method=RequestMethod.POST)
public MyResponse follow(#RequestBody Map<String, Object> allRequestParams){
MyResponse response = new MyResponse();
Integer _userId = (Integer)allRequestParams.get("user_id");
Integer _followUserId = (Integer)allRequestParams.get("follow_user_id");
userFollowService.add(_userId, _followUserId); //this will throw an exception, then rollback
return response;
}
can anyone tell me reason, thanks!
According to http://spring.io/guides/gs/managing-transactions/
#EnableTransactionManagement activates Spring’s seamless transaction features, which makes #Transactional function
so it started to work after you had added #EnableTransactionManagement
I have the following simple controller to catch any unexpected exceptions:
#ControllerAdvice
public class ExceptionController {
#ExceptionHandler(Throwable.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ResponseEntity handleException(Throwable ex) {
return ResponseEntityFactory.internalServerErrorResponse("Unexpected error has occurred.", ex);
}
}
I'm trying to write an integration test using Spring MVC Test framework. This is what I have so far:
#RunWith(MockitoJUnitRunner.class)
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new ExceptionController(), statusController).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
I register the ExceptionController and a mock StatusController in the Spring MVC infrastructure.
In the test method I setup an expectation to throw an exception from the StatusController.
The exception is being thrown, but the ExceptionController isn't dealing with it.
I want to be able to test that the ExceptionController gets exceptions and returns an appropriate response.
Any thoughts on why this doesn't work and how I should do this kind of test?
Thanks.
I just had the same issue and the following works for me:
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(statusController)
.setControllerAdvice(new ExceptionController())
.build();
}
This code will add ability to use your exceptions controlled advice.
#Before
public void setup() {
this.mockMvc = standaloneSetup(commandsController)
.setHandlerExceptionResolvers(withExceptionControllerAdvice())
.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
}
private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
#Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod,
final Exception exception) {
Method method = new ExceptionHandlerMethodResolver(ExceptionController.class).resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(new ExceptionController(), method);
}
return super.getExceptionHandlerMethod(handlerMethod, exception);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
Since you are using stand alone setup test you need to provide exception handler manually.
mockMvc= MockMvcBuilders.standaloneSetup(adminCategoryController).setSingleView(view)
.setHandlerExceptionResolvers(getSimpleMappingExceptionResolver()).build();
I had same problem a few days back, you can see my problem and solution answered by myself here Spring MVC Controller Exception Test
Hoping my answer help you out
Use Spring MockMVC to emulate a servletContainer to a point where you can incorporate any request filtering or exception handling tests in your unit tests suite.
You can configure this setup with the following approach:
Given a custom RecordNotFound exception...
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Record not found") //
public class RecordNotFoundException extends RuntimeException {
private static final long serialVersionUID = 8857378116992711720L;
public RecordNotFoundException() {
super();
}
public RecordNotFoundException(String message) {
super(message);
}
}
... and a RecordNotFoundExceptionHandler
#Slf4j
#ControllerAdvice
public class BusinessExceptionHandler {
#ExceptionHandler(value = RecordNotFoundException.class)
public ResponseEntity<String> handleRecordNotFoundException(
RecordNotFoundException e,
WebRequest request) {
//Logs
LogError logging = new LogError("RecordNotFoundException",
HttpStatus.NOT_FOUND,
request.getDescription(true));
log.info(logging.toJson());
//Http error message
HttpErrorResponse response = new HttpErrorResponse(logging.getStatus(), e.getMessage());
return new ResponseEntity<>(response.toJson(),
HeaderFactory.getErrorHeaders(),
response.getStatus());
}
...
}
Configure a tailored test context: set a #ContextConfiguration to specify the classes you need for your test. Set Mockito MockMvc as a servlet container emulator and set your tests fixture and dependencies.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {
WebConfig.class,
HeaderFactory.class,
})
#Slf4j
public class OrganisationCtrlTest {
private MockMvc mvc;
private Organisation coorg;
#MockBean
private OrganisationSvc service;
#InjectMocks
private OrganisationCtrl controller = new OrganisationCtrl();
//Constructor
public OrganisationCtrlTest() {
}
....
Configure a mock MVC "servlet emulator": register handler beans in the context and build the mockMvc emulator (Note: there are two possible configuration: standaloneSetup or webAppContextSetup; refer to the documentation). The builder rightfully implements the Builder pattern so you can chain configuration commands for exception resolvers and handlers before calling build().
#Before
public void setUp() {
final StaticApplicationContext appContext = new StaticApplicationContext();
appContext.registerBeanDefinition("BusinessExceptionHandler",
new RootBeanDefinition(BusinessExceptionHandler.class, null, null));
//InternalExceptionHandler extends ResponseEntityExceptionHandler to //handle Spring internally throwned exception
appContext.registerBeanDefinition("InternalExceptionHandler",
new RootBeanDefinition(InternalExceptionHandler.class, null,
null));
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(getExceptionResolver(appContext))
.build();
coorg = OrganisationFixture.getFixture("orgID", "name", "webSiteUrl");
}
....
Get the exception resolver
private ExceptionHandlerExceptionResolver getExceptionResolver(
StaticApplicationContext context) {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.getMessageConverters().add(
new MappingJackson2HttpMessageConverter());
resolver.setApplicationContext(context);
resolver.afterPropertiesSet();
return resolver;
}
Run your tests
#Test
public void testGetSingleOrganisationRecordAnd404() throws Exception {
System.out.println("testGetSingleOrganisationRecordAndSuccess");
String request = "/orgs/{id}";
log.info("Request URL: " + request);
when(service.getOrganisation(anyString())).
thenReturn(coorg);
this.mvc.perform(get(request)
.accept("application/json")
.andExpect(content().contentType(
.APPLICATION_JSON))
.andExpect(status().notFound())
.andDo(print());
}
....
}
Hope this helps.
Jake.
Try it;
#RunWith(value = SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { MVCConfig.class, CoreConfig.class,
PopulaterConfiguration.class })
public class ExceptionControllerTest {
private MockMvc mockMvc;
#Mock
private StatusController statusController;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
#Test
public void checkUnexpectedExceptionsAreCaughtAndStatusCode500IsReturnedInResponse() throws Exception {
when(statusController.checkHealth()).thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/api/status"))
.andDo(print())
.andExpect(status().isInternalServerError())
.andExpect(jsonPath("$.error").value("Unexpected Exception"));
}
}
This is better:
((HandlerExceptionResolverComposite) wac.getBean("handlerExceptionResolver")).getExceptionResolvers().get(0)
And do not forget to scan for #ControllerAdvice beans in your #Configuration class:
#ComponentScan(basePackages = {"com.company.exception"})
...tested on Spring 4.0.2.RELEASE