DeleteFlagFieldについて
開発ツールが生成したコードでデータの削除を行った場合、SQLのdelete文を発行してレコードを物理削除する処理が実行されます。業務アプリケーションではレコードの物理削除を行わず、削除フラグを用意して論理削除を実装することがよくあります。これは、業務の履歴をなるべく取っておきたいというニーズがあるからです。また、誤って大事なデータを削除してしまった場合にも、復旧対応をしやすくなります。
論理削除を実装する場合、ほとんどの問い合わせで“delete_flag='0'”のような条件式を付けて対応することになります。ところが多くのSQLでこれを行うと、この条件式の記述漏れで削除したはずのデータが顔を出してしまうような不具合がよく発生します。これを避けるためにdataforms.jarのQueryクラスは、DeleteFlagFieldを含むテーブルに対し、自動的に“delete_flag='0'”を生成するようになっています。前に示した「ArticleTableQueryから生成されるSQL」を見ると、“delete_flag='0'”が生成されていることがわかります。
この機能は、QueryクラスのsetEffectivenessOfDeleteFlagメソッドで無効にすることもできます。削除済みデータのバックアップ処理等は、この機能を無効にして実装することになります。
今回は、記事の削除を論理削除に変更します。また、スレッドの先頭記事は、スレッド内の記事が存在する場合削除できないように制御します。「ArticleDao.java」のdeleteメソッドを以下の通りに変更してください。
/** * 記事数の問い合わせクラスです。 * */ public static class ArticleCountQuery extends Query { /** * コンストラクタ。 */ public ArticleCountQuery() { ArticleTable table = new ArticleTable(); FieldList flist = new FieldList(); flist.addField(new CountField("cnt", table.getArticleIdField())); this.setFieldList(flist); this.setMainTable(table); } } /** * データを削除します。 * @param data データ。 * @return 削除件数。 * @throws Exception 例外。 */ public int delete(final Map<String, Object> data) throws Exception { ArticleTable.Entity e = new ArticleTable.Entity(data); Long articleId = e.getArticleId(); ArticleCountQuery query = new ArticleCountQuery(); query.setCondition("m.thread_id=:thread_id"); ArticleTable.Entity p = new ArticleTable.Entity(); p.setThreadId(articleId); query.setQueryFormData(p.getMap()); Integer cnt = (Integer) this.executeScalarQuery(query); if (cnt > 1) { throw new ApplicationException(this.getPage(), "error.cannotdelete"); } else { return this.executeRemove(new ArticleTable(), data); // レコードの論理削除(DeleteFlagの設定) } // return this.executeDelete(new ArticleTable(), data); // レコードの物理削除 }
まず、指定された“articleId”がスレッドIDとなっている記事の数をカウントします。ArticleCountQueryクラスでは、CountFieldクラスを使用してSQLのcount関数を生成しています。このQueryクラスが生成するSQLは以下のようになります。
select count(m.article_id) as cnt from article as m where m.delete_flag='0'
ArticleDaoのdeleteメソッドには、ArticleTableの主キーである記事ID(ID:“articleId”)が引数に指定されたマップに入ってきます。この記事IDを使用し、ArticleCountQueryクラスに「スレッドID=記事ID」の条件を指定して記事数をカウントします。このQueryでは、指定された記事IDがスレッドの先頭記事の場合、スレッド内の記事数を返します。また、スレッド内の記事の場合は必ず0を返します。つまりこの値が1より大きい場合、複数の記事を含むスレッドということになるので、削除できないという例外を投げるようにします。投げる例外はApplicationExceptionクラスで、指定するメッセージはリソースのキーを指定します。メッセージリソースは「ArticlePage.html」と同じ場所に「ArticlePage.properties」というリソースファイルを作成し、そのファイルに登録します。特定のページでしか使用しないメッセージリソースは、ページのHTMLと同じ名前の*.propertiesファイルに設定することができます。
error.cannotdelete=スレッド内に複数の記事が存在するため削除できません。
複数の記事を含むスレッドでない場合は、executeRemoveメソッドを使用してArticleTableを削除します。ソースのコメントにある通り、このメソッドではDeleteFlagFieldに対して“1”を設定するメソッドです。
検索結果リストの改善
自動生成された検索結果リストはあまり適切なリストではありません。リスト中に記事の内容が含まれていますが、複数行のテキストになるため、リストに表示するには情報量が多すぎます。また投稿日時や、添付ファイルの数などはリストに欲しい項目です。
検索結果リスト中に登録日時はすでに存在しますが、添付ファイルの数は存在しません。そこで、各記事の添付ファイル数を返すようにArticleTableQueryを修正します。
/** * 記事ごとの添付ファイルの数を求める問い合わせクラスです。 * */ public static class FileCountQuery extends Query { public static final String ID_FILE_COUNT = "fileCount"; /** * コンストラクタ。 */ public FileCountQuery() { AttachFileTable t = new AttachFileTable(); FieldList flist = new FieldList(); flist.add(t.getArticleIdField()); flist.add(new CountField(ID_FILE_COUNT, t.getFileIdField())); this.setFieldList(flist); this.setMainTable(t); } } /** * QueryFormで指定した条件で行う問い合わせクラスです。 */ public static class ArticleTableQuery extends Query { private static final String FILE_COUNT_ALIAS = "fc"; private static final String THREAD_ALIAS = "th"; public static final String ID_THREAD_NAME = "threadName"; /** * コンストラクタ。 */ public ArticleTableQuery() { SubQuery thread = new SubQuery(new ThreadQuery()); thread.setAlias(THREAD_ALIAS); SubQuery fileCount = new SubQuery(new FileCountQuery()); fileCount.setAlias(FILE_COUNT_ALIAS); Table table = new ArticleTable() { @Override public String getJoinCondition(final Table joinTable, final String alias) { if (THREAD_ALIAS.equals(alias)) { return this.getLinkFieldCondition(ArticleTable.Entity.ID_THREAD_ID, joinTable, alias, ArticleTable.Entity.ID_THREAD_ID); } if (FILE_COUNT_ALIAS .equals(alias)) { return this.getLinkFieldCondition(ArticleTable.Entity.ID_ARTICLE_ID, joinTable, alias, ArticleTable.Entity.ID_ARTICLE_ID); } return super.getJoinCondition(joinTable, alias); } }; this.setFieldList(table.getFieldList()); this.getFieldList().addField(new AliasField(ID_THREAD_NAME, thread.getField(ArticleTable.Entity.ID_TITLE))); this.getFieldList().addField(fileCount.getField(FileCountQuery.ID_FILE_COUNT)); this.setMainTable(table); this.setJoinTableList(new TableList(thread)); this.setLeftJoinTableList(new TableList(fileCount)); } }
この修正では、記事ごとの添付ファイル数を求めるFileCountQueryを作成し、ArticleTableQueryではFileCountQueryのサブクエリをLeft Joinし、添付ファイル数を求めるようにしています。
次に、ArticleQueryResultFormのコンストラクタを以下の通り修正します。
/** * コンストラクタ。 */ public ArticleQueryResultForm() { ArticleTable table = new ArticleTable(); this.addPkFieldList(table.getPkFieldList()); PageScrollHtmlTable htmltable = new PageScrollHtmlTable(Page.ID_QUERY_RESULT, ArticleDao.getQueryResultFieldList()); htmltable.getFieldList().get(ArticleTableQuery.ID_THREAD_NAME).setSortable(true); htmltable.getFieldList().get(ArticleTable.Entity.ID_TITLE).setSortable(true); htmltable.getFieldList().get(ArticleTable.Entity.ID_AUTHER).setSortable(true); // htmltable.getFieldList().get(ArticleTable.Entity.ID_CONTENTS).setSortable(true); htmltable.getFieldList().get(ArticleDao.FileCountQuery.ID_FILE_COUNT).setSortable(true); TimestampField ct = (TimestampField) htmltable.getFieldList().get(ArticleTable.Entity.ID_CREATE_TIMESTAMP); ct.setDateFormat("format.timestampfield"); ct.setSortable(true, SortOrder.DESC); this.addHtmlTable(htmltable); }
この修正では、添付ファイル数のフィールドを追加しています。また記事の作成日時の表示フォーマットを変更し、ソート順を降順に設定しています。
次に、「ArticleQueryResultForm.html」を以下の通り修正します。
<table id="queryResult"> <thead> <tr> <th> No. </th> <th> スレッド </th> <th> 記事タイトル </th> <th> 著者 </th> <th> ファイル数 </th> <th> 登録日時 </th> <th> 操作 </th> </tr> </thead> <tbody> <tr> <td> <span id="queryResult[0].rowNo"></span> <input type="hidden" id="queryResult[0].articleId" /> <input type="hidden" id="queryResult[0].threadId" /> <input type="hidden" id="queryResult[0].createUserId" /> <!-- <input type="hidden" id="queryResult[0].createTimestamp" /> --> <input type="hidden" id="queryResult[0].updateUserId" /> <input type="hidden" id="queryResult[0].updateTimestamp" /> </td> <td> <span id="queryResult[0].threadName"></span> </td> <td> <a id="queryResult[0].updateButton" href="JavaScript:void(0);"><span id="queryResult[0].title"></span></a> </td> <td> <span id="queryResult[0].auther"></span> </td> <td> <span id="queryResult[0].fileCount"></span> </td> <td> <span id="queryResult[0].createTimestamp"></span> </td> <td> <input type="button" id="queryResult[0].viewButton" value="表示"> <!-- <input type="button" id="queryResult[0].referButton" value="参照登録"> --> <input type="button" id="queryResult[0].deleteButton" value="削除"> </td> </tr> </tbody> </table>
この修正で記事内容のフィールドが削除され、添付ファイル数と記事の作成日時が表示されるようになります。
<input type="file" .../>を含むフォームの場合、[参照登録]ボタンの動作は不十分なものになってしまいます。また、この機能は項目の多いテーブルの場合には便利ですが、今回は項目も少ないテーブルであるため、あまりメリットはありません。そのため[参照登録]ボタンを削除しています。この修正で、検索結果リストは以下のようになります。