メンバーが誰かより、誰をどこに配置するかが重要

今までプロジェクトとの体制について思うことといえば、
誰がメンバーにいるかだったが、
それよりも、誰をどこに配置するかが重要だと今回のプロジェクトで思った。

みんながみんな声が大きいわけではない。
どこに配置されるかによっていいたいことがいえなかったり、
自由に動けなかったりしてしまうもの。

推奨のパッケージ構成は?

SAStrutsを使用した場合の、パッケージ構成はどれがいいのだろう?
まず、HOT Deploy対象・非対象は分けておいたほうがよさそう。(公式ページには書いてないが)

では、HOT Deploy対象配下はどのようにするべきか?
考えられるパターンとしては、以下のどちらかになるかと思っている。

  1. <ルートパッケージ>.<サブパッケージ>.action.xxxAction
  2. <ルートパッケージ>.action.<サブパッケージ>.xxxAction

メーリングリストでは基本的に2を推奨するが、どちらでもよいとの回答がある。

[Seasar-user:15429] Re: [SAStruts] パッケージ構成の規約について

ただし、1の構成の場合NamingConventionを修正する必要があるとのこと。
あまり標準に手を入れたくないので、2の方法が無難かな。

findByIdで論理削除を除く

findByIdで論理削除されているレコードを除くには、AbstractServiceでやるのがいいみたい。
S2JDBC の弱点を補完するS2AbstractService - 出羽ブログ
けどこの方法は、DBの主キーにIDが導入されている場合なら、
作成するメソッドが一つでいいが、IDが導入されていないと主キーのパターンだけメソッドが必要となってしまう。
もっとスマートな方法はないですかね。。

楽観的ロック発生時の例外処理

楽観的ロック発生時には自画面を表示し、"既に変更されてます"というエラーメッセージを出したい。
Serviceクラスがトランザクション境界の場合は、ActionでSOptimisticLockExceptionをキャッチして、
自画面へreturnすればいいのだが、Actionクラスがトランザクション境界の場合はそうもいかない。
Actionでthrowした例外を、ExceptionHandlerかInterceptorで補足すればうまくできそうだが、
"自画面"を示すJSPがうまくとれるか?

こういうのは一般的な要件の気もするが、みんなどうやってるんだろ?
一律システムエラー画面へ遷移?

2010/08/23追記

Actionクラスがトランザクション境界の場合、Actionから例外スローしないと
ロールバックされないと思っていましたが、Serviceで例外スローしてもされるのですね。。
ActionでSOptimisticLockExceptionをcatchして、自画面へ遷移すればいいだけの話でした。

トランザクション境界はActionかServiceか

今までずっと、Service層でやってきたけど、Actionの方が単純でよいのか?

  • Action層にすると
    • DBを使用しない処理でもトランザクションが発生してしまうので少し気持ち悪い。
    • 1リクエストで複数のテーブルを更新する場合に、Service層に各テーブルを更新するメソッドを作成でき、シンプルになる。
  • Service層にすると
    • 1リクエストで複数のテーブルを更新する場合に、全テーブルの項目をまとめたDTOをつくらないといけない。

DBアクセス時の共通処理の実装

DBアクセス時に共通化したい処理はいくつかあるが、まずは、"INSERT時に更新日時をセットする"という単純なものをS2JDBCで作ってみた。
(以下の記事を非常に参考にさせていただきました。)
S2JDBC の弱点を補完するS2AbstractService - 出羽ブログ

テーブル構造

下記のようなシンプルなテーブルを用意。

CREATE TABLE EMP_TEST(
ID INT(3),
NAME VARCHAR(20),
UPDATE_TIME TIMESTAMP
);

エンティティの作成

全てのテーブルに共通に存在するカラムを、共通のスーパークラスとして定義。
@MappedSuperclassをつけないと、サブクラスの永続プロパティと見なされないので注意。

@MappedSuperclass
public abstract class AbstractEntity {
    
    @Temporal(TemporalType.TIMESTAMP)
    public Date updateTime;
}

次に、共通のスーパクラスを継承したサブクラスを作成。

@Entity
public class EmpTest extends AbstractEntity {

    @Id
    public Integer id;

    public String name;
}

サービスの作成

S2AbstractServiceを継承して、共通のスーパークラスを作成。引数に先ほど作成した、共通のエンティティクラスを持つ、insertメソッドを作成。ここに共通の処理を実装。

public abstract class AbstractService<ENTITY> extends S2AbstractService<ENTITY> {

    public int insert(AbstractEntity entity) {

        //updateTimeにシステム時間をセット       
        Date currentDate = new Date(System.currentTimeMillis());
        entity.updateTime = currentDate;

        return jdbcManager.insert(entity).execute();
    }
}

次に、共通のスーパクラスを継承したサブクラスを作成。
特に固有の処理が必要なければ、空っぽにする。

public class EmpTestService extends AbstractService<AbstractEntity> {
}

呼び出し部分の作成

...
emp.id=1;
emp.name="TEST";
empTestService.insert(emp);
...

これで完成。
他にも"登録者のユーザIDをセットする"ということもやりたいのだが、ユーザIDはセッションから取得したいのでサービスクラスでは取得できない。皆さんどのように実装しているのか?いい方法があれば、教えてください。