001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.taglib;
017
018// import java.util.Locale;
019import java.util.Set;
020import java.sql.Connection;
021import java.sql.ResultSet;
022import java.sql.Statement;
023import java.sql.PreparedStatement;
024import java.sql.SQLException;
025import java.sql.ParameterMetaData;
026
027import org.opengion.hayabusa.common.HybsSystem;
028import org.opengion.hayabusa.common.HybsSystemException;
029import org.opengion.hayabusa.resource.GUIInfo;
030import org.opengion.fukurou.system.Closer ;
031import org.opengion.fukurou.util.ErrorMessage;
032import org.opengion.fukurou.util.StringUtil;
033import org.opengion.fukurou.util.ArraySet;
034import org.opengion.fukurou.db.Transaction;
035import org.opengion.fukurou.db.QueryMaker;
036import org.opengion.fukurou.db.ResultSetValue;
037import org.opengion.fukurou.db.ConnectionFactory;
038
039import static org.opengion.fukurou.util.StringUtil.nval;
040
041/**
042 * データベースのデータコピー/移動/更新/削除を行うタグです。
043 *
044 * <pre>
045 * 検索結果のデータを、action に応じた方法で、処理します。
046 * SELECT文は、BODY部に記述することも可能です。
047 * BODY にSELECT文を記述しない場合は、names と、table から、SELECT文を作成します。
048 * names2 は、INSERTやUPDATE の カラム名で、SELECT文の先頭から順に適用します。
049 * WHERE条件は、SELECT結果を利用できますが、必ず、names2 のカラムか、そうでないならば、
050 * それ以降に記述してください。
051 *
052 * このタグは、DBTableModel を経由せず、直接、接続元から接続先へデータ処理を行います。
053 * 接続元の1レコード単位に、接続先に対して、処理を実行します。
054 * よって、大量データ処理が可能ですが、まとめ処理を行っていない分、時間が掛かります。
055 *
056 * 用途としては、WORKテーブルへのデータコピーや、BKUPテーブルへのコピーが考えられ
057 * ますが、それらは、select insert などの直接的な処理のほうが良いです。
058 * ここでは、別ユーザーや、別インスタンス、または、別データベース(ORACLEから、MySQLへ)など、
059 * dbid違いのテーブルへのデータ処理用途を、想定しています。
060 * なので、複雑な処理や、PL/SQL等のデータベース独自処理は行えません。
061 * SELECT文は、直接記述できるため、データベース固有の関数や、構文を記載可能ですが、
062 * INSERT,UPDATE,DELETE 文は、基本的に共通構文であり、WHERE条件等も、一般的は範囲に
063 * とどめてください。
064 *
065 * SELECTカラムとINSERTカラムが異なる場合は、name 指定と、name2 指定のカラムが対応します。
066 * 追加、更新先のカラム名に変更して置いてください。
067 * BODY部にSELECT文を記述した場合は、カラム順が、name 順となり、name2 と対応されます。
068 * constKeys,constVals も、更新先のカラム名で指定します。
069 * 処理の途中でエラー(例えば、ユニークキー制約等)になった場合は、stopError属性の
070 * 値に応じて処理を継続するかどうかを決定します。
071 * stopError="true" が初期値なので、エラー時点で、処理を中断します。
072 *
073 * action="INSERT"
074 *    SELECT結果を、table2 に、INSERT します。where2,whereNames2 は使用しません。
075 *    name2 を使用しない場合は、name と同じカラム配列で、INSERT します。
076 *    stopError="false"(エラー時も継続する) とした場合、SELECT結果は、最後まで
077 *    INSERTを試みます。
078 *
079 * action="UPDATE"
080 *    SELECT結果を、table2 に、where2,whereNames2 に基づいて UPDATE します。
081 *    SELECTには、更新で使用する where条件となるカラムを含める必要があります。
082 *    更新するカラムは、name2 で指定することになります。
083 *    更新対象が存在しなかった場合は、エラーとは判定していません。
084 *
085 * action="DELETE"
086 *    SELECT結果を、table2 に、where2,whereNames2 に基づいて table2 のデータを 削除 します。
087 *    SELECTには、削除で使用する where条件となるカラムを含める必要があります。
088 *    削除対象が存在しなかった場合は、エラーとは判定していません。
089 *
090 * action="MERGE"
091 *    SELECT結果を、table2 に、where2,whereNames2 に基づいて UPDATE/INSERT します。
092 *    SELECTには、更新で使用する where条件となるカラムを含める必要があります。
093 *    更新するカラムは、name2 で指定することになります。
094 *    更新対象が存在しなかった場合は、INSERT になります。
095 *    (つまり、更新を一度試みて、更新件数が、0件の場合に、INSERTします。)
096 *    INSERTするカラムは、SELECTしたすべてのカラムが対象になります。
097 *
098 * useDelete="true" を指定すると、検索元のデータを削除します。
099 * INSERT 時に指定すれば、MOVE と同じ効果になります。
100 * stopError="false" (エラー時でも処理を継続する)にした場合、検索元のデータ削除は、
101 * エラー行については、実行されません。ただし、UPDATE,DELETE 等で、対象データが
102 * 存在しない場合は、エラーと判断しないため、検索元のデータを削除します。
103 *
104 * SystemData の USE_SQL_INJECTION_CHECK が true か、quotCheck 属性が true の場合は、
105 * SQLインジェクション対策用のシングルクォートチェックを行います。リクエスト引数に
106 * シングルクォート(')が含まれると、エラーになります。
107 *
108 * DBLastSql はセットされません。
109 * つまり、このタグでSELECTされたデータを、ファイル出力することはできません。
110 *
111 * 実行後にリクエストパラメータに以下の値がセットされます。
112 *   DB.COUNT     : 検索結果の件数
113 *   DB.UPCOUNT   : 追加/更新/削除結果の件数
114 *   DB.ERR_CODE  : 検索結果のエラーコード(複数合った場合は、最後のエラーコード)
115 *
116 * ※ このタグは、Transaction タグの対象です。
117 *
118 * @og.formSample
119 * ●形式:
120 *       ・&lt;og:dbCopy action="INSERT" table="TEST_A" table2="TEST_B" /&gt;
121 *         TEST_A のすべてカラム、データを、TEST_B にコピーします。
122 *
123 *       ・&lt;og:dbCopy action="UPDATE" names2="A2,B2" table2="TEST_B" where2="C2=[c1]" &gt;
124 *              select a1,b1,c1 from TEST_A where d1='XXX' order by a1
125 *         &lt;/og:dbCopy&gt;
126 *         TEST_A のa1→A2 , b1→B2 カラムに、WHERE条件 TEST_B.C2 が、TEST_A.c1 に一致するデータのみ 更新します。
127 *
128 * ●body:あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
129 *
130 * ●Tag定義:
131 *   &lt;og:dbCopy
132 *       action             【TAG】実行方法[INSERT/UPDATE/DELETE/MERGE]を指定します(初期値:INSERT)。
133 *       useDelete          【TAG】(jdbcオプション)検索した元のデータを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
134 *       maxRowCount        【TAG】(通常は使いません)データの最大読み込み件数を指定します (初期値:0:[無制限])
135 *       stopZero           【TAG】検索結果が0件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
136 *       dbid               【TAG】検索する対象のDB接続IDを指定します(初期値:null)
137 *       table              【TAG】検索する対象のテーブル名を指定します
138 *       names              【TAG】検索する対象のカラム名をCSV形式で複数指定します(初期値:*)
139 *       where              【TAG】検索する対象を特定するキー条件(where句)を指定します
140 *       orderBy            【TAG】検索する対象の検索順(order by句)を指定します
141 *       dbid2              【TAG】登録する対象のDB接続IDを指定します(初期値:null)
142 *       table2             【TAG】登録する対象のテーブル名を指定します
143 *       names2             【TAG】登録する対象のカラム名をCSV形式で複数指定します
144 *       omitNames2         【TAG】登録する対象外のカラム名をCSV形式で複数指定します
145 *       where2             【TAG】登録する対象を特定するキー条件(where句)を指定します
146 *       whereNames2        【TAG】登録する対象を特定するキー条件(where句)をCSV形式で複数指定します
147 *       constKeys2         【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します
148 *       constVals2         【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します
149 *       quotCheck          【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します (初期値:USE_SQL_INJECTION_CHECK[=true])
150 *       stopError          【TAG】登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
151 *       dispError          【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
152 *       fetchSize          【TAG】(通常は使いません)データのフェッチサイズを指定します(初期値:DB_FETCH_SIZE[={@og.value SystemData#DB_FETCH_SIZE}])
153 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null)
154 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null)
155 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない)
156 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない)
157 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない)
158 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
159 *   &gt;   ... Body ...
160 *   &lt;/og:dbCopy&gt;
161 *
162 * ●使用例
163 *       ・&lt;og:dbCopy action="INSERT" names2="A2,B2,C2" table2="TEST_B" &gt;
164 *              select a1,b1,c1 from TEST_A where d1='XXX' order by a1
165 *         &lt;/og:dbCopy&gt;
166 *         TEST_A のa1→A2 , b1→B2 , c1→C2 カラムに、追加します。
167 *
168 *       ・&lt;og:dbCopy action="INSERT" names="a1,b1,c1" table="TEST_A" names2="A2,B2,C2" table2="TEST_B" /&gt;
169 *         TEST_A のa1→A2 , b1→B2 , c1→C2 カラムに、追加します。 (先の例と同じ処理)
170 *
171 *       ・&lt;og:dbCopy action="INSERT" table="TEST_A" where="d1='1'" dbid="LOCAL" dbid2="OTHER" &gt;
172 *         接続先:LOCAL の TEST_A の 全カラムのd1='1' のレコードを、接続先:OTHER のTEST_A に追加します。
173 *         接続先違い(ユーザー、やデータベース違い)へのINSERTです。
174 *         table2 を指定しない場合は、table と同じとみなされます。
175 *
176 *       ・&lt;og:dbCopy action="INSERT" table="TEST_A" where="d1='1'" dbid="LOCAL" dbid2="OTHER" stopError="false" useDelete="true" &gt;
177 *         接続先:LOCAL の TEST_A の 全カラムのd1='1' のレコードを、接続先:OTHER のTEST_A に移動します。
178 *         接続先違い(ユーザー、やデータベース違い)への移動です。
179 *         先のINSERT が成功したレコードは削除され、最後まで処理が行われます。
180 *         INSERTが失敗(つまり、接続先:OTHER にすでに、ユニークレコードが存在する場合など)時の、検索元のレコードは
181 *         削除されません。
182 *
183 *       ・&lt;og:dbCopy action="MERGE" table="TEST_A" where="d1='1'" dbid="LOCAL" names2="a1,b1,c1" dbid2="OTHER" where="ukey=[ukey]" stopError="false" useDelete="true" &gt;
184 *         接続先:LOCAL の TEST_A の 全カラムのd1='1' のレコードを、接続先:OTHER のTEST_A に移動します。
185 *         接続先:OTHER に、移動先.ukey=[移動元ukey] のデータがあれば、name2="a1,b1,c1" カラムだけ、UPDATE を行い、
186 *         更新件数が、0件の場合は、検索したすべてのカラムで、INSERT を行います。
187 * </pre>
188 *
189 * @og.group DB検索
190 * @og.group DB登録
191 *
192 * @og.rev 6.8.6.0 (2018/01/19) 新規作成
193 *
194 * @version  6.8.6.0 (2018/01/19)
195 * @author       Kazuhiko Hasegawa
196 * @since    JDK8.0,
197 */
198public class DBCopyTag extends CommonTagSupport {
199        /** このプログラムのVERSION文字列を設定します。   {@value} */
200        private static final String VERSION = "6.9.1.0 (2018/02/26)" ;
201        private static final long serialVersionUID = 691020180226L ;
202
203        /** action 引数に渡す事の出来る アクションコマンド  追加する {@value} */
204        public static final String ACT_INSERT   = "INSERT" ;
205        /** action 引数に渡す事の出来る アクションコマンド  更新する {@value} */
206        public static final String ACT_UPDATE   = "UPDATE" ;
207        /** action 引数に渡す事の出来る アクションコマンド  削除する {@value} */
208        public static final String ACT_DELETE   = "DELETE" ;
209        /** action 引数に渡す事の出来る アクションコマンド  マージする {@value} */
210        public static final String ACT_MERGE    = "MERGE" ;
211
212        /** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ  {@value} */
213        private static final int DB_FETCH_SIZE          = HybsSystem.sysInt( "DB_FETCH_SIZE" ) ;
214
215//      /** fetchSize の初期値 {@value} */
216//      public static final int FETCH_SIZE      = 1000 ;                // 6.9.3.0 (2018/03/26) 初期値を100→1000 に変更
217
218        private static final Set<String> ACTION_SET = new ArraySet<>( ACT_INSERT , ACT_UPDATE , ACT_DELETE , ACT_MERGE );
219
220        /** エラーメッセージID {@value} */
221        private static final String ERR_MSG_ID   = HybsSystem.ERR_MSG_KEY;
222
223        private QueryMaker      query   = new QueryMaker();             // 検索元のSELECTのSQL文
224        private QueryMaker      query2  = new QueryMaker();             // 登録先のSQL文
225
226        private ErrorMessage    errMessage      ;
227
228        private String          action          = ACT_INSERT;           // 実行方法[INSERT/UPDATE/DELETE/MERGE]を指定します(初期値:INSERT)。
229        private boolean         useDelete       ;                                       // (jdbcオプション)検索した元のデータを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
230        private int                     maxRowCount     ;                                       // データの最大読み込み件数を指定します (初期値:0:[無制限])
231//      private String          displayMsg      = HybsSystem.sys( "VIEW_DISPLAY_MSG" );         // 検索結果を画面上に表示するメッセージリソースIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
232//      private String          overflowMsg     = "MSG0007";            // 検索結果が、制限行数を超えましたので、残りはカットされました。
233//      private String          notfoundMsg     = "MSG0077";            // 対象データはありませんでした。
234        private boolean         stopZero        ;                                       // 検索結果が0件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
235        private String          dbid            ;                                       // 検索する対象のDB接続IDを指定します(初期値:null)
236        private String          dbid2           ;                                       // 登録する対象のDB接続IDを指定します(初期値:null)
237        private boolean         quotCheck       = HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );              // シングルクォート(') 存在チェックを実施するかどうか[true/false]
238                                                                                                                                                                                        // (初期値:USE_SQL_INJECTION_CHECK[=true])
239        private boolean         stopError       = true;                         // 登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
240        private boolean         dispError       = true;                         // エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
241        private int                     fetchSize       = DB_FETCH_SIZE ;       // フェッチする行数(初期値を100→SystemData.DB_FETCH_SIZE に変更)
242
243//      private int                     errCode         = ErrorMessage.OK;      // 処理結果のエラーコード(複数合った場合は、最後のエラーコード)
244        private long            dyStart         ;                                       // 実行時間測定用のDIV要素を出力します。
245
246        private String          selSQL          ;                                       // 検索元のSELECT文を一時保管する変数。
247        private int                     selCnt          ;                                       // DB.COUNT   : 検索結果の件数
248        private int                     upCnt           ;                                       // DB.UPCOUNT : 追加/更新/削除結果の件数
249
250        /**
251         * デフォルトコンストラクター
252         *
253         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
254         */
255        public DBCopyTag() { super(); }         // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
256
257        /**
258         * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
259         *
260         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
261         *
262         * @return      後続処理の指示
263         */
264        @Override
265        public int doStartTag() {
266                useXssCheck( false );                                   // XSS対策:チェックしません。
267
268                if( useTag() && check( action, ACTION_SET ) ) {
269                        dyStart = System.currentTimeMillis();
270        //              removeSessionAttribute( ERR_MSG_ID );   // 先に、エラーデータを削除しておきます。
271
272                        errMessage = new ErrorMessage( "DBCopyTag Database Error!" );
273                        setSessionAttribute( ERR_MSG_ID,errMessage );
274
275                        query.setQueryType( "SELECT" );                 // 検索元のQUERYタイプ(SELECT固定)
276                        query2.setQueryType( action );                  // 登録先のQUERYタイプ(actionと同じ)
277
278                        // 初期設定
279                        // table2 を指定しない場合は、table と同じテーブルが使用されます。
280                        if( StringUtil.isNull( query2.getTable() ) ) { query2.setTable( query.getTable() ); }
281
282                        // names2を、指定しない場合は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
283                        if( StringUtil.isNull( query2.getNames() ) ) { query2.setNames( query.getNames() ); }
284
285                        return EVAL_BODY_BUFFERED ;                     // Body を評価する。( extends BodyTagSupport 時)
286                }
287                return SKIP_BODY ;                                              // Body を評価しない
288        }
289
290        /**
291         * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
292         *
293         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
294         *
295         * @return      後続処理の指示(SKIP_BODY)
296         */
297        @Override
298        public int doAfterBody() {
299                debugPrint();
300
301                useQuotCheck( quotCheck );              // SQLインジェクション対策
302
303                selSQL = getBodyString();
304
305                return SKIP_BODY ;
306        }
307
308        /**
309         * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
310         *
311         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
312         *
313         * @return      後続処理の指示
314         */
315        @Override
316        public int doEndTag() {
317                debugPrint();
318
319                if( useTag() && check( action, ACTION_SET ) ) {
320                        if( StringUtil.isNull( selSQL ) ) {
321                                selSQL = query.getSelectSQL();                  // この段階で、SELECT文の整合性チェックが行われます。
322                        }
323
324                        execute();              // 実際の処理
325
326                        final int errCode = errMessage.getKekka();
327
328                        setRequestAttribute( "DB.COUNT"         , String.valueOf( selCnt ) );           // DB.COUNT   : 検索結果の件数
329                        setRequestAttribute( "DB.UPCOUNT"       , String.valueOf( upCnt  ) );           // DB.UPCOUNT : 追加/更新/削除結果の件数
330                        setRequestAttribute( "DB.ERR_CODE"      , String.valueOf( errCode ) );          // 検索結果のエラーコード(複数合った場合は、最後のエラーコード)
331
332                        final int rtnCode ;
333                        if( errCode >= ErrorMessage.NG )  {     // 異常
334                                setSessionAttribute( ERR_MSG_ID,errMessage );
335
336                                final String err = TaglibUtil.makeHTMLErrorTable( errMessage,getResource() );
337                                // エラーメッセージをリクエスト変数で持つようにしておく
338                                setRequestAttribute( "DB.ERR_MSG", err );
339
340                                if( dispError ) { jspPrint( err ); }            // dispErrorで表示をコントロール
341
342                                rtnCode = stopError ? SKIP_PAGE : EVAL_PAGE ;
343                        }
344                        else {
345                                removeSessionAttribute( ERR_MSG_ID );   // 問題なければ、エラーデータを削除しておきます。
346                                // 件数0件かつ stopZero = true
347                                rtnCode = selCnt == 0 && stopZero ? SKIP_PAGE : EVAL_PAGE ;
348                        }
349                        return rtnCode ;
350                }
351
352                return EVAL_PAGE ;
353        }
354
355        /**
356         * タグリブオブジェクトをリリースします。
357         * キャッシュされて再利用されるので、フィールドの初期設定を行います。
358         *
359         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
360         * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
361         *
362         */
363        @Override
364        protected void release2() {
365                super.release2();
366                errMessage      = null;
367                selSQL          = null;                         // 検索SELECT文
368                action          = ACT_INSERT;           // 実行方法[INSERT/UPDATE/DELETE/MERGE]を指定します(初期値:INSERT)。
369                useDelete       = false;                        // (jdbcオプション)検索した元のデータを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
370                maxRowCount     = 0;                            // データの最大読み込み件数を指定します (初期値:0:[無制限])
371//              displayMsg      = HybsSystem.sys( "VIEW_DISPLAY_MSG" );         // 検索結果を画面上に表示するメッセージリソースIDを指定します (初期値:VIEW_DISPLAY_MSG[=])
372//              overflowMsg     = "MSG0007";            // 検索結果が、制限行数を超えましたので、残りはカットされました。
373//              notfoundMsg     = "MSG0077";            // 対象データはありませんでした。
374                stopZero        = false;                        // 検索結果が0件のとき処理を続行するかどうか[true/false]を指定します(初期値:false[続行する])
375                dbid            = null;                         // 検索する対象のDB接続IDを指定します(初期値:null)
376                query           = new QueryMaker();             // 検索元のSELECTのSQL文
377                dbid2           = null;                         // 登録する対象のDB接続IDを指定します(初期値:null)
378                query2          = new QueryMaker();             // 登録先のSQL文
379                quotCheck       = HybsSystem.sysBool( "USE_SQL_INJECTION_CHECK" );
380                stopError       = true;                         // 登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)
381                dispError       = true;                         // エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)
382                fetchSize       = DB_FETCH_SIZE ;       // フェッチする行数(初期値を100→SystemData.DB_FETCH_SIZE に変更)
383//              errCode         = ErrorMessage.OK;      // 処理結果のエラーコード(複数合った場合は、最後のエラーコード)
384                dyStart         = 0L;                           // 実行時間測定用のDIV要素を出力します。
385                selCnt          = 0;                            // DB.COUNT   : 検索結果の件数
386                upCnt           = 0;                            // DB.UPCOUNT : 追加/更新/削除結果の件数
387        }
388
389        /**
390         * Query を実行します。
391         *
392         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
393         * @og.rev 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
394         */
395        private void execute() {
396                Statement                       stmt   = null;          // 検索に使用するステートメント
397                ResultSetValue          rsv    = null;          // 検索に使用
398                PreparedStatement       pstmt2 = null;          // INSERT/UPDATE/DELETE に使用するステートメント
399                PreparedStatement       pstmt3 = null;          // MERGE時に使用する INSERT 用ステートメント
400                ParameterMetaData       pMeta2 = null;
401                ParameterMetaData       pMeta3 = null;
402
403                final boolean useParamMetaData = ConnectionFactory.useParameterMetaData( dbid2 );
404
405                String sql2 = null ;
406                String sql3 = null ;
407                final StringBuilder val2Buf = new StringBuilder( BUFFER_MIDDLE );               // 6.9.0.2 (2018/02/13) デバッグ情報用
408                final StringBuilder val3Buf = new StringBuilder( BUFFER_MIDDLE );               // 6.9.0.2 (2018/02/13) デバッグ情報用
409
410                // Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
411                try( final Transaction tran = getTransaction() ) {
412        //              errMessage = new ErrorMessage( "DBCopyTag Database Error!" );
413
414                        final Connection conn = tran.getConnection( dbid );
415                        stmt = conn.createStatement();
416                        if( fetchSize > 0 ) { stmt.setFetchSize( fetchSize ); }
417                        final ResultSet resSer = stmt.executeQuery( selSQL );                           // useDelete で使うため
418                        rsv = new ResultSetValue( resSer );                                                                     // ResultSet を引数に、インスタンス作成
419
420                        // names2を、指定しない場合は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
421                        // 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
422//                      if( StringUtil.isNull( query2.getNames() ) ) {                                          // nullチェックは、すでに終了している。
423                        if( "*".equals( query2.getNames() ) ) {
424                                final String names2 = String.join( "," , rsv.getNames() );              // SELECT文の名前配列から、CSV文字列を作成
425                                query2.setNames( names2 );
426                        }
427
428                        switch( action ) {
429                                case "INSERT" : sql2 = query2.getInsertSQL(); break;
430                                case "UPDATE" : sql2 = query2.getUpdateSQL(); break;
431                                case "DELETE" : sql2 = query2.getDeleteSQL(); break;
432                                case "MERGE"  : sql2 = query2.getUpdateSQL();
433                                                                sql3 = query2.getInsertSQL(); break;
434                                default : break;
435                        }
436
437                        if( StringUtil.isNull( sql2 ) ) {
438                                final String errMsg = "更新用QUERY が作成できませんでした。 "
439                                                                                + " action=[" + action + "]"
440                                                                                + " query2=[" + sql2 + "]" ;
441                                errMessage.addMessage( errMsg );
442                                throw new HybsSystemException( errMsg );
443                        }
444
445                        final String[]  prmNms2 = query2.getParamNames( false );                // 登録QUERYの、変数設定されているカラム名配列。
446                        final int[]             clmNos2 = rsv.getColumnNos( prmNms2,true );             // 変数設定カラムのカラム番号。無ければ、Exception
447
448                        int[] clmNos3 = null;   // MERGE のときの変数設定カラム
449
450                        final boolean useMerge = "MERGE".equals( action );
451
452                        final Connection conn2 = tran.getConnection( dbid2 );
453                        pstmt2 = conn2.prepareStatement( sql2 );
454                        pMeta2 = useParamMetaData ? pstmt2.getParameterMetaData() : null ;
455                        if( useMerge ) {
456                                final Connection conn3 = tran.getConnection( dbid2 );
457                                pstmt3 = conn3.prepareStatement( sql3 );
458                                pMeta3 = useParamMetaData ? pstmt3.getParameterMetaData() : null ;
459
460                                final String[]  prmNms3 = query2.getParamNames( true );         // MERGE のときの INSERT時なので、true を指定。
461                                clmNos3 = rsv.getColumnNos( prmNms3,true );                             // 変数設定カラムのカラム番号。無ければ、Exception
462                        }
463
464                        while( rsv.next() ) {
465                                try {
466                                        selCnt++;                                                                                       // 検索件数の加算
467                                        val2Buf.setLength(0);                                                           // 初期化
468                                        final String[] vals = rsv.getValues();
469                                        for( int no=0; no<clmNos2.length; no++ ) {
470                                                final int cno = clmNos2[no];                                    // 変数設定カラムに該当するアドレス。
471                                                final String val = vals[cno];
472                                                val2Buf.append( val ).append( ',' );                    // valueのCSV形式
473                                                if( useParamMetaData ) {
474                                                        final int type = pMeta2.getParameterType( no+1 );
475                                                        if( val == null || val.isEmpty() ) {
476                                                                pstmt2.setNull( no+1, type );
477                                                        }
478                                                        else {
479                                                                pstmt2.setObject( no+1,val,type );
480                                                        }
481                                                }
482                                                else {
483                                        //              pstmt2.setString( no+1,vals[cno] );
484                                                        pstmt2.setObject( no+1,val );
485                                                }
486                                        }
487
488                                        int cnt = pstmt2.executeUpdate();                                       // 更新件数
489                                        if( useMerge && cnt == 0 ) {                                            // マージアクションで、更新が0件の場合は、INSERTを行う。
490                                                val3Buf.setLength(0);                                                   // 初期化
491                                                for( int no=0; no<clmNos3.length; no++ ) {
492                                                        final int cno = clmNos3[no];                            // 変数設定カラムに該当するアドレス。
493                                                        final String val = vals[cno];
494                                                        val3Buf.append( val ).append( ',' );    // valueのCSV形式
495                                                        if( useParamMetaData ) {
496                                                                final int type = pMeta3.getParameterType( no+1 );
497                                                                if( val == null || val.isEmpty() ) {
498                                                                        pstmt3.setNull( no+1, type );
499                                                                }
500                                                                else {
501                                                                        pstmt3.setObject( no+1,val,type );
502                                                                }
503                                                        }
504                                                        else {
505                                                //              pstmt3.setString( no+1,vals[cno] );
506                                                                pstmt3.setObject( no+1,val );
507                                                        }
508                                                }
509                                                cnt = pstmt3.executeUpdate();                                   // 追加件数
510                                        }
511
512                                        upCnt += cnt;                                                                           // 更新件数の加算
513                                        if( useDelete ) { resSer.deleteRow(); }                         // 途中でエラーになった場合は、ResultSet の削除は行いません。
514                                }
515                                catch( final SQLException ex ) {
516                                        errMessage.addMessage( selCnt,ErrorMessage.NG,ex.getSQLState(),ex.getMessage() );
517                                        if( stopError ) {
518                                                tran.rollback();                                                                // stopError=false の場合、最後まで処理され、commit() されています。
519                                                throw ex;                                                                               // SELECTループ中のSQLExceptionは、stopErrorの判定を行う。
520                                        }
521                                }
522                        }
523
524                        tran.commit();
525                }
526                catch( final SQLException ex ) {
527                        // 6.9.0.2 (2018/02/13) デバッグ情報
528                        final String errMsg = new StringBuilder( BUFFER_MIDDLE )
529                                                                .append( "更新処理実行中にエラーが発生しました。action=[" )
530                                                                .append( action ).append( ']' ).append( CR )
531                                                                .append( " query =[" ).append( selSQL  ).append( ']' ).append( CR )
532                                                                .append( " query2=[" ).append( sql2    ).append( ']' ).append( CR )
533                                                                .append( " query3=[" ).append( sql3    ).append( ']' ).append( CR )
534                                                                .append( " value2=[" ).append( val2Buf ).append( ']' ).append( CR )
535                                                                .append( " value3=[" ).append( val3Buf ).append( ']' ).append( CR )
536                                                                .toString();
537
538//                      final String errMsg = "更新処理実行中にエラーが発生しました。action=[" + action + "]" + CR
539//                                                                      + " query=[" + selSQL + "]"  + CR
540//                                                                      + " query2=[" + sql2 + "]";
541
542                        errMessage.addMessage( ex );
543        //              errMessage.addMessage( errMsg );
544                        errMessage.addMessage( -1,ErrorMessage.NG,ex.getSQLState(),errMsg );
545                        throw new HybsSystemException( errMsg,ex );
546                }
547                finally {
548                        rsv.close();
549                        Closer.stmtClose( stmt  );
550                        Closer.stmtClose( pstmt2 );
551                        Closer.stmtClose( pstmt3 );
552                }
553
554                // 変数の関係で、こちらにもって来ました(データアクセス件数登録)
555                final long dyTime = System.currentTimeMillis()-dyStart;
556                final GUIInfo guiInfo = (GUIInfo)getSessionAttribute( HybsSystem.GUIINFO_KEY );
557                if( guiInfo != null ) {
558                        guiInfo.addReadCount( selCnt,dyTime,selSQL );
559                        guiInfo.addWriteCount( upCnt,dyTime,sql2 );
560                }
561        }
562
563        /**
564         * 【TAG】実行方法を指定します[INSERT/UPDATE/DELETE/MERGE] (初期値:INSERT)。
565         *
566         * @og.tag
567         * 指定できるアクションは、追加(INSERT)、更新(UPDATE)、削除(DELETE)、マージ(MERGE)です。
568         * マージ以外は、お馴染みのSQL処理です。
569         * マージは、条件にしたがって、UPDATEを行い、更新件数が、0件の場合に、INSERTを行う、複合処理です。
570         * 初期値は、INSERT です。
571         *
572         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
573         *
574         * @param       action アクション [INSERT/UPDATE/DELETE/MERGE]
575         */
576        public void setAction( final String action ) {
577                this.action = nval( getRequestParameter( action ),this.action );
578        }
579
580        /**
581         * 【TAG】(jdbcオプション)検索した元のデータを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
582         *
583         * @og.tag
584         * アクションで指定した処理とともに、検索元のデータを削除するかどうかを指定します。
585         * 例えば、action="INSERT" で、useDelete="true" を指定すると、 ResultSet#deleteRow() を実行して、
586         * 検索元のデータを削除し、更新先にINSERT するため見かけ上、データ移動することになります。
587         * stopError="false" (エラー時でも処理を継続する)にした場合、検索元のデータ削除は、
588         * エラー行については、実行されません。ただし、UPDATE,DELETE 等で、対象データが
589         * 存在しない場合は、エラーと判断しないため、検索元のデータを削除します。
590         * 初期値は、false です。
591         * ※ ResultSet#deleteRow() をサポートしない場合もあるため、仕様の有無は、対象DBをご確認ください。
592         *
593         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
594         *
595         * @param       useDel 検索した元のデータを削除するかどうか
596         */
597        public void setUseDelete( final String useDel ) {
598                useDelete = nval( getRequestParameter( useDel ),useDelete );
599        }
600
601        /**
602         * 【TAG】(通常は使いません)データの最大読み込み件数を指定します(初期値:0:[無制限])。
603         *
604         * @og.tag
605         * 検索処理の最大件数を指定します。
606         * このタグでは、検索都度、更新するため、メモリ等の負荷は、DBTableModel を使用する
607         * 通常の検索より少なくてすみます。
608         * 初期値は、0(無制限=実際は、Integer.MAX_VALUE)です。
609         *
610         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
611         *
612         * @param       count 最大件数
613         */
614        public void setMaxRowCount( final String count ) {
615                maxRowCount = nval( getRequestParameter( count ),maxRowCount );
616                if( maxRowCount == 0 ) { maxRowCount = Integer.MAX_VALUE ; }
617        }
618
619//      /**
620//       * 【TAG】検索結果を画面上に表示するメッセージリソースIDを指定します
621//       *              (初期値:VIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
622//       *
623//       * @og.tag
624//       * ここでは、検索結果の件数や登録された件数をまず出力し、
625//       * その次に、ここで指定したメッセージをリソースから取得して表示します。
626//       * 件数を表示させる場合は、displayMsg = "MSG0033"[ 件検索しました] をセットしてください。
627//       * 表示させたくない場合は, displayMsg = "" をセットしてください。
628//       * (初期値:システム定数のVIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
629//       *
630//       * @og.rev 6.8.6.0 (2018/01/19) 新規作成
631//       * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
632//       *
633//       * @param       id 表示メッセージID
634//       * @see         org.opengion.hayabusa.common.SystemData#VIEW_DISPLAY_MSG
635//       */
636//      public void setDisplayMsg( final String id ) {
637//              final String ids = getRequestParameter( id );
638//              if( ids != null ) { displayMsg = ids; }
639//      }
640
641//      /**
642//       * 【TAG】検索データが最大検索数をオーバーした場合に表示するメッセージリソースIDを指定します
643//       *              (初期値:MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました])。
644//       *
645//       * @og.tag
646//       * 検索結果が、maxRowCount で設定された値より多い場合、何らかのデータは検索されず
647//       * 切り捨てられたことになります。
648//       * ここでは、displayMsg を表示した後、必要に応じて、このメッセージを表示します。
649//       * 表示させたくない場合は, overflowMsg = "" をセットしてください。
650//       * 初期値は、MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました]です。
651//       *
652//       * @og.rev 6.8.6.0 (2018/01/19) 新規作成
653//       * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
654//       *
655//       * @param       id オーバー時メッセージID
656//       */
657//      public void setOverflowMsg( final String id ) {
658//              final String ids = getRequestParameter( id );
659//              if( ids != null ) { overflowMsg = ids; }
660//      }
661
662//      /**
663//       * 【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])。
664//       *
665//       * @og.tag
666//       * ここでは、検索結果がゼロ件の場合のみ、特別なメッセージを表示させます。
667//       * 従来は、displayMsg と兼用で、『0 件検索しました』という表示でしたが、
668//       * displayMsg の初期表示は、OFF になりましたので、ゼロ件の場合のみ別に表示させます。
669//       * 表示させたくない場合は, notfoundMsg = "" をセットしてください。
670//       * 初期値は、MSG0077[対象データはありませんでした]です。
671//       *
672//       * @og.rev 6.8.6.0 (2018/01/19) 新規作成
673//       * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
674//       *
675//       * @param       id ゼロ件メッセージID
676//       */
677//      public void setNotfoundMsg( final String id ) {
678//              final String ids = getRequestParameter( id );
679//              if( ids != null ) { notfoundMsg = ids; }
680//      }
681
682        /**
683         * 【TAG】検索結果が0件のとき処理を停止するかどうか[true/false]を指定します(初期値:false[続行する])。
684         *
685         * @og.tag
686         * 初期値は、false(続行する)です。
687         *
688         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
689         *
690         * @param  cmd 0件時停止可否 [true:処理を中止する/false:続行する]
691         */
692        public void setStopZero( final String cmd ) {
693                stopZero = nval( getRequestParameter( cmd ),stopZero );
694        }
695
696        /**
697         * 【TAG】(通常は使いません)検索する対象のDB接続IDを指定します(初期値:null)。
698         *
699         * @og.tag
700         * 検索側のSELECT文を実行するDB接続IDを指定します。
701         * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
702         * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
703         * データベースにアクセスできます。
704         * 初期値は、Default(=null) です。
705         *
706         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
707         *
708         * @param       id データベース接続ID
709         */
710        public void setDbid( final String id ) {
711                dbid = nval( getRequestParameter( id ),dbid );
712        }
713
714        /**
715         * 【TAG】検索する対象のテーブル名を指定します(初期値:null)。
716         *
717         * @og.tag
718         * 検索は、この table名を検索するか、BODYに記述された SQL 文を実行します。
719         * 単独検索の場合(JOIN等を行わない場合)に、使用します。
720         *
721         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
722         *
723         * @param       table テーブル名
724         */
725        public void setTable( final String table ) {
726                query.setTable( getRequestParameter( table ) );
727        }
728
729        /**
730         * 【TAG】検索する対象のカラム名をCSV形式で複数指定します(初期値:*)。
731         *
732         * @og.tag
733         * 複数ある場合は、CSV形式で渡します。
734         * BODYにSELECT文を記述した場合は、names 属性は不要です。
735         * 記述した場合は、SELECTしたカラムから、names属性に指定されたカラムだけを
736         * SELECT対象にします。
737         * 検索元の names と、登録先の、names2 が、対応関係になります。
738         * 初期値は、指定のカラムすべて(*)です。
739         *
740         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
741         *
742         * @param       names 引数の名称 (CSV形式)
743         */
744        public void setNames( final String names ) {
745                query.setNames( getRequestParameter( names ) );
746        }
747
748        /**
749         * 【TAG】検索する対象を特定するキー条件(where句)を指定します。
750         *
751         * @og.tag
752         * 検索するSELECT文のwhere 句を指定します。通常の WHERE 句の書き方と同じで、
753         * {&#064;XXXX} などが使えます。
754         * 複雑な場合は、BODY に記述してください。where タグや、andタグ等を使って、
755         * 通常のquery タグで指定する方法を、そのまま使います。
756         *
757         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
758         *
759         * @param       where 検索条件 (where句)
760         */
761        public void setWhere( final String where ) {
762                query.setWhere( getRequestParameter( where ) );
763        }
764
765        /**
766         * 【TAG】検索する対象の検索順(order by句)を指定します。
767         *
768         * @og.tag
769         * 検索するSELECT文のorder by 句を指定します。通常の order by 句の書き方と同じで、
770         * {&#064;XXXX} などが使えます。
771         *
772         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
773         *
774         * @param       orderBy 検索条件 (order By句)
775         */
776        public void setOrderBy( final String orderBy ) {
777                query.setOrderBy( getRequestParameter( orderBy ) );
778        }
779
780        /**
781         * 【TAG】登録する対象のDB接続IDを指定します(初期値:null)。
782         *
783         * @og.tag
784         * 登録側のINSERT/UPDATE/DELETE文を実行するDB接続IDを指定します。
785         * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
786         * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
787         * データベースにアクセスできます。
788         * 初期値は、Default(=null) です。
789         *
790         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
791         *
792         * @param       id データベース接続ID
793         */
794        public void setDbid2( final String id ) {
795                dbid2 = nval( getRequestParameter( id ),dbid2 );
796        }
797
798        /**
799         * 【TAG】登録する対象のテーブル名を指定します(初期値:null)。
800         *
801         * @og.tag
802         * 登録は、この table名を使用します。
803         * table2 を指定しない場合は、table と同じテーブルが使用されます。
804         * その場合は、必ず、table が指定されます。
805         *
806         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
807         *
808         * @param       table テーブル名
809         */
810        public void setTable2( final String table ) {
811                query2.setTable( getRequestParameter( table) );
812        }
813
814        /**
815         * 【TAG】登録する対象のカラム名をCSV形式で複数指定します(初期値:null)。
816         *
817         * @og.tag
818         * 登録する対象のカラム名は、検索したカラム名の順番に割り当てられます。
819         * 例えば、names 属性に、a1,b1,c1 と指定した場合、names2 に、A2,B2,C2 と指定すれば、
820         * 順番に、a1→A2 , b1→B2 , c1→C2 に割り当てられます。
821         * BODY にSELECT文を記述した場合も、names2 を指定すれば、指定のカラムの順番に割り当てます。
822         * これは、SELECT 側と、INSERT/UPDATE 側のカラム名が異なる場合に、検索側に、別名(as 別名)を
823         * 指定する必要がありません。
824         * 指定しない場合(初期値)は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
825         *
826         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
827         *
828         * @param       names 引数の名称 (CSV形式)
829         */
830        public void setNames2( final String names ) {
831                query2.setNames( getRequestParameter( names) );
832        }
833
834        /**
835         * 【TAG】登録対象外のカラム名をCSV形式で複数指定します(初期値:null)。
836         *
837         * @og.tag
838         * names2 の逆で、登録対象から省くカラム名を指定します。
839         * table 指定や、select * from で、カラム名を大量に指定したい場合、names2 で
840         * 指定するより、除外するカラム名を指定するほうが、少なく(判りやすく)なる
841         * 場合があります。
842         *
843         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
844         *
845         * @param   omitNames 登録対象外のカラム列 (CSV形式)
846         */
847        public void setOmitNames2( final String omitNames ) {
848                query2.setOmitNames( getRequestParameter( omitNames ) );
849        }
850
851        /**
852         * 【TAG】登録する対象を特定するキー条件(where句)を指定します。
853         *
854         * @og.tag
855         * 登録するUPDATE/DELETE文のwhere 句を指定します。通常の{&#064;XXXX} のほかに、
856         * [検索カラム名] も使用できます。これは、検索側の where 属性と異なります。
857         * ただし、複雑な where 条件は使えませんので、できるだけ、検索側で調整して置いてください。
858         * action="UPDATE/DELETE/MERGE" でのみ有効です。
859         *
860         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
861         *
862         * @param       where 検索条件 (where句)
863         */
864        public void setWhere2( final String where ) {
865                query2.setWhere( getRequestParameter( where) );
866        }
867
868        /**
869         * 【TAG】登録する対象を特定するキー条件(where句)をCSV形式で複数指定します。
870         *
871         * @og.tag
872         * 生成するUPDATEのwhere 句を指定する方法として、複数のカラム名をCSV指定し、内部で
873         * KEY=[KEY] 文字列を作成します。
874         * ここでは、カラム名は、データベースのカラム名と同じで、かつ、検索側にも
875         * 同じカラムのデータが存在していること、という条件付きとします。
876         * また、where 条件との併用を行いますが、こちらの条件が先に使用され、where 条件は、
877         * and を付けて、文字列結合されます。
878         * 例: CLM,SYSTEM_ID,KBSAKU   ⇒   CLM=[CLM] and SYSTEM_ID=[SYSTEM_ID] and KBSAKU=[KBSAKU]
879         *
880         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
881         *
882         * @param       names 登録条件カラム (where句)作成のためのカラム名(CSV形式)
883         */
884        public void setWhereNames2( final String names ) {
885                query2.setWhereNames( getRequestParameter( names) );
886        }
887
888        /**
889         * 【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します。
890         *
891         * @og.tag
892         * names 属性のカラムや table 属性より、INSERT/UPDATE文を作成する場合
893         * 外部から指定した固定値を指定するための、カラム名をCSV形式(CSV)で複数指定します。
894         *
895         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
896         *
897         * @param       keys 固定値カラム (CSV形式)
898         * @see         #setConstVals2( String )
899         */
900        public void setConstKeys2( final String keys ) {
901                query2.setConstKeys( getRequestParameter( keys ) );
902        }
903
904        /**
905         * 【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します。
906         *
907         * @og.tag
908         * names 属性のカラムや table 属性より、INSERT/UPDATE文を作成する場合
909         * 外部から指定した固定値を指定するための、カラム名に対応する設定値をCSV形式(CSV)で
910         * 複数指定します。ここで指定する設定値は、constKeys2 属性と対応させます。
911         *
912         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
913         *
914         * @param       vals 設定値(CSV形式)
915         * @see         #setConstKeys2( String )
916         */
917        public void setConstVals2( final String vals ) {
918                query2.setConstVals( getRequestParameter( vals ) );
919        }
920
921        /**
922         * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
923         *              (初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。
924         *
925         * @og.tag
926         * SQLインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに
927         * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。
928         * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、
929         * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、
930         * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。
931         * (') が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。
932         * 初期値は、SystemData#USE_SQL_INJECTION_CHECK です。
933         *
934         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
935         *
936         * @param   flag クォートチェック [true:する/それ以外:しない]
937         */
938        public void setQuotCheck( final String flag ) {
939                quotCheck = nval( getRequestParameter( flag ),quotCheck );
940        }
941
942        /**
943         * 【TAG】登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)。
944         *
945         * @og.tag
946         * false(中止しない)に設定する場合、後続処理では、{&#064;DB.ERR_CODE}の値により、
947         * 異常/正常判断を行いますが、処理は、継続されます。
948         * ちなみに、更新/削除処理で、対象データが存在しない場合(0件更新や、0件削除)は、エラーでは
949         * ありません。
950         * 初期値は、true(中止する)です。
951         *
952         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
953         *
954         * @param   flag エラー時処理中止 [true:中止する/false:中止しない]
955         */
956        public void setStopError( final String flag ) {
957                stopError = nval( getRequestParameter( flag ),stopError );
958        }
959
960        /**
961         * 【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)。
962         *
963         * @og.tag
964         * false(表示しない)に設定する場合、後続処理では、{&#064;DB.ERR_MSG}の値により、
965         * 本来表示されるはずだったメッセージを取得可能です。
966         * stopErrorと併用して、JSON形式でエラーを返す場合等に利用します。
967         * 初期値は、true(表示する)です。
968         * ※false指定の場合は件数等も表示されなくなります。
969         *
970         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
971         *
972         * @param   flag  [true:表示する/false:表示しない]
973         */
974        public void setDispError( final String flag ) {
975                dispError = nval( getRequestParameter( flag ),dispError );
976        }
977
978        /**
979         * 【TAG】(通常は使いません)データのフェッチサイズを指定します
980         *              (初期値:DB_FETCH_SIZE[={@og.value SystemData#DB_FETCH_SIZE}])。
981         *
982         * @og.tag
983         * より多くの行が必要なときに、データベースから取り出す必要がある行数に
984         * ついてのヒントを JDBC ドライバに提供します。
985         * 指定された行数は、この Statement を使って作成された結果セットにだけ影響します。
986         * 指定された値が 0 の場合、ヒントは無視されます。
987         * (初期値:システム定数のDB_FETCH_SIZE[={@og.value SystemData#DB_FETCH_SIZE}])。
988         *
989         * @param       size フェッチ行数
990         */
991        public void setFetchSize( final String size ) {
992                fetchSize = nval( getRequestParameter( size ),fetchSize );
993        }
994
995        /**
996         * このオブジェクトの文字列表現を返します。
997         * 基本的にデバッグ目的に使用します。
998         *
999         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
1000         *
1001         * @return このクラスの文字列表現
1002         */
1003        @Override
1004        public String toString() {
1005                return selSQL == null ? ""
1006                                                   : selSQL.replaceAll( "[\\\t]+"," " ).replaceAll( "[\\s]+\\\n","\\\n" ) ;
1007                //                                                                        連続するTABをスペースに     連続する空白文字と改行を改行のみに
1008        }
1009}