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