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