Reputation: 11804
I am following the structure suggested above at ( http://viralpatel.net/blogs/spring3-mvc-hibernate-maven-tutorial-eclipse-example/ ). I tried adding a duplicate entry, which resulted in the following exception:
SEVERE: Servlet.service() for servlet [appServlet] in context with path [/cct] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Duplicate entry '[email protected]' for key 'PRIMARY'; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: Duplicate entry '[email protected]' for key 'PRIMARY'] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '[email protected]' for key 'PRIMARY'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at << removed for readability>> org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy26.addUser(Unknown Source)
at com.bilitutor.cct.control.HomeController.signup(HomeController.java:56)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
<< removed for readability>>
I have the following questions:
Why is the exception being caught by the Controller ( userService.addUser(user)
in com.bilitutor.cct.control.HomeController
) and not by the DAO ( sessionFactory.getCurrentSession().save(user);
) and then bubbled up to the Controller?
I understand that I am getting a org.springframework.dao.DataIntegrityViolationException
because I am using the @Repository
annotation, which perhaps does exception translation (correct me if I am wrong). In that case, when I catch the exception, how do I find the error code for it?
As a best practice, on which layer (DAO, Service or Controller) is the best spot to catch the exception?
Relevant classes :
COntroller :
package com.bilitutor.cct.control;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.web.bind.annotation.ModelAttribute;
import com.bilitutor.cct.bean.*;
import com.bilitutor.cct.service.*;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
@Autowired
UserService userService;
@ModelAttribute("user")
public User getUserObect() {
return new User();
}
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Landing page. Just return the login.jsp
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Model model) {
logger.info("home() called");
return "login";
}
/**
* Login. Either forward to the user's homepage or return back the error
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(Model model) {
logger.info("login() called");
return "login";
}
/**
* New User signup. If user already exists, send back the error, or send an email and forward to the email validation page
*/
@RequestMapping(value = "/signup", method = RequestMethod.POST)
public String signup(@ModelAttribute("user")User user, BindingResult result) {
logger.info("signup() : email="+user.getEmail()+" pass="+user.getPassword()+" accessCode="+user.getAccessCode());
userService.addUser(user);
return "login";
}
}
Service:
package com.bilitutor.cct.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bilitutor.cct.dao.UserDAO;
import com.bilitutor.cct.bean.User;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Transactional
public void addUser(User user) {
userDAO.addUser(user);
}
@Transactional
public void removeUser(String email) {
userDAO.removeUser(email);
}
}
DAO:
package com.bilitutor.cct.dao;
import com.bilitutor.cct.bean.User;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class UserDAOImpl implements UserDAO {
@Autowired
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void addUser(User user) {
sessionFactory.getCurrentSession().save(user);
}
public void removeUser(String email) {
User user = (User) sessionFactory.getCurrentSession().load(User.class, email);
if (user!=null) {
sessionFactory.getCurrentSession().delete(user);
}
}
}
Upvotes: 9
Views: 11600
Reputation: 12688
1. I believe the exception is being caught in the controller as UserDAO.addUser() is the transactional boundary.
2. DataIntegrityViolationException extends NestedRuntimeException so you can use getMessage, getMostSpecificCause, getRootCause
3. In your case above I you should catch the exception in your controller. You want to catch exceptions at the layer where you can deal with them. So far example if I was writing a DAO method to get a list of object. I probably would check for the exception in the DAO and return an empty list and log the error vs throw the exception.
Upvotes: 0
Reputation: 10997
Exception is occuring at service layer. You can see this in trace
at com.sun.proxy.$Proxy26.addUser(Unknown Source)
@Transactional
public void addUser(User user) {
userDAO.addUser(user);
}
As previous answer says your transaction boundary is at service layer, so exception occurs there.
I would recommend to catch/throw proper business exceptions(checked exceptions) from service methods. Service methods incorporate your business logic so , if anything goes wrong it should be properly communicated to outer world through the exceptions that service methods throw. For eg : WeakPasswordException, UserNameExistsException etc
Regarding org.springframework.dao.DataIntegrityViolationException
try calling
getCause()
to see the wrapped exception
Upvotes: 1
Reputation: 41123
1.
I suspect you put your controller handler method as the transaction boundary, eg something like this:
@RequestMapping
@Transactional
public String myHandler(..) {
...
}
A duplicate rows can only be detected once the update is synchronized into the database, this often occurs on transaction close if you use hibernate/jpa, hence you see as if it's coming from your controller
Recall that @Transactional
works by jdk proxy / aspect weaving.
3.
The answer depends on your business requirement. What do you need to happen when a particular exception occur?
Upvotes: 0