I previously had a post on this issue that was resolved. However since rebuilding the project with auto wired beans and less XML configuration I find I am revisiting this issue. I have followed the way my previous project implemented this but it doesn't work. Can someone help me with why or what I should change to make it work?
I am on purpose using a non existent table name in the insert user details method to deliberately throw an exception. However the statements for insert user and insert user roles are not rolled back. Please help.
My current design for the registration is like this.
Part of servlet.xml:
<context:component-scan base-package="com.doyleisgod.golfer.controllers"/> <context:component-scan base-package="com.doyleisgod.golfer.dao"/> <context:component-scan base-package="com.doyleisgod.golfer.services"/> <context:component-scan base-package="com.doyleisgod.golfer.validators"/>
Part of application context:
<context:annotation-config /> <tx:annotation-driven /> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
Registration controller:
package com.doyleisgod.golfer.controllers; import javax.validation.Valid; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.doyleisgod.golfer.formdata.RegistrationForm; import com.doyleisgod.golfer.services.IRegistrationService; import com.doyleisgod.golfer.validators.RegistrationFormValidator; /** * Description: Registration controller provides and processes the registration form. * @author Chris Doyle */ @Controller @RequestMapping("/registration.htm") public class RegistrationController { protected final Log logger = LogFactory.getLog(getClass()); @Autowired private IRegistrationService iRegistrationService; @Autowired private RegistrationFormValidator registrationFormValidator; // sets a customer validator for the registration form @InitBinder protected void initBinder(WebDataBinder binder) { binder.setValidator(registrationFormValidator); } // Description: Method called by a get request to the registration controller. Returns the @RequestMapping(method=RequestMethod.GET) public String registration (Model model){ model.addAttribute(new RegistrationForm()); return "registration"; } // Description: Method called by a post request to the registration controller. Method calls validation on the registration form using custom validator and returning // any errors back to the user. @RequestMapping(method=RequestMethod.POST) public String processRegistration (@Valid RegistrationForm registrationForm, BindingResult bindingResult, Model model){ logger.info("Received the following registration form details"); logger.info(registrationForm.toString()); if (bindingResult.hasErrors()) { logger.warn("Registration Validation Failed"); model.addAttribute("validationError", "Please correct the fields marked with errors"); return "registration"; } try { iRegistrationService.registerUser(registrationForm); } catch (Exception e) { logger.error("An Exception has occured processing the registration form"); model.addAttribute("exceptionError", "An exception has occured, please try again."); e.printStackTrace(); return "registration"; } return "redirect:login.htm?registration=sucessful"; } }
Registration service:
package com.doyleisgod.golfer.services; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.encoding.ShaPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronizationManager; import com.doyleisgod.golfer.dao.IRegistrationDAO; import com.doyleisgod.golfer.formdata.RegistrationForm; @Service("IRegistrationService") public class RegistrationService implements IRegistrationService { @Autowired private IRegistrationDAO iRegistrationDAO; private final boolean enabled = true; private final String roles = "ROLE_USER"; @Override @Transactional (rollbackFor = Exception.class) public void registerUser(RegistrationForm registrationForm) throws Exception { System.out.println("inside the registerUser method. is wrapped in transaction: "+TransactionSynchronizationManager.isActualTransactionActive()); String username = registrationForm.getUsername(); String password = registrationForm.getPassword(); String firstname = registrationForm.getFirstname(); String lastname = registrationForm.getLastname(); String email = registrationForm.getEmail(); int handicap = Integer.parseInt(registrationForm.getHandicap()); String encryptedPassword = ((new ShaPasswordEncoder()).encodePassword(password, username)); iRegistrationDAO.insertUser(username, encryptedPassword, enabled); iRegistrationDAO.insertRoles(username, roles); iRegistrationDAO.insertUserDetails(username, firstname, lastname, email, handicap); } @Override public boolean checkUser(String username) { return iRegistrationDAO.checkUserName(username); } }
Registration DAO:
package com.doyleisgod.golfer.dao; import javax.annotation.Resource; import org.apache.commons.dbcp.BasicDataSource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.support.TransactionSynchronizationManager; @Repository("iRegistrationDAO") public class RegistrationDAO extends JdbcTemplate implements IRegistrationDAO { @Resource private BasicDataSource dataSource; @Override public boolean checkUserName(String username) { int db_user = queryForInt("select count(username) from users where username = ?", username); if (db_user == 1 ){ return true; } return false; } @Override public void insertUser(String username, String password, boolean enabled) throws Exception { System.out.println("inside the insertuser method. is wrapped in transaction: "+TransactionSynchronizationManager.isActualTransactionActive()); update("insert into users (username, password, enabled) VALUES (?,?,?)", username, password, enabled); } @Override public void insertRoles(String username, String roles) throws Exception { update("insert into user_roles (username, authority) VALUES (?,?)", username, roles); } @Override public void insertUserDetails(String username, String firstname, String lastname, String email, int handicap) throws Exception { update("insert into user_detailss (username, first_name, last_name, email_address, handicap)" + "VALUES (?,?,?,?,?)", username, firstname, lastname, email, handicap); } public void setDataSource(BasicDataSource dataSource) { this.dataSource = dataSource; } public BasicDataSource getDataSource() { return dataSource; } }
You can check if transaction is active using TransactionSynchronizationManager. isActualTransactionActive() . But you should call it before a service method executing. TransactionStatus status = TransactionAspectSupport.
Transactions and Proxies. At a high level, Spring creates proxies for all the classes annotated with @Transactional, either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method, mainly for starting and committing the transaction.
However, if we put the annotation on a private or protected method, Spring will ignore it without an error. Usually it's not recommended to set @Transactional on the interface; however, it is acceptable for cases like @Repository with Spring Data.
Propagation. REQUIRED is the default setting of a @Transactional annotation. The REQUIRED propagation can be interpreted as follows: If there is no existing physical transaction, then the Spring container will create one.
The reason that moving the context:component-scan
tags to the application context xml fixed the transactional behavior is: <tx:annotation-driven />
is a post-processor that wraps @Transactional
annotated bean methods with an AOP method interceptor which handles transactional behavior. Spring post-processors, only operate on the specific application context they are defined in.
In your case, you have defined the <tx:annotation-driven />
post-processor in the application context, while the beans annotated with @Transactional
are in the servlet application context. Thus, the <tx:annotation-driven />
post-processor only operated on the application context beans, not the servlet context beans. When the context:component-scan
tags were moved to the application context, then the <tx:annotation-driven />
post-processor wrapped their transactional methods appropriately.
Hope that makes some sense.
[Edit]
What is the difference between the Application Context and a Servlet Context?
What is a Spring post-processor and how does it work?
What is AOP in Spring?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With