    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.plugin.query;
    017
    018import org.opengion.hayabusa.db.AbstractQuery;
    019import org.opengion.hayabusa.db.DBTableModel;
    020import org.opengion.hayabusa.common.HybsSystemException;
    021import org.opengion.fukurou.util.ErrorMessage;
    022import org.opengion.fukurou.util.StringUtil;
    023// import org.opengion.fukurou.util.HybsDateUtil;                       // 5.5.8.5 (2012/11/27)
    024import org.opengion.fukurou.model.Formatter;
    025import org.opengion.fukurou.db.DBUpdater;                               // 6.9.3.0 (2018/03/26)
    026
    027import java.sql.Connection;
    028import java.sql.PreparedStatement;
    029// import java.sql.ParameterMetaData;
    030import java.sql.SQLException;
    031
    032/**
    033 * 引数引き当て(PreparedStatement) を利用した登録系Queryです。
    034 *
    035 * java.sql.PreparedStatement を用いて、データベース登録処理を行います。
    036 * 引数の指定方法は、DBTableModele のカラム名に対応する名称を、SQL文の[カラム名]形式で
    037 * 記述します。これを解析して、実際に実行する PreparedStatement に対応する文字列を
    038 * 作成します。
    039 * たとえば、INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES ([CLM],[NAME_JA],[LABEL_NAME] )
    040 * と記述すれば、内部で、DBTableModele のカラム名に対応する値を取り出し、SQL文として、
    041 * INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES (?,?,? ) を実行します。
    042 *
    043 * Query_JDBCTableUpdate との違いは、INSERT文とUPDATE文を渡しておき、
    044 * UPDATEの処理結果が、0件の場合は、INSERTを行います。
    045 * そのため、tableUpdateタグのBODY部に直接SQLを書くのではなく、tableUpdateParam ﾀｸﾞを2個書くことになります。
    046 *
    047 * 基本的に、tableUpdateタグのqueryTypeにJDBCTableUpdateと記述しておき、tableUpdateParam の
    048 * sqlType が MERGE の場合は、2種類のSQL文が作成され、自動的に、JDBCTableMerge が呼ばれます。
    049 * ※ つまり、通常は、queryType="JDBCTableUpdate" のままで、sqlType="MERGE" を指定すればよい。
    050 *
    051 * @og.formSample
    052 * ●使用例
    053 *
    054 *    ・JDBCTableUpdate のまま、sqlType="MERGE" を指定する場合。
    055 *    【entry.jsp】
    056 *        &lt;og:tableUpdate
    057 *            command   = "{&#064;command}"
    058 *            queryType = "JDBCTableUpdate"
    059 *            &lt;og:tableUpdateParam
    060 *                sqlType     = "MERGE"                // INSERT or UPDATE
    061 *                table       = "{&#064;TABLE_NAME}"    // 処理対象のテーブル名
    062 *                names       = "{&#064;names}"         // 処理対象のカラム名
    063 *                omitNames   = "{&#064;omitNames}"     // 処理対象外のカラム名
    064 *                where       = "{&#064;where}"         // 処理対象を特定するキー（INSERT時には使われず、UPDAET時に使われる。）
    065 *                constKeys   = "{&#064;constKeys}"     // 処理カラム名の中の固定情報カラム名
    066 *                constVals   = "{&#064;constVals}"     // 処理カラム名の中の固定情報設定値
    067 *            /&gt;
    068 *        &lt;/og:tableUpdate&gt;
    069 *
    070 *    ・JDBCTableMerge を直接的に指定する場合。
    071 *    【entry.jsp】
    072 *        &lt;og:tableUpdate
    073 *            command   = "{&#064;command}"
    074 *            queryType = "JDBCTableMerge"
    075 *            &lt;og:tableUpdateParam
    076 *                sqlType     = "INSERT"                // INSERT or UPDATE
    077 *                table       = "{&#064;TABLE_NAME}"    // 処理対象のテーブル名
    078 *                names       = "{&#064;names}"         // 処理対象のカラム名
    079 *                omitNames   = "{&#064;omitNames}"     // 処理対象外のカラム名
    080 *                constKeys   = "{&#064;constKeys}"     // 処理カラム名の中の固定情報カラム名
    081 *                constVals   = "{&#064;constVals}"     // 処理カラム名の中の固定情報設定値
    082 *            /&gt;
    083 *            &lt;og:tableUpdateParam
    084 *                sqlType     = "UPDATE"                // INSERT or UPDATE
    085 *                table       = "{&#064;TABLE_NAME}"    // 処理対象のテーブル名
    086 *                names       = "{&#064;names}"         // 処理対象のカラム名
    087 *                omitNames   = "{&#064;omitNames}"     // 処理対象外のカラム名
    088 *                where       = "{&#064;where}"         // 処理対象を特定するキー
    089 *                constKeys   = "{&#064;constKeys}"     // 処理カラム名の中の固定情報カラム名
    090 *                constVals   = "{&#064;constVals}"     // 処理カラム名の中の固定情報設定値
    091 *            /&gt;
    092 *        &lt;/og:tableUpdate&gt;
    093 *
    094 * @og.rev 7.2.9.1 (2020/10/23) 新規作成
    095 * @og.group データ編集
    096 *
    097 * @version  7.2
    098 * @author   Kazuhiko Hasegawa
    099 * @since    JDK11.0,
    100 */
    101public class Query_JDBCTableMerge extends AbstractQuery {
    102        /** このプログラムのVERSION文字列を設定します。   {@value} */
    103        private static final String VERSION = "7.2.9.1 (2020/10/23)" ;
    104
    105        /**
    106         * デフォルトコンストラクター
    107         *
    108         * @og.rev 7.2.9.1 (2020/10/23) 新規作成
    109         */
    110        public Query_JDBCTableMerge() { super(); }              // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    111
    112        /**
    113         * 引数配列付のクエリーを実行します。
    114         * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。
    115         * これは、PreparedQuery で使用する引数を配列でセットするものです。
    116         * select * from emp where deptno = ? and job = ? などの PreparedQuery の
    117         * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。
    118         *
    119         * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    120         *
    121         * @param   rowNo 選択された行番号配列(登録する対象行)
    122         * @param   table DBTableModelオブジェクト(登録する元データ)
    123         */
    124        @Override
    125        public void execute( final int[] rowNo, final DBTableModel table ) {
    126
    127                int row = 0;                    // エラー時に表示するエラー行番号
    128                int executeCount = 0;   // 処理件数
    129
    130                final String[] sqls = getMergeStatement();              // UPDATE,INSERTの順番
    131
    132                final DBquery updQuery = new DBquery( table,sqls[0] );
    133                final DBquery insQuery = new DBquery( table,sqls[1] );
    134
    135                // 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。
    136                String[] vals    = null;
    137                boolean isUpdate = false;               // update/insert の区別が分かるように
    138                final Connection conn = getConnection();
    139
    140                // 6.4.2.1 (2016/02/05) try-with-resources 文
    141                try( PreparedStatement updSstmt = updQuery.getPreStmt(conn);
    142                         final PreparedStatement insSstmt = insQuery.getPreStmt(conn) ) {               // 更新系なので、setFetchSize は不要。
    143
    144                        final boolean usePMeta = useParameterMetaData();
    145
    146                        final DBUpdater updDB = new DBUpdater( updQuery.size(),updSstmt,usePMeta,updQuery.getUseTime() );
    147                        final DBUpdater insDB = new DBUpdater( insQuery.size(),insSstmt,usePMeta,insQuery.getUseTime() );
    148
    149                        // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合
    150                        for( int i=0; i<rowNo.length; i++ ) {
    151                                row = rowNo[i];
    152                                vals = updQuery.getVals( row );
    153
    154                                isUpdate = true;
    155                                int cnt = updDB.update( vals ) ;
    156                                if( cnt == 0 ) {                                                // UPDATE を実行して、結果が 0 件なら
    157                                        vals = insQuery.getVals( row );
    158                                        isUpdate = false;
    159                                        cnt = insDB.update( vals );                     // INSERT 処理を行う。
    160                                }
    161                                executeCount += cnt ;
    162                        }
    163
    164                        setExecuteCount( executeCount );
    165                        setErrorCode( ErrorMessage.OK );
    166                }
    167                catch( final SQLException ex) {         // catch は、close() されてから呼ばれます。
    168                        setErrorCode( ErrorMessage.EXCEPTION );
    169                        final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
    170                                .append( ex.getMessage() ).append( ':' ).append( ex.getSQLState() ).append( CR )
    171                                .append( isUpdate ? "UPDATE" : "INSERT" ).append( CR )
    172                                .append( "  UPDATE=" ).append( sqls[0] ).append( CR )
    173                                .append( "  INSERT=" ).append( sqls[1] ).append( CR )
    174                                .append( "  ROW =["  ).append( (row+1) ).append( ']' ).append( CR )
    175                                .append( "  VALS=["  ).append( StringUtil.array2csv( vals )).append( ']' )      // 6.9.8.0 (2018/05/28)
    176                                .append( CR ) ;
    177
    178                        throw new HybsSystemException( errMsg.toString(),ex );          // 3.5.5.4 (2004/04/15) 引数の並び順変更
    179                }
    180        }
    181
    182        /**
    183         * DBUpdater で処理するにあたり、
    184         * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。
    185         * これは、PreparedQuery で使用する引数を配列でセットするものです。
    186         * select * from emp where deptno = ? and job = ? などの PreparedQuery の
    187         * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。
    188         *
    189         * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    190         */
    191        private static final class DBquery {
    192                private final DBTableModel      table;
    193                private final int[]                     clmNos;
    194                private final String            query ;
    195                private final int                       cnt   ;
    196                private final boolean[]         isTime;
    197                private final boolean           useTime;
    198
    199                /**
    200                 * DBTableModelとQUERY文を指定した、コンストラクター
    201                 *
    202                 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    203                 *
    204                 * @param   table DBTableModelオブジェクト(登録する元データ)
    205                 * @param   sql   実行するQUERY文
    206                 */
    207                public DBquery( final DBTableModel table , final String sql ) {
    208                        this.table = table;
    209                        final Formatter form = new Formatter( table,sql );
    210                        clmNos = form.getClmNos();                                      // 引数の個数分の配列。カラム番号を保存
    211                        query  = form.getQueryFormatString();
    212                        cnt    = clmNos.length;                                         // 引数の個数(カラムの個数ではありません。)
    213
    214                        isTime = new boolean[cnt];
    215
    216                        boolean useTimeStamp = false;
    217                        for( int j=0; j<cnt; j++ ) {
    218                                isTime[j] = table.getDBColumn( clmNos[j] ).isDateType();        // 6.4.6.0 (2016/05/27)
    219                                if( !useTimeStamp && isTime[j] ) { useTimeStamp = true; }       // isTime[j] == true 時に、一度だけ実行される。
    220                        }
    221                        useTime = useTimeStamp;
    222                }
    223
    224                /**
    225                 * Connectionから、PreparedStatement を作成して返します。
    226                 *
    227                 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    228                 *
    229                 * @param   conn Connectionオブジェクト
    230                 * @return  PreparedStatementｵﾌﾞｼﾞｪｸﾄ
    231                 */
    232                public PreparedStatement getPreStmt( final Connection conn ) throws SQLException {
    233                        final PreparedStatement pstmt = conn.prepareStatement( query );
    234
    235                        pstmt.setQueryTimeout( DB_MAX_QUERY_TIMEOUT );
    236
    237                        return pstmt;
    238                }
    239
    240                /**
    241                 * パラメータの個数を返します。
    242                 *
    243                 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    244                 *
    245                 * @return  パラメータの個数
    246                 */
    247                public int size() { return cnt; }
    248
    249                /**
    250                 * TimeStampカラムかどうかの配列を返します。
    251                 * 配列は、パラメータの順番に再設定されています。
    252                 * すべて通常のカラムの場合は、null を返しますのでご注意ください。
    253                 *
    254                 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    255                 *
    256                 * @return  TimeStampカラムかどうかの配列（すべて通常のカラムの場合は、null）
    257                 */
    258                public boolean[] getUseTime() { return useTime ? isTime : null ; }              // 使われない時は、null
    259
    260                /**
    261                 * 指定行のデータのうち、パラメータカラムの値の配列を返します。
    262                 *
    263                 * これは、[カラム]を? に置き換えた処理の、? の順番にDBTableModelの値を再設定します。
    264                 * 取り出した行データは、rTrim して、右側の空白文字を削除しています。
    265                 *
    266                 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    267                 *
    268                 * @param   row 指定行
    269                 * @return  指定行のデータ配列
    270                 */
    271                public String[] getVals( final int row ) {
    272                        final String[] vals = new String[cnt];
    273                        final String[] data = table.getValues( row );
    274                        for( int j=0; j<cnt; j++ ) {
    275                                vals[j] = StringUtil.rTrim( data[ clmNos[j] ] );
    276                        }
    277                        return vals ;
    278                }
    279        }
    280}