Transactions are one of the many areas that Spring uses AOP to add certain functionality. To get a general introduction to AOP in this lab you will add support for declarative transactions.
There are two ways to enable this functionality, with annotations in the Java code or with XML configuration. Both of these methods will be shown along with some of the strengths and weaknesses.
One of the benefits of the transaction support in Spring is the ability to link the operations of multiple components in a single transaction that will succeed or fail together. One of the most common examples of this is linking the inserting or updating of data in a database with other Java components that might throw Exceptions. If an Exception is thrown by one of the Java components we might want to cancel the database update. This is known as “rolling back” the transaction.
One issue that comes up in working with transactions is how to test they are functioning properly. One possibility is running a test that you know will throw an Exception and verifying that the transaction was rolled back. You will do that in this lab by introducing a method in a Spring component that will always throw an Exception. Even though a real component would never do this you will see the behavior of the components change as transaction support is added. Discussion about what transactional behavior is desired and the relationship between the different components will be discussed in the intro to different sections.
Lab Setup
- Import the projects in the ‘<LABFILES>TransactionTransaction‑Starter.zip‘ file as described in the ‘Common Lab Preparation Steps’ at the ‘Lab Setup’ part.
- Open the Spring perspective if needed.
- In the Markers view check that you have only warnings and no errors. If you do see errors select Project → Clean and clean all projects to see if they are resolved.
Add JPA Transaction Support
Since a database is one of the most common transactional resources, existing code in the imported project is already set to use JPA to work with the database. In order for this to function transactional support for JPA must be added since the JPA implementation will use the status of the transaction to perform the database updates. In this section you will add the transaction support for just JPA to see how that is setup.
- In the Package Explorer view, expand the following folders:Transactions -> src -> com.webage.dao
- Open the PurchaseDAOJPAImpl.java file in the com.webage.dao package by double clicking it.
- Add the following code annotation in bold to the existing class.import com.webage.domain.Purchase;@Repository@Transactionalpublic class PurchaseDAOJPAImpl implements PurchaseDAO {
Note: The @Repository annotation marks the class as a Spring component and also provides Spring DAO Exception translation. The @Transactional annotation marks this as a transactional component which is important to make sure the data is saved to the database. It is important to remember that the @Repository annotation does not add transactional support.
- Save the class and make sure there are no errors.
- Open the ‘src/spring-beans.xml‘ file. Notice right now it contains things like the JPA ‘EntityManagerFactory’ configuration and a DataSource. It doesn’t have any transaction support yet though.
- Switch to the Source tab in the editor.
- Add the following Spring bean configuration. Make sure you add it outside of other bean configurations but within the containing <beans> tag. You can also find code to copy/paste from in the <LABFILES>TransactionSolutionspring‑beans‑annotation.xml file.<!– Spring JPA Transaction Manager –><bean id=”txManager”><property name=”entityManagerFactory” ref=”entityManagerFactory” />
</bean>
<!– Spring JPA Entity Manager –>
<bean id=”entityManagerFactory” …
Note: The choice for what Spring class to use for the TransactionManager is CRITICAL and depends on the environment. This application is running as a standalone Java project so we are linking transactions to the database connection that will be used by JPA. If we were running the application on a Java EE server, where the Java Transaction API (or JTA) is available, we would use some form of JTA transaction manager. The easiest way to do this is to add the following to the Spring configuration which will enable the correct JTA transaction manager based on the server being used:
<tx:jta-transaction-manager/>
Usually the Spring bean that is used as the ‘TransactionManager’ is given the id ‘transactionManager’ so that some of the other configuration is simpler. The bean name here is different so you will see how this is linked to other parts of the configuration.
- Add the following Spring bean configuration. Make sure you add it outside of other bean configurations but within the containing <beans> tag. You can also find code to copy/paste from in the <LABFILES>TransactionSolutionspring‑beans‑annotation.xml file.<!– Spring JPA Transaction Manager –><bean id=”txManager”><property name=”entityManagerFactory” ref=”entityManagerFactory” />
- Add the following configuration which will tell the Spring transaction management to locate transaction details through the annotations in code like @Transactional.</bean><tx:annotation-driven transaction-manager=”txManager” /><!– Spring JPA Entity Manager –>Note: This configuration must link to the Spring bean that is being used as the “TransactionManager”. Normally this configuration would look for a bean called ‘transactionManager’ but since we didn’t use the “conventional” name we must add the attribute here to make sure they are linked.
This syntax will only work if you already have the ‘tx’ Spring schema referenced in your Spring configuration file. This was already done in the file you imported but you could normally go to the ‘Namespaces’ tab in the editor to do this if it wasn’t already referenced.
- Save the file and make sure there are no errors.
- Close all open files.
Verify Current Functionality
Before adding in additional components and transactions it will be good to verify that the current JPA functionality works. That way once we start seeing different behaviors with transactions we can always verify that it is not because the underlying JPA database code isn’t working.
- Start the database following the steps in the ‘Common Lab Preparation Steps‘ at the ‘Start Derby Database‘ part.
- Right click on the Transactions project and select Properties.
- Select the Java Build Path project properties on the left.
- Select the Libraries tab of the Java build path properties and click the button for ‘Add External JAR‘ on the right.
- Navigate to the ‘<SOFTWARE>db-derby-10.7.1.1-binlib‘ directory and select the ‘derbyclient.jar‘ file and press the Open button to complete the selection.
- Check that the JAR file is listed as shown and then click the OK button to close the project properties.
- In the Package Explorer view, expand the following folders:Transactions -> src -> com.webage.test
- Open the DAOTests.java file in the com.webage.test package by double clicking it. Notice that this class already has tests that will directly test the ‘PurchaseDAO’ interface. Since this is the component that works directly with JPA these tests will independently verify the JPA functionality. Examine the tests in more detail if you wish to understand what they do.
- Close the file making sure you did not make any changes. All tests we add will be in a different file.
- Open the TransactionTest.java file in the com.webage.test package by double clicking it. In the future we will add tests to this class to test the ‘PurchaseManager’ interface with transactions but right now no changes will be made.
- With the TransactionTest.java file opened select ‘Run → Run As → JUnit Test‘. If you do not see this option you may not have the editor on the ‘TransactionTest’ class selected and active.
Note: The ‘TransactionTest’ class extends the ‘DAOTests’ class so even though the file is “empty” it does include the 4 JPA tests. This will make it easier to run the transaction tests in the future instead of running the ‘DAOTests’ class directly.
- Verify that you see in the JUnit view that all four existing tests have passed.
Write Additional Transaction Tests
Right now the only part of the application being used is the ‘PurchaseDAO’ JPA implementation that works directly with JPA. We want to introduce an additional component that is included in the transactions managed by Spring. This will show how Spring can initiate transactions in “higher layers” of an application and include the execution of other components in that transaction.
In this application we will introduce a ‘PurchaseManager’ component. This component will use the ‘PurchaseDAO’ component much like our current tests do but could add additional logic around that process. In this case we will just have a method that can cause an Exception to be thrown to demonstrate how transactions behave differently as different transaction support is added to the ‘PurchaseManager’ component.
- In the Package Explorer view, expand the following folders:Transactions -> src -> com.webage.service
- Open the PurchaseManagerImpl.java file in the com.webage.service package by double clicking it.
- Examine the methods and notice that most of the methods simply pass through calls to the injected ‘purchaseDAO’ component. In particular notice that the ‘savePurchaseWithException’ method saves the purchase and then calls a method that will always throw an Exception. What we want to find out is how we can use transactions with this method to have the database updated be “rolled back” when the Exception occurs.@Overridepublic void savePurchaseWithException(Purchase purchase) {purchaseDao.savePurchase(purchase);throwException();
}
- Also notice there is another method, ‘savePurchaseNoException’ which saves the purchase but does not call the method to throw an Exception. This is so we can manually verify transaction behavior by calling different methods in the tests.@Overridepublic void savePurchaseNoException(Purchase purchase) {purchaseDao.savePurchase(purchase);}
- Close the file and make sure you didn’t make any changes.
- Return to the TransactionTest.java file in the com.webage.test package and open it again if you closed it.
- Add the following two new test methods to the class. You can also find code to copy/paste from in the <LABFILES>TransactionSolutionTransactionTest.javafile although only copy these lines of code if you use it.// Implement tests here@Test@DirtiesContextpublic void testSaveExpectingCommit() {
int startingRows = SimpleJdbcTestUtils.countRowsInTable(
simpleJdbcTemplate, “Purchase”);
Purchase purchase2 = (Purchase)
applicationContext.getBean(“purchase2”);
purchaseManager.savePurchaseNoException(purchase2);
int endingRows = SimpleJdbcTestUtils.countRowsInTable(
simpleJdbcTemplate, “Purchase”);
Assert.assertEquals(startingRows + 1, endingRows);
}
@Test
public void testGetPurchaseWithManager() {
int numRows = SimpleJdbcTestUtils.countRowsInTable(
simpleJdbcTemplate, “Purchase”);
Assert.assertTrue(“Table contains no data”, numRows != 0);
Purchase found = purchaseManager.getPurchase(1);
Assert.assertNotNull(“Expected purchase not found”, found);
}
Note: These two tests call methods that do not throw the Exception. These mean that they shouldn’t behave any differently whether in a transaction or not. They are being added mainly to verify the function of the ‘PurchaseManager’ component before adding tests for transactions.
- Save the file and make sure there are no errors.
- From the menus select ‘Run → Run History → TransactionTest‘. You should see that all six tests pass.
- Return to the TransactionTest.java file.
- Add the following new test that will call the method that throws an Exception and verify that the Exception was indeed thrown.@Test@DirtiesContext@ExpectedException(RuntimeException.class)public void testSaveThrowsException() {
Purchase purchase2 = (Purchase)
applicationContext.getBean(“purchase2”);
purchaseManager.savePurchaseWithException(purchase2);
}
Note: Any time a method could throw an Exception it is good to have a test verify that this happens under the conditions expected to throw an Exception. In this case creating those conditions is easy as simply calling the method always throws an Exception.
- Save the file and make sure there are no errors.
- From the menus select ‘Run → Run History → TransactionTest‘. You should see that all seven tests pass.
Modify Transactional Behavior
Up until this point the ‘PurchaseManager’ component has not been transactional. This has not been a problem since none of the tests have examined the state of the database after an Exception is thrown. In this section we will define a test that does this, see that it currently fails, convert the ‘PurchaseManager’ component into a transactional component and then see that the test passes.
Before writing the test it is important to understand the behavior we WANT to see. What we should see is that the database is in the same state when the Exception is thrown in the ‘PurchaseManager’ component. This would verify that the database update was “rolled back” as part of the transaction that was started when a method on the ‘PurchaseManager’ is called. The test should fail if the database is not in the same state at the end of the test as it was at the beginning. This would mean the database update was “committed” even though an Exception was thrown.
- Return to the TransactionTest.java file in the com.webage.test package and open it again if you closed it.
- Add the following new test that will call the method that throws an Exception. Notice the empty ‘catch’ block which will ignore the Exception that is thrown so the test can check if the number of rows in the database has changed.@Test@DirtiesContextpublic void testSaveExpectingRollback() {int startingRows = SimpleJdbcTestUtils.countRowsInTable(
simpleJdbcTemplate, “Purchase”);
Purchase purchase2 = (Purchase)
applicationContext.getBean(“purchase2”);
try {
purchaseManager.
savePurchaseWithException(purchase2);
} catch (Exception e) {
// ignore the exception
}
// see if the database changed
int endingRows = SimpleJdbcTestUtils.countRowsInTable(
simpleJdbcTemplate, “Purchase”);
Assert.assertEquals(startingRows, endingRows);
}
Note: If no Exception were thrown the other test that checks for an Exception would fail. This allows this test method to only concentrate on the state of the database before and after the Exception.
- Save the file and make sure there are no errors.
- From the menus select ‘Run → Run History → TransactionTest‘. You should see that the ‘testSaveExpectingRollback’ test is the only one that fails. This verifies the ‘PurchaseManager’ is not transactional and that the database update was still made.
- In the Package Explorer view, expand the following folders:Transactions -> src -> com.webage.service
- Open the PurchaseManagerImpl.java file in the com.webage.service package by double clicking it.
- Add the following annotation to the class declaration. You will have errors until the next step.@Component@Transactionalpublic class PurchaseManagerImpl implements PurchaseManager {Note: This marks the component as transactional. It is also possible to put this annotation on the ‘PurchaseManager’ interface instead of in this class. Putting this annotation, which is a Spring annotation, in the interface would add “Spring code” to an interface otherwise free of Spring code which is why the annotation was added to the class instead. Transactional behavior is also an important part of the contract for any implementation of this interface though so there are some arguments that adding the annotation to the interface would be warranted.
- Select Source → Organize Imports from the Eclipse menus.
- Save the file and make sure there are no errors.
- From the menus select ‘Run → Run History → TransactionTest‘. You should now see that all eight tests pass. Now that the ‘PurchaseManager’ component is transactional, the Exception causes the interaction with the JPA component to be “rolled back” and the database remains in the same state as the start of the test.
- Return to the PurchaseManagerImpl.java file.
- Add the following annotation only to the ‘getPurchase’ method.@Override@Transactional(readOnly=true)public Purchase getPurchase(int id) {Purchase toReturn = purchaseDao.getPurchase(id);
return toReturn;
}
- Save the file and make sure there are no errors.
- From the menus select ‘Run → Run History → TransactionTest‘. You should see that all eight tests still pass.Note: Marking this method “read only” might give us a performance improvement although it would only be seen with many more tests or concurrent users.You could also control the “propagation” of transactions although we won’t do this. One common thing is to require a new transaction for certain methods. Adding the following to the ‘save’ method in the JPA component would cause our test to fail again since the JPA transaction would be a new transaction and not part of the ‘PurchaseManager’ transaction anymore.@Transactional(propagation=Propagation.REQUIRES_NEW)The default propagation of the @Transactional annotation is REQUIRED which will join an existing transaction if present or start a new one if there is no active transaction.
- Close all open files.
Configure Transaction Behavior With XML
Adding annotations to the code is one way to configure transactions. Another way is to configure this in XML. Annotations are easier to figure out the behavior of individual methods or components because the annotation is in the source code right next to the class or method declaration. Having XML configuration means there is no “Spring code” in your source code but configuring the way transaction behavior is applied to methods is harder because you have to use AOP “Pointcut” expressions.
- Open the ‘src/spring-beans.xml‘ file from the Transactions project.
- Find the line with the <tx:annotation-driven> syntax and comment out or completely delete this entire tag. Make sure you don’t alter any other elements. Also make sure you get an ending tag if there is one. If you try to add a comment Eclipse will automatically close the comment so you will need to move the end comment tag.<tx:annotation-driven transaction-manager=”txManager” />
- Save the file and make sure there are no errors. Errors would occur if you didn’t remove the entire <tx:annotation-driven> element correctly.
- From the menus select ‘Run → Run History → TransactionTest‘. You should see a number of tests fail because none of the Spring components are transactional anymore which means even the basic JPA functionality isn’t working.
- Open the pom.xml file from the root of the ‘Transactions‘ project.
- Switch to the source of the pom.xml by selecting the ‘pom.xml‘ tab along the bottom of the editor. You might need to look for some tabs that are hidden on the far right side and expand those to select the proper view.
- Near the bottom of the file, find the section that is commented out (<!– .. –>) that has the dependency for the AspectJ “Tools” module. Remove the comment tags from before and after this section so the AspectJ JAR is an active dependency.<dependency><groupId>org.aspectj</groupId><artifactId>com.springsource.org.aspectj.tools</artifactId><version>1.6.8.RELEASE</version>
</dependency>
Note: This JAR is not required if you define transaction behavior with annotations. It is only required for XML transaction configuration because we will be adding AOP “Pointcut” expressions to the Spring configuration. These are evaluated with code from this JAR.
- Save the file and be sure there are no red errors in the margin for the file. These will occur if you don’t remove the comments properly.Note: You might also notice that we did not need to add a dependency to the Spring Transaction module for the first parts of the lab. Since the imported application was already a JPA application the Spring Transaction module was already an implicit dependency. This is true for all of the database support modules of Spring.
- Return to the ‘spring-beans.xml‘ file and open it if you had closed it.
- Add the following <tx:advice> syntax to the Spring configuration file, making sure it is outside other XML elements but within the overall <beans> root element. You can also find code to copy/paste from in the <LABFILES>TransactionSolutionspring‑beans‑XML.xml file.<bean id=”txManager”><property name=”entityManagerFactory”ref=”entityManagerFactory” />
</bean>
<tx:advice id=”daoAdvice” transaction-manager=”txManager”>
<tx:attributes>
<tx:method name=”save*” propagation=”REQUIRED” />
<tx:method name=”get*” read-only=”true” />
</tx:attributes>
</tx:advice>
Note: This is a template for method names and the transactional behavior that will be associated with them. Having a common pattern for method names in your Java code would help with this configuration and allow wildcards as is shown here.
Also note that the Transaction Manager bean is referenced from this configuration. This needs to be done since we don’t use the name ‘transactionManager’ for that bean.
- Add the following <aop:config> syntax, making sure it is outside of all other elements. Pay close attention to the spaces and asterisks in the ‘expression’ attribute of the pointcut configuration. The syntax of this expression is the main reason XML configuration is more difficult.</tx:attributes></tx:advice><aop:config><aop:pointcut id=”daoPointcut”
expression=”execution(* com.webage.dao.*.*(..))” />
<aop:advisor advice-ref=”daoAdvice”
pointcut-ref=”daoPointcut” />
</aop:config>
Note: Right now this configuration only turns the JPA component in the ‘dao’ package into a transactional component. We will see how some tests will now pass, similar to when only the JPA component had the @Transactional annotation.
- Save the file and be sure there are no red errors in the margin for the file.
- From the menus select ‘Run → Run History → TransactionTest‘. You should see that the only test that fails is the ‘testSaveExpectingRollback’ test. This proves our JPA component is transactional and functioning again but that the ‘PurchaseManager’ component isn’t transactional.
- Return to the ‘spring-beans.xml‘ file and open it if you had closed it.
- Add the following additional <aop:pointcut> and <aop:advisor> syntax within the existing <aop:config> element. Notice that the order of the elements matters so you need to group the <aop:pointcut> elements together before the <aop:advisor> elements.</tx:attributes></tx:advice><aop:config><aop:pointcut id=”daoPointcut”
expression=”execution(* com.webage.dao.*.*(..))” />
<aop:pointcut id=”servicePointcut”
expression=”execution(* com.webage.service.*.*(..))” />
<aop:advisor advice-ref=”daoAdvice”
pointcut-ref=”daoPointcut” />
<aop:advisor advice-ref=”daoAdvice”
pointcut-ref=”servicePointcut” />
</aop:config>
Note: This additional syntax will add the transactional behavior to the ‘PurchaseManager’ component in the ‘service’ package.
- Save the file and be sure there are no red errors in the margin for the file.
- From the menus select ‘Run → Run History → TransactionTest‘. You should now see that all of the tests pass since the appropriate components are transactional again and behaving as expected.
- Close any open files.
Lab Cleanup
- Delete the TransactionTest run configuration from the JUnit run configurations as described in the ‘Common Lab Preparation Steps’ at the ‘Lab Cleanup’ part.
- Close the Transactions project as described in the ‘Common Lab Preparation Steps’ at the ‘Lab Cleanup’ part.
- Follow the steps in the ‘Common Lab Preparation Steps’ at the ‘Stop Derby Database’ part.
Review
In this lab you added support for declarative transactions in the project. This can include components that are not even directly working with database data.
Using annotations was easier to configure the behavior of individual components or methods but added some “Spring code” to your code. Using XML configuration avoided using Spring code but was much more difficult to configure method behavior. In general it is suggested to use annotations when possible for configuring transactions.
One thing to note however is that no matter which method of configuration was used you did not directly write any transactional code. This was implemented by Spring and you simply controlled the behavior. This is the benefit of the Spring Transaction support.