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.fileexec; 017 018import java.sql.Connection; 019import java.sql.ResultSet; 020import java.sql.PreparedStatement; 021import java.sql.ParameterMetaData; 022import java.sql.SQLException; 023import java.sql.SQLIntegrityConstraintViolationException; // 7.4.1.0 (2021/04/23) 024import java.util.Map; 025import java.util.List; 026import java.util.ArrayList; 027import java.util.Arrays; 028 029import org.apache.tomcat.jdbc.pool.DataSource; 030import org.apache.tomcat.jdbc.pool.PoolProperties; 031 032import org.opengion.fukurou.system.HybsConst; // 7.2.3.1 (2020/04/17) 033import org.opengion.fukurou.db.Transaction; // 7.4.2.0 (2021/05/14) 034import org.opengion.fukurou.db.ResultSetValue; // 8.5.6.1 (2024/03/29) org.opengion.fukurou.db.ResultSetValue を使用する 035 036/** 037 * データベース処理を行う、簡易的なユーティリティークラスです。 038 * staticメソッドしか持っていません。 039 * sql文を execute( query ) する事により、データベースに書き込みます。 040 * 041 * このクラスは、マルチスレッドに対して、安全です。 042 * 043 * @version 4.0 044 * @author Kazuhiko Hasegawa 045 * @since JDK5.0, 046 */ 047public final class DBUtil { 048 private static final XLogger LOGGER= XLogger.getLogger( DBUtil.class.getSimpleName() ); // ログ出力 049 050 /** データベースのキーワード {@value} */ 051 public static final String DATABASE_KEY = "DATABASE"; 052 053 /** 接続先URL {@value} */ 054 public static final String URL_KEY = "REALM_URL"; 055 /** ドライバー {@value} */ 056 public static final String DRIVER_KEY = "REALM_DRIVER"; 057 /** ユーザーID {@value} */ 058 public static final String NAME_KEY = "REALM_NAME"; 059 /** パスワード {@value} */ 060 public static final String PASSWORD_KEY = "REALM_PASSWORD"; 061 062 /** データベースリトライの待ち時間(ミリ秒) {@value} */ 063 public static final int CONN_SLEEP_TIME = 2000 ; // 6.8.2.2 (2017/11/02) コネクションの獲得まで、2秒待つ 064 /** データベースリトライ回数 {@value} */ 065 public static final int CONN_RETRY_COUNT = 10 ; // 6.8.2.2 (2017/11/02) コネクションの獲得まで、10回リトライする。 066 /** データベースValid タイムアウト時間(秒) {@value} */ 067 public static final int CONN_VALID_TIMEOUT = 10 ; // 6.8.2.2 (2017/11/02) コネクションのValidチェックのタイムアウト時間。 068 069 /** データ検索時のフェッチサイズ {@value} */ 070 public static final int DB_FETCH_SIZE = 251 ; 071 072 private static final DataSource DATA_SOURCE = new DataSource(); 073 074 private static boolean readyFlag ; // 準備が出来た場合は、true 075 private static boolean oracleFlag ; // 接続先がORACLEの場合は、true 076 077 private static final int BUFFER_MIDDLE = 200 ; 078 079 /** 080 * デフォルトコンストラクターをprivateにして、 081 * オブジェクトの生成をさせないようにする。 082 */ 083 private DBUtil() {} 084 085 /** 086 * 引数を指定せず、オブジェクトを作成します。 087 * 088 * System.getProperty より取得し、さらに、そこから取得できなかった 089 * 場合は、環境変数から、取得します。 090 * 091 * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応) 092 * 093 * @see #URL_KEY 094 */ 095 public static void init() { 096// init( System.getProperty( URL_KEY , System.getenv( URL_KEY ) ) , 097// System.getProperty( DRIVER_KEY , System.getenv( DRIVER_KEY ) ) , 098// System.getProperty( NAME_KEY , System.getenv( NAME_KEY ) ) , 099// System.getProperty( PASSWORD_KEY , System.getenv( PASSWORD_KEY ) ) 100// ); 101 init( HybsConst.getenv( URL_KEY ) , 102 HybsConst.getenv( DRIVER_KEY ) , 103 HybsConst.getenv( NAME_KEY ) , 104 HybsConst.getenv( PASSWORD_KEY ) 105 ); 106 } 107 108 /** 109 * 接続先URL、ドライバー、ユーザーID、パスワードなどを含んだMapを指定して、オブジェクトを作成します。 110 * 111 * Mapに指定のキーが含まれない場合は、System.getProperty より取得し、さらに、そこから取得できなかった 112 * 場合は、環境変数から、取得します。 113 * 114 * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応) 115 * 116 * @param prmMap 必要情報を含んだMapオブジェクト 117 * @see #URL_KEY 118 */ 119 public static void init( final Map<String,String> prmMap ) { 120// init( prmMap.getOrDefault( URL_KEY , System.getProperty( URL_KEY , System.getenv( URL_KEY ) ) ) , 121// prmMap.getOrDefault( DRIVER_KEY , System.getProperty( DRIVER_KEY , System.getenv( DRIVER_KEY ) ) ) , 122// prmMap.getOrDefault( NAME_KEY , System.getProperty( NAME_KEY , System.getenv( NAME_KEY ) ) ) , 123// prmMap.getOrDefault( PASSWORD_KEY , System.getProperty( PASSWORD_KEY , System.getenv( PASSWORD_KEY ) ) ) 124// ); 125 init( prmMap.getOrDefault( URL_KEY , HybsConst.getenv( URL_KEY ) ) , 126 prmMap.getOrDefault( DRIVER_KEY , HybsConst.getenv( DRIVER_KEY ) ) , 127 prmMap.getOrDefault( NAME_KEY , HybsConst.getenv( NAME_KEY ) ) , 128 prmMap.getOrDefault( PASSWORD_KEY , HybsConst.getenv( PASSWORD_KEY ) ) 129 ); 130 } 131 132 /** 133 * 接続先URL、ドライバー、ユーザーID、パスワードを指定して、オブジェクトを作成します。 134 * 135 * params は、必ず、4つ必要です。 136 * 137 * @param params 接続先URL、ドライバー、ユーザーID、パスワード 138 * @see #isReady() 139 */ 140 public static void init( final String... params ) { 141 if( readyFlag ) { 142 // MSG0024 = すでに、接続先設定は完了しています。[{0}] 143 throw MsgUtil.throwException( "MSG0024" , DATA_SOURCE ); 144 } 145 146 if( params == null || params.length != 4 ) { 147 // MSG0027 = 接続先設定情報が不足しています。[{0}] 148 throw MsgUtil.throwException( "MSG0027" , Arrays.toString( params ) ); 149 } 150 151 final PoolProperties pp = new PoolProperties(); 152 pp.setUrl( params[0] ); 153 pp.setDriverClassName( params[1] ); 154 pp.setUsername( params[2] ); 155 pp.setPassword( params[3] ); 156 157 DATA_SOURCE.setPoolProperties( pp ); 158 readyFlag = true; 159 160 oracleFlag = params[0] != null && params[0].startsWith( "jdbc:oracle" ); 161 } 162 163 /** 164 * DataSourceの初期化が完了していれば、true を返します。 165 * 166 * 初期化は、#init(String...) メソッドの呼び出して、完了します。 167 * #crear() で、未完了に戻ります。 168 * 169 * @return 初期化が完了しているかどうか 170 * @see #init(String...) 171 */ 172 public static boolean isReady() { return readyFlag; } 173 174 /** 175 * 接続先がORACLEかどうかを返します。 176 * 177 * ORACLE の場合は、true を返します。 178 * 179 * @return 接続先がORACLEかどうか[true:ORACLE false:その他] 180 */ 181 public static boolean isOracle() { return oracleFlag; } 182 183 /** 184 * DataSource から、Connectionを取得して、返します。 185 * 186 * @og.rev 6.8.2.2 (2017/11/02) コネクションの再取得をリトライします。 187 * @og.rev 7.2.5.0 (2020/06/01) DB処理の実行に失敗のエラーは、3回までは、何も出さない。 188 * 189 * @return DataSourceから、Connectionを取得して、返します。 190 * @throws SQLException SQLエラーが発生した場合 191 */ 192 public static Connection getConnection() throws SQLException { 193 if( !readyFlag ) { 194 // // MSG0025 = 接続先設定が完了していません。 195 // throw MsgUtil.throwException( "MSG0025" , "getConnection() Error!!" ); 196 init(); 197 } 198 199 SQLException errEX = null; 200 for( int i=0; i<CONN_RETRY_COUNT; i++ ) { 201 try { 202 final Connection conn = DATA_SOURCE.getConnection(); 203 conn.setAutoCommit( false ); 204 205 if( conn.isValid( CONN_VALID_TIMEOUT ) ) { return conn; } 206 } 207 catch( final SQLException ex ) { 208 if( i >= 3 ) { // とりあえず3回までは、何も出さない 209// // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 210// MsgUtil.errPrintln( "MSG0019" , ex.getMessage() ); 211 // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 212 MsgUtil.errPrintln( "MSG0019" , ex.getMessage() , "" ); 213 } 214 215 errEX = ex ; 216// try{ Thread.sleep( CONN_SLEEP_TIME ); } catch( final InterruptedException ex2 ){} 217 try { Thread.sleep( CONN_SLEEP_TIME ); } catch( final InterruptedException ignored ) {} // 8.5.4.2 (2024/01/12) PMD 7.0.0 EmptyCatchBlock 218 } 219 } 220 221 final String errMsg = errEX == null ? "COUNT Over" : errEX.getMessage() ; 222// // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 223// throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" , "" ); 224 // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 225 throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" ); 226 } 227 228 /** 229 * データ配列を渡してPreparedStatementの引数に、値をセットします。 230 * 231 * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、 232 * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。 233 * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。 234 * 235 * @param pstmt PreparedStatementオブジェクト 236 * @param values ?に割り当てる設定値 237 * @param pMeta オラクル系以外のDBに対して、type指定する場合に使用する ParameterMetaDataオブジェクト 238 * 239 * @throws SQLException DB処理の実行に失敗した場合 240 */ 241 private static void setObject( final PreparedStatement pstmt , final String[] values , final ParameterMetaData pMeta ) throws SQLException { 242 if( values != null && values.length > 0 ) { 243 // ORACLE では、ParameterMetaDataは、使わない。 244 if( pMeta == null ) { 245 int clmNo = 1; // JDBC のカラム番号は、1から始まる。 246 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach 247// for( int i=0; i<values.length; i++ ) { 248// final String val = values[i]; 249// pstmt.setObject( clmNo++,val ); 250// } 251 for( final String val : values ) { 252 pstmt.setObject( clmNo++,val ); 253 } 254 } 255 else { 256 int clmNo = 1; // JDBC のカラム番号は、1から始まる。 257 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach 258// for( int i=0; i<values.length; i++ ) { 259// final String val = values[i]; 260 for( final String val : values ) { 261 final int type = pMeta.getParameterType( clmNo ); 262 if( val == null || val.isEmpty() ) { 263 pstmt.setNull( clmNo++, type ); 264 } 265 else { 266 pstmt.setObject( clmNo++,val,type ); 267 } 268 } 269 } 270 } 271 } 272 273 /** 274 * データ配列を渡して実際のDB処理を実行します。 275 * 276 * ここでは、1行だけ処理するための簡易メソッドを提供します。 277 * 278 * @param query 実行するSQL文 279 * @param values ?に割り当てる設定値 280 * @return ここでの処理件数 281 * 282 * @throws RuntimeException Connection DB処理の実行に失敗した場合 283 */ 284 public static int execute( final String query , final String... values ) { 285// final List<String[]> list = new ArrayList<>(); 286// list.add( values ); 287// 288// return execute( query,list ); 289 290 int execCnt = 0; 291 292 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 293 SQLException throwEx = null; 294 // try-with-resources 文 (AutoCloseable) 295 try( Connection conn = getConnection() ) { 296 // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。 297 try( PreparedStatement pstmt = conn.prepareStatement( query ) ) { 298 // ORACLE では、ParameterMetaDataは、使わない。 299 final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData(); 300 301 setObject( pstmt , values , pMeta ); 302 execCnt = pstmt.executeUpdate(); // 1回なので、+= の必要性は無い。 303 304 conn.commit(); 305 } 306 catch( final SQLException ex ) { 307 conn.rollback(); 308 conn.setAutoCommit(true); 309 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 310// throw ex; 311 throwEx = ex; 312 } 313 } 314 catch( final SQLException ex ) { 315 // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 316 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 317// throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) ); 318 throwEx = ex; 319 } 320 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 321 // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally 322// finally { 323 if( throwEx != null ) { 324 // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 325 throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( values ) ); 326 } 327// } 328 329 return execCnt; 330 } 331 332 /** 333 * データ配列を渡して実際のDB処理を実行します。 334 * 335 * ここでは、1行だけ処理するための簡易メソッドを提供します。 336 * 337 * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionオブジェクト 対応 338 * 339 * @param tarn 外部から指定するTransactionオブジェクト 340 * @param query 実行するSQL文 341 * @param values ?に割り当てる設定値 342 * @return ここでの処理件数 343 * 344 * @throws RuntimeException Connection DB処理の実行に失敗した場合 345 */ 346 public static int execute( final Transaction tarn , final String query , final String... values ) { 347 // 8.5.4.2 (2024/01/12) PMD 7.0.0 UnusedAssignment 348// int execCnt = 0; 349 final int execCnt ; 350 351// // try-with-resources 文 (AutoCloseable) 352// try( Connection conn = tarn.getConnection( null ) ) { 353 final Connection conn = tarn.getConnection( null ); 354// try { 355 // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。 356 try( PreparedStatement pstmt = conn.prepareStatement( query ) ) { 357 // ORACLE では、ParameterMetaDataは、使わない。 358 final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData(); 359 360 setObject( pstmt , values , pMeta ); 361 execCnt = pstmt.executeUpdate(); // 1回なので、+= の必要性は無い。 362 363 tarn.commit(); 364 } 365 catch( final SQLException ex ) { 366 tarn.rollback(); 367 // conn.setAutoCommit(true); 368 // throw ex; 369 // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 370 throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) ); 371 } 372 // } 373 // catch( final SQLException ex ) { 374 // // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 375 // throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) ); 376 // } 377 378 return execCnt; 379 } 380 381 /** 382 * データ配列のListを渡して実際のDB処理を実行します。 383 * 384 * データ配列は、1行分のデータに対する設定値の配列です。 385 * これは、keys で指定した並び順と一致している必要があります。 386 * 387 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。 388 * 389 * @param query 実行するSQL文 390 * @param list ?に割り当てる設定値 391 * @return ここでの処理件数 392 * 393 * @throws RuntimeException Connection DB処理の実行に失敗した場合 394 */ 395 public static int execute( final String query , final List<String[]> list ) { 396 return execute( query,list,true ); // 互換性確保。true でエラー発生時に、Exception を throw する。 397 } 398 399 /** 400 * データ配列のListを渡して実際のDB処理を実行します。 401 * 402 * データ配列は、1行分のデータに対する設定値の配列です。 403 * これは、keys で指定した並び順と一致している必要があります。 404 * 405 * 処理の途中で整合性制約違反が発生した場合に継続するかどうかを指定できるフラグを追加しています。 406 * false に設定した場合は、エラーが発生しても処理を継続して、commit します。(7.4.1.0 (2021/04/23) ) 407 * 408 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加 409 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。 410 * 411 * @param query 実行するSQL文 412 * @param list ?に割り当てる設定値 413 * @param useErrro false に設定すると、途中で整合性制約違反が発生しても処理を継続する。 414 * @return ここでの処理件数 415 * 416 * @throws RuntimeException Connection DB処理の実行に失敗した場合 417 */ 418 public static int execute( final String query , final List<String[]> list , final boolean useErrro ) { 419 LOGGER.debug( () -> "execute query=" + query ); 420 421 String[] debugLine = null; 422 int execCnt = 0; 423 424 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 425 SQLException throwEx = null; 426 // try-with-resources 文 (AutoCloseable) 427 try( Connection conn = getConnection() ) { 428 // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。 429 try( PreparedStatement pstmt = conn.prepareStatement( query ) ) { // 更新系なので、setFetchSize は不要。 430 431 // ORACLE では、ParameterMetaDataは、使わない。 432 final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData(); 433 434 for( final String[] values : list ) { 435 debugLine = values; 436 LOGGER.debug( () -> "execute values=" + Arrays.toString( values ) ); 437 setObject( pstmt , values , pMeta ); 438 439 // 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。 440 try { 441 execCnt += pstmt.executeUpdate(); 442 } 443 catch( final SQLIntegrityConstraintViolationException ex ) { 444 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 445// if( useErrro ) { throw ex; } 446 if( useErrro ) { throwEx = ex; break; } 447 else { 448 // MSG0033 = 整合性制約違反が発生しました。\n\tメッセージ=[{0}]\n\tquery=[{1}]\n\tvalues={2} 449 MsgUtil.errPrintln( "MSG0033" , ex.getMessage() , query , Arrays.toString( debugLine ) ); 450 } 451 } 452 } 453 454 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 455// conn.commit(); 456 if( throwEx == null ) { conn.commit(); } 457 } 458 catch( final SQLException ex ) { 459 conn.rollback(); 460 conn.setAutoCommit(true); 461 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 462// throw ex; 463 throwEx = ex; 464 } 465 } 466 catch( final SQLException ex ) { 467// // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 468// throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) ); 469 // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 470 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 471// throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) ); 472 throwEx = ex; 473 } 474 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 475 // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally 476// finally { 477 if( throwEx != null ) { 478 // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 479 throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( debugLine ) ); 480 } 481// } 482 483 return execCnt; 484 } 485 486 /** 487 * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド) 488 * 489 * これは、updQueryで、更新してみて、0件の場合、insQuery で追加処理を行います。 490 * 491 * データ配列は、1行分のデータに対する設定値の配列です。 492 * これは、keys で指定した並び順と一致している必要があります。 493 * 494 * 処理の途中で整合性制約違反が発生した場合に継続するかどうかを指定できるフラグを追加しています。 495 * false に設定した場合は、エラーが発生しても処理を継続して、commit します。 (7.4.1.0 (2021/04/23) ) 496 * 497 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。 498 * 499 * @param insQuery 追加するSQL文 500 * @param updQuery 更新するSQL文 501 * @param insList ?に割り当てる設定値 502 * @param updList ?に割り当てる設定値 503 * @return ここでの処理件数 504 * 505 * @throws RuntimeException Connection DB処理の実行に失敗した場合 506 */ 507 public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList ) { 508 return execute( insQuery,updQuery,insList,updList,true ); // 互換性確保。true でエラー発生時に、Exception を throw する。 509 } 510 511 /** 512 * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド) 513 * 514 * これは、updQueryで、更新してみて、0件の場合、insQuery で追加処理を行います。 515 * 516 * データ配列は、1行分のデータに対する設定値の配列です。 517 * これは、keys で指定した並び順と一致している必要があります。 518 * 519 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加 520 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。 521 * 522 * @param insQuery 追加するSQL文 523 * @param updQuery 更新するSQL文 524 * @param insList ?に割り当てる設定値 525 * @param updList ?に割り当てる設定値 526 * @param useErrro false に設定すると、途中で整合性制約違反が発生しても処理を継続する。 527 * @return ここでの処理件数 528 * 529 * @throws RuntimeException Connection DB処理の実行に失敗した場合 530 */ 531 public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList , final boolean useErrro ) { 532 LOGGER.debug( () -> "execute insQuery=" + insQuery + " , updQuery=" + updQuery ); 533 534 String[] debugLine = null; 535 String query = null; 536 537 int execCnt = 0; 538 539 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 540 SQLException throwEx = null; 541 // try-with-resources 文 (AutoCloseable) 542 try( Connection conn = getConnection() ) { 543 // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。 544 try( PreparedStatement inPstmt = conn.prepareStatement( insQuery ); 545 PreparedStatement upPstmt = conn.prepareStatement( updQuery ) ) { 546 547 // ORACLE では、ParameterMetaDataは、使わない。 548 final ParameterMetaData inpMeta = oracleFlag ? null : inPstmt.getParameterMetaData(); 549 final ParameterMetaData uppMeta = oracleFlag ? null : upPstmt.getParameterMetaData(); 550 551 for( int i=0; i<updList.size(); i++ ) { // 更新処理と、挿入処理は、同じ数のListを用意する。 552 query = updQuery; 553 // 更新処理を行う。 554 final String[] upVals = updList.get(i); 555 debugLine = upVals; 556 setObject( upPstmt , upVals , uppMeta ); 557 558 int cnt = upPstmt.executeUpdate(); 559 560 if( cnt <= 0 ) { // 更新が無い、つまり、追加対象 561 query = insQuery; 562 // 挿入処理を行う。 563 final String[] inVals = insList.get(i); 564 debugLine = inVals; 565 setObject( inPstmt , inVals , inpMeta ); 566 567 LOGGER.debug( () -> "execute INSERT=" + Arrays.toString( inVals ) ); 568 569 // 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。 570 try { 571 cnt = inPstmt.executeUpdate(); 572 } 573 catch( final SQLIntegrityConstraintViolationException ex ) { 574 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 575// if( useErrro ) { throw ex; } 576 if( useErrro ) { throwEx = ex; break; } 577 else { 578 cnt = 0; // エラー時に設定されないと、-1 のままなので。 579 // MSG0033 = 整合性制約違反が発生しました。\n\tメッセージ=[{0}]\n\tquery=[{1}]\n\tvalues={2} 580 MsgUtil.errPrintln( "MSG0033" , ex.getMessage() , insQuery , Arrays.toString( debugLine ) ); 581 } 582 } 583 } 584 else { // 元々、このelse は必要ない。UPDATE は、先に処理済 585 LOGGER.debug( () -> "execute UPDATE=" + Arrays.toString( upVals ) ); 586 } 587 588 execCnt += cnt; 589 } 590 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 591// conn.commit(); 592 if( throwEx == null ) { conn.commit(); } 593 } 594 catch( final SQLException ex ) { 595 conn.rollback(); 596 conn.setAutoCommit(true); 597 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 598// throw ex; 599 throwEx = ex; 600 } 601 } 602 catch( final SQLException ex ) { 603// // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 604// throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) ); 605 // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 606 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 607// throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) ); 608 throwEx = ex; 609 } 610 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 611 // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally 612// finally { 613 if( throwEx != null ) { 614 // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 615 throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( debugLine ) ); 616 } 617// } 618 619 return execCnt; 620 } 621 622 /** 623 * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。 624 * 625 * ステートメントと引数により、Prepared クエリーの検索のみ実行します。 626 * 結果は、すべて文字列に変換されて格納されます。 627 * 628 * @param query ステートメント文字列 629 * @param args オブジェクトの引数配列 630 * 631 * @return 検索結果のリスト配列(結果が無ければ、サイズゼロのリスト) 632 * @throws RuntimeException DB検索処理の実行に失敗した場合 633 * @og.rtnNotNull 634 */ 635 public static List<String[]> dbQuery( final String query , final String... args ) { 636 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 637 SQLException throwEx = null; 638 // try-with-resources 文 (AutoCloseable) 639 try( Connection conn = getConnection() ) { 640 // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。 641 try( PreparedStatement pstmt = conn.prepareStatement( query ) ) { 642 // ORACLE では、ParameterMetaDataは、使わない。 643 final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData(); 644 // 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。 645 setObject( pstmt , args , pMeta ); 646 647 if( pstmt.execute() ) { 648 try( ResultSet resultSet = pstmt.getResultSet() ) { 649 return resultToArray( resultSet ); 650 } 651 } 652 conn.commit(); 653 } 654 catch ( final SQLException ex ) { 655 conn.rollback(); 656 conn.setAutoCommit(true); 657 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 658// throw ex; 659 throwEx = ex; 660 } 661 } 662 catch ( final SQLException ex ) { 663// // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 664// throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( args ) ); 665 // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 666 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 667// throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) ); 668 throwEx = ex; 669 } 670 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応 671 // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally (return で抜けない) 672// finally { 673 if( throwEx != null ) { 674 // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2} 675 throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( args ) ); 676 } 677// } 678 679 // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseDiamondOperator 対応 680// return new ArrayList<String[]>(); 681 return new ArrayList<>(); 682 } 683 684 /** 685 * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。 686 * 687 * ステートメントと引数により、Prepared クエリーの検索のみ実行します。 688 * 結果は、すべて文字列に変換されて格納されます。 689 * 690 * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionオブジェクト 対応 691 * 692 * @param tarn 外部から指定するTransactionオブジェクト 693 * @param query ステートメント文字列 694 * @param args オブジェクトの引数配列 695 * 696 * @return 検索結果のリスト配列(結果が無ければ、サイズゼロのリスト) 697 * @throws RuntimeException DB検索処理の実行に失敗した場合 698 * @og.rtnNotNull 699 */ 700 public static List<String[]> dbQuery( final Transaction tarn , final String query , final String... args ) { 701 final Connection conn = tarn.getConnection( null ); 702 703 // try-with-resources 文 (AutoCloseable) 704 // try( Connection conn = tarn.getConnection( null ) ) { 705 // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。 706 try( PreparedStatement pstmt = conn.prepareStatement( query ) ) { 707 // ORACLE では、ParameterMetaDataは、使わない。 708 final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData(); 709 // 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。 710 setObject( pstmt , args , pMeta ); 711 712 if( pstmt.execute() ) { 713 try( ResultSet resultSet = pstmt.getResultSet() ) { 714 return resultToArray( resultSet ); 715 } 716 } 717 tarn.commit(); 718 } 719 catch ( final SQLException ex ) { 720 tarn.rollback(); 721 // conn.setAutoCommit(true); 722 // throw ex; 723 // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 724 throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) ); 725 } 726 // } 727 // catch ( final SQLException ex ) { 728 // // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1} 729 // throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) ); 730 // } 731 732 // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseDiamondOperator 対応 733// return new ArrayList<String[]>(); 734 return new ArrayList<>(); 735 } 736 737 /** 738 * ResultSet より、結果の文字列配列を作成します。 739 * 740 * 結果は、すべて文字列に変換されて格納されます。 741 * 移動したメソッドで使われているのでこれも移動 742 * 743 * @param resultSet ResultSetオブジェクト 744 * 745 * @return ResultSetの検索結果リスト配列 746 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合 747 * @og.rtnNotNull 748 */ 749 public static List<String[]> resultToArray( final ResultSet resultSet ) throws SQLException { 750// final ArrayList<String[]> data = new ArrayList<>(); 751 final List<String[]> data = new ArrayList<>(); // 8.5.4.2 (2024/01/12) PMD 7.0.0 LooseCoupling 752 753 final ResultSetValue rsv = new ResultSetValue( resultSet ); 754 755 while( rsv.next() ) { 756 data.add( rsv.getValues() ); 757 } 758 759 return data; 760 } 761 762 /** 763 * データをインサートする場合に使用するSQL文を作成します。 764 * 765 * これは、key に対応した ? 文字列で、SQL文を作成します。 766 * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。 767 * conKeysとconValsは、固定値のキーと値です。 768 * conKeys,conVals がnullの場合は、これらの値を使用しません。 769 * 770 * @param table テーブルID 771 * @param keys 設定値に対応するキー配列 772 * @param conKeys 固定値の設定値に対応するキー配列 773 * @param conVals 固定値に対応する値配列 774 * @return インサートSQL 775 * @og.rtnNotNull 776 */ 777 public static String getInsertSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals ) { 778 final String[] vals = new String[keys.length]; 779 Arrays.fill( vals , "?" ); 780 781 final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ; 782 783 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 784 .append( "INSERT INTO " ).append( table ) 785 .append( " ( " ) 786 .append( String.join( "," , keys ) ); 787 788 if( useConst ) { 789 sql.append( ',' ).append( String.join( "," , conKeys ) ); 790 } 791 792 sql.append( " ) VALUES ( " ) 793 .append( String.join( "," , vals ) ); 794 795 if( useConst ) { 796 sql.append( ",'" ).append( String.join( "','" , conVals ) ).append( '\'' ); 797 } 798 799 return sql.append( " )" ).toString(); 800 } 801 802 /** 803 * データをアップデートする場合に使用するSQL文を作成します。 804 * 805 * これは、key に対応した ? 文字列で、SQL文を作成します。 806 * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。 807 * WHERE 文字列は、この、? も含めたWHERE条件の文字列を渡します。 808 * WHERE条件の場合は、この、?に、関数を設定したり、条件を指定したり、 809 * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が 810 * 限られるためです。 811 * conKeysとconValsは、固定値のキーと値です。 812 * conKeys,conVals,where がnullの場合は、これらの値を使用しません。 813 * 814 * @og.rev 7.2.5.0 (2020/06/01) UPDATEで、? を含むキーワードを、処理できるようにします。 815 * 816 * @param table テーブルID 817 * @param keys 設定値に対応するキー配列 818 * @param conKeys 固定値の設定値に対応するキー配列 819 * @param conVals 固定値に対応する値配列(VARCHARのみ) 820 * @param where WHERE条件式 821 * @return アップデートSQL 822 * @og.rtnNotNull 823 */ 824 public static String getUpdateSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals , final String where ) { 825 final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ; 826 827 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 828 .append( "UPDATE " ).append( table ).append( " SET " ); 829// .append( String.join( " = ? ," , keys ) ) // key[0] = ? , ・・・ = ? , key[n-1] という文字列が作成されます。 830// .append( " = ? " ); // 最後の key[n-1] の後ろに = ? という文字列を追加します。 831 for( final String key : keys ) { 832 sql.append( key ); 833 if( ! key.contains( "?" ) ) { 834 sql.append( " = ? " ); // key = ? という文字列が作成されます。 835 } 836 sql.append( ',' ); 837 } 838 sql.deleteCharAt( sql.length() - 1 ); // 最後の一文字(,)を削除します。 839 840 if( useConst ) { 841 for( int i=0; i<conKeys.length; i++ ) { 842 sql.append( ',' ).append( conKeys[i] ).append( " = '" ).append( conVals[i] ).append( "' " ); 843 } 844 } 845 846 if( where != null && !where.isEmpty() ) { 847 sql.append( " WHERE " ).append( where ); // WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。 848 } 849 850 return sql.toString(); 851 } 852 853 /** 854 * データをデリートする場合に使用するSQL文を作成します。 855 * 856 * これは、key に対応した ? 文字列で、SQL文を作成します。 857 * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。 858 * WHERE 文字列は、この、? も含めたWHERE条件の文字列を渡します。 859 * WHERE条件の場合は、この、?に、関数を設定したり、条件を指定したり、 860 * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が 861 * 限られるためです。 862 * 863 * @param table テーブルID 864 * @param where 設定値に対応するキー配列(可変長引数) 865 * @return デリートSQL 866 * @og.rtnNotNull 867 */ 868 public static String getDeleteSQL( final String table , final String where ) { 869 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 870 .append( "DELETE FROM " ).append( table ); 871 872 if( where != null && !where.isEmpty() ) { 873 sql.append( " WHERE " ).append( where ); // WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。 874 } 875 876 return sql.toString(); 877 } 878}