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 のカラム番号は、1から始まる。
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 のカラム番号は、1から始まる。
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}