cypher256's blog

Pleiades とか作った

フォームとアクションの標準構造

上の「アクションの粒度」で書きましたが、アクションと同じ粒度、つまり 1 機能に対し 1 フォームを作成します。一覧の明細はインナークラスにします。こんな感じ。
追記:下記から transient 指定は削除しました。コメント参照。

@Component(instance = InstanceType.SESSION)
public class HogeDto implements Serializable {

    // 検索項目系
    public String searchHogeCd;
    public String searchHogeNm;

    // 登録更新項目系
    public String id;
    @Required(target = ...)
    public String hogeCd;
    @MaxBytesLength(target = ..., length = ...)
    public String hogeNm;

    // DB 更新用に SELECT 時に退避したエンティティのインスタンス
    public THoge tHoge;

    // 検索結果一覧明細表示用の行リスト
    public List<DetailDto> detailDtoList;

    // 検索結果一覧明細表示用の行クラス
    public static class DetailDto implements Serializable {
        public String id;
        public String hogeCd;
        public String hogeNm;
    }
}

上で「ネストしたプロパティの入力チェック」を有効にする方法を書きましたが、結局それは使いません。JSP の name 属性に . がたくさんあるのは開発メンバに受けがよくないようです:)。tHoge は一覧から詳細画面に遷移したときに、SELECT したエンティティを退避しておき、更新時や論理削除時に入力値を上書きコピーします。こんな感じ。論理削除には S2JDBC のカラム指定更新が使えますが、今回はそのあたりは開発基準としては使用禁止にしました。

public class HogeAction {

    @ActionForm
    public HogeDto hogeDto;
    public HogeLogic hogeLogic;
    public Dao dao;

    // 初期表示
    @Execute(validate = false)
    public void index() {
        // フォームのプロパティをすべて null に。
        Objects.clear(hogeDto);
        return "list.jsp";
    }

    // 検索一覧表示
    @Execute(input = "list.jsp")
    public void search() {

        Sql sql = new Sql(hogeDto.isAndOr)
            .append("select * from T_HOGE where")
            .append("HOGE_CD = ?", hogeDto.searchHogeCd)
            .append("HOGE_NM like ?||'%'", hogeDto.searchHogeNm);

        long count = dao.getCountBySql(sql);
        if (count > 0) {
            hogeDto.detailDtoList = new LinkedList<DetailDto>();
            List<BeanMap> list = dao.selectBySql(BeanMap.class, sql);
            for (BeanMap map : list) {
                DetailDto detailDto = Beans.createAndCopy(DetailDto.class, map).execute();
                // 必要に応じて画面表示用に編集
                ...
                hogeDto.detailDtoList.add(detailDto);
            }
        }
        return "list.jsp";
    }

    // 詳細画面表示
    @Execute(input = "list.jsp")
    public void detail() {
        // セッション節約のためクリア
        hogeDto.detailDtoList = null;
        // DB から 1 件取得
        THoge tHoge = dao.from(THoge.class).id(hogeDto.id).getSingleResult();
        // 更新用に退避
        hogeDto.tHoge = tHoge;
        // 画面表示用にコピー
        Beans.copy(tHoge, hogeDto).execute();
        // 必要に応じて画面表示用に編集
        ...
        return "input.jsp";
    }

    // 登録
    @Execute(input = "input.jsp")
    public void regist() {
        Beans.copy(hogeDto, hogeDto.tHoge).execute();
        // 必要に応じて登録用に編集
        ...
        dao.insertExecute(hogeDto.tHoge);
        // セッション節約のためクリア
        hogeDto.tHoge = null;
        return "successRegist.jsp";
    }

    // 更新
    @Execute(input = "input.jsp")
    public void update() {
        Beans.copy(hogeDto, hogeDto.tHoge).execute();
        // 必要に応じて更新用に編集
        ...
        dao.updateExecute(hogeDto.tHoge);
        // セッション節約のためクリア
        hogeDto.tHoge = null;
        return "successUpdate.jsp";
    }

    // 削除
    @Execute(input = "input.jsp")
    public void delete() {
        hogeDto.tHoge.delFlg = Flg.ON;
        dao.updateExecute(hogeDto.tHoge);
        // セッション節約のためクリア
        hogeDto.tHoge = null;
        return "successDelete.jsp";
    }
}

ついでに Dao はこんな。システム共通。AbstractEntity はエンティティの共通項目を定義した共通親クラス。Dao で ActionMessagesException なんかスローすると、エラい人に指摘されそうですけどね。

public class Dao extends JdbcManagerImpl {

    // SELECT 作成
    public <T> SqlSelect<T> selectBySql(Class<T> baseClass, Sql sql) {
        return super.selectBySql(baseClass, sql.getString(), sql.getParams());
    }

    // SELECT で件数取得
    public long getCountBySql(Sql sql) {
        return super.getCountBySql(sql.getString(), sql.getParams());
    }

    // INSERT
    public int insertExecute(AbstractEntity entity) {
        // 共通項目をセット
        entity.insTime = HogeThread.transactionStartTime;
        entity.insUserId = HogeThread.userId;
        ...
        try {
            return super.insert(entity).execute();
        } catch (EntityExistsException e) {
            // ユニーク制約は必要に応じて事前に呼び出し側で
            // チェックするのでシステムエラーに
            throw new ActionMessagesException(Message.ERRORS_SYSTEM);
        }
    }

    // UPDATE
    public int updateExecute(AbstractEntity entity) {
        // 共通項目をセット
        entity.updTime = HogeThread.transactionStartTime;
        entity.updUserId = HogeThread.userId;
        ...
        try {
            return super.update(entity).execute();
        } catch (OptimisticLockException e) {
            // 楽観的ロックされているか、存在しない場合
            throw new ActionMessagesException(Message.ERRORS_MODIFIED);
        }
    }
}