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.util.StringUtil; 019import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 020import org.opengion.fukurou.system.Closer; 021import org.opengion.fukurou.model.Formatter; 022import org.opengion.fukurou.model.ArrayDataModel; 023import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 024import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 025 026import java.sql.Connection; 027import java.sql.PreparedStatement; 028import java.sql.ParameterMetaData; 029import java.sql.SQLException; 030 031import java.util.Arrays; 032 033/** 034 * DBTableModel インターフェースを継承した TableModel の実装クラスです。 035 * sql文を execute( query ) する事により,データベースを検索した結果を 036 * DBTableModel に割り当てます。 037 * 038 * メソッドを宣言しています 039 * DBTableModel インターフェースは,データベースの検索結果(Resultset)をラップする 040 * インターフェースとして使用して下さい。 041 * 042 * @og.rev 5.2.2.0 (2010/11/01) パッケージ移動(hayabusa.db ⇒ fukurou.db) 043 * @og.group DB/Shell制御 044 * 045 * @version 4.0 046 * @author Kazuhiko Hasegawa 047 * @since JDK5.0, 048 */ 049public class DBSimpleTable { 050 051 private final String[] names ; // データ配列に対応するカラム配列(names) 052 private String[] keys ; // 登録に使用するカラムキー配列(keys) 053 private int[] keysNo ; // 登録に使用するカラムキー配列番号 054 private String table ; // 登録テーブル名 055 private String where ; // where 条件式[カラム名]を含む 056 private int[] whereNo ; // [カラム名]に対応するデータ配列番号 057 private String[] constrain ; // key に対応した制約条件 058 059 private String connID ; // 登録に使用するコネクションID 060 private boolean useWhere ; // where が設定されると true にセットされます。 061 062 private Connection conn ; 063 private PreparedStatement pstmt ; 064 private ParameterMetaData pMeta ; // 5.1.2.0 (2010/01/01) setObject に、Type を渡す。(PostgreSQL対応) 065 private String query ; // エラーメッセージ用の変数 066 private int execCnt ; 067 private ApplicationInfo appInfo ; // 3.8.7.0 (2006/12/15) 068 private boolean useParamMetaData; // 5.1.2.0 (2010/01/01) setObject に、Type を渡す。(PostgreSQL対応) 069 070 /** 071 * データ配列のカラム名称配列を指定してオブジェクトを構築します。 072 * 073 * @param nm カラム名称配列 074 * @throws RuntimeException tbl が null の場合 075 */ 076 public DBSimpleTable( final String[] nm ) { 077 if( nm == null ) { 078 final String errMsg = "データ配列のカラム名称に null は設定できません。"; 079 throw new OgRuntimeException( errMsg ); 080 } 081 082 names = new String[nm.length]; 083 System.arraycopy( nm,0,names,0,names.length ); 084 } 085 086 /** 087 * 登録に使用するカラムキー配列(keys)を登録します。 088 * 089 * 引数のkey配列が null の場合は、names と同じカラム名称配列(names)が使用されます。 090 * キー配列(keys)は、一度しか登録できません。また、addConstrain等のメソッド 091 * 呼び出しを先に実行すると、カラム名称配列(names)が設定されてしまう為、 092 * その後にこのメソッドを呼び出すとエラーが発生します。 093 * 094 * @param key 登録カラム名称配列(可変長引数) 095 * @see #addConstrain( String ,String ) 096 * @throws RuntimeException すでに キー配列(keys)が登録済み/作成済みの場合 097 */ 098 public void setKeys( final String... key ) { 099 if( keys != null ) { 100 final String errMsg = "すでに キー配列(keys)が登録済みです。"; 101 throw new OgRuntimeException( errMsg ); 102 } 103 104 if( key != null && key.length > 0 ) { // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。 105 final int size = key.length; 106 keys = new String[size]; 107 System.arraycopy( key,0,keys,0,size ); 108 109 constrain = new String[size]; 110 Arrays.fill( constrain,"?" ); 111 112 keysNo = new int[size]; 113 for( int i=0; i<size; i++ ) { 114 final int address = findAddress( names,keys[i] ); 115 if( address >= 0 ) { 116 keysNo[i] = address; 117 } 118 else { 119 final String errMsg = "指定の key は、カラム配列(names)に存在しません" 120 + " key[" + i + "]=" + key[i] 121 + " names=" + StringUtil.array2csv( names ) ; 122 throw new OgRuntimeException( errMsg ); 123 } 124 } 125 } 126 } 127 128 /** 129 * カラム名称配列(names)と同じキー配列(keys)を作成します。 130 * 131 * これは、キー配列(keys) が作成されなかった場合の処理です。 132 * keys が null の場合のみ、処理を実行します。 133 * 134 * @see #setKeys( String[] ) 135 */ 136 private void makeKeys() { 137 // キー配列(keys) が未設定の場合は、カラム名称配列(names)が設定されます。 138 if( keys == null ) { 139 keys = names; 140 final int size = keys.length; 141 142 constrain = new String[size]; 143 Arrays.fill( constrain,"?" ); 144 145 keysNo = new int[size]; 146 for( int i=0; i<size; i++ ) { 147 keysNo[i] = i; 148 } 149 } 150 } 151 152 /** 153 * Insert/Update/Delete 時の登録するテーブル名。 154 * 155 * @param tbl テーブル名 156 * @throws RuntimeException tbl が null の場合 157 */ 158 public void setTable( final String tbl ) { 159 if( tbl == null ) { 160 final String errMsg = "table に null は設定できません。"; // 5.1.8.0 (2010/07/01) errMsg 修正 161 throw new OgRuntimeException( errMsg ); 162 } 163 164 table = tbl; 165 } 166 167 /** 168 * データベースの接続先IDを設定します。 169 * 170 * @param conn 接続先ID 171 */ 172 public void setConnectionID( final String conn ) { 173 connID = conn; 174 } 175 176 /** 177 * アクセスログ取得の為,ApplicationInfoオブジェクトを設定します。 178 * 179 * @og.rev 3.8.7.0 (2006/12/15) 新規追加 180 * 181 * @param appInfo アプリ情報オブジェクト 182 */ 183 public void setApplicationInfo( final ApplicationInfo appInfo ) { 184 this.appInfo = appInfo; 185 } 186 187 /** 188 * Insert/Update/Delete 時の PreparedStatement の引数(クエスチョンマーク)制約。 189 * 190 * 制約条件(val)は、そのまま引数に使用されます。通常、? で表される 191 * パラメータに、文字長を制限する場合、SUBSTRB( ?,1,100 ) という 192 * val 変数を与えます。 193 * また、キー一つに対して、値を複数登録したい場合にも、使用できます。 194 * 例えば、NVAL( ?,? ) のような場合、キー一つに値2つを割り当てます。 195 * 値配列の並び順は、キー配列(keys)に対する(?の個数)に対応します。 196 * 注意:カラム名称配列(names)ではありません。また、先にキー配列(keys)を登録 197 * しておかないと、キー配列登録時にエラーが発生します。 198 * 制約条件は、処理するQUERYに対して適用されますので、 199 * key または、val が null の場合は、RuntimeException を Throwします。 200 * 201 * @param key 制約をかけるキー 202 * @param val 制約条件式 203 * @see #setKeys( String[] ) 204 * @throws RuntimeException key または、val が null の場合 205 */ 206 public void addConstrain( final String key,final String val ) { 207 if( key == null || val == null ) { 208 final String errMsg = "key または、val に null は設定できません。" 209 + " key=[" + key + "] , val=[" + val + "]" ; 210 throw new OgRuntimeException( errMsg ); 211 } 212 213 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。 214 if( keys == null ) { makeKeys(); } 215 216 // 制約条件のアドレスは、カラム名称配列(names)でなく、キー配列(keys)を使用します。 217 final int address = findAddress( keys,key ); 218 if( address >= 0 ) { 219 constrain[address] = val; 220 } 221 else { 222 final String errMsg = "指定の key は、キー配列(keys)に存在しません" 223 + " key=[" + key + "] , val=[" + val + "]" 224 + " keys=" + StringUtil.array2csv( keys ) ; 225 throw new OgRuntimeException( errMsg ); 226 } 227 } 228 229 /** 230 * Update/Delete 時のキーとなるWHERE 条件のカラム名を設定します。 231 * 232 * 通常の WHERE 句の書き方と同じで、カラム配列(names)に対応する設定値(values)の値を 233 * 割り当てたい箇所に[カラム名] を記述します。文字列の場合、設定値をセットする 234 * ときに、シングルコーテーションを使用しますが、[カラム名]で指定する場合は、 235 * その前後に、(')シングルコーテーションは、不要です。 236 * WHERE条件は、登録に使用するキー配列(keys)に現れない条件で行を特定することがありますので 237 * カラム名称配列(names)を元にカラム名のアドレスを求めます。 238 * [カラム名]は、? に置き換えて、PreparedStatement として、実行される形式に変換されます。 239 * 例:FGJ='1' and CLM=[CLM] and SYSTEM_ID in ([SYSID],'**') 240 * 241 * @og.rev 4.3.4.0 (2008/12/01) キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てる 242 * @og.rev 5.0.2.0 (2009/11/01) バグ修正(keysはデータセットのキーなので、where句のカラムに含まれて入いるわけではない) 243 * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。 244 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。 245 * @og.rev 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw するかどうかを指定可能にする。 246 * 247 * @param wh WHERE条件のカラム名 248 * @throws RuntimeException [カラム名]がカラム配列(names)に存在しない場合 249 */ 250 public void setWhere( final String wh ) { 251 252 // 6.4.1.2 (2016/01/22) PMD refactoring. 253 if( wh == null || wh.isEmpty() ) { 254 useWhere= false; 255 } 256 else { 257 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。 258 // 5.0.2.0 (2009/11/01) 259// final ArrayDataModel data = new ArrayDataModel( names ); 260 final ArrayDataModel data = new ArrayDataModel( names,true ); // 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw する 261 final Formatter format = new Formatter( data,wh ); // 6.4.3.4 (2016/03/11) 262 where = format.getQueryFormatString(); 263 whereNo = format.getClmNos(); 264 useWhere= true; 265 } 266 } 267 268 /** 269 * データをインサートする場合に使用するSQL文を作成します。 270 * 271 * @og.rev 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。 272 * 273 * @return インサートSQL 274 * @og.rtnNotNull 275 */ 276 private String getInsertSQL() { 277 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。 278 if( keys == null ) { makeKeys(); } 279 280 // 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。 281 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 282 .append( "INSERT INTO " ).append( table ) 283 .append( " ( " ) 284 .append( String.join( "," , keys ) ) // 6.2.3.0 (2015/05/01) 285 .append( " ) VALUES ( " ) 286 .append( String.join( "," , constrain ) ) // 6.2.3.0 (2015/05/01) 287 .append( " )" ); 288 289 useWhere = false; 290 291 return sql.toString(); 292 } 293 294 /** 295 * データをアップデートする場合に使用するSQL文を作成します。 296 * 297 * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。 298 * 299 * @return アップデートSQL 300 * @og.rtnNotNull 301 */ 302 private String getUpdateSQL() { 303 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。 304 if( keys == null ) { makeKeys(); } 305 306 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 307 .append( "UPDATE " ).append( table ).append( " SET " ) 308 .append( keys[0] ).append( " = " ).append( constrain[0] ); 309 310 for( int i=1; i<keys.length; i++ ) { 311 sql.append( " , " ) 312 .append( keys[i] ).append( " = " ).append( constrain[i] ); 313 } 314 315 // 6.4.1.2 (2016/01/22) PMD refactoring. 316 if( useWhere ) { 317 sql.append( " WHERE " ).append( where ); 318 } 319 320 return sql.toString(); 321 } 322 323 /** 324 * データをデリートする場合に使用するSQL文を作成します。 325 * 326 * @og.rev 5.0.2.0 (2009/11/01) バグ修正(削除時はkeysは必要ない) 327 * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。 328 * 329 * @return デリートSQL 330 * @og.rtnNotNull 331 */ 332 private String getDeleteSQL() { 333 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。 334 // 5.0.2.0 (2009/11/01) 335 keys = new String[0]; 336 337 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 338 .append( "DELETE FROM " ).append( table ); 339 340 // 6.4.1.2 (2016/01/22) PMD refactoring. 341 if( useWhere ) { 342 sql.append( " WHERE " ).append( where ); 343 } 344 345 return sql.toString(); 346 } 347 348 /** 349 * Insert 処理の開始を宣言します。 350 * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。 351 * このメソッドと、close() メソッドは必ずセットで処理してください。 352 * 353 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定 354 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 355 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応) 356 * 357 * @throws SQLException Connection のオープンに失敗した場合 358 */ 359 public void startInsert() throws SQLException { 360 execCnt = 0; 361 query = getInsertSQL(); 362 conn = ConnectionFactory.connection( connID,appInfo ); 363 pstmt = conn.prepareStatement( query ); 364 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 365 useParamMetaData = ConnectionFactory.useParameterMetaData( connID ); // 5.3.8.0 (2011/08/01) 366 if( useParamMetaData ) { 367 pMeta = pstmt.getParameterMetaData(); 368 } 369 } 370 371 /** 372 * Update 処理の開始を宣言します。 373 * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。 374 * このメソッドと、close() メソッドは必ずセットで処理してください。 375 * 376 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定 377 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 378 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応) 379 * 380 * @throws SQLException Connection のオープンに失敗した場合 381 */ 382 public void startUpdate() throws SQLException { 383 execCnt = 0; 384 query = getUpdateSQL(); 385 conn = ConnectionFactory.connection( connID,appInfo ); 386 pstmt = conn.prepareStatement( query ); 387 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 388 useParamMetaData = ConnectionFactory.useParameterMetaData( connID ); // 5.3.8.0 (2011/08/01) 389 if( useParamMetaData ) { 390 pMeta = pstmt.getParameterMetaData(); 391 } 392 } 393 394 /** 395 * Delete 処理の開始を宣言します。 396 * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。 397 * このメソッドと、close() メソッドは必ずセットで処理してください。 398 * 399 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定 400 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 401 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応) 402 * 403 * @throws SQLException Connection のオープンに失敗した場合 404 */ 405 public void startDelete() throws SQLException { 406 execCnt = 0; 407 query = getDeleteSQL(); 408 conn = ConnectionFactory.connection( connID,appInfo ); 409 pstmt = conn.prepareStatement( query ); 410 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 411 useParamMetaData = ConnectionFactory.useParameterMetaData( connID ); // 5.3.8.0 (2011/08/01) 412 if( useParamMetaData ) { 413 pMeta = pstmt.getParameterMetaData(); 414 } 415 } 416 417 /** 418 * データ配列を渡して実際のDB処理を実行します。 419 * 420 * この処理の前に、startXXXX をコールしておき、INSER,UPDATE,DELETEのどの 421 * 処理を行うか、宣言しておく必要があります。 422 * 戻り値は、この処理での処理件数です。 423 * 最終件数は、close( boolean ) 時に取得します。 424 * 425 * @og.rev 4.0.0.0 (2007/11/28) SQLException をきちんと伝播させます。 426 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 427 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData 時の setNull 対応(PostgreSQL対応) 428 * 429 * @param values カラム配列(names)に対応する設定値配列(可変長引数) 430 * 431 * @return ここでの処理件数 432 * 433 * @see #close( boolean ) 434 * @throws SQLException Connection のクロースに失敗した場合 435 * @throws RuntimeException Connection DB処理の実行に失敗した場合 436 */ 437 public int execute( final String... values ) throws SQLException { 438 final int cnt; 439 try { 440 int clmNo = 1; // JDBC のカラム番号は、1から始まる。 441 442 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応) 443 if( useParamMetaData ) { 444 // keys に値を割り当てます。 445 for( int i=0; i<keys.length; i++ ) { 446 final int type = pMeta.getParameterType( clmNo ); 447 // 5.3.8.0 (2011/08/01) setNull 対応 448 final String val = values[keysNo[i]]; 449 if( val == null || val.isEmpty() ) { 450 pstmt.setNull( clmNo++, type ); 451 } 452 else { 453 pstmt.setObject( clmNo++,val,type ); 454 } 455 } 456 457 // where 条件を使用する場合は、値を割り当てます。 458 if( useWhere ) { 459 for( int i=0; i<whereNo.length; i++ ) { 460 final int type = pMeta.getParameterType( clmNo ); 461 // 5.3.8.0 (2011/08/01) setNull 対応 462 final String val = values[whereNo[i]]; 463 if( val == null || val.isEmpty() ) { 464 pstmt.setNull( clmNo++, type ); 465 } 466 else { 467 pstmt.setObject( clmNo++,val,type ); 468 } 469 } 470 } 471 } 472 else { 473 // keys に値を割り当てます。 474 for( int i=0; i<keys.length; i++ ) { 475 pstmt.setObject( clmNo++,values[keysNo[i]] ); 476 } 477 478 // where 条件を使用する場合は、値を割り当てます。 479 if( useWhere ) { 480 for( int i=0; i<whereNo.length; i++ ) { 481 pstmt.setObject( clmNo++,values[whereNo[i]] ); 482 } 483 } 484 } 485 486 cnt = pstmt.executeUpdate(); 487 execCnt += cnt; 488 } 489 catch( final SQLException ex) { 490 Closer.stmtClose( pstmt ); 491 pMeta = null; // 5.1.2.0 (2010/01/01) 492 if( conn != null ) { 493 conn.rollback(); 494 ConnectionFactory.remove( conn,connID ); 495 conn = null; 496 } 497 final String errMsg = "DB処理の実行に失敗しました。" + CR 498 + " query=[" + query + "]" + CR 499 + " values=" + StringUtil.array2csv( values ); 500 throw new OgRuntimeException( errMsg ,ex ); 501 } 502 return cnt; 503 } 504 505 /** 506 * DB処理をクロースします。 507 * 508 * 引数には、commit させる場合は、true を、rollback させる場合は、false をセットします。 509 * 戻り値は、今まで処理された合計データ件数です。 510 * この処理は、SQLException を内部で RuntimeException に変換している為、catch 節は 511 * 不要ですが、必ず finally 節で呼び出してください。そうしないと、リソースリークの 512 * 原因になります。 513 * 514 * @og.rev 5.1.2.0 (2010/01/01) pMeta のクリア 515 * 516 * @param commitFlag コミットフラグ [true:commitする/false:rollbacする] 517 * 518 * @return 今までの合計処理件数 519 */ 520 public int close( final boolean commitFlag ) { 521 try { 522// if( conn != null ) { // 8.1.1.2 (2022/02/25) Modify 523 if( conn != null && !conn.isClosed() ) { 524 if( commitFlag ) { conn.commit(); } 525 else { conn.rollback(); } 526 } 527 } 528 catch( final SQLException ex) { 529 ConnectionFactory.remove( conn,connID ); 530 conn = null; 531 final String errMsg = "DB処理を確定(COMMIT)できませんでした。" + CR 532 + " query=[" + query + "]" + CR ; 533 throw new OgRuntimeException( errMsg,ex ); 534 } 535 finally { 536 Closer.stmtClose( pstmt ); 537 pMeta = null; // 5.1.2.0 (2010/01/01) 538 ConnectionFactory.close( conn,connID ); 539 conn = null; 540 } 541 542 return execCnt; 543 } 544 545 /** 546 * 文字列配列中の値とマッチするアドレスを検索します。 547 * 文字列配列がソートされていない為、バイナリサーチが使えません。よって、 548 * 総当りでループ検索しています。 549 * 総数が多い場合は、遅くなる為、マップにセットして使用することを検討ください。 550 * 551 * @param data ターゲットの文字列配列中 552 * @param key 検索する文字列 553 * 554 * @return ターゲットの添え字(存在しない場合は、-1) 555 */ 556 private int findAddress( final String[] data,final String key ) { 557 int address = -1; 558 if( data != null && key != null ) { 559 for( int i=0; i<data.length; i++ ) { 560 if( key.equalsIgnoreCase( data[i] ) ) { 561 address = i; 562 break; 563 } 564 } 565 } 566 return address; 567 } 568}