Exception handling in Spring MVC with 3 layer architecture - java

I am building a simple web applications with 3 layers - DAO, Service, MVC. When in my Controller I want to delete menu group and it contains menus I am getting ConstraintViolationException.
Where should I handle this exception? In DAO, Service, or in Controller? Currently I am handling the exception in Controller.
My code below.
DAO method for deleting menu groups:
#Override
public void delete(E e){
if (e == null){
throw new DaoException("Entity can't be null.");
}
getCurrentSession().delete(e);
}
Service method for deleting menu groups:
#Override
#Transactional(readOnly = false)
public void delete(MenuGroupEntity menuGroupEntity) {
menuGroupDao.delete(menuGroupEntity);
}
Controller method for deleting menu groups in Controller:
#RequestMapping(value = "/{menuGroupId}/delete", method = RequestMethod.GET)
public ModelAndView delete(#PathVariable Long menuGroupId, RedirectAttributes redirectAttributes){
MenuGroupEntity menuGroupEntity = menuGroupService.find(menuGroupId);
if (menuGroupEntity != null){
try {
menuGroupService.delete(menuGroupEntity);
redirectAttributes.addFlashAttribute("flashMessage", "admin.menu-group-deleted");
redirectAttributes.addFlashAttribute("flashMessageType", "success");
} catch (Exception e){
redirectAttributes.addFlashAttribute("flashMessage", "admin.menu-group-could-not-be-deleted");
redirectAttributes.addFlashAttribute("flashMessageType", "danger");
}
}
return new ModelAndView("redirect:/admin/menu-group");
}

You should handle exceptions in service layer only, as part of design unless required. Think of the requirement where you need a same functionality deleteMenu for some other mapping too.
From any design point of view. Keep controller very specific to handling model attributes only serving the request mapping to business logic. Keep a method in service layer to take menuGroupId and throw exception from that service if parameter is thrown or DB error has occurred.
Refer more: Model-View-Controller, what every part really does?

Related

3 tiers architecture best practice using Spring Boot

I work with 3-tier architecture in a Spring Boot app. I created 3 packages (model, service, controller), but what I did, service calls a repo function with try catch, and then I call it in controller
Example:
Service:
public ResponseEntity<List<Customer>> getAllCustomers() {
try {
List<Customer> customers = new ArrayList<Customer>();
cutomerRepository.findAll().forEach(customers::add);
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Controller
#GetMapping("/viewList")
private ResponseEntity<?> getAllCustomers()
{
try{
return customerService.getAllCustomers();
}catch (Exception exception){
return new ResponseEntity<String>("Customers is not found", HttpStatus.METHOD_FAILURE);
}
}
Is that correct? I think I should put in services only customerRepository.findAll() without any other logic or code, but I'm not sure. Any idea?
The Service layer should contain logic, so that is OK.
But it should not contain any classes from the Controller layer, as this would leak information from an "upper" layer into a "lower" layer. This means your Service should not return a ResponseEntity as this is from the Controller layer. Instead it should return simply a list of Customers and let the Controller construct the ResponseEntity out of it.
Otherwise your Service will always be limited to be called by this specific Controller. It would not be reusable to be called by another service of a different type of Controller, that does not use an HTTP ResponseEntity.
The best approach in my opinion is the following.
Your Service layer should not return ResponseEntity<List<Customer>> as it currently does. It should instead return List<Customer>.
This is already in the above answer but wanted to answer to extend the content a bit more.
The service also when modified to return List<Customer> should handle the exceptions with Application specific exceptions. So you create your own exception for your application, the model for this exception and also you create an Exception Advice class where all those application exceptions are handled in a general way. So your service will just throw the exception, the controller will not catch it and it will be handled by the Advice class (annotated with #ControllerAdvice) which will handle all the uncaught exceptions and return appropriate responses. There are also some more options to handle exceptions in generic way in Spring.
I am attaching the following code as an example
Class to handle all exceptions that bubble up from controllers.
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(ApplicationException.class)
public ResponseEntity handleApplicationException(ApplicationException e) {
return ResponseEntity.status(e.getCustomError().getCode()).body(e.getCustomError());
}
}
Some application specific exception (The name could be more specific)
#Getter
#Setter
public class ApplicationException extends RuntimeException {
private CustomError customError;
public ApplicationException(CustomError customError){
super();
this.customError = customError;
}
}
An Error object to be returned to the client when exception happens
#Getter
#Setter
#NoArgsConstructor
public class CustomError {
private int code;
private String message;
private String cause;
public CustomError(int code, String message, String cause) {
this.code = code;
this.message = message;
this.cause = cause;
}
#Override
public String toString() {
return "CustomError{" +
"code=" + code +
", message='" + message + '\'' +
", cause='" + cause + '\'' +
'}';
}
}
Your Service
public List<Customer> getAllCustomers() {
try {
List<Customer> customers = new ArrayList<Customer>();
cutomerRepository.findAll().forEach(customers::add);
if (customers.isEmpty()) {
throw new ApplicationException(new CustomError(204, "No Content", "Customers do not exist"));
}
return new ResponseEntity<>(customers, HttpStatus.OK);
} catch (Exception e) {
throw new ApplicationException(new CustomError(500, "Server Error", "Disclose to the client or not what the cause of the error in the server was"));
}
}
The controller it self could also inspect the input information that it receives and if needed could throw it self an application specific exception or just return an appropriate response with what is false in the input.
This way the Controller is just handling the input/output between the user and the service layer.
The Service is just handling input/output of data from persistent layer.

Constraints check controller layer vs service layer

I'm trying to learn spring and to achieve that i'm building a REST application from scratch. I'm confused where should I check constraints in my application: Controller layer vs. Service layer.
For example, in create user method I want to check if there is any other user with the same email, since email is unique in my database. I also want to check if password matches(password and "confirm password" fields) etc.
Currently, in my implementation, all this things are verified in Controller layer so I can return a ResponseEntity for every approach.
#PostMapping("/signUp")
public ResponseEntity<Object> createUser(#RequestBody RegisterUserDto user) {
if (userService.getUserByEmail(user.getEmailAddress()) != null) {
return ResponseEntity.badRequest().body("email already exists");
}
if (!user.getPassword().equals(user.getConfirmPassword())) {
return ResponseEntity.badRequest().body("passwords are not the same");
}
User savedUser = null;
try {
savedUser = userService.createUser(userDtoConversions.convertToEntityRegister(user));
} catch (ParseException e) {
e.printStackTrace();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedUser.getId()).toUri();
return ResponseEntity.created(location).build();
}
Create user method in Service layer:
#Override
#Transactional
public User createUser(User newUser) {
newUser.setDateCreated(new Date());
return userRepository.save(newUser);
}
So which approach is better? If I checks constraints and validations in Service layer, what should I return so I would know in my controller why create user fails?
In my mind the best place to handle exceptions is the service layer. For my a REST controller method should, at most, handle the request and pass it over to a service method.
With this approach you have very clearly defined layers that do a very clearly defined job. For example your service layer will handle the validation of the request, the persisting action and also will provide (if needed) a return object to the controller, which then will wrap into the appropriate response object (ResponseEntity in you case).
With that in mind, there is nothing stopping you to throw any kind of exceptions in the service layer and have translated into proper responses. Spring has a very neat and powerful mechanism that does precisely that which is called an exception handler.
So in your case for the password checking action you could do something like:
if (!user.getPassword().equals(user.getConfirmPassword())) {
throw new PasswordMismatchException("Passwords are not the same for user:: " + user.getName());
}
Where the PasswordMismatchException is a RuntimeException. With something like that, you can then go ahead and setup an ExceptionHandler along with the appropriate method to intercept this and translate it into a response. A simple example would be:
#RestControllerAdvice
public class ApplicationExceptionHandler {
#ExceptionHandler(PasswordMismatchException.class)
public ResponseEntity<String> handleBadPasswords(PasswordMismatchException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
You can read up for more on this on Spring's documentation:
Spring ExceptionHandler
Exception Handling in Spring

Custom Spring Data Rest Controller with HATEOAS Support

I am implementing Spring Data REST in one of my project. I have to write a few custom Repository in order to write custom queries. I am using a Controller in front of my REST Repository. In order to get a HATEOAS response, I am using PersistentEntityResourceAssembler in my controller. This controller works fine for a single entity but in case of a list, I get an exception "PersistentEntity must not be null!"
#RequestMapping(value="/employmentType", method=RequestMethod.GET, produces="application/hal+json")
#ResponseBody
public ResponseEntity<?> getEmploymentTypes(HttpServletRequest request, HttpServletResponse response,PersistentEntityResourceAssembler resourceAssembler) throws TenantUnavailableException, TenantInvalidException
{
try
{
List<EmploymentType> employmentTypeList = employmentTypeRepository.findAll();
if(null==employmentTypeList || employmentTypeList.size()==0)
return new ResponseEntity<ApiResponse>(new ApiResponse(false, ENTITY_NOT_FOUND),
HttpStatus.NOT_FOUND);
// Accessing the 0th index works fine
//In case of a full list, it throws "Persistant Entity must not be null !" exception
return ResponseEntity.ok(resourceAssembler.toResource(employmentTypeList.get(0)));
}
catch (Exception e)
{
e.printStackTrace();
return new ResponseEntity<ApiResponse>(new AppResponse(false, REQUEST_NOT_PROCESSED),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I am trying to leverage maximum spring functionality with minimum coding support from my end. I do not want to write a ResourceAssembler for each and every persistent entity in my project.
Please suggest if anyone has any ideas.
To work with list of 'resources' you can use class Resources, for example, like this:
List<EmploymentType> types = employmentTypeRepository.findAll();
Resources<Resource<EmploymentType>> resources = Resources.wrap(types);
resources.add(/* you can add some links here */);
return ResponseEntity.ok(resources);
From Resources javadoc:
General helper to easily create a wrapper for a collection of entities.

Spring : Controller and Service Layer coding standard example

I am checking out my project code and found below method in controller. On the net I found that controller is for receive request and provide response. Service Layer is for Business Logic and Dao layer is for data CRUD related operation.
In below method I can see business logic. Now I am not getting which code should be moved to service layer or below is fine.
I am reviewing code so I need to provide comments but I am confused.
#RequestMapping(value = "/admin/app", method = RequestMethod.POST)
public ModelAndView saveApp(
#ModelAttribute("application") #Validated Application application,
BindingResult result) {
ModelAndView model = new ModelAndView();
ApplicationFormValidator formValidation = new ApplicationFormValidator();
boolean messageFlag = false;
String operationalStatus = null;
formValidation.validate(application, result);
if (result.hasErrors()) {
model.addObject(APPLICATION, application);
model.setViewName(ADD_APP);
} else {
if(checkActive(application)){
status = FormBeanValidator.encodeStatus(application.getStatus());
application.setStatus(status);
// calling service layer and convert model into entity
messageFlag = applicationService.addApp(application);
if (messageFlag) {
Application applicationForm = new Application();
applicationForm.setSuccessMessage(PropertyHandler.getPropertyInstance().getPropertyValue(Constants.SUCCESS_MESSAGE));
model.addObject(APPLICATION, applicationForm);
model.setViewName(ADD_APP);
} else {
application.setErrorMessage(PropertyHandler.getPropertyInstance().getPropertyValue(Constants.ERROR_MESSAGE));
model.addObject(APPLICATION, application);
model.setViewName(ADD_APP);
}
}
else{
application.setErrorMessage(PropertyHandler.getPropertyInstance().getPropertyValue(Constants.OTHER));
model.addObject(APPLICATION, application);
model.setViewName(ADD_APP);
}
}
return model;
}
The code looks fine,but I would suggest some modifications:
1) Your checkActive(application) seems to check something about your business object(application),so move it to the service layer.You could merge your checkActive() method logic by moving the checkActive() method into service layer and calling it inside your applicationService.addApp(application) as a local method.
2) You have been setting the view name as same in both the if as well as else block.Try and move this code out of the if-else block as it becomes redundant.
3) It is a practice to send only the required data from the controller to the view.This is done by creating a DTO(Data Transfer Object) and making a Converter class which maps your business object fields to the DTO.You can look for example use case for DTO here
Everything is fine if you are checking for null or incorrect format validation inside your ApplicationFormValidator

Localizing Exception Messages using Spring

I want to localize the exception messages thrown from POJO classes using Spring. I have a Spring MVC application through which I can add books. If the added book's title is null the implementation class throws an Exception. I want to localize this.
I know I can use the localeResolvers in the JSP pages and I have done that already. Can I leverage this to pick up localized error messages in the POJO? If so how do I inject the locale resolver (Cookie or Session) or locale which was set on the Cookie/Session into the POJO class?
addBook method throwing exception
public void addBook(IBook book) throws Exception {
if (book.getTitle() == null || book.getTitle() == "") {
throw new Exception("Title is null");
}
I want the throw new Exception("Title is null"); to be something like
String msg = rBundle.getMessage(propKey)
throw new Exception(msg);
where rBundle is a bundle object which knows its locale and the properties file from which it should pick the value for propKey
my controller class method which receives the form submission
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(
#RequestParam("siteLanguage") String siteLanguage,
#ModelAttribute("book") Book book, BindingResult result,
SessionStatus status, HttpServletRequest arg0) {
logger.debug("Adding a Book");
Locale loc = RequestContextUtils.getLocale(arg0);
if (result.hasErrors()) {
return "error.htm";
} else {
try {
Author author = new Author("Gabriel Garcia Marquez");
book.setAuthor(author);
library.addBook(book);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "redirect:home.htm";
}
}
Is this possible? Or is it better I control the loading of java ResourceBundle for the respective locale.
Thanks
Why do you want to localize Exception?: I guess the exceptions are stored in a log file, the administrator should be able to read them, without knowledge of all the languages the users speak.
If you are talking about form validation, then have a look at spring form validation and jsr303 bean validation, both include concepts of localisation. (But both does not work with exceptions.)

Categories

Resources