This page demonstrates very simple 1 resource (Local Transaction) commit and roll-back capabilities of the spring framework. The example on this page is heavy on practice and light on theory. For more information about spring transaction please check the resources section below for spring framework documentation.
Background
Based on the default behavior, when the application throws a run time exception any method that is defined to be transactional issues a roll-back on whatever resource is currently participating in the transaction. To define a method as transactional just put an @Transactional annotation before it.
Requirements
- Java 5
- MySQL installed and configured (not required if you just want to follow along)
- Maven – (tutorial available on the right nav)
- Basic understanding of Transactions
Implementation
The following example will demonstrate the transaction capabilities of the spring framework. Consider it a very basic “hello world” program for transactional code.
First step… Create the table… Its important that you specify the InnoDB engine. Otherwise the table will not support transactional capabilities.
Issue the following create statement in the mySQL database. If you don’t have access to a database to do this then just follow along with me below.
CREATE TABLE `test`.`test_table` ( `id` SMALLINT NOT NULL AUTO_INCREMENT , `data` TEXT NULL , PRIMARY KEY ( `id` ) ) ENGINE = InnoDB
The Second step is to create a project using Maven archetype. Open up the command prompt and navigate to an empty directory.
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart
groupId: com.test
artifactId: springTXTest
Answer the rest of the questions with defaults “Just hit the enter key”
Next, add a couple of entries in the dependencies section. Use the following pom.xml file as a guide. The 2 additional dependencies you need are the spring framework 2.5.5 and the mysql jdbc driver files. Both of these items are available using the maven central repository.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelversion>4.0.0</modelversion>
<groupId>com.test</groupId>
<artifactId>springTXTest</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>springTXTest</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
</dependencies>
</project>
The applicationContext sets up the data source and transaction manager. Replace the database url, username, password “xxxx” with actual values.
src/main/resources/com/test/applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!-- The following <span class="hiddenGrammarError" pre="following ">is used</span> for the @Repository, @Component, @Service, @Controller --> <context:component-scan base-package="com.test"></context:component-scan> <!-- The following <span class="hiddenGrammarError" pre="following ">is used</span> for the @Autowired, @Required, @Resource etc... --> <context:annotation-config></context:annotation-config> <!-- The following <span class="hiddenGrammarError" pre="following ">is used</span> for the @Transactional --> <tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven> <bean name="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> mysql.jdbc.Driver"> xxxx:3306/test"> <property name="username" value="xxxx"></property> <property name="password" value="xxxx"></property> <!--<span class="hiddenSpellError" pre=""-->bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
The following is just an interface.
src/main/java/com/test/TestTransactionDataManager.java
package com.test;
public interface TestTransactionDataManager {
public abstract Number writeData(String data);
}
The following DataManager inserts a row into the test_table using SimpleJdbcInsert. Since SimpleJDBCInsert is aware of any ongoing transactions the insert will happen in a transaction safe way. You could also use JdbcTemplate to achieve the same result.
src/main/java/com/test/TestTransactionDataManagerImpl.java
package com.test;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
/**
* This data manager is used to test the transactional capabilities
* of the spring framework. The code below is not marked as transactional
* however it participates in one when called from a transactional method.
*/
@Repository("testTransactionDataManager")
public class TestTransactionDataManagerImpl implements TestTransactionDataManager {
private SimpleJdbcInsert simpleInsert;
@Required
@Autowired
public void setDataSource(DataSource dataSource) {
simpleInsert = new SimpleJdbcInsert(dataSource).withTableName(
"test_table").usingGeneratedKeyColumns("id");
}
/**
* Responsible for writing data to a transactional resource.
* @param data
*/
public Number writeData(String data) {
Map<string, object=""> parameters = new HashMap<string, object="">(1);
parameters.put("data", data);
Number newId = simpleInsert.executeAndReturnKey(parameters);
return newId.longValue();
}
}
Just an interface…
src/main/java/com/test/TestTransactionModel.java
package com.test;
public interface TestTransactionModel {
public abstract void writeDataWithTx();
public abstract void writeDataWithOutTx();
public abstract void writeDataWithMultipleCalls();
}
This is our Model class. The model layer is a natural place to define unit of work. Model methods are called from “control layer” in the MVC architecture. (Struts actions, SpringMVC Command, or Servlets) THERE SHOULD BE AT MOST ONE call to a transaction enabled model method to get a the “unit of work” behavior. Otherwise if you have more than one call to these methods and one succeeds and the other fails you will not get the “unit of work” behavior. Please keep this in mind… Only one call to a transactional method from the “control layer”.
src/main/java/com/test/TestTransactionModelImpl.java
package com.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("testTransactionModel")
public class TestTransactionModelImpl implements TestTransactionModel {
private TestTransactionDataManager testTransactionDataManager;
/**
* This method will throw a runtime exception and the data will be rolled
* back.
*/
@Transactional // remember @Transaction for public methods only!
public void writeDataWithTx() {
System.out.println("writing data within a transaction");
Number number = testTransactionDataManager.writeData("writeDataWithTx");
System.out.println("wrote item: " + number);
System.out.println("issuing runtime exception in transactional method");
throw new RuntimeException();
}
/**
* This method will throw a runtime exception and the data will still be
* persisted.
*/
public void writeDataWithOutTx() {
System.out.println("writing data without a transaction");
Number number = testTransactionDataManager.writeData("writeDataWithTx");
System.out.println("wrote item: " + number);
System.out.println("issuing runtime exception. you will still see this record in db.");
throw new RuntimeException();
}
/**
* This method tests the capability of calling another method that is
* declared to be transactional where the second method throws an exception
* but this method still commits.
*/
@Transactional
public void writeDataWithMultipleCalls() {
try {
writeDataWithTx();
} catch(RuntimeException ex) {
System.out.println("suppressing runtimeexception and committing anyway you should see this record in DB");
}
}
@Autowired
@Required
public void setTestTransactionDataManager(
TestTransactionDataManager testTransactionDataManager) {
this.testTransactionDataManager = testTransactionDataManager;
}
}
App.java#main() method is our entry point into the system. The doIt() method will call methods on the model layer. Catch any exceptions and continue processing the rest of the model methods.
src/main/java/com/test/App.java
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
private ApplicationContext context = new ClassPathXmlApplicationContext("com/test/applicationContext.xml");
public void doIt() {
TestTransactionModel model = (TestTransactionModel)context.getBean("testTransactionModel");
try {
model.writeDataWithOutTx();
} catch (RuntimeException ex) { // do nothing }
try {
model.writeDataWithTx();
} catch (RuntimeException ex) { // do nothing }
try {
model.writeDataWithMultipleCalls();
} catch (RuntimeException ex) { // do nothing }
}
public App() {
doIt();
}
public static void main(String[] args) {
new App();
}
}
Results
The following is the console output of the program.
writing data without a transaction wrote item: 1 issuing runtime exception. you will still see this record in db. writing data within a transaction wrote item: 2 issuing runtime exception in transactional method writing data within a transaction wrote item: 3 issuing runtime exception in transactional method suppressing runtimeexception and committing anyway you should see this record in DB
Checking the database you will see that only row id’s 1 and 3 got saved on the table.
Resources
That’s all for now!