Recommended way of dealing with spring hibernate SQL errors - java

I have a post endpoint in my controller in spring that looks like so
#Autowired
private BookRepository bookRepository;
#PostMapping(path="/book")
public #ResponseBody BookEntity addBook(#RequestBody BookEntity bookEntity)
{
return this.bookRepository.save(bookEntity);
}
Now, if invalid json data is submitted and the save function is called my console will output an error such as h.engine.jdbc.spi.SqlExceptionHelper : Duplicate entry '1091209' for key 'isbn'
And the user will see a very non-user friendly api response.
Another issue this brings it seems is that an auto_incremented id is skipped e.g. I go 1,2,4, skipping 3 due the 3rd api call being the failing api call.
What is best practice for handling the above scenarios?

You can define your own error handler which extends ResponseEntityExceptionHandler and catch db specific exception. Here is a code...
#RestControllerAdvice
#RequestMapping(produces = "application/json")
public class DefaultExceptionHandler extends ResponseEntityExceptionHandler {
// This method handles constraint violation exception raised by DB.
// Similarly other type exceptions like custom exception and HTTP status related
//exception can be handled here.
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(value = HttpStatus.CONFLICT)
public Map<String, String> handleConstraintViolationException(ConstraintViolationException ex) {
// write your own logic to return user friendly response
}
// Below method is to handle _SqlExceptionHelper_ exception
#ExceptionHandler(SqlExceptionHelper.class)
#ResponseStatus(value = HttpStatus.CONFLICT)
public Map<String, String> handleConstraintViolationException(SqlExceptionHelper ex) {
// write your own logic to return user friendly response
}
}

Related

Java Spring Boot: Exception Handling

A little new to the Java Spring Boot flavor of webservices -- so please be gentle. Why do most Spring Boot Controller examples not show any exceptions being captured? I see some of my fellow developers do this a lot. And the answer is: it's a common convention. But why. Am I missing a key concept of web services created using Spring Boot?
For example:
#PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(#RequestBody MyEndpointRequest myEndpointRequest) {
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}
I would think, with respects to the architecture you would add AT LEAST a try/catch block with say some logging, and throw a new exception with the exceptions message:
#PostMapping(path = "/sampleEndpoint/v1/myEndpoint", produces = "application/json")
public ResponseEntity<String> myEndpoint(#RequestBody MyEndpointRequest myEndpointRequest) {
try{
MyEndpointResponse response = someService.myEndpoint(myEndpointRequest);
return new ResponseEntity<>(response, HttpStatus.OK);
}catch(Exception ex){
//Your favorite logger:
log.error("STACK_TRACE: {}", StaticClass.stackTraceToString(ex));
//throw exception for calling or consuming system/application:
throw new MiscException(ex.getMessage());
}
}
A couple of things to give context to this question (observation):
Use multiple data sources: a couple of databases, and some other web services (gives our client a one stop place to get their data.
Using this webservice with potentially 4 different client side /presentation layer type of applications.
My team would like to capture unexpected exceptions emanating from the data sources we tap into...and log them.
Well it's up to developer to implement catch of exceptions mechanism. But it's a good practise to define exceptions types and error codes/messages for that. Let's say you have an endpoint which fetch product with id, but there is no product with that id, in that case client will receive http 500 code with internal server error message. This will confuse users and also developers, what was the real cause of that error.
So prevent those, you can get help from #ControllerAdvice annotation, which will allow to apply exception handlers to more than one or all controllers.
First you will define your custom exceptions like :
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(Long id) {
super(String.format("Product with id %d not found", id));
}
}
and then you can define your ControllerAdvice class:
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ProductNotFound.class)
public ResponseEntity<Object> handleProductNotFoundException(
ProductNotFoundException ex, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now());
body.put("message", "Product not found");
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
}
}

I want to transmit a status code if the query is successful with Spring boot, how can I do it?

Although this situation is easy for ready overrided methods, I couldn't find a way for my own query.
This is my repository :
public interface CommentRepository extends JpaRepository<User , Long >{
#Modifying
#Transactional
#Query( value="delete from users where first_name=:name" , nativeQuery=true )
public void delete( String name );
}
This is my controller :
#RestController
#RequestMapping(path="/api/v1/users")
public class CommentController {
#Autowired
CommentRepository repository ;
// Delete user
#DeleteMapping(path="/delete")
public void delete(#RequestParam String name) {
repository.delete(name) ;
}
}
For example, if I delete a user, I want to pass a status code of 200 to the developer if the query is successful.
However I want to pass different codes if the query fails.
ResponseEntity represents the whole HTTP response: status code, headers, and body. As a result, we can use it to fully configure the HTTP response.
Have a look at Response entity using which you will be able to configure everything including status codes .
https://www.baeldung.com/spring-response-entity
In the rest controller you can do something like:
#RestController
#RequestMapping(path="/api/v1/users")
public class CommentController {
#Autowired
CommentRepository repository ;
// Delete user
#DeleteMapping(path="/delete")
public ResponseEntity<Void> delete(#RequestParam String name) {
repository.delete(name);
return ResponseEntity.ok().build();
}
}
Since I don't know your database structure, let's say a SQLIntegrityConstraintViolationException can be thrown, you can create a service layer that will handle the exception. You will end up with something like:
#RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {
private final CommentRepository commentRepository;
#Override
public void deleteUsersByName(String name) {
try {
commentRepository.delete(name); //consider changing the repo method name 'delete' to be more contextual like 'deleteAllByName(String name)'
} catch (Exception | SQLIntegrityConstraintViolationException e) //or other type, depending on your database structure
throw new MyCustomException("my message: " + e); //create new RuntimeException with the name you prefer
}
}
Then you have lots of ways to handle your new exception. Please read more here: https://www.baeldung.com/exception-handling-for-rest-with-spring
One of the ways is to have this inside your #RestController class
#ExceptionHandler({MyCustomException.class})
public ResponseEntity<Void> handleConstrainViolationException() {
return ResponseEntity.internalServerError(); //just an example
}
for the last part you can play around with the exceptions thrown on the service layer and return appropriate status code from the corresponding exception handler. Consider having a global exception handler as stated into the article on Baeldung above. Hope it helps a little bit.

Generic response for #RestController in Spring Boot?

In my Spring Boot app, I just use Optional for the first time and after examining several projects and topics, now I am trying to build an approach as shown below:
Repository:
Optional<Employee> findByEmail(String email);
Service:
public Response findByEmail(String email) {
return employeeRepository.findByEmail(email)
// if record is found, I think no need to return status or message
.map(e -> Response.builder().data(e).build())
.orElseGet(() -> Response.builder().status(404)
.data(null).message("Not found!").build());
}
Response:
#Data
#Builder
public class Response {
private int status;
private Object data;
private String message;
}
Controller:
#GetMapping("/employees/{email}")
public ResponseEntity<Response> findByEmail(#PathVariable String email) {
final Response response = employeeService.findByEmail(email);
return ResponseEntity
.status(response.getStatus())
.body(response.getMessage(), response.getData());
// throws "Expected 1 arguments but found 2" error
}
Here is the points that I need to be clarified:
1. Is this a proper approach to use a common response for all the Optional types in a Spring Boot app? If not, how should I change it (I want to return a common response from the Service)?
2. How to fix the throws "Expected 1 arguments but found 2" error in the Controller?
From my comment above - You are mixing concerns. Service is supposed to only care about business logic (e.g. not HTTP Status codes). That's controller's job. Use of Optional is correct, but the Response return type from service layer is not. Also errors like Not Found are automatically handled by a Rest Controller in Spring boot if a resource is not found. If you want to add custom logic and prepare generic responses, include a proper exception handling e.g. #ControllerAdvice (which allows you reuse exceptions for controllers).
As an example, one of the solutions would be to throw NoSuchElementException.
This is illustrative and would apply if you want to handle other such situations (e.g. null pointers, internal server error, authentication errors in a more custom manner) in a generic manner.
public Employee findByEmail(String email) {
return employeeRepository.findByEmail(email) //assuming findByEmail is returning an Optional<Employee>, otherwise - simply use a null check.
.orElseThrow(NoSuchElementException::new)
}
Inside #ControllerAdvice class
#ExceptionHandler(NoSuchElementException.class)
#ResponseBody
#Order(Ordered.HIGHEST_PRECEDENCE)
public final ResponseEntity<APIResponseErrorContainer> handleNotFound(
NoSuchElementException ex) {
// log exception here if you wish to
return new ResponseEntity<>(createCustomResponseBody(), HttpStatus.NOT_FOUND);
}

Custom exception handle with spring boot

Here,my requirement is that i want separate code in my application for exception handling,i saw a nice option of spring there using #controller advice to handle exceptions globally.
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
But there i want to cutomization there,like proper dynamic messages,own error code. so how can i do this,i am new to spring boot and even i don't have knowledge of spring.Need basic example.
You can come up with a class like this to capture information to be sent in response in case of exception:-
public class APIResponse {
int errorCode;
String description;
String someInformation;
// any other information that you want to send back in case of exception.
}
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ResponseBody
#ExceptionHandler(DataIntegrityViolationException.class)
public APIResponse handleConflict(DataIntegrityViolationException exception) {
APIResponse response = createResponseFromException(exception);
return response;
}
}
In your controller advice class:-
Have the return type APIResponse instead of void.
The handler method can have the exception raised as the argument.
Using the exception object to create the APIResponse object.
Put #ResponseBody on the handler method.

How can I handle exceptions with Spring Data Rest and the PagingAndSortingRepository?

Let's say I have a repository like:
public interface MyRepository extends PagingAndSortingRepository<MyEntity, String> {
#Query("....")
Page<MyEntity> findByCustomField(#Param("customField") String customField, Pageable pageable);
}
This works great. However, if the client sends a formed request (say, searching on a field that does not exist), then Spring returns the exception as JSON. Revealing the #Query, etc.
// This is OK
http://example.com/data-rest/search/findByCustomField?customField=ABC
// This is also OK because "secondField" is a valid column and is mapped via the Query
http://example.com/data-rest/search/findByCustomField?customField=ABC&sort=secondField
// This throws an exception and sends the exception to the client
http://example.com/data-rest/search/findByCustomField?customField=ABC&sort=blahblah
An example of the exception thrown and sent to client:
{
message:null,
cause: {
message: 'org.hibernate.QueryException: could not resolve property: blahblah...'
}
}
How can I handle those exceptions? Normally, I use the #ExceptionHandler for my MVC controllers but I'm not using a layer between the Data Rest API and the client. Should I?
Thanks.
You could use a global #ExceptionHandler with the #ControllerAdvice annotation. Basically, you define which Exception to handle with #ExceptionHandler within the class with #ControllerAdvice annotation, and then you implement what you want to do when that exception is thrown.
Like this:
#ControllerAdvice(basePackageClasses = RepositoryRestExceptionHandler.class)
public class GlobalExceptionHandler {
#ExceptionHandler({QueryException.class})
public ResponseEntity<Map<String, String>> yourExceptionHandler(QueryException e) {
Map<String, String> response = new HashMap<String, String>();
response.put("message", "Bad Request");
return new ResponseEntity<Map<String, String>>(response, HttpStatus.BAD_REQUEST); //Bad Request example
}
}
See also: https://web.archive.org/web/20170715202138/http://www.ekiras.com/2016/02/how-to-do-exception-handling-in-springboot-rest-application.html
You could use #ControllerAdvice and render the content your way. Here is tutorial if you need know how to work on ControllerAdvice, just remember to return HttpEntity

Categories