    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.fukurou.db;
    017
    018import org.opengion.fukurou.system.OgRuntimeException ;
    019import org.opengion.fukurou.util.HybsDateUtil;
    020import static org.opengion.fukurou.system.HybsConst.DB_BATCH_SIZE;      // 6.9.4.1 (2018/04/09)
    021
    022import java.sql.PreparedStatement;
    023import java.sql.ParameterMetaData;
    024import java.sql.SQLException;
    025import java.sql.Timestamp;
    026import java.sql.ResultSet;                                                                                      // 7.4.1.0 (2021/04/23)
    027
    028/**
    029 * PreparedStatementを利用した更新処理を行う、簡易的なクラスです。
    030 *
    031 * ParameterMetaDataの使用有無を指定することで、ﾊﾟﾗﾒｰﾀを処理する際に、
    032 * sqlType を使用するかどうかを指定します。
    033 * また、データ登録時のバッチサイズに基づいた処理を行っています。
    034 * execute(String[]) で、行ごとのパラメータデータを渡します。
    035 * 一番最後に、execEnd() を呼ぶことで、更新件数を返します。
    036 * 更新件数を取得しない場合でも、このメソッドを呼んでください。
    037 *
    038 * このクラスは、マルチスレッドに対応していません。
    039 *
    040 * @version  6.9
    041 * @author   Kazuhiko Hasegawa
    042 * @since    JDK9.0,
    043 */
    044public final class DBUpdater {
    045        private final PreparedStatement pstmt ;
    046        private final boolean                   usePMeta ;
    047        private final int[]                             types ;
    048        private final boolean[]                 isTime;                 // 7.2.9.1 (2020/10/23) メソッドを統合します。
    049        private final boolean                   useSelect;              // 7.4.1.0 (2021/04/23)
    050
    051        private int             rowCnt;
    052        private int             updCnt;
    053
    054        /**
    055         * PreparedStatement を指定して、インスタンスを作成します。
    056         *
    057         * 内部で、ParameterMetaData を作成して、sqlType を使用します。
    058         *
    059         * @param       prmSize パラメータの個数
    060         * @param       pstmt   PreparedStatementオブジェクト
    061         */
    062        public DBUpdater( final int prmSize , final PreparedStatement pstmt ) {
    063                this( prmSize , pstmt , true );
    064        }
    065
    066        /**
    067         * PreparedStatement を指定して、インスタンスを作成します。
    068         *
    069         * 内部で、ParameterMetaData を作成して、sqlType を使用します。
    070         *
    071         * @param       prmSize パラメータの個数
    072         * @param       pstmt   PreparedStatementオブジェクト
    073         * @param       usePMeta        sqlType を使用するかどうか [true:使用する/false:使用しない]
    074         */
    075        public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta ) {
    076                this( prmSize , pstmt , usePMeta, null );
    077        }
    078
    079        /**
    080         * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。
    081         *
    082         * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを
    083         * 指定します。ORACLEのようなタイプの
    084         *
    085         * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない
    086         *
    087         * @param       prmSize パラメータの個数
    088         * @param       pstmt           PreparedStatementオブジェクト
    089         * @param       usePMeta        sqlType を使用するかどうか [true:使用する/false:使用しない]
    090         * @param       isTime          sqlType を使用するかどうか [true:使用する/false:使用しない]
    091         */
    092        public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , final boolean[] isTime ) {
    093                this( prmSize , pstmt , usePMeta, isTime , false );
    094        }
    095
    096        /**
    097         * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。
    098         *
    099         * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを
    100         * 指定します。ORACLEのようなタイプの
    101         *
    102         * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。
    103         * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない
    104         *
    105         * @param       prmSize パラメータの個数
    106         * @param       pstmt           PreparedStatementオブジェクト
    107         * @param       usePMeta        sqlType を使用するかどうか [true:使用する/false:使用しない]
    108         * @param       isTime          sqlType を使用するかどうか [true:使用する/false:使用しない]
    109         * @param       useSelect       true の場合は、UPDATE/INSERTではなく、SELECT します。
    110         */
    111//      public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , final boolean[] isTime ) {
    112        public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta ,
    113                                                final boolean[] isTime , final boolean useSelect ) {
    114                this.usePMeta   = usePMeta;
    115                this.pstmt              = pstmt;
    116                this.isTime             = isTime;                               // 7.2.9.1 (2020/10/23) メソッドを統合します。
    117                this.useSelect  = useSelect;                    // 7.4.1.0 (2021/04/23)
    118
    119                if( usePMeta ) {
    120                        types = new int[prmSize];
    121
    122                        try {
    123                                final ParameterMetaData pMeta = pstmt.getParameterMetaData();
    124                                for( int j=0; j<prmSize; j++ ) {
    125                                        types[j] = pMeta.getParameterType( j+1 );       // ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。
    126                                }
    127                        }
    128                        catch( final SQLException ex ) {
    129                                final String errMsg = "ParameterMetaData の取得に失敗しました。" ;
    130                                throw new OgRuntimeException( errMsg,ex );
    131                        }
    132                }
    133                else {
    134                        types = null;
    135                }
    136        }
    137
    138        /**
    139         * データ配列を渡してPreparedStatementの引数に、値をセットします。
    140         *
    141         * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
    142         * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
    143         * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
    144         *
    145         * ※ このメソッドでは、useSelect は使いません。
    146         *
    147         * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。
    148         * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない
    149         *
    150         * @param       values  ？に割り当てる設定値
    151         *
    152         * @throws SQLException DB処理の実行に失敗した場合
    153         */
    154        public void execute( final String[] values ) throws SQLException {
    155                if( values != null && values.length > 0 ) {
    156                        rowCnt++;                               // 行番号(処理行数)
    157
    158                        // ORACLE では、ParameterMetaDataは、使わない。
    159                        if( usePMeta ) {
    160                                for( int j=0; j<values.length; j++ ) {
    161                                        final String val = values[j];
    162                                        if( val == null || val.isEmpty() ) {
    163                                                pstmt.setNull( j+1, types[j] );                 // JDBC のカラム番号は、１から始まる。
    164                                        }
    165                                        else {
    166                                                pstmt.setObject( j+1,val,types[j] );
    167                                        }
    168                                }
    169                        }
    170                        else {
    171                                if( isTime == null ) {
    172                                        for( int j=0; j<values.length; j++ ) {
    173                                                final String val = values[j];                           // JDBC のカラム番号は、１から始まる。
    174                                                pstmt.setObject( j+1,val );
    175                                        }
    176                                }
    177                                else {
    178                                        // Timestamp オブジェクトを登録する場合の特別版です。
    179                                        // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
    180                                        for( int j=0; j<values.length; j++ ) {
    181                                                final String val = values[j];
    182                                                if( isTime[j] && val != null && !val.isEmpty() ) {
    183                                                        // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
    184                                                        final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
    185                                                        pstmt.setObject( j+1,time );
    186                                                }
    187                                                else {
    188                                                        pstmt.setObject( j+1,val );
    189                                                }
    190                                        }
    191                                }
    192                        }
    193                        pstmt.addBatch();
    194
    195                        if( rowCnt % DB_BATCH_SIZE == 0 ) {
    196                                final int[] execCnt = pstmt.executeBatch();
    197                                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
    198                                updCnt += execCnt.length;
    199                        }
    200                }
    201        }
    202
    203//      /**
    204//       * データ配列を渡してPreparedStatementの引数に、値をセットします。
    205//       *
    206//       * Timestamp オブジェクトを登録する場合の特別版です。
    207//       * 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
    208//       *
    209//       * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。
    210//       *
    211//       * @param       values  ？に割り当てる設定値
    212//       * @param       isTime  Timestampを設定するカラムの場合は、true
    213//       *
    214//       * @throws SQLException DB処理の実行に失敗した場合
    215//       */
    216//      public void execute( final String[] values , final boolean[] isTime ) throws SQLException {
    217//              if( values != null && values.length > 0 ) {
    218//                      rowCnt++;                               // 行番号(処理行数)
    219//
    220//                      for( int j=0; j<values.length; j++ ) {
    221//                              final String val = values[j];
    222//                              if( isTime[j] && val != null && !val.isEmpty() ) {
    223//                                      // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
    224//                                      final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
    225//                                      pstmt.setObject( j+1,time );
    226//                              }
    227//                              else {
    228//                                      pstmt.setObject( j+1,val );
    229//                              }
    230//                      }
    231//
    232//                      pstmt.addBatch();
    233//
    234//                      if( rowCnt % DB_BATCH_SIZE == 0 ) {
    235//                              final int[] execCnt = pstmt.executeBatch();
    236//
    237//                              // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
    238//                              updCnt += execCnt.length;
    239//                      }
    240//              }
    241//      }
    242
    243        /**
    244         * データ配列を渡してPreparedStatementの引数に、値をセットします。
    245         *
    246         * useSelect=true の場合は、更新系ではなく検索系の処理を行います。
    247         * ここでは、マージ処理を考慮しているため、検索結果が、0件か、それ以上かのみ判定します。
    248         *
    249         * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
    250         * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない
    251         *
    252         * @param       values  ？に割り当てる設定値
    253         * @return      更新件数
    254         *
    255         * @throws SQLException DB処理の実行に失敗した場合
    256         */
    257        public int update( final String[] values ) throws SQLException {
    258                int rtnCnt = 0 ;
    259
    260                if( values != null && values.length > 0 ) {
    261                        rowCnt++;                               // 行番号(処理行数)
    262
    263                        // ORACLE では、ParameterMetaDataは、使わない。
    264                        if( usePMeta ) {
    265                                for( int j=0; j<values.length; j++ ) {
    266                                        final String val = values[j];
    267                                        if( val == null || val.isEmpty() ) {
    268                                                pstmt.setNull( j+1, types[j] );                 // JDBC のカラム番号は、１から始まる。
    269                                        }
    270                                        else {
    271                                                pstmt.setObject( j+1,val,types[j] );
    272                                        }
    273                                }
    274                        }
    275                        else {
    276                                if( isTime == null ) {
    277                                        for( int j=0; j<values.length; j++ ) {
    278                                                final String val = values[j];                           // JDBC のカラム番号は、１から始まる。
    279                                                pstmt.setObject( j+1,val );
    280                                        }
    281                                }
    282                                else {
    283                                        // Timestamp オブジェクトを登録する場合の特別版です。
    284                                        // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
    285                                        for( int j=0; j<values.length; j++ ) {
    286                                                final String val = values[j];
    287                                                if( isTime[j] && val != null && !val.isEmpty() ) {
    288                                                        // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
    289                                                        final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
    290                                                        pstmt.setObject( j+1,time );
    291                                                }
    292                                                else {
    293                                                        pstmt.setObject( j+1,val );
    294                                                }
    295                                        }
    296                                }
    297                        }
    298//                      return pstmt.executeUpdate();
    299                        if( useSelect ) {                                                       // 7.4.1.0 (2021/04/23) 検索します。
    300                                // 8.0.0.0 (2021/08/20) spotbugs:Bug kind and pattern: OBL - OBL_UNSATISFIED_OBLIGATION
    301                                try( ResultSet resultSet = pstmt.executeQuery() ) {
    302                                        if( resultSet.next() ) {
    303                                                rtnCnt = resultSet.getInt(1);           // select count(*) なら…
    304                                        }
    305                                }
    306
    307        //                      final ResultSet resultSet = pstmt.executeQuery();
    308        //                      if( resultSet.next() ) {
    309        //                              rtnCnt = resultSet.getInt(1);           // select count(*) なら…
    310        //                      }
    311                        }
    312                        else {
    313                                rtnCnt = pstmt.executeUpdate();
    314                        }
    315                }
    316//              return 0;
    317                return rtnCnt;                  // 7.4.1.0 (2021/04/23)
    318        }
    319
    320        /**
    321         * データの最後の処理を行います。
    322         *
    323         * 具体的には、executeBatch() で、所定のバッチ数に届いていない場合の処理です。
    324         *
    325         * @return      更新件数
    326         * @throws      SQLException ﾃﾞｰﾀﾍﾞｰｽ処理で例外が発生した場合。
    327         */
    328        public int execEnd() throws SQLException {
    329                final int[] execCnt = pstmt.executeBatch();
    330                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
    331                updCnt += execCnt.length;
    332
    333                return updCnt;
    334        }
    335}