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        private final PreparedStatement pstmt ;
045        private final boolean                   usePMeta ;
046        private final int[]                             types ;
047        private final boolean[]                 isTime;                 // 7.2.9.1 (2020/10/23) メソッドを統合します。
048
049        private int             rowCnt;
050        private int             updCnt;
051
052        /**
053         * PreparedStatement を指定して、インスタンスを作成します。
054         *
055         * 内部で、ParameterMetaData を作成して、sqlType を使用します。
056         *
057         * @param       prmSize パラメータの個数
058         * @param       pstmt   PreparedStatementオブジェクト
059         */
060        public DBUpdater( final int prmSize , final PreparedStatement pstmt ) {
061                this( prmSize , pstmt , true );
062        }
063
064        /**
065         * PreparedStatement を指定して、インスタンスを作成します。
066         *
067         * 内部で、ParameterMetaData を作成して、sqlType を使用します。
068         *
069         * @param       prmSize パラメータの個数
070         * @param       pstmt   PreparedStatementオブジェクト
071         * @param       usePMeta        sqlType を使用するかどうか [true:使用する/false:使用しない]
072         */
073        public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta ) {
074                this( prmSize , pstmt , usePMeta, null );
075        }
076
077        /**
078         * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。
079         *
080         * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを
081         * 指定します。ORACLEのようなタイプの
082         *
083         * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。
084         *
085         * @param       prmSize パラメータの個数
086         * @param       pstmt           PreparedStatementオブジェクト
087         * @param       usePMeta        sqlType を使用するかどうか [true:使用する/false:使用しない]
088         * @param       isTime  sqlType を使用するかどうか [true:使用する/false:使用しない]
089         */
090        public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , final boolean[] isTime ) {
091                this.usePMeta = usePMeta;
092                this.pstmt    = pstmt;
093                this.isTime   = isTime;                         // 7.2.9.1 (2020/10/23) メソッドを統合します。
094
095                if( usePMeta ) {
096                        types = new int[prmSize];
097
098                        try {
099                                final ParameterMetaData pMeta = pstmt.getParameterMetaData();
100                                for( int j=0; j<prmSize; j++ ) {
101                                        types[j] = pMeta.getParameterType( j+1 );       // ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。
102                                }
103                        }
104                        catch( final SQLException ex ) {
105                                final String errMsg = "ParameterMetaData の取得に失敗しました。" ;
106                                throw new OgRuntimeException( errMsg,ex );
107                        }
108                }
109                else {
110                        types = null;
111                }
112        }
113
114        /**
115         * データ配列を渡してPreparedStatementの引数に、値をセットします。
116         *
117         * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
118         * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
119         * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
120         *
121         * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。
122         *
123         * @param       values  ?に割り当てる設定値
124         *
125         * @throws SQLException DB処理の実行に失敗した場合
126         */
127        public void execute( final String[] values ) throws SQLException {
128                if( values != null && values.length > 0 ) {
129                        rowCnt++;                               // 行番号(処理行数)
130
131                        // ORACLE では、ParameterMetaDataは、使わない。
132                        if( usePMeta ) {
133                                for( int j=0; j<values.length; j++ ) {
134                                        final String val = values[j];
135                                        if( val == null || val.isEmpty() ) {
136                                                pstmt.setNull( j+1, types[j] );                 // JDBC のカラム番号は、1から始まる。
137                                        }
138                                        else {
139                                                pstmt.setObject( j+1,val,types[j] );
140                                        }
141                                }
142                        }
143                        else {
144                                if( isTime == null ) {
145                                        for( int j=0; j<values.length; j++ ) {
146                                                final String val = values[j];                           // JDBC のカラム番号は、1から始まる。
147                                                pstmt.setObject( j+1,val );
148                                        }
149                                }
150                                else {
151                                        // Timestamp オブジェクトを登録する場合の特別版です。
152                                        // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
153                                        for( int j=0; j<values.length; j++ ) {
154                                                final String val = values[j];
155                                                if( isTime[j] && val != null && !val.isEmpty() ) {
156                                                        // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
157                                                        final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
158                                                        pstmt.setObject( j+1,time );
159                                                }
160                                                else {
161                                                        pstmt.setObject( j+1,val );
162                                                }
163                                        }
164                                }
165                        }
166                        pstmt.addBatch();
167
168                        if( rowCnt % DB_BATCH_SIZE == 0 ) {
169                                final int[] execCnt = pstmt.executeBatch();
170                                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
171                                updCnt += execCnt.length;
172                        }
173                }
174        }
175
176//      /**
177//       * データ配列を渡してPreparedStatementの引数に、値をセットします。
178//       *
179//       * Timestamp オブジェクトを登録する場合の特別版です。
180//       * 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
181//       *
182//       * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。
183//       *
184//       * @param       values  ?に割り当てる設定値
185//       * @param       isTime  Timestampを設定するカラムの場合は、true
186//       *
187//       * @throws SQLException DB処理の実行に失敗した場合
188//       */
189//      public void execute( final String[] values , final boolean[] isTime ) throws SQLException {
190//              if( values != null && values.length > 0 ) {
191//                      rowCnt++;                               // 行番号(処理行数)
192//
193//                      for( int j=0; j<values.length; j++ ) {
194//                              final String val = values[j];
195//                              if( isTime[j] && val != null && !val.isEmpty() ) {
196//                                      // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
197//                                      final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
198//                                      pstmt.setObject( j+1,time );
199//                              }
200//                              else {
201//                                      pstmt.setObject( j+1,val );
202//                              }
203//                      }
204//
205//                      pstmt.addBatch();
206//
207//                      if( rowCnt % DB_BATCH_SIZE == 0 ) {
208//                              final int[] execCnt = pstmt.executeBatch();
209//
210//                              // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
211//                              updCnt += execCnt.length;
212//                      }
213//              }
214//      }
215
216        /**
217         * データ配列を渡してPreparedStatementの引数に、値をセットします。
218         *
219         * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応
220         *
221         * @param       values  ?に割り当てる設定値
222         * @return      更新件数
223         *
224         * @throws SQLException DB処理の実行に失敗した場合
225         */
226        public int update( final String[] values ) throws SQLException {
227                if( values != null && values.length > 0 ) {
228                        rowCnt++;                               // 行番号(処理行数)
229
230                        // ORACLE では、ParameterMetaDataは、使わない。
231                        if( usePMeta ) {
232                                for( int j=0; j<values.length; j++ ) {
233                                        final String val = values[j];
234                                        if( val == null || val.isEmpty() ) {
235                                                pstmt.setNull( j+1, types[j] );                 // JDBC のカラム番号は、1から始まる。
236                                        }
237                                        else {
238                                                pstmt.setObject( j+1,val,types[j] );
239                                        }
240                                }
241                        }
242                        else {
243                                if( isTime == null ) {
244                                        for( int j=0; j<values.length; j++ ) {
245                                                final String val = values[j];                           // JDBC のカラム番号は、1から始まる。
246                                                pstmt.setObject( j+1,val );
247                                        }
248                                }
249                                else {
250                                        // Timestamp オブジェクトを登録する場合の特別版です。
251                                        // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
252                                        for( int j=0; j<values.length; j++ ) {
253                                                final String val = values[j];
254                                                if( isTime[j] && val != null && !val.isEmpty() ) {
255                                                        // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
256                                                        final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
257                                                        pstmt.setObject( j+1,time );
258                                                }
259                                                else {
260                                                        pstmt.setObject( j+1,val );
261                                                }
262                                        }
263                                }
264                        }
265                        return pstmt.executeUpdate();
266                }
267                return 0;
268        }
269
270        /**
271         * データの最後の処理を行います。
272         *
273         * 具体的には、executeBatch() で、所定のバッチ数に届いていない場合の処理です。
274         *
275         * @return      更新件数
276         * @throws      SQLException データベース処理で例外が発生した場合。
277         */
278        public int execEnd() throws SQLException {
279                final int[] execCnt = pstmt.executeBatch();
280                // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
281                updCnt += execCnt.length;
282
283                return updCnt;
284        }
285}