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 = "7.0.6.1 (2019/10/11)" ;
202        private static final long serialVersionUID = 706120191011L ;
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         * @og.rev 7.0.6.1 (2019/10/11) useParamMetaData ではなく、MetaDataが存在しているかどうかで判定します。
398         */
399        private void execute() {
400                Statement                       stmt   = null;          // 検索に使用するステートメント
401                ResultSetValue          rsv    = null;          // 検索に使用
402                PreparedStatement       pstmt2 = null;          // INSERT/UPDATE/DELETE に使用するステートメント
403                PreparedStatement       pstmt3 = null;          // MERGE時に使用する INSERT 用ステートメント
404                ParameterMetaData       pMeta2 = null;
405                ParameterMetaData       pMeta3 = null;
406
407                final boolean useParamMetaData = ConnectionFactory.useParameterMetaData( dbid2 );
408
409                String sql2 = null ;
410                String sql3 = null ;
411                final StringBuilder val2Buf = new StringBuilder( BUFFER_MIDDLE );               // 6.9.0.2 (2018/02/13) デバッグ情報用
412                final StringBuilder val3Buf = new StringBuilder( BUFFER_MIDDLE );               // 6.9.0.2 (2018/02/13) デバッグ情報用
413
414                // Transaction でAutoCloseableを使用したtry-with-resources構築に対応。
415                try( final Transaction tran = getTransaction() ) {
416        //              errMessage = new ErrorMessage( "DBCopyTag Database Error!" );
417
418                        final Connection conn = tran.getConnection( dbid );
419                        stmt = conn.createStatement();
420                        if( fetchSize > 0 ) { stmt.setFetchSize( fetchSize ); }
421                        final ResultSet resSer = stmt.executeQuery( selSQL );                           // useDelete で使うため
422                        rsv = new ResultSetValue( resSer );                                                                     // ResultSet を引数に、インスタンス作成
423
424                        if( rsv.getColumnCount() == 0 ) { return; }                                                     // 6.9.9.1 (2018/08/27) SELECT検索できなかった場合
425
426                        // names2を、指定しない場合は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
427                        // 6.9.0.2 (2018/02/13) カラム名が、"*" の場合の対応
428//                      if( StringUtil.isNull( query2.getNames() ) ) {                                          // nullチェックは、すでに終了している。
429                        if( "*".equals( query2.getNames() ) ) {
430                                final String names2 = String.join( "," , rsv.getNames() );              // SELECT文の名前配列から、CSV文字列を作成
431                                query2.setNames( names2 );
432                        }
433
434                        switch( action ) {
435                                case "INSERT" : sql2 = query2.getInsertSQL(); break;
436                                case "UPDATE" : sql2 = query2.getUpdateSQL(); break;
437                                case "DELETE" : sql2 = query2.getDeleteSQL(); break;
438                                case "MERGE"  : sql2 = query2.getUpdateSQL();
439                                                                sql3 = query2.getInsertSQL(); break;
440                                default : break;
441                        }
442
443                        if( StringUtil.isNull( sql2 ) ) {
444                                final String errMsg = "更新用QUERY が作成できませんでした。 "
445                                                                                + " action=[" + action + "]"
446                                                                                + " query2=[" + sql2 + "]" ;
447                                errMessage.addMessage( errMsg );
448                                throw new HybsSystemException( errMsg );
449                        }
450
451                        final String[]  prmNms2 = query2.getParamNames( false );                // 登録QUERYの、変数設定されているカラム名配列。
452                        final int[]             clmNos2 = rsv.getColumnNos( prmNms2,true );             // 変数設定カラムのカラム番号。無ければ、Exception
453
454                        int[] clmNos3 = null;   // MERGE のときの変数設定カラム
455
456                        final boolean useMerge = "MERGE".equals( action );
457
458                        final Connection conn2 = tran.getConnection( dbid2 );
459                        pstmt2 = conn2.prepareStatement( sql2 );
460                        pMeta2 = useParamMetaData ? pstmt2.getParameterMetaData() : null ;
461                        if( useMerge ) {
462                                final Connection conn3 = tran.getConnection( dbid2 );
463                                pstmt3 = conn3.prepareStatement( sql3 );
464                                pMeta3 = useParamMetaData ? pstmt3.getParameterMetaData() : null ;
465
466                                final String[]  prmNms3 = query2.getParamNames( true );         // MERGE のときの INSERT時なので、true を指定。
467                                clmNos3 = rsv.getColumnNos( prmNms3,true );                             // 変数設定カラムのカラム番号。無ければ、Exception
468                        }
469
470                        while( rsv.next() ) {
471                                try {
472                                        selCnt++;                                                                                       // 検索件数の加算
473                                        val2Buf.setLength(0);                                                           // 初期化
474                                        final String[] vals = rsv.getValues();
475                                        for( int no=0; no<clmNos2.length; no++ ) {
476                                                final int cno = clmNos2[no];                                    // 変数設定カラムに該当するアドレス。
477                                                final String val = vals[cno];
478                                //              final String val = nval(vals[cno],"");                  // 7.0.6.1 (2019/10/11) where条件の is null 対策(仮)
479                                                val2Buf.append( val ).append( ',' );                    // valueのCSV形式
480//                                              if( useParamMetaData ) {
481                                                if( pMeta2 != null ) {                                                  // 7.0.6.1 (2019/10/11) ParameterMetaDataの有無で判定
482                                                        final int type = pMeta2.getParameterType( no+1 );
483                                                        if( val == null || val.isEmpty() ) {
484                                                                pstmt2.setNull( no+1, type );                   // where条件がnull の場合、うまく検索できない
485                                                        }
486                                                        else {
487                                                                pstmt2.setObject( no+1,val,type );
488                                                        }
489                                                }
490                                                else {
491                                                        pstmt2.setObject( no+1,val );
492                                                }
493                                        }
494
495                                        int cnt = pstmt2.executeUpdate();                                       // 更新件数
496                                        if( useMerge && cnt == 0 ) {                                            // マージアクションで、更新が0件の場合は、INSERTを行う。
497                                                val3Buf.setLength(0);                                                   // 初期化
498                                                for( int no=0; no<clmNos3.length; no++ ) {
499                                                        final int cno = clmNos3[no];                            // 変数設定カラムに該当するアドレス。
500                                                        final String val = vals[cno];
501                                //                      final String val = nval(vals[cno],"");          // 7.0.6.1 (2019/10/11) where条件の is null 対策(仮)
502                                                        val3Buf.append( val ).append( ',' );            // valueのCSV形式
503//                                                      if( useParamMetaData ) {
504                                                        if( pMeta3 != null ) {                                          // 7.0.6.1 (2019/10/11) ParameterMetaDataの有無で判定
505                                                                final int type = pMeta3.getParameterType( no+1 );
506                                                                if( val == null || val.isEmpty() ) {
507                                                                        pstmt3.setNull( no+1, type );           // where条件がnull の場合、うまく検索できない
508                                                                }
509                                                                else {
510                                                                        pstmt3.setObject( no+1,val,type );
511                                                                }
512                                                        }
513                                                        else {
514                                                                pstmt3.setObject( no+1,val );
515                                                        }
516                                                }
517                                                cnt = pstmt3.executeUpdate();                                   // 追加件数
518                                        }
519
520                                        upCnt += cnt;                                                                           // 更新件数の加算
521                                        if( useDelete ) { resSer.deleteRow(); }                         // 途中でエラーになった場合は、ResultSet の削除は行いません。
522                                }
523                                catch( final SQLException ex ) {
524                                        errMessage.addMessage( selCnt,ErrorMessage.NG,ex.getSQLState(),ex.getMessage() );
525                                        if( stopError ) {
526                                                tran.rollback();                                                                // stopError=false の場合、最後まで処理され、commit() されています。
527                                                throw ex;                                                                               // SELECTループ中のSQLExceptionは、stopErrorの判定を行う。
528                                        }
529                                }
530                        }
531
532                        tran.commit();
533                }
534                catch( final SQLException ex ) {
535                        // 6.9.0.2 (2018/02/13) デバッグ情報
536                        final String errMsg = new StringBuilder( BUFFER_MIDDLE )
537                                                                .append( "更新処理実行中にエラーが発生しました。action=[" )
538                                                                .append( action ).append( ']' ).append( CR )
539                                                                .append( " query =[" ).append( selSQL  ).append( ']' ).append( CR )
540                                                                .append( " query2=[" ).append( sql2    ).append( ']' ).append( CR )
541                                                                .append( " query3=[" ).append( sql3    ).append( ']' ).append( CR )
542                                                                .append( " value2=[" ).append( val2Buf ).append( ']' ).append( CR )
543                                                                .append( " value3=[" ).append( val3Buf ).append( ']' ).append( CR )
544                                                                .toString();
545
546//                      final String errMsg = "更新処理実行中にエラーが発生しました。action=[" + action + "]" + CR
547//                                                                      + " query=[" + selSQL + "]"  + CR
548//                                                                      + " query2=[" + sql2 + "]";
549
550                        errMessage.addMessage( ex );
551        //              errMessage.addMessage( errMsg );
552                        errMessage.addMessage( -1,ErrorMessage.NG,ex.getSQLState(),errMsg );
553                        throw new HybsSystemException( errMsg,ex );
554                }
555                finally {
556//                      rsv.close();
557                        Closer.autoClose( rsv  );                       // 6.9.8.0 (2018/05/28) FindBugs: null 値を例外経路で利用している可能性がある
558                        Closer.stmtClose( stmt  );
559                        Closer.stmtClose( pstmt2 );
560                        Closer.stmtClose( pstmt3 );
561                }
562
563                // 変数の関係で、こちらにもって来ました(データアクセス件数登録)
564                final long dyTime = System.currentTimeMillis()-dyStart;
565                final GUIInfo guiInfo = (GUIInfo)getSessionAttribute( HybsSystem.GUIINFO_KEY );
566                if( guiInfo != null ) {
567                        guiInfo.addReadCount( selCnt,dyTime,selSQL );
568                        guiInfo.addWriteCount( upCnt,dyTime,sql2 );
569                }
570        }
571
572        /**
573         * 【TAG】実行方法を指定します[INSERT/UPDATE/DELETE/MERGE] (初期値:INSERT)。
574         *
575         * @og.tag
576         * 指定できるアクションは、追加(INSERT)、更新(UPDATE)、削除(DELETE)、マージ(MERGE)です。
577         * マージ以外は、お馴染みのSQL処理です。
578         * マージは、条件にしたがって、UPDATEを行い、更新件数が、0件の場合に、INSERTを行う、複合処理です。
579         * 初期値は、INSERT です。
580         *
581         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
582         *
583         * @param       action アクション [INSERT/UPDATE/DELETE/MERGE]
584         */
585        public void setAction( final String action ) {
586                this.action = nval( getRequestParameter( action ),this.action );
587        }
588
589        /**
590         * 【TAG】(jdbcオプション)検索した元のデータを削除するかどうか[true:削除する/false:なにもしない]を指定します(初期値:false)。
591         *
592         * @og.tag
593         * アクションで指定した処理とともに、検索元のデータを削除するかどうかを指定します。
594         * 例えば、action="INSERT" で、useDelete="true" を指定すると、 ResultSet#deleteRow() を実行して、
595         * 検索元のデータを削除し、更新先にINSERT するため見かけ上、データ移動することになります。
596         * stopError="false" (エラー時でも処理を継続する)にした場合、検索元のデータ削除は、
597         * エラー行については、実行されません。ただし、UPDATE,DELETE 等で、対象データが
598         * 存在しない場合は、エラーと判断しないため、検索元のデータを削除します。
599         * 初期値は、false です。
600         * ※ ResultSet#deleteRow() をサポートしない場合もあるため、仕様の有無は、対象DBをご確認ください。
601         *
602         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
603         *
604         * @param       useDel 検索した元のデータを削除するかどうか
605         */
606        public void setUseDelete( final String useDel ) {
607                useDelete = nval( getRequestParameter( useDel ),useDelete );
608        }
609
610        /**
611         * 【TAG】(通常は使いません)データの最大読み込み件数を指定します(初期値:0:[無制限])。
612         *
613         * @og.tag
614         * 検索処理の最大件数を指定します。
615         * このタグでは、検索都度、更新するため、メモリ等の負荷は、DBTableModel を使用する
616         * 通常の検索より少なくてすみます。
617         * 初期値は、0(無制限=実際は、Integer.MAX_VALUE)です。
618         *
619         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
620         *
621         * @param       count 最大件数
622         */
623        public void setMaxRowCount( final String count ) {
624                maxRowCount = nval( getRequestParameter( count ),maxRowCount );
625                if( maxRowCount == 0 ) { maxRowCount = Integer.MAX_VALUE ; }
626        }
627
628//      /**
629//       * 【TAG】検索結果を画面上に表示するメッセージリソースIDを指定します
630//       *              (初期値:VIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
631//       *
632//       * @og.tag
633//       * ここでは、検索結果の件数や登録された件数をまず出力し、
634//       * その次に、ここで指定したメッセージをリソースから取得して表示します。
635//       * 件数を表示させる場合は、displayMsg = "MSG0033"[ 件検索しました] をセットしてください。
636//       * 表示させたくない場合は, displayMsg = "" をセットしてください。
637//       * (初期値:システム定数のVIEW_DISPLAY_MSG[={@og.value SystemData#VIEW_DISPLAY_MSG}])。
638//       *
639//       * @og.rev 6.8.6.0 (2018/01/19) 新規作成
640//       * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
641//       *
642//       * @param       id 表示メッセージID
643//       * @see         org.opengion.hayabusa.common.SystemData#VIEW_DISPLAY_MSG
644//       */
645//      public void setDisplayMsg( final String id ) {
646//              final String ids = getRequestParameter( id );
647//              if( ids != null ) { displayMsg = ids; }
648//      }
649
650//      /**
651//       * 【TAG】検索データが最大検索数をオーバーした場合に表示するメッセージリソースIDを指定します
652//       *              (初期値:MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました])。
653//       *
654//       * @og.tag
655//       * 検索結果が、maxRowCount で設定された値より多い場合、何らかのデータは検索されず
656//       * 切り捨てられたことになります。
657//       * ここでは、displayMsg を表示した後、必要に応じて、このメッセージを表示します。
658//       * 表示させたくない場合は, overflowMsg = "" をセットしてください。
659//       * 初期値は、MSG0007[検索結果が、制限行数を超えましたので、残りはカットされました]です。
660//       *
661//       * @og.rev 6.8.6.0 (2018/01/19) 新規作成
662//       * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
663//       *
664//       * @param       id オーバー時メッセージID
665//       */
666//      public void setOverflowMsg( final String id ) {
667//              final String ids = getRequestParameter( id );
668//              if( ids != null ) { overflowMsg = ids; }
669//      }
670
671//      /**
672//       * 【TAG】検索結果がゼロ件の場合に表示するメッセージリソースIDを指定します(初期値:MSG0077[対象データはありませんでした])。
673//       *
674//       * @og.tag
675//       * ここでは、検索結果がゼロ件の場合のみ、特別なメッセージを表示させます。
676//       * 従来は、displayMsg と兼用で、『0 件検索しました』という表示でしたが、
677//       * displayMsg の初期表示は、OFF になりましたので、ゼロ件の場合のみ別に表示させます。
678//       * 表示させたくない場合は, notfoundMsg = "" をセットしてください。
679//       * 初期値は、MSG0077[対象データはありませんでした]です。
680//       *
681//       * @og.rev 6.8.6.0 (2018/01/19) 新規作成
682//       * @og.rev 6.9.1.0 (2018/02/26) displayMsg,overflowMsg,notfoundMsg は未使用のため、削除
683//       *
684//       * @param       id ゼロ件メッセージID
685//       */
686//      public void setNotfoundMsg( final String id ) {
687//              final String ids = getRequestParameter( id );
688//              if( ids != null ) { notfoundMsg = ids; }
689//      }
690
691        /**
692         * 【TAG】検索結果が0件のとき処理を停止するかどうか[true/false]を指定します(初期値:false[続行する])。
693         *
694         * @og.tag
695         * 初期値は、false(続行する)です。
696         *
697         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
698         *
699         * @param  cmd 0件時停止可否 [true:処理を中止する/false:続行する]
700         */
701        public void setStopZero( final String cmd ) {
702                stopZero = nval( getRequestParameter( cmd ),stopZero );
703        }
704
705        /**
706         * 【TAG】(通常は使いません)検索する対象のDB接続IDを指定します(初期値:null)。
707         *
708         * @og.tag
709         * 検索側のSELECT文を実行するDB接続IDを指定します。
710         * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
711         * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
712         * データベースにアクセスできます。
713         * 初期値は、Default(=null) です。
714         *
715         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
716         *
717         * @param       id データベース接続ID
718         */
719        public void setDbid( final String id ) {
720                dbid = nval( getRequestParameter( id ),dbid );
721        }
722
723        /**
724         * 【TAG】検索する対象のテーブル名を指定します(初期値:null)。
725         *
726         * @og.tag
727         * 検索は、この table名を検索するか、BODYに記述された SQL 文を実行します。
728         * 単独検索の場合(JOIN等を行わない場合)に、使用します。
729         *
730         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
731         *
732         * @param       table テーブル名
733         */
734        public void setTable( final String table ) {
735                query.setTable( getRequestParameter( table ) );
736        }
737
738        /**
739         * 【TAG】検索する対象のカラム名をCSV形式で複数指定します(初期値:*)。
740         *
741         * @og.tag
742         * 複数ある場合は、CSV形式で渡します。
743         * BODYにSELECT文を記述した場合は、names 属性は不要です。
744         * 記述した場合は、SELECTしたカラムから、names属性に指定されたカラムだけを
745         * SELECT対象にします。
746         * 検索元の names と、登録先の、names2 が、対応関係になります。
747         * 初期値は、指定のカラムすべて(*)です。
748         *
749         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
750         *
751         * @param       names 引数の名称 (CSV形式)
752         */
753        public void setNames( final String names ) {
754                query.setNames( getRequestParameter( names ) );
755        }
756
757        /**
758         * 【TAG】検索する対象を特定するキー条件(where句)を指定します。
759         *
760         * @og.tag
761         * 検索するSELECT文のwhere 句を指定します。通常の WHERE 句の書き方と同じで、
762         * {&#064;XXXX} などが使えます。
763         * 複雑な場合は、BODY に記述してください。where タグや、andタグ等を使って、
764         * 通常のquery タグで指定する方法を、そのまま使います。
765         *
766         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
767         *
768         * @param       where 検索条件 (where句)
769         */
770        public void setWhere( final String where ) {
771                query.setWhere( getRequestParameter( where.replaceAll("="," ") ) );
772
773//              query.setWhere( getRequestParameter( where ) );
774        }
775
776        /**
777         * 【TAG】検索する対象の検索順(order by句)を指定します。
778         *
779         * @og.tag
780         * 検索するSELECT文のorder by 句を指定します。通常の order by 句の書き方と同じで、
781         * {&#064;XXXX} などが使えます。
782         *
783         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
784         *
785         * @param       orderBy 検索条件 (order By句)
786         */
787        public void setOrderBy( final String orderBy ) {
788                query.setOrderBy( getRequestParameter( orderBy ) );
789        }
790
791        /**
792         * 【TAG】登録する対象のDB接続IDを指定します(初期値:null)。
793         *
794         * @og.tag
795         * 登録側のINSERT/UPDATE/DELETE文を実行するDB接続IDを指定します。
796         * これは、システムリソースで、DEFAULT_DB_URL 等で指定している データベース接続先
797         * 情報に、XX_DB_URL を定義することで、 dbid="XX" とすると、この 接続先を使用して
798         * データベースにアクセスできます。
799         * 初期値は、Default(=null) です。
800         *
801         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
802         *
803         * @param       id データベース接続ID
804         */
805        public void setDbid2( final String id ) {
806                dbid2 = nval( getRequestParameter( id ),dbid2 );
807        }
808
809        /**
810         * 【TAG】登録する対象のテーブル名を指定します(初期値:null)。
811         *
812         * @og.tag
813         * 登録は、この table名を使用します。
814         * table2 を指定しない場合は、table と同じテーブルが使用されます。
815         * その場合は、必ず、table が指定されます。
816         *
817         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
818         *
819         * @param       table テーブル名
820         */
821        public void setTable2( final String table ) {
822                query2.setTable( getRequestParameter( table) );
823        }
824
825        /**
826         * 【TAG】登録する対象のカラム名をCSV形式で複数指定します(初期値:null)。
827         *
828         * @og.tag
829         * 登録する対象のカラム名は、検索したカラム名の順番に割り当てられます。
830         * 例えば、names 属性に、a1,b1,c1 と指定した場合、names2 に、A2,B2,C2 と指定すれば、
831         * 順番に、a1→A2 , b1→B2 , c1→C2 に割り当てられます。
832         * BODY にSELECT文を記述した場合も、names2 を指定すれば、指定のカラムの順番に割り当てます。
833         * これは、SELECT 側と、INSERT/UPDATE 側のカラム名が異なる場合に、検索側に、別名(as 別名)を
834         * 指定する必要がありません。
835         * 指定しない場合(初期値)は、names または、SELECT文のすべてのカラムが、同一名として処理されます。
836         *
837         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
838         *
839         * @param       names 引数の名称 (CSV形式)
840         */
841        public void setNames2( final String names ) {
842                query2.setNames( getRequestParameter( names) );
843        }
844
845        /**
846         * 【TAG】登録対象外のカラム名をCSV形式で複数指定します(初期値:null)。
847         *
848         * @og.tag
849         * names2 の逆で、登録対象から省くカラム名を指定します。
850         * table 指定や、select * from で、カラム名を大量に指定したい場合、names2 で
851         * 指定するより、除外するカラム名を指定するほうが、少なく(判りやすく)なる
852         * 場合があります。
853         *
854         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
855         *
856         * @param   omitNames 登録対象外のカラム列 (CSV形式)
857         */
858        public void setOmitNames2( final String omitNames ) {
859                query2.setOmitNames( getRequestParameter( omitNames ) );
860        }
861
862        /**
863         * 【TAG】登録する対象を特定するキー条件(where句)を指定します。
864         *
865         * @og.tag
866         * 登録するUPDATE/DELETE文のwhere 句を指定します。通常の{&#064;XXXX} のほかに、
867         * [検索カラム名] も使用できます。これは、検索側の where 属性と異なります。
868         * ただし、複雑な where 条件は使えませんので、できるだけ、検索側で調整して置いてください。
869         * action="UPDATE/DELETE/MERGE" でのみ有効です。
870         *
871         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
872         *
873         * @param       where 検索条件 (where句)
874         */
875        public void setWhere2( final String where ) {
876                query2.setWhere( getRequestParameter( where) );
877        }
878
879        /**
880         * 【TAG】登録する対象を特定するキー条件(where句)をCSV形式で複数指定します。
881         *
882         * @og.tag
883         * 生成するUPDATEのwhere 句を指定する方法として、複数のカラム名をCSV指定し、内部で
884         * KEY=[KEY] 文字列を作成します。
885         * ここでは、カラム名は、データベースのカラム名と同じで、かつ、検索側にも
886         * 同じカラムのデータが存在していること、という条件付きとします。
887         * また、where 条件との併用を行いますが、こちらの条件が先に使用され、where 条件は、
888         * and を付けて、文字列結合されます。
889         * 例: CLM,SYSTEM_ID,KBSAKU   ⇒   CLM=[CLM] and SYSTEM_ID=[SYSTEM_ID] and KBSAKU=[KBSAKU]
890         *
891         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
892         *
893         * @param       names 登録条件カラム (where句)作成のためのカラム名(CSV形式)
894         */
895        public void setWhereNames2( final String names ) {
896                query2.setWhereNames( getRequestParameter( names) );
897        }
898
899        /**
900         * 【TAG】設定値を固定値と置き換える対象となるカラム名をCSV形式で複数指定します。
901         *
902         * @og.tag
903         * names 属性のカラムや table 属性より、INSERT/UPDATE文を作成する場合
904         * 外部から指定した固定値を指定するための、カラム名をCSV形式(CSV)で複数指定します。
905         *
906         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
907         *
908         * @param       keys 固定値カラム (CSV形式)
909         * @see         #setConstVals2( String )
910         */
911        public void setConstKeys2( final String keys ) {
912                query2.setConstKeys( getRequestParameter( keys ) );
913        }
914
915        /**
916         * 【TAG】設定値を固定値と置き換える対象となる設定値をCSV形式で複数指定します。
917         *
918         * @og.tag
919         * names 属性のカラムや table 属性より、INSERT/UPDATE文を作成する場合
920         * 外部から指定した固定値を指定するための、カラム名に対応する設定値をCSV形式(CSV)で
921         * 複数指定します。ここで指定する設定値は、constKeys2 属性と対応させます。
922         *
923         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
924         *
925         * @param       vals 設定値(CSV形式)
926         * @see         #setConstKeys2( String )
927         */
928        public void setConstVals2( final String vals ) {
929                query2.setConstVals( getRequestParameter( vals ) );
930        }
931
932        /**
933         * 【TAG】リクエスト情報の シングルクォート(') 存在チェックを実施するかどうか[true/false]を設定します
934         *              (初期値:USE_SQL_INJECTION_CHECK[={@og.value SystemData#USE_SQL_INJECTION_CHECK}])。
935         *
936         * @og.tag
937         * SQLインジェクション対策の一つとして、暫定的ではありますが、SQLのパラメータに
938         * 渡す文字列にシングルクォート(') を許さない設定にすれば、ある程度は防止できます。
939         * 数字タイプの引数には、 or 5=5 などのシングルクォートを使用しないコードを埋めても、
940         * 数字チェックで検出可能です。文字タイプの場合は、必ず (')をはずして、
941         * ' or 'A' like 'A のような形式になる為、(')チェックだけでも有効です。
942         * (') が含まれていたエラーにする(true)/かノーチェックか(false)を指定します。
943         * 初期値は、SystemData#USE_SQL_INJECTION_CHECK です。
944         *
945         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
946         *
947         * @param   flag クォートチェック [true:する/それ以外:しない]
948         */
949        public void setQuotCheck( final String flag ) {
950                quotCheck = nval( getRequestParameter( flag ),quotCheck );
951        }
952
953        /**
954         * 【TAG】登録処理エラーの時に処理を中止するかどうか[true/false]を設定します(初期値:true)。
955         *
956         * @og.tag
957         * false(中止しない)に設定する場合、後続処理では、{&#064;DB.ERR_CODE}の値により、
958         * 異常/正常判断を行いますが、処理は、継続されます。
959         * ちなみに、更新/削除処理で、対象データが存在しない場合(0件更新や、0件削除)は、エラーでは
960         * ありません。
961         * 初期値は、true(中止する)です。
962         *
963         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
964         *
965         * @param   flag エラー時処理中止 [true:中止する/false:中止しない]
966         */
967        public void setStopError( final String flag ) {
968                stopError = nval( getRequestParameter( flag ),stopError );
969        }
970
971        /**
972         * 【TAG】エラー時にメッセージを表示するか[true/false]を設定します。通常はstopErrorと併用(初期値:true)。
973         *
974         * @og.tag
975         * false(表示しない)に設定する場合、後続処理では、{&#064;DB.ERR_MSG}の値により、
976         * 本来表示されるはずだったメッセージを取得可能です。
977         * stopErrorと併用して、JSON形式でエラーを返す場合等に利用します。
978         * 初期値は、true(表示する)です。
979         * ※false指定の場合は件数等も表示されなくなります。
980         *
981         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
982         *
983         * @param   flag  [true:表示する/false:表示しない]
984         */
985        public void setDispError( final String flag ) {
986                dispError = nval( getRequestParameter( flag ),dispError );
987        }
988
989        /**
990         * 【TAG】(通常は使いません)データのフェッチサイズを指定します
991         *              (初期値:DB_FETCH_SIZE[={@og.value org.opengion.fukurou.system.HybsConst#DB_FETCH_SIZE}])。
992         *
993         * @og.tag
994         * より多くの行が必要なときに、データベースから取り出す必要がある行数に
995         * ついてのヒントを JDBC ドライバに提供します。
996         * 指定された行数は、この Statement を使って作成された結果セットにだけ影響します。
997         * 指定された値が 0 の場合、ヒントは無視されます。
998         * (初期値:システム定数のDB_FETCH_SIZE[={@og.value org.opengion.fukurou.system.HybsConst#DB_FETCH_SIZE}])。
999         *
1000         * @param       size フェッチ行数
1001         */
1002        public void setFetchSize( final String size ) {
1003                fetchSize = nval( getRequestParameter( size ),fetchSize );
1004        }
1005
1006        /**
1007         * このオブジェクトの文字列表現を返します。
1008         * 基本的にデバッグ目的に使用します。
1009         *
1010         * @og.rev 6.8.6.0 (2018/01/19) 新規作成
1011         *
1012         * @return このクラスの文字列表現
1013         */
1014        @Override
1015        public String toString() {
1016                return selSQL == null ? ""
1017                                                   : selSQL.replaceAll( "[\\\t]+"," " ).replaceAll( "[\\s]+\\\n","\\\n" ) ;
1018                //                                                                        連続するTABをスペースに     連続する空白文字と改行を改行のみに
1019        }
1020}