This time I wanted to explore Spring Transaction Management. There are two types of transaction implementations
1. Programmatic
2. Declarative
I would like to try the programmatic one first. The example I have used is the classic account transfer. For that first I have created a table in PostgreSQL DB.
1. Table Creation
CREATE TABLE account_table
( account_number character varying(20) NOT NULL, owner_name character varying(50) NOT NULL, balance double precision NOT NULL, CONSTRAINT account_table_pk PRIMARY KEY (account_number) )
My account_table has three fields:
1. account_number
2. owner_name
3. balance
with account_number as the primary key.
Also I have inserted some data into the table using the insert commands.
2. Table Bean
Next I have created the java bean corresponding to account_table
public class Account {
@Override
public String toString() {
return "Account [accountNumber=" + accountNumber + ", balance="+ balance + ", name=" + name + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
private String accountNumber;
private double balance;
private String name;
}
3. DAO Interface.
I have created the dao with two operations: update & findByPrimaryKey
public interface AccountDao {
public int update(Account account);
public Account findByPrimaryKey(String id);
}
4. Dao Implementation.
package com.binarynovae.spring.trans;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public int update(Account account) {
String sql = "update account_table set owner_name = ?, balance= ? where account_number = ?";
Object[] args = {account.getName(),account.getBalance(),account.getAccountNumber()};
int status=this.getJdbcTemplate().update(sql, args);
return status;
}
@Override
public Account findByPrimaryKey(String id) {
String sql = "select account_number,balance,owner_name from account_table where account_number = ?";
Object[] args = {id};
List<?> accountList = this.getJdbcTemplate().query(sql, args, new ParameterizedRowMapper(){
public Account mapRow(ResultSet rs, int rowNum)
throws SQLException {
Account account = new Account();
account.setAccountNumber(rs.getString("account_number"));
account.setBalance(rs.getDouble("balance"));
account.setName(rs.getString("owner_name"));
return account;
}
});
if(accountList!=null && accountList.size()>0){
return (Account) accountList.get(0);
}
else{
return null;
}
}
}
5. Service Method
The method which implements the transaction. First I wanted to know if I am not putting the transaction what will happen. Here the class is having transfer method, which will accept from account number, to account number and the amount needs to be transferred. I have put a condition for throwing an exception after deduction the amount from the source account but before adding to the target account. So here if we are entering an amount, which is multiple of 1000, will throw the exception and the transaction become inconsistent.
package com.binarynovae.spring.trans;
import java.util.Scanner;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class AccountServiceUsingTxManager {
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
private AccountDao accountDao;
public PlatformTransactionManager getTxManager() {
return txManager;
}
public void setTxManager(PlatformTransactionManager txManager) {
this.txManager = txManager;
}
private PlatformTransactionManager txManager;
/**
* @param args
*/
public static void main(String[] args) {
BeanFactory BeanFactory = new ClassPathXmlApplicationContext("ApplicationContext.xml");
AccountServiceUsingTxManager accountService = (AccountServiceUsingTxManager)BeanFactory.getBean("accountService");
Scanner scanner = new Scanner(System.in);
System.out.println("Source A/c Number: ");
String srcAcNo = scanner.next();
System.out.println("Dest A/c Number: ");
String dstAcNo = scanner.next();
System.out.println("Amount: ");
double amount = scanner.nextDouble();
System.out.println("Before Transaction: ");
accountService.displayAccount(srcAcNo);
accountService.displayAccount(dstAcNo);
accountService.transfer(srcAcNo, dstAcNo, amount);
System.out.println("After Transaction: ");
accountService.displayAccount(srcAcNo);
accountService.displayAccount(dstAcNo);
}
private void displayAccount(String acNo) {
Account ac = accountDao.findByPrimaryKey(acNo);
System.out.println(ac);
}
private boolean breakTx(double amount){
if(amount%1000==0)
return true;
else
return false;
}
public void transfer(String srcAcNo, String dstAcNo, double amount){
Account src = accountDao.findByPrimaryKey(srcAcNo);
Account target = accountDao.findByPrimaryKey(dstAcNo);
src.setBalance(src.getBalance()-amount);
target.setBalance(target.getBalance()+amount);
//TransactionDefinition txDef = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED);
//TransactionStatus txStatus = txManager.getTransaction(txDef);
try {
accountDao.update(src);
if(this.breakTx(amount))
throw new RuntimeException();
accountDao.update(target);
//txManager.commit(txStatus);
} catch (Exception e) {
e.printStackTrace();
//txManager.rollback(txStatus);
}
}
}
Now to enable transaction management just uncomment the lines which are commented.
6. Jars required in the classpath.
postgresql-8.4-701.jdbc4.jar
spring-jdbc.jar
spring.jar
commons-logging.jar
6. Application Context file
We can see we are creating the instance of DataSourceTransactionManager and injecting into AccountServiceUsingTxManager instance.
<?xml version="1.0" encoding="UTF-8"?>