Wednesday, January 6, 2016

Spring Framework Declarative vs Transaction Management
- By Example

Spring Declarative Transactions

In Spring's terminology, transaction management can be done in the following ways:
TypeDefinitionWay of implementing
Declarative TransactionsWhen you do something outside your business logic i.e with some simple configuration you apply a common functionality across all your code.Using @Transactional annotation
Using xml based aop configuration to have a transactional advice
Programmatic TransactionsWhen you add code some functionality by urself so that you could control each and everything by urself. This usually mixes the unrelated functionality in your business logic.Using PlatformTransactionManager
Using TransactionTemplate

Spring Declarative transaction management


1. Annotation style transaction

To use the annotation style transaction management all you have to do is to add a 3 simple bean configuration in your xml file i.e:
  1. : Tells Spring framework to read @Transactional annotation
  2. : Automatically adds transaction support which eventually wraps your code in transaction scope
  3. Initializing DataSourceTransactionManager bean

Xml configuration for enabling @Transactional annotation

  1. transaction-manager="transactionManager"/>
  2. id="transactionManager"
  3. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  4. name="dataSource" ref="dataSource">
  • id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
  • name="driverClassName" value="com.mysql.jdbc.Driver">
  • name="url" value="jdbc:mysql://localhost:3306/apu">
  • name="username" value="root">
  • name="password" value="">
  • id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  • name="dataSource" ref="dataSource">
  • id="userDao" class="springjdbc.transactions.declarative.annotations.AnnotatedUserDao">
  • name="jdbcTemplate" ref="jdbcTemplate">
  • NOTE! Dont forget to change the username and password in the above bean accordingly.

    Using @Transaction in code

    Following is a Dao class that uses @Transactional annotation to add declarative transactions. @Transactional at class level wraps all method in transaction scope. The@Transactional annotation has several properties like readOnly, isolation, propagation,rollbackFor, noRollbackFor etc that can be used to control how one transaction behaves and communicate with other transactions. We use readOnly property of @Transactional to mark the method eligible only for making select queries when@Transactional(readOnly=true) and update, insert queries in case @Transactional(readOnly=false). By default readOnly is false, which means you can perform all the CRUD operations, change it to true will allow only select statements to be executed. Other very useful properties of @Transactional is rollbackFor and noRollbackFor. By default Spring rolls back the transaction for exceptions of type RuntimeException or unchecked exceptions.

    AnnotatedUserDao.java

    1. /**
    2. * @author achauhan
    3. */
    4. @Transactional
    5. public class AnnotatedUserDao implements IUserDao {
    6. private JdbcTemplate jdbcTemplate;
    7.  
    8. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    9. this.jdbcTemplate = jdbcTemplate;
    10. }
    11. public void deleteUser(int uid) {
    12. String delQuery = "delete from users where id = ?";
    13. jdbcTemplate.update(delQuery, new Object[] { uid });
    14.  
    15. }
    16.  
    17. public int insertUser(User user) {
    18. String inserQuery = "insert into users (username, password, enabled , id) values (?, ?, ?, ?) ";
    19. Object[] params = new Object[] { user.getUserName(),
    20. user.getPassword(), user.isEnabled(), user.getId() };
    21. int[] types = new int[] { Types.VARCHAR, Types.VARCHAR, Types.BIT,
    22. Types.INTEGER };
    23. int number = jdbcTemplate.update(inserQuery, params, types);
    24. return number;
    25. }
    26.  
    27. // override the class level transactional behaviour for select method
    28. @Transactional(readOnly = true)
    29. public User selectUser(int uid) {
    30. // for all the RuntimeExceptions the transactions will be automatically
    31. // rolled back
    32. throw new RuntimeException("A runtime exception");
    33.  
    34. }
    35.  
    36. public int updateUser(User user) throws Exception {
    37. throw new Exception("A checked exception");
    38. }
    IMPORTANT! The above methods selectUser and updateUser are intentionally left blank so that users could understand the rollback behaviour of transactions
    The selectUser method throws a RuntimeException. When Spring notices that the method is throwing a RuntimeException it automatically rolls back the transaction. Contrary to this is the call to updateUser which throws a checked exception. When spring sees that exception is checked exception, it proceeds to commit the transactions. To get a deep understanding of what Spring is doing behind the scenes, you can switch the log4j level to trace and check out the console.

    Overriding default rollback behaviour

    To override the default rollback behaviour, we use the rollbackFor and noRollbackFor properties of @Transactional. So taking the example of selectUser method which throws aRuntimeException. To tell the Spring that it should not roll back the transaction in this case, we will write :
    @Transactional(readOnly = true,noRollbackFor=RuntimeException.class)
    Now if you call the selectUser method, following trace is seen on console.
    Following is the comparison of console output with default rollback setting and with overriden rollback setting for selectUser method call.
    @Transactional(readOnly = true)
    1. 20:57:29,141 TRACE [TransactionInterceptor] Getting transaction for [springjdbc.IUserDao.selectUser]
    2. 20:57:29,142 TRACE [TransactionInterceptor] Completing transaction for [springjdbc.IUserDao.selectUser] after exception: java.lang.RuntimeException: A runtime exception
    3. 20:57:29,142 TRACE [RuleBasedTransactionAttribute] Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: A runtime exception
    4. 20:57:29,142 TRACE [RuleBasedTransactionAttribute] Winning rollback rule is: null
    5. 20:57:29,142 TRACE [RuleBasedTransactionAttribute] No relevant rollback rule found: applying default rules
    6. 20:57:29,142 DEBUG [DataSourceTransactionManager] Initiating transaction rollback
    @Transactional(readOnly = true,noRollbackFor=RuntimeException.class)
    1. 21:25:07,694 TRACE [TransactionInterceptor] Getting transaction for [springjdbc.IUserDao.selectUser]
    2. 21:25:07,695 TRACE [TransactionInterceptor] Completing transaction for [springjdbc.IUserDao.selectUser] after exception: java.lang.RuntimeException: A runtime exception
    3. 21:25:07,695 TRACE [RuleBasedTransactionAttribute] Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: A runtime exception
    4. 21:25:07,696 TRACE [RuleBasedTransactionAttribute] Winning rollback rule is: NoRollbackRuleAttribute with pattern [java.lang.RuntimeException]
    5. 21:25:07,697 DEBUG [DataSourceTransactionManager] Initiating transaction commit
    Our test class is similar to the one we used in JdbcTemplate tutorial.

    2. Xml based Transaction configuration

    To use the Xml based Spring Transaction management all you have to do is to add 3 simple bean configuration in your xml file i.e:
    1. Initializing DataSourceTransactionManager bean
    2. : Configures the transaction advice. In this advice we configure various transaction properties for different methods. So what was earlier written as@Transactional(readOnly = true) will now be written as .
    3. : Final step is to apply the transaction advice using aop configuration.
    1. id="transactionManager"
    2. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    3. name="dataSource" ref="dataSource">
  • id="txAdvice" transaction-manager="transactionManager">
  • name="select*" read-only="true" />
  • name="*" />
  • id="userDaoTxPointcut" expression="execution(* springjdbc.IUserDao.*(..))" />
  • advice-ref="txAdvice" pointcut-ref="userDaoTxPointcut" />
  • id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
  • name="driverClassName" value="com.mysql.jdbc.Driver">
  • name="url" value="jdbc:mysql://localhost:3306/apu">
  • name="username" value="root">
  • name="password" value="">
  • id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  • name="dataSource" ref="dataSource">
  • id="userDao" class="springjdbc.transactions.declarative.xml.UserDao">
  • name="jdbcTemplate" ref="jdbcTemplate">
  • The dao class will be same as the AnnotatedUserDao we have seen above but without Spring @Transactional annotation.

    ##########################################################


    ×Spring Programmatic Transactions

    Programmatic transaction management


    1. Using org.springframework.transaction.PlatformTransactionManager

    The DataSourceTransactionManager bean that we have been declaring in the previous xml files, is of type of PlatformTransactionManager and we can directly use is to programmatically handle the transactions.

    Xml configuration for using PlatformTransactionManager

    1. id="transactionManager"
    2. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    3. name="dataSource" ref="dataSource">
  •  
  • id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  • name="dataSource" ref="dataSource">
  • id="userDao" class="springjdbc.transactions.programmatic.usingplatformmanager.PlatformTxManagerUserDao">
  • name="platformTransactionManager" ref="transactionManager">
  • name="jdbcTemplate" ref="jdbcTemplate">
  • Following is a Dao class that uses PlatformTransactionManager. The code can be seen in deleteUser and selectUser method. So lets understand the deleteUser now.
    1. Step 1 is to create an object of DefaultTransactionDefinition. This object exposes various setter methods to configure the transaction accordingly. For eg:defaultTransactionDefinition.setReadOnly(..) etc.
    2. Step 2 is to to create the TransactionStatus object using the transaction definition we creataed in Step 1. Eventually this TransactionStatus will be used to commit or rollback the transaction like platformTransactionManager.commit(transactionStatus); or platformTransactionManager.rollback(transactionStatus);

    PlatformTxManagerUserDao.java

    1. public class PlatformTxManagerUserDao implements IUserDao {
    2.  
    3. private JdbcTemplate jdbcTemplate;
    4. private PlatformTransactionManager platformTransactionManager;
    5. public void setPlatformTransactionManager(
    6. PlatformTransactionManager platformTransactionManager) {
    7. this.platformTransactionManager = platformTransactionManager;
    8. }
    9. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    10. this.jdbcTemplate = jdbcTemplate;
    11. }
    12. public void deleteUser(final int uid) {
    13. DefaultTransactionDefinition paramTransactionDefinition = new DefaultTransactionDefinition();
    14.  
    15. TransactionStatus status=platformTransactionManager.getTransaction(paramTransactionDefinition );
    16. try{
    17. String delQuery = "delete from users where id = ?";
    18. jdbcTemplate.update(delQuery, new Object[]{uid});
    19. platformTransactionManager.commit(status);
    20. }catch (Exception e) {
    21. platformTransactionManager.rollback(status);
    22. }
    23. }

    2. Using org.springframework.transaction.support.TransactionTemplate

    Spring TransactionTemplate is similar to JdbcTemplate and is used to abstract away the boilerplate code from the user. It provides simple callback methods which are automatically wrapped in a transaction. It also exposes direct setter methods for configuring various properties of transactions like readOnly, isolationLevel and propagation behaviour etc. The Spring TransactionTemplate bean is initialized by providing a reference of transactionManager bean. See below(line 21)
    1. id="transactionManager"
    2. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    3. name="dataSource" ref="dataSource">
  • id="txTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  • name="transactionManager" ref="transactionManager">
  • id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  • name="dataSource" ref="dataSource">
  • id="userDao" class="springjdbc.transactions.programmatic.usingtxtemplate.TxTemplatedUserDao">
  • name="jdbcTemplate" ref="jdbcTemplate">
  • name="transactionTemplate" ref="txTemplate">
  • The Dao class the uses TransactionTemplate can be seen in the deleteUser below. Using TransactionTemplate, we dont have to explicitly create a TransactionDefinition and Transaction object. The TransactionTemplate provides a callback method called execute where in we can add our business logic that we want to wrap in a transaction. There are two types of callback methods that we can use to wrap our code i.e TransactionCallbackWithoutResult and TransactionCallback(T). Both these variations can be seen below. The deleteUser method uses TransactionCallbackWithoutResult (line 13) and insertUser method uses TransactionCallback (line 27).

    TxTemplatedUserDao.java

    1. public class TxTemplatedUserDao implements IUserDao {
    2. private JdbcTemplate jdbcTemplate;
    3. private TransactionTemplate transactionTemplate;
    4.  
    5. public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
    6. this.transactionTemplate = transactionTemplate;
    7. }
    8. public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
    9. this.jdbcTemplate = jdbcTemplate;
    10. }
    11. public void deleteUser(final int uid) {
    12. //use TransactionCallbackWithoutResult handler if ur query doesnt result anything
    13. transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    14. protected void doInTransactionWithoutResult(TransactionStatus paramTransactionStatus) {
    15. try{
    16. String delQuery = "delete from users where id = ?";
    17. jdbcTemplate.update(delQuery, new Object[]{uid});
    18. }catch (Exception e) {
    19. //use this to rollback exception in case of exception
    20. paramTransactionStatus.setRollbackOnly();
    21. }
    22. }
    23. });
    24. }
    25. public int insertUser(final User user) {
    26. //use TransactionCallback handler if some result is returned
    27. return transactionTemplate.execute(new TransactionCallback<Integer>() {
    28. public Integer doInTransaction(TransactionStatus paramTransactionStatus) {
    29. String inserQuery = "insert into users (username, password, enabled , id) values (?, ?, ?, ?) ";
    30. Object[] params = new Object[]{user.getUserName(), user.getPassword(),user.isEnabled(),user.getId()};
    31. int[] types = new int[]{Types.VARCHAR,Types.VARCHAR,Types.BIT,Types.INTEGER};
    32. return jdbcTemplate.update(inserQuery,params,types);
    33. }
    34. });
    35. }}

    No comments: