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}