    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;
    026
    027/**
    028 * PreparedStatementを利用した更新処理を行う、簡易的なクラスです。
    029 *
    030 * ParameterMetaDataの使用有無を指定することで、ﾊﾟﾗﾒｰﾀを処理する際に、
    031 * sqlType を使用するかどうかを指定します。
    032 * また、データ登録時のバッチサイズに基づいた処理を行っています。
    033 * execute(String[]) で、行ごとのパラメータデータを渡します。
    034 * 一番最後に、execEnd() を呼ぶことで、更新件数を返します。
    035 * 更新件数を取得しない場合でも、このメソッドを呼んでください。
    036 *
    037 * このクラスは、マルチスレッドに対応していません。
    038 *
    039 * @version  6.9
    040 * @author   Kazuhiko Hasegawa
    041 * @since    JDK9.0,
    042 */
    043public final class DBUpdater {
    044
    045//      /** 6.9.3.0 (2018/03/26) データ登録時のバッチサイズ  {@value} */
    046//      public static final int DB_BATCH_SIZE = 100 ;
    047
    048        private final PreparedStatement pstmt ;
    049        private final boolean                   usePMeta ;
    050        private final int[]                             types ;
    051
    052//      private boolean[]       isTime ;                        // Timestamp オブジェクトのカラム
    053//      private boolean         useTimeStamp ;          // Timestamp を利用するかどうか
    054
    055        private int             rowCnt;
    056        private int             updCnt;
    057
    058        /**
    059         * PreparedStatement を指定して、インスタンスを作成します。
    060         *
    061         * 内部で、ParameterMetaData を作成して、sqlType を使用します。
    062         *
    063         * @param       prmSize パラメータの個数
    064         * @param       pstmt   PreparedStatementオブジェクト
    065         */
    066        public DBUpdater( final int prmSize , final PreparedStatement pstmt ) {
    067                this( prmSize , pstmt , true );
    068        }
    069
    070        /**
    071         * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。
    072         *
    073         * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを
    074         * 指定します。ORACLEのようなタイプの
    075         *
    076         * @param       prmSize パラメータの個数
    077         * @param       pstmt           PreparedStatementオブジェクト
    078         * @param       usePMeta        sqlType を使用するかどうか [true:使用する/false:使用しない]
    079         */
    080        public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta ) {
    081                this.usePMeta = usePMeta;
    082                this.pstmt    = pstmt;
    083
    084                if( usePMeta ) {
    085                        types = new int[prmSize];
    086
    087                        try {
    088                                final ParameterMetaData pMeta = pstmt.getParameterMetaData();
    089                                for( int j=0; j<prmSize; j++ ) {
    090                                        types[j] = pMeta.getParameterType( j+1 );       // ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。
    091                                }
    092                        }
    093                        catch( final SQLException ex ) {
    094                                final String errMsg = "ParameterMetaData の取得に失敗しました。" ;
    095                                throw new OgRuntimeException( errMsg,ex );
    096                        }
    097                }
    098                else {
    099                        types = null;
    100                }
    101        }
    102
    103//      /**
    104//       * Timestamp オブジェクトを登録するカラムに、true をセットした配列を渡します。
    105//       *
    106//       * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
    107//       * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
    108//       * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
    109//       *
    110//       * @param       isTime  ？に割り当てる設定値
    111//       */
    112//      public void setTimeStampClms( final boolean[] isTime ) {
    113//              this.isTime = isTime;
    114//              for( final boolean isUse : isTime ) {
    115//                      if( isUse ) { useTimeStamp = true; break; }
    116//              }
    117//      }
    118
    119        /**
    120         * データ配列を渡してPreparedStatementの引数に、値をセットします。
    121         *
    122         * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
    123         * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
    124         * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
    125         *
    126         * @param       values  ？に割り当てる設定値
    127         *
    128         * @throws SQLException DB処理の実行に失敗した場合
    129         */
    130        public void execute( final String[] values ) throws SQLException {
    131                if( values != null && values.length > 0 ) {
    132                        rowCnt++;                               // 行番号(処理行数)
    133
    134                        // ORACLE では、ParameterMetaDataは、使わない。
    135                        if( usePMeta ) {
    136                                for( int j=0; j<values.length; j++ ) {
    137                                        final String val = values[j];
    138                                        if( val == null || val.isEmpty() ) {
    139                                                pstmt.setNull( j+1, types[j] );                 // JDBC のカラム番号は、１から始まる。
    140                                        }
    141                                        else {
    142                                                pstmt.setObject( j+1,val,types[j] );
    143                                        }
    144                                }
    145                        }
    146                        else {
    147                                for( int j=0; j<values.length; j++ ) {
    148                                        final String val = values[j];                           // JDBC のカラム番号は、１から始まる。
    149                                        pstmt.setObject( j+1,val );
    150                                }
    151                        }
    152                        pstmt.addBatch();
    153
    154                        if( rowCnt % DB_BATCH_SIZE == 0 ) {
    155                                final int[] execCnt = pstmt.executeBatch();
    156                                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
    157                                updCnt += execCnt.length;
    158                //              for( final int cnt : execCnt ) {
    159                //                      if( cnt > 0 ) { updCnt += cnt; }                        // SUCCESS_NO_INFO:更新数不明、EXECUTE_FAILED:失敗したコマンドが正常に実行された
    160                //              }
    161                        }
    162                }
    163        }
    164
    165        /**
    166         * データ配列を渡してPreparedStatementの引数に、値をセットします。
    167         *
    168         * Timestamp オブジェクトを登録する場合の特別版です。
    169         * 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
    170         *
    171         * @param       values  ？に割り当てる設定値
    172         * @param       isTime  Timestampを設定するカラムの場合は、true
    173         *
    174         * @throws SQLException DB処理の実行に失敗した場合
    175         */
    176        public void execute( final String[] values , final boolean[] isTime ) throws SQLException {
    177                if( values != null && values.length > 0 ) {
    178                        rowCnt++;                               // 行番号(処理行数)
    179
    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                        pstmt.addBatch();
    193
    194                        if( rowCnt % DB_BATCH_SIZE == 0 ) {
    195                                final int[] execCnt = pstmt.executeBatch();
    196
    197                                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
    198                                updCnt += execCnt.length;
    199                //              for( final int cnt : execCnt ) {
    200                //                      if( cnt > 0 ) { updCnt += cnt; }                        // SUCCESS_NO_INFO:更新数不明、EXECUTE_FAILED:失敗したコマンドが正常に実行された
    201                //              }
    202                        }
    203                }
    204        }
    205
    206        /**
    207         * データの最後の処理を行います。
    208         *
    209         * 具体的には、executeBatch() で、所定のバッチ数に届いていない場合の処理です。
    210         *
    211         * @return      更新件数
    212         * @throws      SQLException ﾃﾞｰﾀﾍﾞｰｽ処理で例外が発生した場合。
    213         */
    214        public int execEnd() throws SQLException {
    215//              try {
    216//                      return pstmt.executeBatch().length;
    217//              }
    218//              catch( final java.sql.BatchUpdateException ex ) {
    219//                      final int[] cnt = ex.getUpdateCounts();
    220//                      for( int i=0; i<cnt.length; i++ ) {
    221//                              if( cnt[i] < 0 ) {
    222//                                      System.out.println( "Error Row=" + i );
    223//                              }
    224//                      }
    225//                      throw ex;
    226//              }
    227
    228                final int[] execCnt = pstmt.executeBatch();
    229                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
    230                updCnt += execCnt.length;
    231        //      for( final int cnt : execCnt ) {
    232        //              if( cnt > 0 ) { updCnt += cnt; }                        // SUCCESS_NO_INFO:更新数不明、EXECUTE_FAILED:失敗したコマンドが正常に実行された
    233        //      }
    234
    235                return updCnt;
    236        }
    237}