I am implementing a REST api webservice which is fetching data from a MySql database. It is written here that we dont need to handle database exception explicitly. I have catch blocks in the Service layer. I have the following questions.
1- How do i send the appropriate error message to the respective model view from the catch block?
2- Is Service the right layer to catch the exception?
I have the following code
Controller
#RequestMapping(value = "/saveUser", method = RequestMethod.POST)
public ModelAndView saveUser(#ModelAttribute User user, BindingResult result)
{
ModelAndView mv = new ModelAndView();
validator.validate(user, result);
if(result.hasErrors()) {
mv.setViewName("addUser");
}
else {
service.saveUser(user);
mv.setViewName("redirect:/users/listAllUsers");
}
return mv;
}
Service
public void saveUser(User user) {
try {
userDao.saveUser(user);
} catch(DuplicateKeyException e) {
//Here i want to send "User already exist"
} catch(DataAccessException e) {
//Here i want to send "Databae unreachable"
}
}
UserDAO
public void saveUser(User user) {
String sql = "INSERT INTO User (fname, lname, address, phone)"
+ " VALUES (?, ?, ?, ?)";
jdbcTemplate.update(sql, user.getFname(), user.getLname(),
user.getAddress(), user.getPhone());
}
}
#dbreaux 's answer is correct.You should customize an exception.
public class UserException extends RuntimeException {
// You can add some custom variables
// such as error codes, error types, etc.
}
Then,you should define a ControllerAdvice to handle this Exception:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class UserControllerAdvice{
#ExceptionHandler(value = UserException.class)
public ModelAndView handleUserException(UserException ex){
// Generate corresponding results(ModelAndView) based on exception.
// example: Put the error message to model.
return new ModelAndView("prompt_page",ex.getMessage());
}
}
Last,you can throws UserException in your Service.
public void saveUser(User user) {
try {
userDao.saveUser(user);
} catch(DuplicateKeyException e) {
throw new UserException("User already exist");
} catch(DataAccessException e) {
throw new UserException("Databae unreachable");
}
}
You want to isolate the web-specific behaviors from the service layer, and from the data layer, and I think the best way to do that is to throw a new, checked, domain-specific Exception that matches the meaning of each case you want to handle differently in the Controller.
For example, DuplicateUserException, SystemUnavailableException. Then the Controller catches those and adds the correct case to the Model.
Related
Spring 5 has introduced ResponseStatusException, is it a good practice to throw this exception directly from the service layer.
Case 1:
#Service
public class UserService {
public User findUserByName(String username) {
User user = userRepository.findByUsernName(username);
if(null == user) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user not found");
}
}
}
Case 2:
Or do we need to use custom exception and handle it in controller level ? We are catching the CustomException and throwing ResponseStatusException in this case, why do we have to catch the custom exception again instead of going with Case 1
#Service
public class UserService {
public User findUserByName(String username) {
User user = userRepository.findByUsernName(username);
if(null == user) {
throw new UserNotFoundException("user not found");
}
}
}
#RestController
public class UserController {
#GetMapping(path="/get-user")
public ResponseEntity<User> getUser(String username) {
try {
userService.findUserByName(username);
} catch (UserNotFoundException ex) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "user not found");
}
}
}
As it was mentioned in comments, you can create mapping in your error. Then you do not need to use try block in controller.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
#ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "user not found")
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
I trying to implement spring security + jwt.I done log-in and log-out methods, jwt filter,provider and web config is configured. So the main problem is my controller and how to return error messages to user, for example if user typed wrong password/username or user account is banned etc.
I got a structure built on exception handling, looks terible.
controller
#PostMapping("/log-in")
public ResponseEntity logIn(#RequestBody UserDto userDto) {
log.info("[LOG-IN] user with username " + userDto.getUsername());
try {
HashMap<String, String> response = userService.logIn(userDto);
return ResponseEntity.ok(response);
} catch (UserStatusException ex) {
return ResponseEntity.badRequest().body("Account is Pending");
} catch (UsernameNotFoundException ex) {
return ResponseEntity.badRequest().body("Could not find account!");
} catch (AuthenticationException ex) {
log.error("Wrong username or password!");
return ResponseEntity.badRequest().body("Wrong username or password!");
}
}
service
#Override
public HashMap<String, String> logIn(UserDto userDto)throws AuthenticationException, UserStatusException{
User user = findByUsername(userDto.getUsername());
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userDto.getUsername(), userDto.getPassword())); //login
checkUserStatus(user); //check if user pending or banned
user.setUserStatus(UserStatus.ACTIVE);
String token = jwtTokenProvider.createToken(user.getUsername(), user.getUserRoles());
HashMap<String, String> response = new HashMap<>();
response.put("token", token);
response.put("username", user.getUsername());
userRepository.save(user);
return response;
}
#Override
public User findByUsername(String username)throws UsernameNotFoundException {
log.info("[UserService, findByUsername]");
User user = userRepository.findByUsername(username);
if(user == null){
log.error("User not found with {} username: ", username);
throw new UsernameNotFoundException("User not found!");
}
log.info("User {} successfully loaded ",username);
return user;
}
#Override
public void checkUserStatus(User user)throws UserStatusException {
if (user.getUserStatus().equals(UserStatus.BANNED)
|| user.getUserStatus().equals(UserStatus.PENDING)) {
throw new UserStatusException("Not confirmed");
}
}
Is there any other way to replace this structure?
You should use a ControllerAdvice (see a tutorial here).
It's a special class that look like this
#ControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(PersonNotFoundException.class)
public ResponseEntity <VndErrors > notFoundException(final PersonNotFoundException e) {
return error(e, HttpStatus.NOT_FOUND, e.getId().toString());
}
}
It will allow you to bind a specific return code and response to each exception you need to handle, and it will automatically catch all exception returned by your controller. It's also a good way to handle all exceptions at the same place instead of over each exceptions...
I'm not sure about it, but I think you even can bind it to a specific mapping of your API for more granularity.
Hope this help! Have fun!
You could add the repose status directly to your custom exception class:
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class UsernameNotFoundException extends RuntimeException {
public UsernameNotFoundException(String message) {
super(message);
}
public UsernameNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
In this way you don't need anymore to catch them in the controller and add the message and the status in ResponseEntity.
DAO code
public List<UserBean> list() throws SQLException {
Connection con = null;
ResultSet rs = null;
List<UserBean> retbean = new ArrayList<UserBean>();
try {
con = dataSource.getConnection();
PreparedStatement statement = con.prepareStatement("select username from customer");
rs = statement.executeQuery();
while (rs.next()) {
rb.setUsername(rs.getString("username"));
retbean.add(rb);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return retbean;
}
}
Controller code
#RequestMapping(value = "doLogin")
public ModelAndView doLogin(#ModelAttribute #Valid UserBean
userBean, BindingResult result) {
ModelAndView view = new ModelAndView("login");
if (!result.hasFieldErrors()) {
if (!combatService.authenticateUser(userBean)) {
result.addError(new ObjectError("err", "Invalid Credentials"));
} else {
if (retrieveService.list(userBean) != null) {
view.setViewName("welcomes");
}
}
}
return view;
}
Actually If I login it will go to welcome page, To retrieve data from database to display in welcome page in angular js
Use $http service of angularJs.
$http.get("/your_url").then(function(data){
//do something with data here.
}, function(err){
});
And in your Controller add a mapping for url that returns data.
#RequestMapping("/your_url")
public #ResponseBody List<UserBean> list() throws SQLException{
return myDao.list();
}
// By Convention you should not directly call DAO from controller use services and delegates
Welcome to Stackoverflow. First you should access your DAO over controller. So you should write a new method inside controller which will talk to DAO and serve data to client side code (for you, angular) over JSON for instance.
Depending on the version of Spring you are using you can do the following:
#RestController combines #Controller and #ResponseBody annotations. By using it you can omit the #ResponseBody annotations.
#GetMapping replaces #RequestMapping(method = GET).
#RestController
public class SampleController {
// autowire myDao object
#GetMapping("/your_url")
public ResponseEntity<List<UserBean>> list() {
return ResponseEntity.ok(myDao.list());
}
}
I am looking for a way to handle custom exception thrown during binding of request parameter to DTO field.
I have a cantroller in Spring Boot application as follows
#GetMapping("/some/url")
public OutputDTO filterEntities(InputDTO inputDTO) {
return service.getOutput(inputDTO);
}
input DTO has few fields, one of which is of enum type
public class InputDTO {
private EnumClass enumField;
private String otherField;
/**
* more fields
*/
}
user will hit the URL in ths way
localhost:8081/some/url?enumField=wrongValue&otherField=anyValue
Now if user sends wrong value for enumField, I would like to throw my CustomException with particular message. Process of enum instance creation and throwing of exception is implemented in binder
#InitBinder
public void initEnumClassBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(
EnumClass.class,
new PropertyEditorSupport() {
#Override
public void setAsText(final String text) throws IllegalArgumentException {
try {
setValue(EnumClass.valueOf(text.toUpperCase()));
} catch (Exception exception) {
throw new CustomException("Exception while deserializing EnumClass from " + text, exception);
}
}
}
);
}
Problem is that when exception is thrown it is impossible to handle it with
#ExceptionHandler(CustomException.class)
public String handleException(CustomException exception) {
// log exception
return exception.getMessage();
}
Spring wraps initial exception with BindException. That instance contains my initial error message, but concatenated with other text which is redundant for me. I don't think that parsing and substringing that message is good...
Am I missing something? What is the proper way to get message from initial
CustomException here?
You will not be able to handle exceptions thrown before entering your controller method by using #ExceptionHandler annotated methods. Spring handles these exceptions before entering the controller, by registering DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver handler.
This is the case of BindingException, thrown when Spring cannot bind request parameters to match your InputDTO object.
What you can do is to register your own handler (create a Component implementing HandlerExceptionResolver and Ordered interfaces), give it the highest priority in handling errors and play with exceptions as needed.
You have also to pay attention to BindException as it wrappes your custom exception, CustomException.class
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import yourpackage.CustomException;
#Component()
public class BindingExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final Logger logger = LoggerFactory.getLogger(BindingExceptionResolver.class);
public BindingExceptionResolver() {
}
private ModelAndView handleException(ObjectError objectError, HttpServletResponse response){
if (objectError == null) return null;
try {
if(objectError.contains(CustomException.class)) {
CustomException ex = objectError.unwrap(CustomException.class);
logger.error(ex.getMessage(), ex);
return handleCustomException(ex, response);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
protected ModelAndView handleCustomException(CustomException ex, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof org.springframework.validation.BindException) {
BindException be = (BindException) ex;
logger.debug("Binding exception in {} :: ({}) :: ({})=({})", be.getObjectName(), be.getBindingResult().getTarget().getClass(), be.getFieldError().getField(), be.getFieldError().getRejectedValue());
return be.getAllErrors().stream()
.filter(o->o.contains(Exception.class))
.map(o ->handleException(o, response))
.filter(mv ->mv !=null)
.findFirst().orElse(null);
}
} catch (Exception handlerException) {
logger.error("Could not handle exception", handlerException);
}
return null;
}
#Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}
Hope it helps
I'm trying to use #DELETE request after a made some simple web application which I've tested using soapui. With this application I can add and get users/book to database. Now I'm trying to made a #DELETE request but I can't make it. Here is the code:
//UserServiceImpl
#PersistenceContext
private EntityManager em;
#Override
public void deleteUser(Long id) {
if (null == id || id.longValue() < 1) {
throw new IllegalArgumentException(" User id can not be null or less than zero. ");
}
User u = em.find(User.class, id);
em.remove(u);
}
//UserResource
#Autowired
private UserService userService;
#DELETE
#Path("/delete/{id}")
public Response deleteUser(#PathParam("id") String id) {
Response response;
try {
User user = userService.deleteUser(Long.valueOf(id));//here is the error
if (user != null) {
response = Response.status(HttpServletResponse.SC_OK).entity(user).build();
} else {
response = Response.status(HttpServletResponse.SC_NOT_FOUND).build();
}
} catch (IllegalArgumentException e) {
response = Response.status(HttpServletResponse.SC_NOT_FOUND).build();
}
return response;
}
I`ve fix my problem. The delete method which is in UserServiceImpl must not be void.... it must be public User deleteUser(Long id). The other delete method in Resource class ... just need to be of void type. There i do not use Response and i simply print the result like this:
System.out.print(Response.status(HttpServletResponse.SC_OK).entity(user).build());