kuniku’s diary

はてなダイアリーから移行(旧 d.hatena.ne.jp/kuniku/)、表示がおかしな箇所はコメントをお願いします。記載されている内容は日付およびバージョンに注意してください。直近1年以上前は古い情報の可能性が高くなります。

SpringとiBatisでのTestCase のつづき(1)

2008-04-08 SpringとiBatisでのTestCase
でSpringとiBatisのバージョン書いてなかったので追記。
Spring-Version: 1.2.9
iBatis-Version: 2.3.0 build# 677

前回は簡単なテストクラスを書いたけども、Springの用意したテストクラスを拡張するほうが便利のようです。
それについての実装は、またいつかの機会で。

前回記述したコードで
ApplicationContextを取得する処理の

/** <code>CONTEXT_PATH</code> xmlファイルの指定 */
private static final String CONTEXT_PATH 
   ="/WEB-INF/conf/spring/applicationContext*.xml";//・・・(A)

ApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_PATH);//必要なcontextのみロードさせる場合

と書いたけど、これだとapplicationContextではじまるxmlファイルすべてを読み込んでしまい、ロードにめっちゃ時間がかかる。

なので、指定したファイルだけ読み込まるには、
ClassPathXmlApplicationContextクラスのコンストラクタにいくつか種類があるので、そのうちのString配列を引数とするものを利用すればよい。

public ClassPathXmlApplicationContext(java.lang.String[] configLocations) 
   throws org.springframework.beans.BeansException;

これを利用して下記のように、ApplicationContextのxmlファイルを個別に指定すればよい。

  private static final String contextFile4Common 
       = "/WEB-INF/conf/spring/applicationContext-common.xml";//・・・(E)
  private static final String contextFile4Maintest 
       = "/WEB-INF/conf/spring/applicationContext-dev.xml";//・・・(F)
  
  private static final String[] configFiles 
       = new String[] {contextFile4Common,contextFile4Maintest};//・・・(G)


  /* (非 Javadoc)
   * @see junit.framework.TestCase#setUp()
   */
  @Override
  protected void setUp() throws Exception {

    super.setUp();

    //ApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_PATH); /contextをまとめてすべて取得する場合
    ApplicationContext ctx = new ClassPathXmlApplicationContext(configFiles);//必要なcontextのみロードさせる場合
    

    //取得するBean(コンポーネント)の名称
    final String beanId4DaoName = "mailFindForCsvDao";

    service = (RegistDataManageService) ctx.getBean(beanId4DaoName);

  }

  • E:いつも読み込ませるファイル(DBの接続情報の設定などしているものなど)
  • F:テストしたいServiceなどを設定したファイルや関連ファイル
  • G:ファイルのパス文字列の配列を作成
  • これをClassPathXmlApplicationContextのコンストラクタに指定する

そうすれば必要なファイルだけ読み込みできるのでロード時間を少しは短縮できる。

さらにいろいろと見ていこう。
http://www.infoq.com/jp/articles/testing-in-spring
このへんは参考になりそうだ。。。


SpringとiBatisでのTestCase のつづき(2)

宣言的トランザクションを使ってロールバックができるのかを試してみた。

1つのサービスメソッドにて
insert文を2回実行し、それぞれを独立したトランザクションではなく、1つのトランザクションとして処理できるか
ロールバックされるかどうか調べた。

・データベースは2レコードinsertされていないことを確認済み

ログを見る限りでは、

SQLの実行前に

Getting transaction
Using transaction objec
Creating new transaction with name 
Initializing transaction
Registering transaction synchronization for JDBC Connection

insertできなかったSQLの後に

Initiating transaction rollback
Rolling back JDBC transaction on Connection
Clearing transaction synchronization
Releasing JDBC Connection

と出力されているのでロールバックできていると思う。

以下、実行ログ

[main] INFO  (CollectionFactory.java:66) - JDK 1.4+ collections available
[main] INFO  (CollectionFactory.java:71) - Commons Collections 3.x available
・・・・

[main] DEBUG (AdvisedSupport.java:212) - Added new aspect interface: jp.xxxx.dev.service.RegistDataManageService
[main] DEBUG (JdkDynamicAopProxy.java:102) - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object of type jp.xxxx.dev.service.impl.RegistServiceImpl]
[main] DEBUG (AbstractAutowireCapableBeanFactory.java:295) - Invoking BeanPostProcessors after initialization of bean 'registService'
[main] DEBUG (AbstractBeanFactory.java:850) - Calling code asked for FactoryBean instance for name 'registService'
[main] DEBUG (AbstractBeanFactory.java:190) - Returning cached instance of singleton bean 'registService'
[main] DEBUG (AbstractBeanFactory.java:833) - Bean with name 'registService' is a factory bean
[main] DEBUG (AbstractApplicationContext.java:221) - Publishing event in context [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=23063136]: org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.support.ClassPathXmlApplicationContext: display name [org.springframework.context.support.ClassPathXmlApplicationContext;hashCode=23063136]; startup date [Fri Apr 11 17:12:07 GMT+09:00 2008]; root of context hierarchy]
[main] DEBUG (AbstractBeanFactory.java:190) - Returning cached instance of singleton bean 'registService'
[main] DEBUG (AbstractBeanFactory.java:833) - Bean with name 'registService' is a factory bean

[main] DEBUG (TransactionAspectSupport.java:222) - Getting transaction for jp.xxxx.dev.service.RegistService.insertManageExecuteList
[main] DEBUG (AbstractPlatformTransactionManager.java:263) - Using transaction object [org.springframework.jdbc.datasource.DataSourceTransactionManager$DataSourceTransactionObject@15f7107]
[main] DEBUG (AbstractPlatformTransactionManager.java:291) - Creating new transaction with name [jp.xxxx.dev.service.RegistService.insertManageExecuteList]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
[main] DEBUG (DataSourceTransactionManager.java:188) - Acquired Connection [oracle.jdbc.driver.LogicalConnection@3a5794] for JDBC transaction
[main] DEBUG (DataSourceTransactionManager.java:205) - Switching JDBC Connection [oracle.jdbc.driver.LogicalConnection@3a5794] to manual commit
[main] DEBUG (TransactionSynchronizationManager.java:160) - Bound value [org.springframework.jdbc.datasource.ConnectionHolder@131c89c] for key [oracle.jdbc.pool.OracleDataSource@31f2a7] to thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:212) - Initializing transaction synchronization
[main] INFO  (LoggingInterceptor.java:53) - [AppLog] Entering: method 'insert' of class [jp.xxxx.dev.dao.impl.RegistDaoImpl]
[main] DEBUG (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@131c89c] for key [oracle.jdbc.pool.OracleDataSource@31f2a7] bound to thread [main]
[main] DEBUG (DataSourceUtils.java:116) - Registering transaction synchronization for JDBC Connection
[main] DEBUG (TransactionSynchronizationManager.java:160) - Bound value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] to thread [main]
[main] DEBUG (JakartaCommonsLoggingImpl.java:27) - {conn-100000} Connection
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@131c89c] for key [oracle.jdbc.pool.OracleDataSource@31f2a7] bound to thread [main]
[main] DEBUG (JakartaCommonsLoggingImpl.java:27) - {pstm-100001} Executing Statement: 
		INSERT INTO
		TABLE1(clumn1,clumn2
			)
		VALUES(
			SEQ_PK_XXX.NEXTVAL,
			'abcd'
		)

[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] bound to thread [main]
[main] INFO  (LoggingInterceptor.java:53) - [AppLog] Entering: method 'insert' of class [jp.xxxx.dev.dao.impl.RegistDaoImpl]
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] bound to thread [main]
[main] DEBUG (JakartaCommonsLoggingImpl.java:27) - {conn-100002} Connection
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@131c89c] for key [oracle.jdbc.pool.OracleDataSource@31f2a7] bound to thread [main]
[main] DEBUG (JakartaCommonsLoggingImpl.java:27) - {pstm-100003} Executing Statement: 
		INSERT INTO
		TABLE1(clumn1,clumn2
			)
		VALUES(
			SEQ_PK_XXX.NEXTVAL,
			null
		)

・・・・(略)

[main] DEBUG (AbstractBeanFactory.java:230) - Creating shared instance of singleton bean 'Oracle'
[main] DEBUG (AbstractAutowireCapableBeanFactory.java:335) - Creating instance of bean 'Oracle' with merged definition [Root bean: class [org.springframework.jdbc.support.SQLErrorCodes]; abstract=false; singleton=true; lazyInit=false; autowire=0; dependencyCheck=0; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [org/springframework/jdbc/support/sql-error-codes.xml]]
[main] DEBUG (AbstractAutowireCapableBeanFactory.java:257) - Invoking BeanPostProcessors before instantiation of bean 'Oracle'
[main] DEBUG (CachedIntrospectionResults.java:99) - Using cached introspection results for class [org.springframework.jdbc.support.SQLErrorCodes]
・・・・(略)
[main] DEBUG (AbstractAutowireCapableBeanFactory.java:295) - Invoking BeanPostProcessors after initialization of bean 'Oracle'

・・・・(略)

[main] INFO  (SQLErrorCodesFactory.java:127) - SQLErrorCodes loaded: [DB2, HSQL, MS-SQL, MySQL, Oracle, Informix, PostgreSQL, Sybase]
[main] DEBUG (SQLErrorCodesFactory.java:208) - Looking up default SQLErrorCodes for DataSource [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e]
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] bound to thread [main]
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] bound to thread [main]
[main] DEBUG (SQLErrorCodesFactory.java:264) - Database product name cached for DataSource [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e]: name is 'Oracle'
[main] DEBUG (SQLErrorCodesFactory.java:185) - SQL error codes for 'Oracle' found
[main] DEBUG (SQLErrorCodeSQLExceptionTranslator.java:255) - Unable to translate SQLException with errorCode '20001', will now try the fallback translator
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] bound to thread [main]
[main] INFO  (LoggingInterceptor.java:60) - [AppLog] 例外発生: method 'insert' of class [jp.xxxx.dev.dao.impl.RegistDaoImpl]
org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [72000]; error code [20001];   
--- The error occurred while applying a parameter map.  
--- Check theXXXXXXXXXXXXXXXX-InlineParameterMap.  
--- Check the statement (update failed).  
--- Cause: java.sql.SQLException: ORA-20001: メッセージホゲホゲモバ

・・・・(略)

	at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.executeUpdate(GeneralStatement.java:91)
	at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.insert(SqlMapExecutorDelegate.java:447)
	at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.insert(SqlMapSessionImpl.java:82)
	at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:336)
	at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:169)
	at org.springframework.orm.ibatis.SqlMapClientTemplate.insert(SqlMapClientTemplate.java:334)
	at jp.xxxx.dev.dao.impl.RegistDaoImpl.insert(RegistDaoImpl.java:18)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:291)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:180)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:147)

Caused by: java.sql.SQLException: ORA-20001: ORA-20001: メッセージホゲホゲモバ

・・・・(略)

[main] DEBUG (AbstractPlatformTransactionManager.java:742) - Triggering beforeCompletion synchronization
[main] DEBUG (TransactionSynchronizationManager.java:183) - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@a83a13] for key [org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy@13f136e] from thread [main]
[main] DEBUG (DataSourceUtils.java:291) - Returning JDBC Connection to DataSource
[main] DEBUG (TransactionSynchronizationManager.java:135) - Retrieved value [org.springframework.jdbc.datasource.ConnectionHolder@131c89c] for key [oracle.jdbc.pool.OracleDataSource@31f2a7] bound to thread [main]
[main] DEBUG (AbstractPlatformTransactionManager.java:644) - Initiating transaction rollback
[main] DEBUG (DataSourceTransactionManager.java:258) - Rolling back JDBC transaction on Connection [oracle.jdbc.driver.LogicalConnection@3a5794]
[main] DEBUG (AbstractPlatformTransactionManager.java:766) - Triggering afterCompletion synchronization
[main] DEBUG (TransactionSynchronizationManager.java:266) - Clearing transaction synchronization
[main] DEBUG (TransactionSynchronizationManager.java:183) - Removed value [org.springframework.jdbc.datasource.ConnectionHolder@131c89c] for key [oracle.jdbc.pool.OracleDataSource@31f2a7] from thread [main]
[main] DEBUG (DataSourceTransactionManager.java:299) - Releasing JDBC Connection [oracle.jdbc.driver.LogicalConnection@3a5794] after transaction
[main] DEBUG (DataSourceUtils.java:291) - Returning JDBC Connection to DataSource

宣言的トランザクションの設定方法

以下のようにApplicationContext.xmlにてトランザクション管理を記述する。

xmlおよびサンプルjavaソースにおいては

  • Daoの実装クラス:RegistDaoImpl.java

  • Daoの実装クラスを利用してDBに登録処理を行うサービスクラス:RegistServiceImpl.java
  • としている。
    DB登録処理を行うサービスは、このサービスでトランザクション制御の管理を行おうとしたために作成した。

    注意事項、実ソースから転記する際に、置換などしたため bean:id名やクラス名やクラスのパスなどが
    意図しないものとなっている可能性あり。

    <beans>
    
      <!-- DAO Definitions -->
      <bean id="registDao" class="jp.xxxx.dev.dao.impl.RegistDaoImpl">
        <property name="sqlMapClient"><ref bean="sqlMapClient" /></property>
      </bean>
      
      <!-- トランザクション管理を行うサービスのターゲット -->
      <bean id="registServiceTarget"
        class="jp.xxxx.dev.service.impl.RegistServiceImpl">
        <property name="registDao">
          <ref bean="registDao" />
        </property>
      </bean>
      
      <!-- トランザクション管理を行うサービス -->
      <bean id="registService"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref bean="transactionManager" /></property>
        <property name="target">
          <ref local="registServiceTarget" />
        </property>
        <property name="transactionAttributes">
          <props>
            <prop key="find*">PROPAGATION_REQUIRED, readOnly</prop>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="delete*">PROPAGATION_REQUIRED</prop>
          </props>
        </property>
      </bean>
    
      
      <!-- 2008-04-11 直接service、dao、を関連付けて1つの要素?にした場合 start-->
      <!-- 
      <bean id="registService"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target">
          <bean class="jp.xxxx.dev.service.impl.RegistServiceImpl">
            <property name="registDao">
                  <ref bean="registDao" />
               </property>
          </bean>
            </property>
            
        <property name="transactionManager"><ref bean="transactionManager" /></property>
        <property name="transactionAttributes">
          <props>
            <prop key="find*">PROPAGATION_REQUIRED, readOnly</prop>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="delete*">PROPAGATION_REQUIRED</prop>
          </props>
        </property>
      </bean>
      --> 
      <!-- 2008-04-11 直接service、dao、を関連付けて1つの要素?にした場合 end-->
    
    </beans>
    
    /**
     * Daoの実装クラス springにてiBatisを利用できるSqlMapClientDaoSupportを継承
     */
    public class RegistDaoImpl extends SqlMapClientDaoSupport implements RegistDao {
    
      public void insert(登録するBean bean) {
       getSqlMapClientTemplate().insert("hogehoge.insertB", bean);
      }
    
    }
    
    /**
     * Daoを実行するサービス、このクラスのメソッドがトランザクション対象
     */
    public class RegistServiceImpl implements RegistService {
    
      /** DB処理を担うDAOインターフェイス */
      private RegistDao registDao;
    
      /**
       * setter Injection dao
       */
      public void setRegistDao(RegistDao registDao) {
       this.registDao = registDao;
      }
    
      public void insert(List<登録するBean> beanList) {
        for (登録するBean b : beanList) {
          registDao.insert(b);
        }
      }
    }