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