RestClientException doesnot Rollback entire transaction - spring

Please refer to the below snippet of code
#Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { Throwable.class })
#Override
public MyClass myMethod(myParams) {
try{
myRepo.saveAll(myEntityList);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(myUrl,requestParam,String.class);
if(responseEnity.getBody()!=null && StringUtils.equalsIgnoreCase(responseEnity.getBody(),"Failure")){
log.error("Rest Call Failed");
throw new RestClientException("Error in remote service");
}
}catch(Exception e){
e.printStackTrace();
}
}
Now in the console log, I can see RestClientException being thrown but myRepo.saveAll(myEntityList); doesn't rollback instead it is inserting data into DB.
Can anyone figure out the issue?
My requirement is that in RestServiceCall if I get a failure response then my current transaction should get rollback entirely.

Your transaction is not rollbacked because there is no exception thrown, you're catching it.
As #Sumit Ghost mentioned, you can log it and rethrow the exception

Related

MockMvc : handling unique constraints

In my project I have a unique constraint on a name value which I annotated like so:
#Column(unique = true)
protected String name;
this is fine, and works...what I want to do in the project deployment, is when a user tries to enter an object in the database with a name that already exists, they'll be presented with an error message "an entry with that name already exists" or something similar and then be redirected back to their form.
In testing with MockMvc, I have something like this:
try {
mockMvc.perform(post(uri)
.contentType(MediaType.APPLICATION_JSON)
.content(widgetJson)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isNotFound());
} catch (Exception e) {
logger.error("we got expected exception here", e);
threwException = true;
e.printStackTrace();
}
after that block, if the threwException var is false, the test fails. Normally, I do not have try/catch in my MockMvc unit tests, rather a throws Exception in the signature.
The way I have it now works, and gives me the results I expect, but I don't get a response. I tried adding a MvcResult result = before the perform call, and adding the .andReturn() at the end, but I never get an actual response and can't determine what the response code is.
I figure this has to be a pretty common use case and was wondering if there is a best practice as to how to set up the test.
thanks!
This might help to solve your problem:
MvcResult result = null;
try {
ResultActions resultActions = mockMvc.perform(post(uri)
.contentType(MediaType.APPLICATION_JSON)
.content(widgetJson)
.accept(MediaType.APPLICATION_JSON));
result = resultActions.andReturn();
resultActions
.andDo(print())
.andExpect(status().isNotFound());
} catch (Exception e) {
if(result != null) {
logger.error("Response Body", result.getResponse().getContentAsString());
logger.error("Response Status", result.getResponse().getStatus() ());
}
logger.error("we got expected exception here", e);
threwException = true;
e.printStackTrace();
}
Reference:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/MvcResult.html#getResponse--
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/mock/web/MockHttpServletResponse.html#getContentAsString--
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/ResultActions.html#andReturn--

java.lang.IllegalStateException: getWriter() has already been called for this response, Even thought its called only once

I am using spring-boot. I want to send a CSV as the attachment of response for which I am using opencsv to write bean to response. Even though response.getWriter() is called only once, I am getting this exception.
While searching for solution, I came to know that we cannot use response.getWriter() and response.getOutputStream() together. But this is not the case here.
I am only calling getWriter and exactly once.
I also checked if it was due to opencsv library by writing a plain string to it e.g. "name, test". But still the same error. So it's not due to opencsv either.
private Pair<Boolean, String> writeCSVToResponse(List<QuestionDownloadResponse> qdrList, HttpServletResponse response) {
String fileName = new SimpleDateFormat("'CSV'yyyyMMddHHmmss'.csv'").format(new Date());
response.reset();
response.setContentType("application/csv");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"");
response.setCharacterEncoding("UTF-8");
//Find answer, for now we will return any if exists
try {
Writer writer = response.getWriter();
// Create Mapping Strategy to arrange the
// column name in order
final CustomCSVMappingStrategy<QuestionDownloadResponse> mappingStrategy = new CustomCSVMappingStrategy<>();
mappingStrategy.setType(QuestionDownloadResponse.class);
StatefulBeanToCsv<QuestionDownloadResponse> sbc = new StatefulBeanToCsvBuilder<QuestionDownloadResponse>(writer)
.withSeparator(CSVWriter.DEFAULT_SEPARATOR)
.withMappingStrategy(mappingStrategy)
.build();
sbc.write(qdrList);
} catch (IOException e) {
CMSQuestionServiceImpl.logger.error("Error in CSV IO operations", e);
return Pair.of(false, "Failed to Open file");
} catch (CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
CMSQuestionServiceImpl.logger.error("Error in CSV Write operation", e);
return Pair.of(false,"Failed to write in csv");
}
}
Why is this exception coming, even when response.getWriter is called exactly once. How to solve this issue.
I found the issue. The problem was that I was sending a String (a message) in response in the controller, but in service, I am sending an attachment. So getWriter() was being called first time in service by me, and the second time by spring to write the response message of type String. Solution: I set the return type of method in the controller to void ex::
#GetMapping(value = "/downloadCSV")
public void downloadCSV(#RequestBody(required = false) List<Integer> items, HttpServletResponse response){
The only problem is if something goes wrong in service, you cannot send an error message and there are other alternatives to overcome it.
I had the same error: in spring-boot
This error occures when we use a RestExceptionHandler which tries to modify the response by using response.getOutputStream(), remember, the response.getWriter() is already called (even once in our code).
So when we call response.getWriter() , and we have some exception, the RestExceptionHandler executes the response.getOutputStream() which causes java.lang.IllegalStateException: getWriter() has already been called for this response.
What I suggest to do is to add try-catch to the response.getWriter() and when you catch any exception we have to reset the response by using response.reset() like that:
try {
writer.write(beans);
} catch (Exception e) {
response.reset();
throw e;
}
After throwing the exception the RestExceptionHandler will do it's job.

Spring Boot Security OAuth - handle InternalAuthenticationServiceException

My Spring Boot OAuth REST application returns "401 Unauthorized" status when the database connection failure(Spring Security throws InternalAuthenticationServiceException ).
It's strange, and I need to change status to "500 Internal server error" that client can provide some adequate description, like "service is not available".
If I use WebResponseExceptionTranslator then I can catch response, but if I change HTTP status, it works only when the database active. If the database is shutdown, then I get "401 Unauthorized" again.
How can I solve this problem most gracefully?
Depends on which level the exception is thrown, you might want to add exception handler to your login controller:
#ExceptionHandler(InternalAuthenticationServiceException.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
// convert exception to 500, add logging and
}
Learn more about exception handling here:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
I fix this by adding "try catch" around jdbcTemplate request in my custom UserDetailService.
protected List<UserDetails> loadUsersByUsername(String username) {
try {
userDetailsList = this.getJdbcTemplate().query( USERS_BY_USERNAME, new String[]{username},
new RowMapper() {
public UserDetails mapRow( ResultSet rs, int rowNum ) throws SQLException {
String username = rs.getString( 1 );
/* etc. map user fields */
return new SecurityUser( username, /* other params... */ );
}
});
} catch (CannotGetJdbcConnectionException e){
logger.error( "UserDetailService SQL error: " + e.getMessage(), e );
}
return userDetailsList;
}
And then I check InternalAuthenticationServiceException
by WebResponseExceptionTranslator and change response status.
It seems that when I catch CannotGetJdbcConnectionException then something ruins in chain. It works, but I will leave my question open, maybe someone can offer a more clear solution.

Spring Exception Handler log all exceptions but return original response

So I am trying to log all uncaught exceptions returned by the controllers of a spring project in a generic fashion.
I was able to do this with the following exception handler:
#ControllerAdvice
public class ControllerConfig {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleBadRequest(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
throw e;
}
#ExceptionHandler(AccessDeniedException.class)
public void defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Error in request:" + request.getRequestURL(), e);
throw e;
}
This also returns the error responses of the request, so I don't have to differentiate between all the different error response codes.
However, for every invocation of the method a second error log is created because of the exception thrown in the method:
Code is from org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking #ExceptionHandler method: " + exceptionHandlerMethod);
}
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception);
}
catch (Exception invocationEx) {
if (logger.isErrorEnabled()) {
logger.error("Failed to invoke #ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
}
return null;
}
So is there a smarter way to return the original exception of the method?
It depends on what do you mean by "a smarter way to return the original exception". What exactly would you like to return to the client? If this is just the message of the exception you can simply return it from the exception handler and annotate the method with #ResponseBody. Spring will do the rest for you.
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleBadRequest(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
throw e.getMessage();
}
You can also return some custom object which wraps the exception information and any other data that you desire.

No rollback for ConstraintViolationException in transactional service

I've a service method called add() which is annotated with #Transactional.
I call it but when a ConstraintViolationException occurs inside corresponding DAO method it'll rollback the transaction even when I specify not to.
I expect that ConstraintViolationException to be caught and instead of it NotFoundException checked exception would be thrown.
#Override
#Transactional(noRollbackFor = ConstraintViolationException.class)
public User add(User user) throws NotFoundException {
try {
result = userDao.add(user);
} catch (RuntimeException e) {
throw new NotFoundException("Couldn't find group");
}
}
Is there a way to catch ConstraintViolationException without transaction rollback?
I'm using spring 3.1.1 and hibernate 3.6.
Ah, I see what happens. The ConstraintViolationException happens at commit time, after the method has been executed, when the transaction interceptor around your add() method tries to commit the transaction. Since it can't commit, obviously, the transaction is rollbacked. It can't to anything else.

Resources