Friday, February 22, 2013

Spring Transaction Rollback


  • transaction rollback testing
1) class in DAO layer


public interface AccountDao {

public void addAccount(Account account)  ;

}


@Repository("accountDao")
public class AccountDaoImpl  implements AccountDao {

@Autowired
        private SessionFactory sessionFactory;

public void addAccount(Account account) {
sessionFactory.getCurrentSession().save(account);

}

}

2) class in service layer


@Transactional(rollbackFor=DBException.class)
public interface AccountService {
public void addAccount(Account account) throws DBException;
}


DBException is a self-defined checked exception.

Only unchecked exceptions (that is, subclasses of java.lang.RuntimeException) are rollbacked by default in spring. If want to roll back for checked exception, need to declare by this way @Transactional(rollbackFor=DBException.class)

@Service("accountService")
public class AccountServiceImpl implements AccountService {

@Autowired
private AccountDao accountDao;

public void addAccount(Account account) throws DBException{
try {
accountDao.addAccount(account);
} catch (Exception e) {
e.printStackTrace();
throw new DBException(DBException.ERR_ADD_ACCOUNTS,
e);
}
}

}

3) junit test



@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:spring-context-service-test.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class AccountServiceTester {

@Autowired
AccountService accountService;

@Before
public void setUp() throws Exception {
}

@After
public void tearDown() throws Exception {
}


@Test
@Rollback(false)
public void testAccount2() throws DBException{

Account account=new Account();
account.setUserid("0011abc22");
account.setPassword("111");
account.setTimeStamp(new Date());
accountService.addAccount(account);
Account account2=new Account();
account2.setUserid("0011abc22");
account2.setPassword("222");
account2.setTimeStamp(new Date());
accountService.addAccount(account2);
}

@Test
@Rollback(false)
public void testAccount3() throws DBException{

Account account=new Account();
account.setUserid("0011abc4");
account.setPassword("111");
account.setTimeStamp(new Date());
accountService.addAccount(account);

}

@Test
@Rollback(false)
public void testAccount4() throws DBException{

Account account=new Account();
account.setUserid("0011abc4");
account.setPassword("222");
account.setTimeStamp(new Date());
accountService.addAccount(account);

}
}

@Rollback(false) means the data will be committed into database, otherwise spring will rollback all DB operations.

userId is the private key in DB.

Test Result:
a) The data in method testAccount2()  will not be committed to database because the two accountService.addAccount(account) share the same DB transaction and DB will rollback due to duplicate key error
b) The data in method  testAccount3() will be committed to database as it is a standalone DB transaction
c) The data in method  testAccount4() will not be committed to database due to duplicate key error even it is a standalone DB transaction



  • how to use
public void serviceMethod(){

dao.method1();
....
...
// do something
...
...
dao.method2();

}

It is not good to write the source code if dao.method1() and dao.method2() do NOT share the same DB transaction. If writing code as above, the two dao methods will share the same DB transaction.
what we can do is to write a private method to call the two dao methods individually.

Based on the description in http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html


PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction's rollback status.


in the interface, we have the two methods:


public void addAccount2() throws DBException;
public void addAccount3() throws DBException;

in the implementation class,


@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=DBException.class)

public void addAccount2() throws DBException{
Account account=new Account();
account.setUserid("ccc444");
account.setPassword("111");
account.setTimeStamp(new Date());
try{
accountDao.addAccount(account);
}catch(Exception e){
e.printStackTrace();
throw new DBException(DBException.ERR_ADD_ACCOUNTS,
e);
}
}
@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=DBException.class)

public void addAccount3() throws DBException{
Account account=new Account();
account.setUserid("ccc444");
account.setPassword("222");
account.setTimeStamp(new Date());
try{
accountDao.addAccount(account);
}catch(Exception e){
e.printStackTrace();
throw new DBException(DBException.ERR_ADD_ACCOUNTS,
e);
}
}

in the junit class,


@Test
@Rollback(false) public void testAccount5() throws Exception{
     accountService.addAccount2();
     accountService.addAccount3();
 }

then the two methods have their own DB transactions.


No comments:

Post a Comment