Struts2からデータベースにアクセスしてみました。
Javaでデータベースを扱うとなれば、まずJDBCが思い浮かびます。またWebアプリということもあってコネクションプールを使ってみました。springで生成してもらう為にapplicationContext.xmlに設定します:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>WEB-INF/jdbc.properties</value>
</list>
</property>
</bean>
<!-- jdbc -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="1"/>
<property name="maxActive" value="5"/>
<property name="maxIdle" value="2"/>
<property name="minIdle" value="1"/>
<property name="maxWait" value="-1"/>
<property name="defaultAutoCommit" value="false"/>
<property name="removeAbandoned" value="true"/>
</bean>
コネクションプール系のパラメータはどう設定すべきかわかりませんでしたので適当です、これは見なかったことにしてくださいw
またJDBCドライバの属性については適宜差し替えられるように別ファイル(jdbc.properties)に定義しています:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.0.XXX:3306/Database?useUnicode=true&characterEncoding=utf8
jdbc.username=UserName
jdbc.password=Password
これでJavaのコード中で
@Autowired
private DataSource dataSource = null;
とかすればspringがDataSourceを生成してdataSourceに代入(依存注入)してくれます。
何でもspringにはトランザクションの管理までしてくれる機能があるんだとか。そこで先のコネクションプールに続いてトランザクションマネージャというものをapplicationContext.xmlに追加します:
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
こうするとアノテーションで指定された箇所でトランザクションが効くようになります。不思議ですねwww
トランザクション管理を行っている場合、ConnectionをDataSourceより勝手に取得したり、closeしたりはご法度。然るべき手順で取得や返却をしないといけないそうです:
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Repository;
@Repository
public class MyDao() {
@Autowired
private DataSource dataSource = null;
protected final Connection getConnection() {
return DataSourceUtils.getConnection(this.dataSource);
}
protected final void releaseConnection(Connection con) {
DataSourceUtils.releaseConnection(con, this.dataSource);
}
そりゃ、コネクションプールから好き勝手にConnectionを取得していたら、トランザクションを束ねるなんて出来ませんしね。O/Rマッピングやその関連フレームワークを使わずJDBCを直接操作する場合には注意が必要です。
それとトランザクションに関わるDAOなどには@Repositoryの設定をした方がいいという記事を見かけたので付けてますが、その真意はまだ理解出来ていません、調査中ですw
トランザクションの管理部分に関しては直接Struts2とは関係なくSpringの機能なのですが、アクションから実際にデータを処理するサービスを呼び出し、そのサービスのメソッドにトランザクションの指定をして、そのメソッドからDAO等を使って実際のデータアクセスを行うというシナリオで実験してみました。実に冗長的ですねwww
public class MyAction() {
@Autowired(required=true)
private TxTest tx = null;
@Action(value="action1", results={
@Result(name = "success", location="/WEB-INF/jsp/sample.jsp"),
@Result(name = "failure", location="/WEB-INF/jsp/error.jsp")
})
public String execute() {
try {
:
tx.TxMethod(dto1, dto2);
return "success";
}
catch (Throwable e) {
return "failure";
}
}
上記はアクションからサービスを呼び出しています。引数には2つの異なるテーブル用にそれぞれ新規挿入用のレコードデータを渡しています:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("TxTest")
public class TxTest {
@Autowired(required=true)
private webapp.models.dao.MyDao1 dao1 = null;
@Autowired(required=true)
private webapp.models.dao.MyDao2 dao2 = null;
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Throwable.class)
public void TxMethod(webapp.models.dto.MyDto1 dto1, webapp.models.dto.MyDto2 dto2) throws Throwable {
dao1.insertData(dto1);
dao2.insertData(dto2);
}
サービスは受け取ったレコードをDAOを使ってデータベースにINSERTさせます。ここでトランザクション管理の指定をアノテーションで行っているのでメソッドの入り口でトランザクションが開始され、無事例外も発生せずメソッドを抜ければコミット、例外が発生した場合はロールバックされるはずです。
実験としてまず、dao1によるdto1レコードの挿入を成功させ、dao2によるdto2レコードの挿入をわざと失敗させた場合、dao1の結果がロールバックされているかを確認してみると、確かにちゃんとロールバックされてました。dao1、dao2は同じデータベースの別々のテーブルに対してアクセスしています。勿論、2つとも成功させるとちゃんと両者ともコミットされていました。因みに実験ではMySQLを使っています。
本当にトランザクションとか動いてるの?と思い、トランザクションマネージャを派生させてデバッガで見てみました:
package webapp.common;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
public class MyTransactionManager extends DataSourceTransactionManager {
public MyTransactionManager() {
super();
}
public MyTransactionManager(DataSource dataSource) {
super(dataSource);
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
super.doBegin(transaction, definition);
}
@Override
protected Object doSuspend(Object transaction) {
return super.doSuspend(transaction);
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
super.doCommit(status);
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
super.doRollback(status);
}
}
既存のトランザクションマネージャを継承して単に親クラスに実処理をスルーさせるだけのラッパークラスを作成し、先にapplicationContext.xmlに設定したトランザクションマネージャを上記クラスに置き換えてブレークポイントを置いてデバッガで見てみました。多分、トランザクションの開始時にはdoBegin、コミット時にはdoCommit、ロールバック時にはdoRollbackが呼び出されるんじゃないかな?と思ったのですが、実際の動きは予想通りでしたw リフレクションとか使うと、こういったことが出来るんでしょうかね?
アノテーションで指定するだけで、トランザクションの処理コードを書かなくて済むのは便利そうです。今後の課題としては、トランザクション指定された関数を入れ子で呼び出したり、複数のデータベースに対してアクセスする場合のトランザクションの振る舞いは実際のところどうなんだろう?といったところでしょうか。
尚、本記事に関して間違いなどあればご指摘頂ければ幸いです。