Rohit Bajaj
asked on
Best practice to handle exception within controllers spring mvc
Hi,
Inside the controller i am using DB operations. There is a separate class for Dao for performing it. But it may throw exceptions like : EmptyResultDataAccessExcep tion
Since this DB calls are made from within several methods inside the controller. Writing try catch for them doesnt work.
so i can put try/catch inside the dao class itself. But then i need to return proper response code and error message.
what are the possible ways to do it.
One way i found is to extend my controller with a basecontroller having the following code :
Then within try/catch i use catch (EmptyResultDataAccessExce ption e) {
throw new AppException(e.getMessage( ), 404, "Not Found");
}
Please comment on this approach. Are there better ways ?
Thanks
Inside the controller i am using DB operations. There is a separate class for Dao for performing it. But it may throw exceptions like : EmptyResultDataAccessExcep
Since this DB calls are made from within several methods inside the controller. Writing try catch for them doesnt work.
so i can put try/catch inside the dao class itself. But then i need to return proper response code and error message.
what are the possible ways to do it.
One way i found is to extend my controller with a basecontroller having the following code :
public class BaseController {
@ExceptionHandler(AppException.class)
public void handleServerException(HttpServletRequest request, HttpServletResponse response, AppException e){
try{
String msg = "Page not found";
response.getWriter().write(msg);
response.setStatus(e.getStatusCode());
response.setContentType(MediaType.TEXT_HTML.toString());
}catch(IOException ioe){
//TODO : logger.error("Unable to process error",ioe);
}
}
Then within try/catch i use catch (EmptyResultDataAccessExce
throw new AppException(e.getMessage(
}
Please comment on this approach. Are there better ways ?
Thanks
I would say that using the @ExceptionHandler annotated method should be the most appropriate, however, I would have that method directly handle the EmptyResultDataAccessExcep tion (rather than catching it yourself and just throwing a different Exception). That way you have even less common code in your main controller methods.
ASKER
Hi,
The exact code which is throwing exception is :
So basically i need to handle two exceptions. That way i will need to write two exception handlers one for Empty Exception and other for RunTimeException ?
The exact code which is throwing exception is :
@Override
public Snippet getSnippetById(String id) {
String query = "select id,title,mode,text from snippets where id=?";
Snippet snippet;
try {
snippet = jdbcTemplate.queryForObject(query, new Object[]{id}, new SnippetMaper());
} catch (EmptyResultDataAccessException e) {
throw new AppException(e.getMessage(), 404, "Page not found");
}
catch (RuntimeException re) {
throw new AppException(re.getMessage(),500,"Internal Server Error");
}
return snippet;
}
So basically i need to handle two exceptions. That way i will need to write two exception handlers one for Empty Exception and other for RunTimeException ?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I am using <spring.version>4.2.1.RELE ASE</sprin g.version>
I would say that using the @ExceptionHandler annotated method should be the most appropriate, however, I would have that method directly handle the EmptyResultDataAccessException (rather than catching it yourself and just throwing a different Exception). That way you have even less common code in your main controller methods.
I second that.
Ok, if you can post your full code for your controller(s) I can give you some pointers.
But as a start, your exception handler method should work like this...
But as a start, your exception handler method should work like this...
public class BaseController {
@ExceptionHandler(EmptyResultDataAccessException.class)
public ResponseEntity<String> handleServerException(EmptyResultDataAccessException e){
return new ResponseEntity<String>("Page not found", HttpStatus.NOT_FOUND);
}
ASKER
Thanks. I am posting the full code :
SnippetController :
BaseController.java :
SnippetDaoImpl.java
SnippetController :
package org.directi.code.controller;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.directi.code.dao.SnippetDao;
import org.directi.code.model.Snippet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
@Controller
public class SnippetController extends BaseController {
@Autowired
SnippetDao snippetDao;
private static final Logger logger = Logger.getLogger(SnippetController.class);
@RequestMapping(value = "/")
public String newSnippet(HttpServletResponse response) throws IOException {
return "new";
}
@RequestMapping(value = "/snippets", method = RequestMethod.POST)
public void createSnippet(@RequestBody String jsonString, RedirectAttributes redirectAttributes, HttpServletResponse response, HttpServletRequest request) throws IOException {
Snippet snippet = new ObjectMapper().readValue(jsonString, Snippet.class);
String uuid = snippetDao.insertData(snippet);
response.addHeader("Location", "http://" + request.getHeader("Host") + "/snippets/" + uuid);
}
@RequestMapping(value = "/snippets/{id}", method = RequestMethod.GET)
public ModelAndView showSnippet(@PathVariable String id) {
Snippet snippet = snippetDao.getSnippetById(id);
String mode = snippet.getMode();
ObjectMapper objectMapper = new ObjectMapper();
String extraScripts = "";
try {
byte[] jsonData = Files.readAllBytes(Paths.get("utils/meta.json"));
JsonNode rootNode = objectMapper.readTree(jsonData);
JsonNode deps = rootNode.path("deps");
JsonNode modeString = deps.path(mode);
String jsFileList = modeString.getTextValue();
String[] jsFileNames = jsFileList.split(",");
for (int i = 0; i < jsFileNames.length; i++) {
extraScripts += "<script src=\"/resources/lib/codemirror" + jsFileNames[i] + "\"></script>\n";
}
} catch (IOException e) {
e.printStackTrace();
}
ModelAndView mav = new ModelAndView("snippet");
snippet.setText(StringUtils.replaceEach(snippet.getText(), new String[]{"&", "\"", "<", ">"}, new String[]{"&", """, "<", ">"}));
mav.addObject("snippet", snippet);
mav.addObject("extraScripts", extraScripts);
return mav;
}
@RequestMapping(value = "/snippets/{id}", method = RequestMethod.PUT)
public void showSnippet(@RequestBody String jsonString, @PathVariable String id, HttpServletResponse response) {
try {
Snippet snippet = new ObjectMapper().readValue(jsonString, Snippet.class);
snippet.setId(id);
snippetDao.updateData(snippet);
response.setStatus(200);
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping(value = "/snippets/{id}/edit", method = RequestMethod.GET)
public ModelAndView editSnippet(@PathVariable String id) {
Snippet snippet = snippetDao.getSnippetById(id);
snippet.setText(StringUtils.replaceEach(snippet.getText(), new String[]{"&", "\"", "<", ">"}, new String[]{"&", """, "<", ">"}));
ModelAndView mav = new ModelAndView("edit");
mav.addObject("snippet", snippet);
return mav;
}
@RequestMapping(value = "/snippets/{id}/raw", method = RequestMethod.GET)
@ResponseBody
public String rawSnippet(@PathVariable String id, HttpServletResponse response) {
Snippet snippet = snippetDao.getSnippetById(id);
String filename = snippetFilename(snippet.getTitle(), snippet.getMode());
response.setHeader("Content-Type", "text/plain");
response.setHeader("Content-Disposition", "inline; filename=\"" + filename + "\"");
return snippet.getText();
}
private String snippetFilename(String title, String mode) {
String filename = title.replaceAll("\\s", "_").replaceAll("\\W", "");
ObjectMapper objectMapper = new ObjectMapper();
String extension = "";
try {
byte[] jsonData = Files.readAllBytes(Paths.get("utils/meta.json"));
JsonNode rootNode = objectMapper.readTree(jsonData);
JsonNode extensions = rootNode.path("extensions");
JsonNode extensionNode = extensions.path(mode);
extension = extensionNode.getTextValue();
} catch (IOException e) {
e.printStackTrace();
}
filename += "." + extension;
return filename;
}
}
BaseController.java :
package org.directi.code.controller;
import org.apache.log4j.Logger;
import org.directi.code.exception.AppException;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public abstract class BaseController {
private static final Logger logger = Logger.getLogger(BaseController.class);
@ExceptionHandler(AppException.class)
public void handleServerException(HttpServletRequest request, HttpServletResponse response, AppException e){
try{
response.getWriter().write(e.getErrMessage());
response.setStatus(e.getStatusCode());
response.setContentType(MediaType.TEXT_HTML.toString());
}catch(IOException ioe){
logger.error("Unable to process error",ioe);
}
}
}
SnippetDaoImpl.java
package org.directi.code.dao;
import org.apache.log4j.Logger;
import org.directi.code.model.Snippet;
import org.directi.code.exception.AppException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;
@Component
public class SnippetDaoImpl implements SnippetDao {
@Autowired
private JdbcTemplate jdbcTemplate;
private static final Logger logger = Logger.getLogger(SnippetDaoImpl.class);
public void setJdbcTemplate(JdbcTemplate template) {
this.jdbcTemplate = template;
}
@Override
public String insertData(Snippet snippet) {
UUID uuid = UUID.randomUUID();
String randomUUIDString = uuid.toString();
snippet.setId(randomUUIDString);
jdbcTemplate.update("INSERT INTO snippets (id, title, mode, text) VALUES (?, ?, ?, ?)", snippet.getId(), snippet.getTitle(), snippet.getMode(), snippet.getText());
return randomUUIDString;
}
@Override
public void updateData(Snippet snippet) {
jdbcTemplate.update("UPDATE snippets set text = ? where id = ?", snippet.getText(), snippet.getId());
}
@Override
public Snippet getSnippetById(String id) {
String query = "select id,title,mode,text from snippets where id=?";
Snippet snippet;
try {
snippet = jdbcTemplate.queryForObject(query, new Object[]{id}, new SnippetMaper());
} catch (EmptyResultDataAccessException e) {
logger.error("No records found",e);
throw new AppException(e.getMessage(), 404, "Page not found");
}
catch (RuntimeException e) {
logger.error("Error retrieving data from db",e);
throw new AppException(e.getMessage(),500,"Internal Server Error");
}
return snippet;
}
private static final class SnippetMaper implements RowMapper<Snippet> {
public Snippet mapRow(ResultSet rs, int rowNum) throws SQLException {
Snippet snippet = new Snippet();
snippet.setText(rs.getString("text"));
snippet.setId(rs.getString("id"));
snippet.setMode(rs.getString("mode"));
snippet.setTitle(rs.getString("title"));
return snippet;
}
}
}
Ok, your controller code doesn't look too bad at the moment. I think if you try an implement the things that I have mentioned (across a number of your questions) it would look pretty clean. The things I am talking about are.... using Spring automatic RequestBody conversion using Jackson, moving the meta.json data handling to a separate class, and using ResponseEntity rather than dealing directly with the HttpServletResponse object.