Hi everyone.
I'm fazed by a transaction problem recently. the story becomes I involved Spring security 3.0 into my webapp project.
At very beginning, I loaded all *-context.xml files by "DispatchServlet", the web.xml code was:
At beginning, Every thing was fine. But because spring security filter will be loaded before DispatcherServlet, so my customized authentication provider bean, which defined in applicationContext.xml, can't be found(ClassNotFoundException was thrown), I have to add the ContextLoaderListener in to load necessary beans before security filter works, and because some servlets and my authentication provider bean depends on dataSource and other beans, finally, I have to put all context files to ContextLoaderListener section, now my web.xml file looks like:Code:... <!-- Servlet Dispatcher --> <servlet> <servlet-name>Servlet Dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml classpath:applicationContext-database.xml; classpath:applicationContext-servlet.xml; </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Servlet Dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> ...
And then, I found @Transactional annotation, which works before, doesn't work anymore.Code:... <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:applicationContext.xml; classpath:applicationContext-database.xml; classpath:applicationContext-servlet.xml; </param-value> </context-param> <!-- Servlet Dispatcher --> <servlet> <servlet-name>Servlet Dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Servlet Dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> ...
There I declared annotation drivers asserts in applicationContext.xml like the following:
And applicationContext-database.xml:Code:... <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> <mvc:annotation-driven/> <context:component-scan base-package="com.wang.rms"/> <tx:annotation-driven transaction-manager="transactionManager"/> ...
There is one of my servlet controller:Code:... <context:property-placeholder location="classpath:database.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${database.connection.driver_class}"/> <property name="url" value="${database.connection.url}"/> <property name="username" value="${database.connection.username}"/> <property name="password" value="${database.connection.password}"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean> <bean id="sqlSessionFactory" class="org.springframework.orm.ibatis3.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:com/wang/rms/dao/ibatis/SqlMapConfig.xml" /> <property name="dataSource" ref="dataSource" /> </bean> <bean id="credentialsDao" class="org.springframework.orm.ibatis3.MapperFactoryBean"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> <property name="mapperInterface" value="com.wang.rms.dao.ibatis.CredentialDao" /> </bean> <bean id="branchDao" class="org.springframework.orm.ibatis3.MapperFactoryBean"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> <property name="mapperInterface" value="com.wang.rms.dao.ibatis.BranchDao" /> </bean> ...
Dao bean's code is:Code:package com.wang.rms.web.controllers; import java.io.IOException; import java.util.List; import javax.servlet.ServletException; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.wang.rms.dao.ibatis.BanchDao; import com.wang.rms.dao.vo.Banch; import com.wang.rms.web.forms.validators.BranchFormValidator; import com.wang.rms.web.forms.vo.BranchForm; @Controller @PreAuthorize("hasRole('admin')") @RequestMapping(value="/admin/branch") @TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false) @Transactional public class BranchController { @Autowired BranchDao branchDao; @Autowired private BranchFormValidator validator; @InitBinder protected void initBinder(WebDataBinder binder) { binder.setValidator(this.validator); } @RequestMapping(value={"/create"}, method = RequestMethod.GET) protected String showBranchCreationForm( ModelMap model) { BranchForm branchForm = new BranchForm(); model.put("branchForm", branchForm); return "/admin/branch/create"; } @RequestMapping(value={"/create"}, method = RequestMethod.POST) @Transactional(propagation=Propagation.REQUIRED, readOnly=false, rollbackFor=Exception.class) protected String createBranch( @Valid @ModelAttribute("branchForm") final BranchForm branchForm, BindingResult result, Model model) throws ServletException, IOException { if(result.hasErrors()) { model.addAttribute("branchForm", branchForm); return "/admin/branch/create"; } banchDao.createBanch(branchForm); model.addAttribute("newBranch", branchForm); return "redirect:/admin/branch/" + branchForm.getId(); } }
I'm using ibatis3 as backend database framework.Code:package com.wang.rms.dao.ibatis; import java.util.List; import com.wang.rms.dao.vo.Branch; public interface BranchDao { public void createBranch(Brancho); }
I also tried put tx:advice into applicationContext-database.xml
but it still doesn't work.Code:<tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="create*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="*" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="ServiceOperation" expression="execution(* com.wang.rms.dao.ibatis.BranchDao.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="ServiceOperation" /> </aop:config>
And, more strange thing is @Transactional annotation works with my testCase.
I think the differens between testcase and controller environment are:package com.wang.rms.dao;
import static org.junit.Assert.*;
import java.util.List;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autow ired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfigurat ion;
import org.springframework.test.context.junit4.SpringJUni t4ClassRunner;
import org.springframework.test.context.transaction.Trans actionConfiguration;
import org.springframework.transaction.annotation.Transac tional;
import com.wang.rms.dao.ibatis.BranchDao;
import com.wang.rms.dao.vo.Branch;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applic ationContext-dbunit-tests.xml","classpath:applicationContext-database.xml"})
@TransactionConfiguration(transactionManager="tran sactionManager", defaultRollback=false)
@Transactional
public class BranchTests
{
protected final Logger logger = LogManager.getLogger(getClass());
@Autowired
private BranchDao branchDao;
@Test
@Rollback(true)
public void testCreateBranch()
{
Branch newBranch = new Branch();
newBranch.setName("New Branch");
branchDao.createBranch(newBranch);
org.junit.Assert.assertTrue(newBranch.getId()>0);
logger.debug(newBranch);
}
}
1. There's not security component works in test case but controller has.
2. controllers are initiated by ContextLoaderListener in Tomcat, and test case is not.
Anyway, I still have not idea about why my controller doesn't woks with @Transactional annotation, is there anybody can give me some advice?


Reply With Quote