Dict.CN 在线词典, 英语学习, 在线翻译

都市淘沙者

荔枝FM Everyone can be host

统计

留言簿(23)

积分与排名

优秀学习网站

友情连接

阅读排行榜

评论排行榜

Unit tests with H2 and iBATIS

source : http://tech.shopzilla.com/2010/05/unit-tests-with-h2-and-ibatis/

Unit tests with H2 and iBATIS
Posted by Steve Corwin Friday, May 7th 
2010 

Since my previous post on Using HSQLDB 
for unit testing database access code I began work on a project that needed to use iBATIS instead of JPA or Hibernate for database access. Based on a recommendation from my previous post, I decided to try H2 instead of HSQLDB as the in-memory database. This project still uses Oracle and Spring.

H2 proved much easier when the production database is Oracle. I was able to copy DDL extracted from the Oracle database and use it (almost) unmodified in H2. The DDL ran without error in all cases I’ve tried, including “created_date date 
default sysdate not null”. There were a couple of issues:
1. there was a Java Boolean being stored in a 1-character varchar column. The Oracle JDBC driver translates to “0″ or “1″, which fits. The H2 JDBC driver translates to “true” or “false”. I just changed the column to “varchar2(5 char)” in the test code.
2. date columns in H2 don’t store the time, only the date (no hours, minutes, seconds). I just changed the column to a timestamp in the test code.
(I did 
try H2’s Oracle compatibility mode, but it didn’t make a difference.)

iBATIS was not so pleasant 
for me. Because of some complexities in this service, which needs to effectively combine operations on two different database connections into one transaction, I decided to use iBATIS manual transaction demarcation. This looks simple, but wasn’t so easy to get working. The iBATIS javadocs show manual transactions as a straightforward pattern:

view sourceprint
?1 try {  

2   sqlMap.startTransaction();  

3   // do work  

4   sqlMap.commitTransaction();  

5 } finally {  

6   sqlMap.endTransaction();  

7 } 

However there is a subtlety: depending on the SqlMapClient configuration, the commitTransaction() and endTransaction() methods may not 
do anything at all. This was the configuration we were getting from our existing Spring config files, and the result was that none of my changes were committed to the database. Apparently something was defaulting to having the transactions handled by com.ibatis.sqlmap.engine.transaction.external.ExternalTransaction. Looking at that source you can see that the commit() method has an empty body.

In order to have my transactions commit, I needed to use the 
class com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig instead. In this code the commit() method calls commit() on the underlying JDBC connection.

Using the correct 
class is just a matter of setting the fully-qualified class name as the value of the “transactionConfigClass” property for the SqlMapClient instance. Here is an example from applicationContext.xml:

view sourceprint
?01 <bean id="sqlMapClient" 

02 class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  

03    <qualifier value="account"/>  

04    <property name="configLocation">  

05      <value>classpath:P13NSqlMapConfig.xml</value>  

06    </property>  

07    <property name="dataSource" ref="masterDataSource" />  

08    <!-- without this setting the commitTransaction() method does not  

09 commit --> 

10    <property name="transactionConfigClass" value=  

11 "com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig" />  

12    

13 </bean> 

Note that you also need to have the connections set to not autocommit. For Oracle 
this seems to be the default.

Using 
this unit-testing technique requires completing three steps:
1. Create a class that will create the tables and (optionally) populate with test data.
2. Create a unit test that will use DbTestUtils to test a DAO implementation.
3. Supply a DataSource to the DAO/class under test.

Most of what follows is the same as it was 
for HSQLDB & Hibernate; the only real difference is the config file, which now supplies iBATIS objects instead of Hibernate/JPA. After some thought I decided to include the sections that haven’t changed so that this post could (hopefully) stand on its own.

1. Create a class that will create the tables and (optionally) populate with test data.

view sourceprint
?01 public class DbTestUtils {  

02     public static void setupTables(Connection connection) throws SQLException {  

03    

04         dropTable(connection, "activation_code_mapping");  

05    

06         executeSql(connection, "create table activation_code_mapping (" 

07                 + "activation_code varchar2(80 char) not null, " 

08                 + "email_address varchar2(500 char) not null, " 

09                 + "source varchar2(25 char) not null, " + "merchant_id number(38), " 

10                 + "historical_merchant_name varchar2(1020 char), " 

11                 // date columns in H2 don't store the time, only the date, so I changed it to  

12                 // timestamp  

13                 + "created_date timestamp default sysdate not null," 

14                 + "constraint pk_activation_code_mapping primary key (activation_code)" + ")");  

15     }  

16    

17     public static void dropSequence(Connection connection, String sequence) {  

18         try {  

19    

20             executeSql(connection, "drop sequence " + sequence);  

21             connection.commit();  

22         } catch (SQLException se) {  

23             // ignore exception  

24             // System.out.println(se);  

25         }  

26     }  

27    

28     public static void dropTable(Connection connection, String table) {  

29         try {  

30    

31             executeSql(connection, "drop table " + table);  

32             connection.commit();  

33         } catch (SQLException se) {  

34             // ignore exception  

35             // System.out.println(se);  

36         }  

37    

38     }  

39    

40     public static void executeSql(Connection connection, String sql) throws SQLException {  

41    

42         Statement statement = connection.createStatement();  

43         statement.execute(sql);  

44     }  

45 } 

2. Create a unit test that will use DbTestUtils to test a DAO implementation.

view sourceprint
?001 public class ActivationCodeMappingCacheStoreTest extends AbstractDependencyInjectionSpringContextTests {  

002    

003     private DataSource dataSource;  

004     private ActivationCodeMappingCacheStore activationCodeMappingCacheStore;  

005    

006     @Override 

007     protected String getConfigPath() {  

008         return "unit-test-config.xml";  

009     }  

010    

011     @Required 

012     public void setDataSource(DataSource dataSource) {  

013         this.dataSource = dataSource;  

014     }  

015    

016     @Required 

017     public void setActivationCodeMappingCacheStore(  

018             ActivationCodeMappingCacheStore activationCodeMappingCacheStore) {  

019         this.activationCodeMappingCacheStore = activationCodeMappingCacheStore;  

020     }  

021    

022     @Override 

023     protected void onSetUp() {  

024    

025         try {  

026             Connection connection = dataSource.getConnection();  

027             connection.setAutoCommit(false);  

028    

029             DbTestUtils.setupTables(connection);  

030    

031             insertTestData(connection);  

032    

033             // simple test to verify that the correct data was inserted into the database  

034             Statement statement = connection.createStatement();  

035             ResultSet resultSet = statement.executeQuery("select * from activation_code_mapping");  

036    

037             assertTrue(resultSet.next());  

038             assertEquals("4fe801", resultSet.getString("activation_code"));  

039    

040             connection.commit();  

041    

042             connection.close();  

043    

044         } catch (SQLException se) {  

045             throw new RuntimeException(se);  

046         }  

047     }  

048    

049     private void insertTestData(Connection connection) throws SQLException {  

050    

051         String sql = "insert into activation_code_mapping(activation_code, email_address, source, merchant_id, historical_merchant_name)" 

052                 + "values ('4fe801', 'arthur@hgttg.com', '0001', 1234567, 'Slartibartfast')";  

053         DbTestUtils.executeSql(connection, sql);  

054     }  

055    

056     public void testLoad() {  

057    

058         // test load  

059         String key = "4fe801";  

060         Object foundObject = activationCodeMappingCacheStore.load(key);  

061         assertNotNull(foundObject);  

062    

063         ActivationCodeMapping activationCodeMapping = (ActivationCodeMapping) foundObject;  

064    

065         // verify columns including date  

066         assertEquals("4fe801", activationCodeMapping.getActivationCode());  

067         assertEquals("arthur@hgttg.com", activationCodeMapping.getEmailAddress());  

068         assertEquals("0001", activationCodeMapping.getSource());  

069         assertEquals(1234567L, activationCodeMapping.getMerchantId().longValue());  

070         assertEquals("Slartibartfast", activationCodeMapping.getHistoricalMerchantName());  

071    

072         assertNotNull(activationCodeMapping.getCreatedDate());  

073         assertEquals(1, activationCodeMapping.getImplVersion());  

074     }  

075    

076     public void testStore() {  

077    

078         final String activationCode = "1234";  

079    

080         // test insert  

081         ActivationCodeMapping activationCodeMappingForInsert = new ActivationCodeMapping();  

082         activationCodeMappingForInsert.setActivationCode(activationCode);  

083         activationCodeMappingForInsert.setEmailAddress("ford@hgttg.com");  

084         activationCodeMappingForInsert.setSource("000A");  

085         activationCodeMappingForInsert.setMerchantId(7890L);  

086         activationCodeMappingForInsert.setHistoricalMerchantName("Milliways");  

087    

088         activationCodeMappingCacheStore.store(activationCode, activationCodeMappingForInsert);  

089    

090         // verify insert  

091         Object foundObject = activationCodeMappingCacheStore.load(activationCode);  

092         assertNotNull(foundObject);  

093    

094         ActivationCodeMapping foundActivationCodeMapping = (ActivationCodeMapping) foundObject;  

095         assertEquals(activationCode, foundActivationCodeMapping.getActivationCode());  

096         assertEquals("ford@hgttg.com", foundActivationCodeMapping.getEmailAddress());  

097         assertEquals("000A", foundActivationCodeMapping.getSource());  

098         assertEquals(7890L, foundActivationCodeMapping.getMerchantId().longValue());  

099         assertEquals("Milliways", foundActivationCodeMapping.getHistoricalMerchantName());  

100         assertNotNull(foundActivationCodeMapping.getCreatedDate());  

101    

102         //   

103     } 

3. Supply a DataSource to the DAO/class under test.

Once again we’re using Spring to inject the needed instances. However the config file is now specifying an instance of an iBATIS SqlMapClient.

view sourceprint
?01 <?xml version="1.0" encoding="UTF-8"?>  

02 <beans xmlns="http://www.springframework.org/schema/beans" 

03                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

04                 xmlns:tx="http://www.springframework.org/schema/tx" 

05                 xsi:schemaLocation="http://www.springframework.org/schema/beans  

06    

07 http://www.springframework.org/schema/beans/spring-beans-2.0.xsd  

08    

09 http://www.springframework.org/schema/tx  

10    

11 http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">  

12    

13   <!-- create data sources connected to an in-memory H2 (not HSQLDB) database. --> 

14   <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">  

15     <property name="driverClass" value="org.h2.Driver"/>  

16     <property name="jdbcUrl" value="jdbc:h2:mem:test;AUTOCOMMIT=OFF"/>  

17     <property name="user" value="sa" />  

18     <property name="password" value="" />  

19   </bean>  

20    

21   <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">  

22     <property name="configLocation">  

23       <value>classpath:ActivationCodeMappingSqlMapConfig.xml</value>  

24     </property>  

25     <property name="dataSource" ref="dataSource" />  

26     <!-- without this setting the commitTransaction() method does not commit --> 

27     <property name="transactionConfigClass" value="com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig" />  

28   </bean>  

29    

30   <bean id="activationCodeMappingCacheStore" class="com.shopzilla.site.service.activationcode.cachestore.ActivationCodeMappingCacheStore" >  

31     <property name="sqlMapClient" ref="sqlMapClient" />  

32   </bean>  

33 </beans> 

After using both H2 
& HSQLDB, I definitely prefer H2 and expect to use it going forward.


posted on 2010-10-09 15:10 都市淘沙者 阅读(728) 评论(0)  编辑  收藏 所属分类: Hibernate/ORM


只有注册用户登录后才能发表评论。


网站导航: