2012年1月6日金曜日

Struts2 + Spring + mybatis! applicationContext.xmlに必要なあれこれ

という訳で、次はapplicationContext.xml。これは、web.xmlで指定したとおり、WEB-INFの下に置く。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:jee="http://www.springframework.org/schema/jee"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/jee
      http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd"
 default-autowire="byName">

 <context:annotation-config/>
 <context:component-scan base-package="com.motchi.tagdiary"/>

 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName">
   <value>com.mysql.jdbc.Driver</value>
  </property>
  <property name="url">
   <value>jdbc:mysql://localhost:3306/mytest?useUnicode=true&amp;characterEncoding=utf8</value>
  </property>
  <property name="username">
   <value>mytest</value>
  </property>
  <property name="password">
   <value>mytestpass</value>
  </property>
 </bean>

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
 </bean>

 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
 </bean>

 <aop:config proxy-target-class="true">
  <aop:advisor advice-ref="txAdvice" pointcut="@within(org.springframework.stereotype.Service)"/>
 </aop:config>
 <tx:advice id="txAdvice">
  <tx:attributes>
   <tx:method name="insert*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
   <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
   <tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
   <tx:method name="*" propagation="REQUIRED" read-only="true"/>
  </tx:attributes>
 </tx:advice>

 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
  <property name="basePackage" value="com.motchi.tagdiary"/>
  <property name="annotationClass" value="com.motchi.tagdiary.common.annotation.SqlMapper"/>
 </bean>

</beans> 
まず、"@Resource"でいろいろインジェクトできるようにするため、context:annotation-configを使用。
そして、そのインジェクトするBeanも、やはりXMLではなくアノテーションを書くことでSpringに見つけてもらいたいので、context:component-scanも使った。

データベースの設定は、「bean id="dataSource"」のところにユーザ名やパスワードまでベタ書き。今回はDBにMySQLを使うので、ドライバはcom.mysql.jdbc.Driver。普通DBの設定は、外部のXMLファイルやpropertiesファイルに出すところだが、そんなのは後回し。まずはひと通り動くところまで作ってしまおう。

続く「bean id="sqlSessionFactory"」が、mybatisとSpringの架け橋。ここで先ほど定義したデータソースを使う。

そして、ここからが凝りがいのある(かもしれない)ところ。トランザクション境界の設定だ。
今回のアプリのDBアクセスは、こんな風にしたい。

・アクションは、直接DAOを触らず、必ずサービスを介する。
・サービスのメソッドをトランザクション境界とする。
んで、更新系のメソッドは、チェック例外だろうが非チェック例外だろうが、Exceptionの子クラスの例外が発生したらロールバックしたい。
メソッドが更新系かどうかは、メソッド名でわかるようにする。
それ以外のメソッドはSelectするだけ。
・DAOは、サービスから呼ばれて、ただただSQLを実行する。

まず、「bean id="transactionManager"」のところで、データソースを指定し、DataSourceTransactionManagerを作る。これは普通だと思われる。ちなみに、idを「transactionManager」にしておくと、Springが「あーはいはい、例のアレね」と認識してくれて、その下のトランザクションの設定で名前を出す必要がないらしい。

そして次に、aop:config。proxy-target-class="true"にしているのは、サービスはインタフェースではなくクラスにするから。
ああ分かるさ。オブジェクト指向的にはインタフェースの方が望ましいさ。
でもね、多態性を使うわけじゃなし、

HogeHogeServiceってインタフェースにHogeHogeServiceImplってクラスが、
または、
IHogeHogeServiceってインタフェースにHogeHogeServiceってクラスが、

どーせ一対一で対応すんでしょ!

いいじゃんクラス1個で。

で、クラスは「@Service」アノテーションでSpringに認識してもらうので、pointcutは「@within(org.springframework.stereotype.Service)」で決まり。これは、「@Serviceがついたクラスの中のメソッドが対象」という意味らしい。サービスは全部トランザクショナルになってもらいます。

更新系のメソッドは、安直だけど、プレフィックスにinsert, update, deleteを持つものとし、これらの中でExceptionが起こればロールバックをするようにする。これが「tx:advice id="txAdvice"」の中身。

最後の「bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"」で、DAOを自動的に探してもらう。
このMapperScannerConfigurerの使い方、ちょっとぐぐってみたところ、sqlSessionFactoryとbasePackageを指定する例しか出てこなかった。
まあ、普通はそれで事足りるよね。
DAOをどこかのパッケージに集めて、そのパッケージをbasePackageで指定すればいいんだから。
でもね。
パッケージが画面単位や機能単位に分かれていて、それぞれにDAOがあったりすることもあるじゃないですか。
っていう理由を置いといても、サービスを探す方法がアノテーションなんだから、DAOもアノテーションで探してみたい!という好奇心からAPIを調べると、・・・あるじゃないですか、
setAnnotationClass(Class<? extends Annotation> annotationClass)
なんてメソッドが。
そこで、DAOを識別するための俺々アノテーションSqlMapperを作成(中身は空っぽでOK)し、こいつをannotationClassに指定。basePackageはどどーんとルートパッケージにして完成。

SqlMapper.java
package com.motchi.tagdiary.common.annotation;

public @interface SqlMapper {

}
これで準備完了。あとはクラスやらJSPやらを書くだけだ。