2 * Copyright (c) 2009 The openGion Project.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 * either express or implied. See the License for the specific language
14 * governing permissions and limitations under the License.
16 package org.opengion.fukurou.db;
18 import org.opengion.fukurou.util.StringUtil;
19 import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29)
20 import org.opengion.fukurou.system.Closer;
21 import org.opengion.fukurou.model.Formatter;
22 import org.opengion.fukurou.model.ArrayDataModel;
23 import org.opengion.fukurou.model.DataModel; // 8.2.1.0 (2022/07/15)
24 import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring
25 import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring
27 import java.sql.Connection;
28 import java.sql.PreparedStatement;
29 import java.sql.ParameterMetaData;
30 import java.sql.SQLException;
32 import java.util.Arrays;
35 * DBTableModel インターフェースを継承した TableModel の実装クラスです。
36 * sql文を execute( query ) する事により、データベースを検索した結果を
37 * DBTableModel に割り当てます。
40 * DBTableModel インターフェースは、データベースの検索結果(Resultset)をラップする
43 * @og.rev 5.2.2.0 (2010/11/01) パッケージ移動(hayabusa.db ⇒ fukurou.db)
44 * @og.group DB/Shell制御
47 * @author Kazuhiko Hasegawa
50 public class DBSimpleTable {
52 private final String[] names ; // データ配列に対応するカラム配列(names)
53 private String[] keys ; // 登録に使用するカラムキー配列(keys)
54 private int[] keysNo ; // 登録に使用するカラムキー配列番号
55 private String table ; // 登録テーブル名
56 private String where ; // where 条件式[カラム名]を含む
57 private int[] whereNo ; // [カラム名]に対応するデータ配列番号
58 private String[] constrain ; // key に対応した制約条件
60 private String connID ; // 登録に使用するコネクションID
61 private boolean useWhere ; // where が設定されると true にセットされます。
63 private Connection conn ;
64 private PreparedStatement pstmt ;
65 private ParameterMetaData pMeta ; // 5.1.2.0 (2010/01/01) setObject に、Type を渡す。(PostgreSQL対応)
66 private String query ; // エラーメッセージ用の変数
68 private ApplicationInfo appInfo ; // 3.8.7.0 (2006/12/15)
69 private boolean useParamMetaData; // 5.1.2.0 (2010/01/01) setObject に、Type を渡す。(PostgreSQL対応)
72 * データ配列のカラム名称配列を指定してオブジェクトを構築します。
75 * @throws RuntimeException tbl が null の場合
77 public DBSimpleTable( final String[] nm ) {
79 final String errMsg = "データ配列のカラム名称に null は設定できません。";
80 throw new OgRuntimeException( errMsg );
83 names = new String[nm.length];
84 System.arraycopy( nm,0,names,0,names.length );
88 * 登録に使用するカラムキー配列(keys)を登録します。
90 * 引数のkey配列が null の場合は、names と同じカラム名称配列(names)が使用されます。
91 * キー配列(keys)は、一度しか登録できません。また、addConstrain等のメソッド
92 * 呼び出しを先に実行すると、カラム名称配列(names)が設定されてしまう為、
93 * その後にこのメソッドを呼び出すとエラーが発生します。
95 * @param key 登録カラム名称配列(可変長引数)
96 * @see #addConstrain( String ,String )
97 * @throws RuntimeException すでに キー配列(keys)が登録済み/作成済みの場合
99 public void setKeys( final String... key ) {
101 final String errMsg = "すでに キー配列(keys)が登録済みです。";
102 throw new OgRuntimeException( errMsg );
105 if( key != null && key.length > 0 ) { // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
106 final int size = key.length;
107 keys = new String[size];
108 System.arraycopy( key,0,keys,0,size );
110 constrain = new String[size];
111 Arrays.fill( constrain,"?" );
113 keysNo = new int[size];
114 for( int i=0; i<size; i++ ) {
115 final int address = findAddress( names,keys[i] );
120 final String errMsg = "指定の key は、カラム配列(names)に存在しません"
121 + " key[" + i + "]=" + key[i]
122 + " names=" + StringUtil.array2csv( names ) ;
123 throw new OgRuntimeException( errMsg );
130 * カラム名称配列(names)と同じキー配列(keys)を作成します。
132 * これは、キー配列(keys) が作成されなかった場合の処理です。
133 * keys が null の場合のみ、処理を実行します。
135 * @see #setKeys( String[] )
137 private void makeKeys() {
138 // キー配列(keys) が未設定の場合は、カラム名称配列(names)が設定されます。
141 final int size = keys.length;
143 constrain = new String[size];
144 Arrays.fill( constrain,"?" );
146 keysNo = new int[size];
147 for( int i=0; i<size; i++ ) {
154 * Insert/Update/Delete 時の登録するテーブル名。
157 * @throws RuntimeException tbl が null の場合
159 public void setTable( final String tbl ) {
161 final String errMsg = "table に null は設定できません。"; // 5.1.8.0 (2010/07/01) errMsg 修正
162 throw new OgRuntimeException( errMsg );
169 * データベースの接続先IDを設定します。
173 public void setConnectionID( final String conn ) {
178 * アクセスログ取得の為、ApplicationInfoオブジェクトを設定します。
180 * @og.rev 3.8.7.0 (2006/12/15) 新規追加
182 * @param appInfo アプリ情報オブジェクト
184 public void setApplicationInfo( final ApplicationInfo appInfo ) {
185 this.appInfo = appInfo;
189 * Insert/Update/Delete 時の PreparedStatement の引数(クエスチョンマーク)制約。
191 * 制約条件(val)は、そのまま引数に使用されます。通常、? で表される
192 * パラメータに、文字長を制限する場合、SUBSTRB( ?,1,100 ) という
194 * また、キー一つに対して、値を複数登録したい場合にも、使用できます。
195 * 例えば、NVAL( ?,? ) のような場合、キー一つに値2つを割り当てます。
196 * 値配列の並び順は、キー配列(keys)に対する(?の個数)に対応します。
197 * 注意:カラム名称配列(names)ではありません。また、先にキー配列(keys)を登録
198 * しておかないと、キー配列登録時にエラーが発生します。
199 * 制約条件は、処理するQUERYに対して適用されますので、
200 * key または、val が null の場合は、RuntimeException を Throwします。
202 * @param key 制約をかけるキー
204 * @see #setKeys( String[] )
205 * @throws RuntimeException key または、val が null の場合
207 public void addConstrain( final String key,final String val ) {
208 if( key == null || val == null ) {
209 final String errMsg = "key または、val に null は設定できません。"
210 + " key=[" + key + "] , val=[" + val + "]" ;
211 throw new OgRuntimeException( errMsg );
214 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
215 if( keys == null ) { makeKeys(); }
217 // 制約条件のアドレスは、カラム名称配列(names)でなく、キー配列(keys)を使用します。
218 final int address = findAddress( keys,key );
220 constrain[address] = val;
223 final String errMsg = "指定の key は、キー配列(keys)に存在しません"
224 + " key=[" + key + "] , val=[" + val + "]"
225 + " keys=" + StringUtil.array2csv( keys ) ;
226 throw new OgRuntimeException( errMsg );
231 * Update/Delete 時のキーとなるWHERE 条件のカラム名を設定します。
233 * 通常の WHERE 句の書き方と同じで、カラム配列(names)に対応する設定値(values)の値を
234 * 割り当てたい箇所に[カラム名] を記述します。文字列の場合、設定値をセットする
235 * ときに、シングルコーテーションを使用しますが、[カラム名]で指定する場合は、
236 * その前後に、(')シングルコーテーションは、不要です。
237 * WHERE条件は、登録に使用するキー配列(keys)に現れない条件で行を特定することがありますので
238 * カラム名称配列(names)を元にカラム名のアドレスを求めます。
239 * [カラム名]は、? に置き換えて、PreparedStatement として、実行される形式に変換されます。
240 * 例:FGJ='1' and CLM=[CLM] and SYSTEM_ID in ([SYSID],'**')
242 * @og.rev 4.3.4.0 (2008/12/01) キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てる
243 * @og.rev 5.0.2.0 (2009/11/01) バグ修正(keysはデータセットのキーなので、where句のカラムに含まれて入いるわけではない)
244 * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。
245 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
246 * @og.rev 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw するかどうかを指定可能にする。
248 * @param wh WHERE条件のカラム名
249 * @throws RuntimeException [カラム名]がカラム配列(names)に存在しない場合
251 public void setWhere( final String wh ) {
253 // 6.4.1.2 (2016/01/22) PMD refactoring.
254 if( wh == null || wh.isEmpty() ) {
258 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
259 // 5.0.2.0 (2009/11/01)
260 // final ArrayDataModel data = new ArrayDataModel( names );
261 // final ArrayDataModel data = new ArrayDataModel( names,true ); // 6.9.5.0 (2018/04/23) カラム名が存在しない場合に、Exception を throw する
262 final DataModel<String> data = new ArrayDataModel( names,true ); // 8.2.1.0 (2022/07/15)
263 final Formatter format = new Formatter( data,wh ); // 6.4.3.4 (2016/03/11)
264 where = format.getQueryFormatString();
265 whereNo = format.getClmNos();
271 * データをインサートする場合に使用するSQL文を作成します。
273 * @og.rev 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。
278 private String getInsertSQL() {
279 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
280 if( keys == null ) { makeKeys(); }
282 // 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。
283 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
284 .append( "INSERT INTO " ).append( table )
286 .append( String.join( "," , keys ) ) // 6.2.3.0 (2015/05/01)
287 .append( " ) VALUES ( " )
288 .append( String.join( "," , constrain ) ) // 6.2.3.0 (2015/05/01)
293 return sql.toString();
297 * データをアップデートする場合に使用するSQL文を作成します。
299 * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。
304 private String getUpdateSQL() {
305 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
306 if( keys == null ) { makeKeys(); }
308 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
309 .append( "UPDATE " ).append( table ).append( " SET " )
310 .append( keys[0] ).append( " = " ).append( constrain[0] );
312 for( int i=1; i<keys.length; i++ ) {
314 .append( keys[i] ).append( " = " ).append( constrain[i] );
317 // 6.4.1.2 (2016/01/22) PMD refactoring.
319 sql.append( " WHERE " ).append( where );
322 return sql.toString();
326 * データをデリートする場合に使用するSQL文を作成します。
328 * @og.rev 5.0.2.0 (2009/11/01) バグ修正(削除時はkeysは必要ない)
329 * @og.rev 6.4.1.2 (2016/01/22) PMD refactoring. where は、null をセットするのではなく、useWhere の設定で判定する。
334 private String getDeleteSQL() {
335 // キー配列(keys)が未設定(null)の場合は、カラム名称配列(names)を割り当てます。
336 // 5.0.2.0 (2009/11/01)
337 keys = new String[0];
339 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
340 .append( "DELETE FROM " ).append( table );
342 // 6.4.1.2 (2016/01/22) PMD refactoring.
344 sql.append( " WHERE " ).append( where );
347 return sql.toString();
351 * Insert 処理の開始を宣言します。
352 * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。
353 * このメソッドと、close() メソッドは必ずセットで処理してください。
355 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定
356 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
357 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
359 * @throws SQLException Connection のオープンに失敗した場合
361 public void startInsert() throws SQLException {
363 query = getInsertSQL();
364 conn = ConnectionFactory.connection( connID,appInfo );
365 pstmt = conn.prepareStatement( query );
366 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
367 useParamMetaData = ConnectionFactory.useParameterMetaData( connID ); // 5.3.8.0 (2011/08/01)
368 if( useParamMetaData ) {
369 pMeta = pstmt.getParameterMetaData();
374 * Update 処理の開始を宣言します。
375 * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。
376 * このメソッドと、close() メソッドは必ずセットで処理してください。
378 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定
379 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
380 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
382 * @throws SQLException Connection のオープンに失敗した場合
384 public void startUpdate() throws SQLException {
386 query = getUpdateSQL();
387 conn = ConnectionFactory.connection( connID,appInfo );
388 pstmt = conn.prepareStatement( query );
389 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
390 useParamMetaData = ConnectionFactory.useParameterMetaData( connID ); // 5.3.8.0 (2011/08/01)
391 if( useParamMetaData ) {
392 pMeta = pstmt.getParameterMetaData();
397 * Delete 処理の開始を宣言します。
398 * 内部的に、コネクションを接続して、PreparedStatementオブジェクトを作成します。
399 * このメソッドと、close() メソッドは必ずセットで処理してください。
401 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為、ApplicationInfoオブジェクトを設定
402 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
403 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)
405 * @throws SQLException Connection のオープンに失敗した場合
407 public void startDelete() throws SQLException {
409 query = getDeleteSQL();
410 conn = ConnectionFactory.connection( connID,appInfo );
411 pstmt = conn.prepareStatement( query );
412 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
413 useParamMetaData = ConnectionFactory.useParameterMetaData( connID ); // 5.3.8.0 (2011/08/01)
414 if( useParamMetaData ) {
415 pMeta = pstmt.getParameterMetaData();
420 * データ配列を渡して実際のDB処理を実行します。
422 * この処理の前に、startXXXX をコールしておき、INSER,UPDATE,DELETEのどの
423 * 処理を行うか、宣言しておく必要があります。
425 * 最終件数は、close( boolean ) 時に取得します。
427 * @og.rev 4.0.0.0 (2007/11/28) SQLException をきちんと伝播させます。
428 * @og.rev 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
429 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData 時の setNull 対応(PostgreSQL対応)
431 * @param values カラム配列(names)に対応する設定値配列(可変長引数)
435 * @see #close( boolean )
436 * @throws SQLException Connection のクロースに失敗した場合
437 * @throws RuntimeException Connection DB処理の実行に失敗した場合
439 public int execute( final String... values ) throws SQLException {
442 int clmNo = 1; // JDBC のカラム番号は、1から始まる。
444 // 5.1.2.0 (2010/01/01) setObject に ParameterMetaData の getParameterType を渡す。(PostgreSQL対応)
445 if( useParamMetaData ) {
447 for( int i=0; i<keys.length; i++ ) {
448 final int type = pMeta.getParameterType( clmNo );
449 // 5.3.8.0 (2011/08/01) setNull 対応
450 final String val = values[keysNo[i]];
451 if( val == null || val.isEmpty() ) {
452 pstmt.setNull( clmNo++, type );
455 pstmt.setObject( clmNo++,val,type );
459 // where 条件を使用する場合は、値を割り当てます。
461 for( int i=0; i<whereNo.length; i++ ) {
462 final int type = pMeta.getParameterType( clmNo );
463 // 5.3.8.0 (2011/08/01) setNull 対応
464 final String val = values[whereNo[i]];
465 if( val == null || val.isEmpty() ) {
466 pstmt.setNull( clmNo++, type );
469 pstmt.setObject( clmNo++,val,type );
476 for( int i=0; i<keys.length; i++ ) {
477 pstmt.setObject( clmNo++,values[keysNo[i]] );
480 // where 条件を使用する場合は、値を割り当てます。
482 for( int i=0; i<whereNo.length; i++ ) {
483 pstmt.setObject( clmNo++,values[whereNo[i]] );
488 cnt = pstmt.executeUpdate();
491 catch( final SQLException ex) {
492 Closer.stmtClose( pstmt );
493 pMeta = null; // 5.1.2.0 (2010/01/01)
496 ConnectionFactory.remove( conn,connID );
499 final String errMsg = "DB処理の実行に失敗しました。" + CR
500 + " query=[" + query + "]" + CR
501 + " values=" + StringUtil.array2csv( values );
502 throw new OgRuntimeException( errMsg ,ex );
510 * 引数には、commit させる場合は、true を、rollback させる場合は、false をセットします。
511 * 戻り値は、今まで処理された合計データ件数です。
512 * この処理は、SQLException を内部で RuntimeException に変換している為、catch 節は
513 * 不要ですが、必ず finally 節で呼び出してください。そうしないと、リソースリークの
516 * @og.rev 5.1.2.0 (2010/01/01) pMeta のクリア
518 * @param commitFlag コミットフラグ [true:commitする/false:rollbacする]
522 public int close( final boolean commitFlag ) {
524 // if( conn != null ) { // 8.1.1.2 (2022/02/25) Modify
525 if( conn != null && !conn.isClosed() ) {
526 if( commitFlag ) { conn.commit(); }
527 else { conn.rollback(); }
530 catch( final SQLException ex) {
531 ConnectionFactory.remove( conn,connID );
533 final String errMsg = "DB処理を確定(COMMIT)できませんでした。" + CR
534 + " query=[" + query + "]" + CR ;
535 throw new OgRuntimeException( errMsg,ex );
538 Closer.stmtClose( pstmt );
539 pMeta = null; // 5.1.2.0 (2010/01/01)
540 ConnectionFactory.close( conn,connID );
548 * 文字列配列中の値とマッチするアドレスを検索します。
549 * 文字列配列がソートされていない為、バイナリサーチが使えません。よって、
551 * 総数が多い場合は、遅くなる為、マップにセットして使用することを検討ください。
553 * @param data ターゲットの文字列配列中
556 * @return ターゲットの添え字(存在しない場合は、-1)
558 private int findAddress( final String[] data,final String key ) {
560 if( data != null && key != null ) {
561 for( int i=0; i<data.length; i++ ) {
562 if( key.equalsIgnoreCase( data[i] ) ) {