フォームとアクションの標準構造
上の「アクションの粒度」で書きましたが、アクションと同じ粒度、つまり 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); } } }