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}