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