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