- This topic has 5 replies, 3 voices, and was last updated 13 years, 8 months ago by
jkennedy.
-
AuthorPosts
-
Hi all,
I’m writing for the first time a JUnit test with MyEclipse4Spring and I’m probably doing something wrong.
The transaction isn’t rolled back and the stored objects remain in database.Here’s an part of my Test class :
@RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class }) @ContextConfiguration(locations = { "file:./src/main/resources/AAA-security-context.xml", "file:./src/main/resources/AAA-service-context.xml", "file:./src/main/resources/AAA-dao-context.xml", "file:./src/main/resources/AAA-web-context.xml" }) @Transactional public class QuestionnaireServiceTest { private transient Logger logger = LoggerFactory.getLogger(getClass()); /** * The Spring application context. * */ @SuppressWarnings("unused") private ApplicationContext context; /** * The service being tested, injected by Spring. * */ @Autowired protected QuestionnaireService service; /** * The questionnaireDAO injected by Spring. * */ @Autowired protected QuestionnaireDAO dao; /** * Instantiates a new QuestionnaireServiceTest. * */ public QuestionnaireServiceTest() { setupRequestContext(); } /** * Operation Unit Test * Create a new Questionnaire with associated fields * */ @Test @Transactional @Rollback(true) public void testSaveQuestionnaire() throws Exception { Questionnaire questionnaire = new Questionnaire(); questionnaire.setName("Test JUnit"); // Test questionnaire creation Assert.assertNull(questionnaire.getId()); questionnaire = service.saveQuestionnaire(questionnaire, null); Assert.assertNotNull(questionnaire.getId()); } /** * Autowired to set the Spring application context. * */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Autowired public void setContext(ApplicationContext context) { /*** [SDE] Mock user authentication ***/ String credentials = "pass"; Collection coll = new ArrayList(); coll.add(new GrantedAuthorityImpl("ROLE_ADMIN")); User principal = new User("[email protected]", credentials, true, true, true, true, coll); TestingAuthenticationToken token = new TestingAuthenticationToken(principal, credentials); // Override the regular spring configuration ProviderManager providerManager = (ProviderManager) context.getBean("authenticationManager"); List list = new ArrayList(); list.add(new TestingAuthenticationProvider()); providerManager.setProviders(list); // Create and store the Acegi SecureContext into the ContextHolder. SecurityContextImpl secureContext = new SecurityContextImpl(); secureContext.setAuthentication(token); SecurityContextHolder.setContext(secureContext); /*** [SDE] End mock user authentication ***/ ((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory()).registerScope("session", new SessionScope()); ((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory()).registerScope("request", new RequestScope()); } /** * Sets Up the Request context * */ private void setupRequestContext() { MockHttpServletRequest request = new MockHttpServletRequest(); ServletRequestAttributes attributes = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(attributes); } }
And my tested function :
/** * DWR : Insert or update questionnaire with associated fields. * * @throws Exception * @param questionnaire * @param fields * */ @Transactional public Questionnaire saveQuestionnaire(Questionnaire questionnaire, ArrayList<Field> fields) throws Exception { User currentUser; Calendar currentDate = Calendar.getInstance(); try { currentUser = userService.getCurrentUser(); } catch (UserNotLoggedException unle) { throw new UserNotLoggedException("Vous avez été déconnecté du serveur. Veuillez rafraîchir la page."); } catch (Exception e) { throw new Exception(e.getMessage()); } questionnaire.setUpdater(currentUser); // Create questionnaire if (questionnaire.getId() == null) { questionnaire.setCreation(currentDate); // Update questionnaire } else { Questionnaire oldQuestionnaire = questionnaireDAO.findQuestionnaireByPrimaryKey(questionnaire.getId()); questionnaire.setCreation(oldQuestionnaire.getCreation()); } questionnaire = questionnaireDAO.store(questionnaire); questionnaireDAO.flush(); return questionnaire; }
All assertions pass and I get no error.
Did I do something wrong ?Thanks.
jkennedyMemberWhich Database are you using?
Thanks,
JackHi Jack,
I’m using a MySQL 5.5.10 database (InnoDB) with driver mysql-connector-java.5.1.6-bin.jar
For information (maybe usefull), I’m on MacOSX.Thanks,
Sébastien.
jayperkinsMemberIt appears that there may be a bug with the org.apache.commons.dbcp.BasicDataSource that is configured as the data source in your {project-name}-dao-context.xml:
<bean name=”OracleDS” class=”org.apache.commons.dbcp.BasicDataSource” destroy-method=”close” >
<property name=”driverClassName” value=”${Oracle.connection.driver_class}” />
<property name=”username” value=”${Oracle.connection.username}” />
<property name=”password” value=”${Oracle.connection.password}” />
<property name=”url” value=”${Oracle.connection.url}” />
<property name=”maxIdle” value=”${Oracle.minPoolSize}” />
<property name=”maxActive” value=”${Oracle.maxPoolSize}” />
</bean>I have seen rollbacks ignored with oracle, mysql and derby using this data source both in the junit tests and at runtime. I can’t say if the bug is actually with the BasicDataSource or if it is that data source in combination with the spring transaction annotations.
There are a couple of workarounds that you can try:
1) Use the atomikos non xa data source instead – so your configuration would look like this:
<bean name=”OracleDS” class=”com.atomikos.jdbc.nonxa.AtomikosNonXADataSourceBean” >
<property e=”uniqueResourceName” value=”OracleDS__l-u7AJQFEd-0wrWK7_AOqg” />
<property name=”driverClassName” value=”${Oracle.connection.driver_class}” />
<property name=”user” value=”${Oracle.connection.username}” />
<property name=”password” value=”${Oracle.connection.password}” />
<property name=”url” value=”${Oracle.connection.url}” />
<property name=”minPoolSize” value=”${Oracle.minPoolSize}” />
<property name=”maxPoolSize” value=”${Oracle.maxPoolSize}” />
</bean>This workaround has had the most testing exposure, so it would probably be your best bet.
2) Use the org.apache.commons.dbcp.managed.BasicManagedDataSource instead of the BasicDataSource – so your configuration would look like this:
<bean name=”OracleDS” class=”org.apache.commons.dbcp.managed.BasicManagedDataSource” destroy-method=”close” >
<property name=”transactionManager” ref=”atomikosTransactionManager” />
<property name=”driverClassName” value=”${Oracle.connection.driver_class}” />
<property name=”username” value=”${Oracle.connection.username}” />
<property name=”password” value=”${Oracle.connection.password}” />
<property name=”url” value=”${Oracle.connection.url}” />
<property name=”maxIdle” value=”${Oracle.minPoolSize}” />
<property name=”maxActive” value=”${Oracle.maxPoolSize}” />
</bean>This workaround will probably work, because it appears that the fact that it takes a javax.transaction.TransactionManager allows it to enlist resources in the transaction so that the rollback semantics are honored.
3) Remove the calls to the dao.flush() methods in your service implementation. Removing those calls allowed the rollback semantics to occur as expected. I am not exactly sure why.
This is the least sure workaround, because I don’t know if the BasicDataSource has other problems beside the flush() call appearing to auto commit.
In any event, we will be fixing this for the next release, which is due out soon.
Let me know if any of the workarounds do not actually work for you or if you have other questions.
Thanks,
Jay
Hi Jack,
I used the first solution and the rollback is correctly done.
Thanks again for your expertise.Sébastien.
jkennedyMemberGlad the solution worked, Jay Perkins provided this detail, much appreciation Jay…
Jack
-
AuthorPosts