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 java.sql.PreparedStatement; 019import java.sql.ParameterMetaData; 020import java.sql.SQLException; 021import java.sql.Timestamp; 022import java.sql.ResultSet; // 7.4.1.0 (2021/04/23) 023 024import org.opengion.fukurou.system.OgRuntimeException ; 025import org.opengion.fukurou.util.HybsDateUtil; 026import static org.opengion.fukurou.system.HybsConst.DB_BATCH_SIZE; // 6.9.4.1 (2018/04/09) 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 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 049// private final boolean[] isTime; // 7.2.9.1 (2020/10/23) メソッドを統合します。 050 private final boolean[] timeClms; // 7.2.9.1 (2020/10/23) メソッドを統合します。 051 private final boolean useSelect; // 7.4.1.0 (2021/04/23) 052 053 private int rowCnt; 054 private int updCnt; 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 を指定して、インスタンスを作成します。 070 * 071 * 内部で、ParameterMetaData を作成して、sqlType を使用します。 072 * 073 * @param prmSize パラメータの個数 074 * @param pstmt PreparedStatementオブジェクト 075 * @param usePMeta sqlType を使用するかどうか [true:使用する/false:使用しない] 076 */ 077 public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta ) { 078 this( prmSize , pstmt , usePMeta, null ); 079 } 080 081 /** 082 * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。 083 * 084 * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを 085 * 指定します。ORACLEのようなタイプの 086 * 087 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 088 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 対応 089 * 090 * @param prmSize パラメータの個数 091 * @param pstmt PreparedStatementオブジェクト 092 * @param usePMeta sqlType を使用するかどうか [true:使用する/false:使用しない] 093 * @param timeClms sqlType を使用するかどうか [true:使用する/false:使用しない] 094 */ 095// public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , final boolean[] isTime ) { 096 public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , final boolean[] timeClms ) { 097// this( prmSize , pstmt , usePMeta, isTime , false ); 098 this( prmSize , pstmt , usePMeta, timeClms , false ); // 8.5.4.2 (2024/01/12) 099 } 100 101 /** 102 * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。 103 * 104 * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを 105 * 指定します。ORACLEのようなタイプの 106 * 107 * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。 108 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 109 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 対応 110 * 111 * @param prmSize パラメータの個数 112 * @param pstmt PreparedStatementオブジェクト 113 * @param usePMeta sqlType を使用するかどうか [true:使用する/false:使用しない] 114 * @param timeClms sqlType を使用するかどうか [true:使用する/false:使用しない] 115 * @param useSelect true の場合は、UPDATE/INSERTではなく、SELECT します。 116 */ 117// public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , final boolean[] isTime ) { 118 public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta , 119// final boolean[] isTime , final boolean useSelect ) { 120 final boolean[] timeClms , final boolean useSelect ) { 121 this.usePMeta = usePMeta; 122 this.pstmt = pstmt; 123// this.isTime = isTime; // 7.2.9.1 (2020/10/23) メソッドを統合します。 124 this.timeClms = timeClms; // 7.2.9.1 (2020/10/23) メソッドを統合します。8.5.4.2 (2024/01/12) 125 this.useSelect = useSelect; // 7.4.1.0 (2021/04/23) 126 127 if( usePMeta ) { 128 types = new int[prmSize]; 129 130 try { 131 final ParameterMetaData pMeta = pstmt.getParameterMetaData(); 132 for( int j=0; j<prmSize; j++ ) { 133 types[j] = pMeta.getParameterType( j+1 ); // ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。 134 } 135 } 136 catch( final SQLException ex ) { 137 final String errMsg = "ParameterMetaData の取得に失敗しました。" ; 138 throw new OgRuntimeException( errMsg,ex ); 139 } 140 } 141 else { 142 types = null; 143 } 144 } 145 146 /** 147 * データ配列を渡してPreparedStatementの引数に、値をセットします。 148 * 149 * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、 150 * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。 151 * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。 152 * 153 * ※ このメソッドでは、useSelect は使いません。 154 * 155 * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。 156 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 157 * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 対応 158 * @og.rev 8.5.6.1 (2024/03/29) execute と、update で共通に使われる処理をまとめる。 159 * 160 * @param values ?に割り当てる設定値 161 * 162 * @throws SQLException DB処理の実行に失敗した場合 163 */ 164 public void execute( final String[] values ) throws SQLException { 165 if( values != null && values.length > 0 ) { 166 rowCnt++; // 行番号(処理行数) 167 168 // 8.5.6.1 (2024/03/29) execute と、update で共通に使われる処理をまとめる。 169 pstmtDataset( values ); 170 171// // ORACLE では、ParameterMetaDataは、使わない。 172// if( usePMeta ) { 173// for( int j=0; j<values.length; j++ ) { 174// final String val = values[j]; 175// if( val == null || val.isEmpty() ) { 176// pstmt.setNull( j+1, types[j] ); // JDBC のカラム番号は、1から始まる。 177// } 178// else { 179// pstmt.setObject( j+1,val,types[j] ); 180// } 181// } 182// } 183// else { 184// // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 185//// if( isTime == null ) { 186// if( timeClms == null ) { 187// for( int j=0; j<values.length; j++ ) { 188// final String val = values[j]; // JDBC のカラム番号は、1から始まる。 189// pstmt.setObject( j+1,val ); 190// } 191// } 192// else { 193// // Timestamp オブジェクトを登録する場合の特別版です。 194// // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。 195// for( int j=0; j<values.length; j++ ) { 196// final String val = values[j]; 197// // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 198//// if( isTime[j] && val != null && !val.isEmpty() ) { 199// if( timeClms[j] && val != null && !val.isEmpty() ) { 200// // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。 201// final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) ); 202// pstmt.setObject( j+1,time ); 203// } 204// else { 205// pstmt.setObject( j+1,val ); 206// } 207// } 208// } 209// } 210 pstmt.addBatch(); 211 212 if( rowCnt % DB_BATCH_SIZE == 0 ) { 213 final int[] execCnt = pstmt.executeBatch(); 214 // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。 215 updCnt += execCnt.length; 216 } 217 } 218 } 219 220// /** 221// * データ配列を渡してPreparedStatementの引数に、値をセットします。 222// * 223// * Timestamp オブジェクトを登録する場合の特別版です。 224// * 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。 225// * 226// * @og.rev 7.2.9.1 (2020/10/23) isTimeのメソッドを統合します。 227// * 228// * @param values ?に割り当てる設定値 229// * @param isTime Timestampを設定するカラムの場合は、true 230// * 231// * @throws SQLException DB処理の実行に失敗した場合 232// */ 233// public void execute( final String[] values , final boolean[] isTime ) throws SQLException { 234// if( values != null && values.length > 0 ) { 235// rowCnt++; // 行番号(処理行数) 236// 237// for( int j=0; j<values.length; j++ ) { 238// final String val = values[j]; 239// if( isTime[j] && val != null && !val.isEmpty() ) { 240// // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。 241// final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) ); 242// pstmt.setObject( j+1,time ); 243// } 244// else { 245// pstmt.setObject( j+1,val ); 246// } 247// } 248// 249// pstmt.addBatch(); 250// 251// if( rowCnt % DB_BATCH_SIZE == 0 ) { 252// final int[] execCnt = pstmt.executeBatch(); 253// 254// // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。 255// updCnt += execCnt.length; 256// } 257// } 258// } 259 260 /** 261 * データ配列を渡してPreparedStatementの引数に、値をセットします。 262 * 263 * useSelect=true の場合は、更新系ではなく検索系の処理を行います。 264 * ここでは、マージ処理を考慮しているため、検索結果が、0件か、それ以上かのみ判定します。 265 * 266 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 267 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 268 * @og.rev 8.5.6.1 (2024/03/29) execute と、update で共通に使われる処理をまとめる。 269 * 270 * @param values ?に割り当てる設定値 271 * @return 更新件数 272 * 273 * @throws SQLException DB処理の実行に失敗した場合 274 */ 275 public int update( final String[] values ) throws SQLException { 276 int rtnCnt = 0 ; 277 278 if( values != null && values.length > 0 ) { 279 rowCnt++; // 行番号(処理行数) 280 281 // 8.5.6.1 (2024/03/29) execute と、update で共通に使われる処理をまとめる。 282 pstmtDataset( values ); 283 284// // ORACLE では、ParameterMetaDataは、使わない。 285// if( usePMeta ) { 286// for( int j=0; j<values.length; j++ ) { 287// final String val = values[j]; 288// if( val == null || val.isEmpty() ) { 289// pstmt.setNull( j+1, types[j] ); // JDBC のカラム番号は、1から始まる。 290// } 291// else { 292// pstmt.setObject( j+1,val,types[j] ); 293// } 294// } 295// } 296// else { 297// // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 298//// if( isTime == null ) { 299// if( timeClms == null ) { 300// for( int j=0; j<values.length; j++ ) { 301// final String val = values[j]; // JDBC のカラム番号は、1から始まる。 302// pstmt.setObject( j+1,val ); 303// } 304// } 305// else { 306// // Timestamp オブジェクトを登録する場合の特別版です。 307// // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。 308// for( int j=0; j<values.length; j++ ) { 309// final String val = values[j]; 310// // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 311//// if( isTime[j] && val != null && !val.isEmpty() ) { 312// if( timeClms[j] && val != null && !val.isEmpty() ) { 313// // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。 314// final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) ); 315// pstmt.setObject( j+1,time ); 316// } 317// else { 318// pstmt.setObject( j+1,val ); 319// } 320// } 321// } 322// } 323//// return pstmt.executeUpdate(); 324 if( useSelect ) { // 7.4.1.0 (2021/04/23) 検索します。 325 // 8.0.0.0 (2021/08/20) spotbugs:Bug kind and pattern: OBL - OBL_UNSATISFIED_OBLIGATION 326 try( ResultSet resultSet = pstmt.executeQuery() ) { 327 if( resultSet.next() ) { 328 rtnCnt = resultSet.getInt(1); // select count(*) なら… 329 } 330 } 331 332 // final ResultSet resultSet = pstmt.executeQuery(); 333 // if( resultSet.next() ) { 334 // rtnCnt = resultSet.getInt(1); // select count(*) なら… 335 // } 336 } 337 else { 338 rtnCnt = pstmt.executeUpdate(); 339 } 340 } 341// return 0; 342 return rtnCnt; // 7.4.1.0 (2021/04/23) 343 } 344 345 /** 346 * データの最後の処理を行います。 347 * 348 * 具体的には、executeBatch() で、所定のバッチ数に届いていない場合の処理です。 349 * 350 * @return 更新件数 351 * @throws SQLException データベース処理で例外が発生した場合。 352 */ 353 public int execEnd() throws SQLException { 354 final int[] execCnt = pstmt.executeBatch(); 355 // 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。 356 updCnt += execCnt.length; 357 358 return updCnt; 359 } 360 361 /** 362 * PreparedStatement にデータをセットする簡易メソッドです。 363 * 364 * execute と、update で共通に使われる処理をまとめました。 365 * 366 * @og.rev 8.5.6.1 (2024/03/29) execute と、update で共通に使われる処理をまとめる。 367 * 368 * @param values ?に割り当てる設定値 369 * 370 * @throws SQLException DB処理の実行に失敗した場合 371 */ 372 public void pstmtDataset( final String[] values ) throws SQLException { 373 // ORACLE では、ParameterMetaDataは、使わない。 374 if( usePMeta ) { 375 for( int j=0; j<values.length; j++ ) { 376 final String val = values[j]; 377 if( val == null || val.isEmpty() ) { 378 pstmt.setNull( j+1, types[j] ); // JDBC のカラム番号は、1から始まる。 379 } 380 else { 381 pstmt.setObject( j+1,val,types[j] ); 382 } 383 } 384 } 385 else { 386 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 387 if( timeClms == null ) { 388 for( int j=0; j<values.length; j++ ) { 389 final String val = values[j]; // JDBC のカラム番号は、1から始まる。 390 pstmt.setObject( j+1,val ); 391 } 392 } 393 else { 394 // Timestamp オブジェクトを登録する場合の特別版です。 395 // 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。 396 for( int j=0; j<values.length; j++ ) { 397 final String val = values[j]; 398 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 399 if( timeClms[j] && val != null && !val.isEmpty() ) { 400 // val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。 401 final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) ); 402 pstmt.setObject( j+1,time ); 403 } 404 else { 405 pstmt.setObject( j+1,val ); 406 } 407 } 408 } 409 } 410 } 411}