Transactions in the Sample Application

In this section, after we discuss the various transaction management approaches in small sample applications, we show you how we implemented and tested transaction management in the SpringBlog application.

There are two levels of transaction requirements. First, you need to maintain the integrity of the data during the kind of insert and update operations that change more than one table. You also need to be aware of business requirements for transactions. In this example application, the business requirement is that every modification of an Entry must have an audit trail.

This implementation uses declarative transaction definitions in the context file, because they are easier to manage and because the proxy allows us to specify additional interceptors for the obscenity filter. In Listing 12-30, we create a blogManager bean that implements the BlogManager interface and we specify that the saveComment() and saveEntry() methods require a transaction. Further, we specify an obscenityFilterAdvisor post interceptor.

Listing 12-30: applicationContext.xml Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean bold">transactionManager"          >         <property name="dataSource">             <ref bean="dataSource"/>         </property>     </bean>          <bean bold">blogManager"          bold">org.springframework.transaction.interceptor.¿             TransactionProxyFactoryBean">         <property name="transactionManager">             <ref bean="transactionManager"/>         </property>         <property name="proxyTargetClass">             <value>true</value>         </property>         <property name="target">             <ref local="blogManagerTarget"/>         </property>         <property name="preInterceptors">             <list>                 <ref local="obscenityFilterAdvisor"/>             </list>         </property>         <property name="transactionAttributes">             <props>                 <prop key="saveComment*">PROPAGATION_REQUIRED</prop>                 <prop key="saveEntry*">PROPAGATION_REQUIRED</prop>             </props>         </property>     </bean> </beans>
image from book

In Listing 12-30, the bean definition is pretty straightforward with no hidden catches. Now in Listing 12-31, let's take a look at the target bean.

Listing 12-31: applicationContext.xml Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean bold">blogManagerTarget"          >         <property name="entryDao"><ref local="entryDao"/></property>         <property name="userDao"><ref local="userDao"/></property>         <property name="commentDao"><ref local="commentDao"/></property>         <property name="auditService"><ref local="auditService"/></property>     </bean>          <bean bold">auditService"          >         <property name="auditDao"><ref local="auditDao"/></property>     </bean> </beans>
image from book

When you look at the blogManagerTarget bean, you may be wondering why we did not include the auditDao in its dependencies and instead created another bean called auditService. There are two fundamental reasons for this. First, the requirement for the audit trail did not specify that the audit needs to be recorded in the database; we did not want to tie the blogManagerTarget to using the database for storing the audit information.[2] The second reason is that we need to write deterministic tests that can prove that the transaction proxy is doing its job properly.

Effective Testing of Transactional Methods

We need a way to write tests that prove that the transaction management is working. In other words, we needed to prove that if an exception is thrown from within a transaction, the transaction will be rolled back. We also needed to make sure that the AuditService implementation gets called exactly once for each Entry operation.

In Listing 12-32, we create two TestCases to demonstrate this: TransactionTest proves that the transaction will be committed if and only if the audit is saved successfully; AuditInvokedTest proves that the auditService bean methods are invoked for operations on entry.

Listing 12-32: AuditInvokedTest Unit Test Implementation

image from book
package com.apress.prospring.business;      import java.util.Date; import java.util.List;      import junit.framework.TestCase;      import com.apress.prospring.data.CommentDao; import com.apress.prospring.data.EntryDao; import com.apress.prospring.domain.Comment; import com.apress.prospring.domain.Entry; import com.apress.prospring.domain.User;      public class AuditInvokedTest extends TestCase {          private DefaultBlogManager bm = new DefaultBlogManager();     private CommentDao commentDao = new MockCommentDao();     private EntryDao entryDao = new MockEntryDao();     private MockAuditService auditService;          public AuditInvokedTest() {         bm.setCommentDao(commentDao);         bm.setEntryDao(entryDao);     }          public void setUp() {         auditService = new MockAuditService();         bm.setAuditService(auditService);     }          public void testSaveEntry() {         bm.saveEntry(new Entry());         performAssert();     }          public void testSaveComment() {         bm.saveComment(new Comment(), new User());         performAssert();     }          private void performAssert() {         assertEquals("The Audit Service was not invoked", 1,              auditService.callCount);     }          private class MockAuditService implements AuditService {         private int callCount = 0;         public void writeAuditMessage(String data, User user) {             callCount++;         }         public void purgeAudit(Date oldestDate) {         }              public int getCallCount() {             return callCount;         }     }          private class MockCommentDao implements CommentDao {         // all methods implemented as stubs     }          private class MockEntryDao implements EntryDao {         // all methods implemented as stubs     }      }
image from book

This test invokes the saveEntry() method on the BlogManager implementation, which, in turn, should call writeAuditMessage() on the AuditService. The key to this test is using mock implementations to make sure that the methods get called appropriately. Once this test succeeds, we know that the BlogManager implementation calls AuditService correctly.

Next, we need to prove that the transaction is rolled back if the audit cannot be written (see Listing 12-33).

Listing 12-33: TransactionTest Unit Test Implementation

image from book
package com.apress.prospring.business;      import java.util.Date;      import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext;      import com.apress.prospring.domain.Entry; import com.apress.prospring.domain.User;      import junit.framework.TestCase;      public class TransactionTest extends TestCase {          private ApplicationContext getApplicationContext() {         String[] paths = new String[] {                 "./business/src/resources/applicationContext-db.xml",                 "./business/src/resources/applicationContext.xml" };              return new FileSystemXmlApplicationContext(paths);     }          public void testTransaction() {         ApplicationContext ctx = getApplicationContext();         DefaultBlogManager bm = (DefaultBlogManager) ctx.getBean("blogManager");         bm.setAuditService(new MockAuditService());              int countBefore = bm.getAllEntries().size();              Entry e = new Entry();         e.setSubject("Tester");         e.setBody("Body");         e.setPostDate(new Date());              try {             bm.saveEntry(e);             fail("Should have thrown RuntimeException");         } catch (RuntimeException expected) {         }              int countAfter = bm.getAllEntries().size();              assertEquals("The new Entry should not have been added", countBefore,                 countAfter);          }          private static class MockAuditService implements AuditService {              public void writeAuditMessage(String data, User user) {             throw new RuntimeException("Foo!");         }              public void purgeAudit(Date oldestDate) {             // no-op         }          } } 
image from book

This test builds the Spring application context from the context files we use in the live application, but it then sets the AuditService implementation to an instance of MockAuditService, which throws a RuntimeException in its writeAuditMessage() method.

In Listing 12-33, we count the number of all entries and attempt to add a new one—this should result in an audit record being created—but because MockAuditService never successfully writes the audit record, we catch a RuntimeException. We go as far as stating that the code fails if no RuntimeException is thrown, because this means that the BlogManager implementation is not using the AuditService implementation correctly. Finally, we count the number of entries in the database and fail the test if the new entry is saved. This proves that an exception thrown from the AuditService causes a transaction rollback in BlogManager.saveEntry() method.

[2] As it is, we are using a database table to store the audit information.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net