Before I start this seemingly LONG paragraph, I like to express my gratitude to any suggestions/advices I may receive. -- John Zhu
I have developed a simple test to help me understand how spring's declarative (@Transactional) transaction management framework works in conjunction with spring's RESTful web services.
For this purpose, I developed the following RESTful controller:
@RequestMapping(value="register", method=RequestMethod.POST, produces="application/json", consumes="application/json")
public void accountFacade () {
tester.accountTest();
}
where "tester" is bean of the following service class and is autowired into this controller. The sole method in the class is meant to be transactional:
@Transactional
public class TransactionTest extends ActivityStatus {
@Autowired
private ObjectManager objectManager;
@Autowired
private GenericDB gdb;
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("META-INF/test- context.xml");
TransactionTest tester = (TransactionTest) ac.getBean("tester");
tester.accountTest();
}
@Transactional(readOnly=false, propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED,
rollbackFor=RuntimeException.class)
public void accountTest () {
try {
String sql = "insert into account set account_id=0, ....";
gdb.jdbcTemplateInsert(sql);
sql = "insert into login set account_id=....";
gdb.jdbcTemplateInsert(sql, values);
} catch (DataAccessException dae) {
System.out.println("Catching DataAccessException (dae)");
throw new RuntimeException (dae);
}
}
In above, GenericDB is a class which provides customized jdbcTemplate.
Test runs are to invoke accountFacade from a browser. I intentionally set the data so that the 1st SQL would succeed, and the 2nd would fail, in order to trigger a rollback.
The PROBLEM: the end-result is that a record was inserted into Account, and remained, even the 2nd insert failed as intended.
A few notes:
If I provide the correct data to both SQLs, test runs would be successful.
If I just run TransactionTest as a standalone java application from Eclipse (see main(...) in the class defn) against a local database, rollback seems to happen.
The execution did catch an AccessDataException (see my print statement in the "catch" block). I thus conclude that "new RuntimeException()" was thrown.
The error message (call stack) due to the failed insertion did show, among many other lines, the following:
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) [spring-tx-3.1.1.RELEASE.jar:3.1.1.RELEASE]
which indicates that method "accountTest" was correctly "wrapped" by Spring's transaction interceptor. This seems to imply that my configuration (see below) was effective.
Related lines in applicationContext.xml:
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="tester" class="org.SandRiver.Controllers.TransactionTest">
</bean>
A few words about my execution environment: I ran the application in OpenShift's environment which consists of
- jboss 6.1
- mysql 5.x (I don't know "x").
- I am using Maven to stay up-to-date with Spring versions.