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