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