2 * Copyright (c) 2017 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.fileexec;
18 import java.io.IOException;
19 import java.io.Reader;
20 import java.sql.Struct; // 6.3.3.0 (2015/07/25)
22 import java.sql.ResultSet;
23 import java.sql.ResultSetMetaData;
24 import java.sql.SQLException;
25 import java.sql.Types;
27 import java.sql.Timestamp;
28 import java.util.Locale;
29 import java.util.List; // 6.3.3.0 (2015/07/25)
30 import java.util.ArrayList; // 6.3.3.0 (2015/07/25)
32 import oracle.jdbc.OracleStruct; // 6.3.8.0 (2015/09/11)
33 import oracle.jdbc.OracleTypeMetaData; // 6.3.8.0 (2015/09/11)
36 * ResultSet のデータ処理をまとめたクラスです。
37 * ここでは、ResultSetMetaData から、カラム数、カラム名(NAME列)、
38 * Type属性を取得し、ResultSet で、値を求める時に、Object型の
40 * Object型としては、CLOB、ROWID、TIMESTAMP 型のみ取り扱っています。
41 * STRUCTタイプもサポートしますが、1レベルのみとします。(6.3.3.0 (2015/07/25))
43 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
46 * @author Kazuhiko Hasegawa
49 public class ResultSetValue implements AutoCloseable {
50 private static final XLogger LOGGER= XLogger.getLogger( ResultSetValue.class.getSimpleName() ); // ログ出力
52 private static final int BUFFER_MIDDLE = 10000; // 6.3.3.0 (2015/07/25)
54 // /** システム依存の改行記号(String)。 */
55 // public static final String CR = System.getProperty("line.separator");
57 private final ResultSet resultSet ; // 内部で管理する ResultSet オブジェクト
58 private final List<ColumnInfo> clmInfos ;
60 private boolean skipNext ; // STRUCT 使用時に、next() してデータ構造を取得するため。
61 private boolean firstNext ; // STRUCT 使用時に、next() してデータ構造を取得するため。
64 * ResultSet を引数にとるコンストラクタ
66 * ここで、カラムサイズ、カラム名、java.sql.Types の定数定義 を取得します。
67 * STRUCTタイプもサポートしますが、1レベルのみとします。
68 * つまり、Object型のカラムに、Object型を定義した場合、ここでは取り出すことができません。
69 * また、Object型は、継承関係を構築できるため、個々のオブジェクトの要素数は異なります。
70 * 一番最初のレコードのオブジェクト数を元に、算出しますので、ご注意ください。
72 * @param res 内部で管理するResultSetオブジェクト
73 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合
75 public ResultSetValue( final ResultSet res ) throws SQLException {
78 final ResultSetMetaData metaData = resultSet.getMetaData();
79 final int clmSize = metaData.getColumnCount();
81 clmInfos = new ArrayList<>();
83 for( int i=0; i<clmSize; i++ ) {
84 final int clmNo = i+1;
85 final int type = metaData.getColumnType( clmNo );
86 final String name = metaData.getColumnLabel( clmNo ).toUpperCase(Locale.JAPAN) ;
87 if( type == Types.STRUCT && DBUtil.isOracle() ) {
88 if( !skipNext ) { // オブジェクト型を取得する為、データを取る必要がある。
90 firstNext = resultSet.next(); // 初めての next() の結果を保持(falseなら、データなし)
93 // 最初のオブジェクトのタイプを基準にする。
94 final Object obj = resultSet.getObject( clmNo );
96 // 6.3.8.0 (2015/09/11) Oracle Database 12cリリース1 (12.1)以降、StructDescriptor は非推奨
97 final OracleTypeMetaData omd = ((OracleStruct)obj).getOracleMetaData();
98 final ResultSetMetaData md = ((OracleTypeMetaData.Struct)omd).getMetaData();
100 final int mdsize = md.getColumnCount();
101 for( int j=0; j<mdsize; j++ ) {
102 final int objNo = j+1;
104 final String name2 = name + '.' + md.getColumnLabel(objNo).toUpperCase(Locale.JAPAN);
105 final int type2 = md.getColumnType( objNo );
106 final int size2 = md.getColumnDisplaySize( objNo );
107 final boolean isWrit2 = md.isWritable( objNo );
108 clmInfos.add( new ColumnInfo( name2,type2,size2,isWrit2,clmNo,j ) ); // ※ objNo でなく、「j」
114 final int size = metaData.getColumnDisplaySize( clmNo );
115 final boolean isWrit = metaData.isWritable( clmNo );
116 clmInfos.add( new ColumnInfo( name,type,size,isWrit,clmNo,-1 ) ); // ※ objNo でなく、「-1」
122 * ResultSetMetaData で求めた、カラム数を返します。
124 * @return カラム数(データの列数)
126 public int getColumnCount() {
127 return clmInfos.size();
133 * 配列は、0から始まり、カラム数-1 までの文字型配列に設定されます。
134 * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
140 public String[] getNames() {
141 return clmInfos.stream().map( info -> info.getName() ).toArray( String[]::new );
145 * 指定のカラム番号のカラム名を返します。
147 * カラム名を取得する、カラム番号は、0から始まり、カラム数-1 までの数字で指定します。
148 * データベース上の、1から始まる番号とは、異なります。
149 * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した
152 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字)
153 * @return 指定のカラム番号のカラム名
155 public String getColumnName( final int clmNo ) {
156 return clmInfos.get( clmNo ).name ;
162 * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。
164 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字)
165 * @return 指定のカラム番号のサイズ
167 public int getColumnDisplaySize( final int clmNo ) {
168 return clmInfos.get( clmNo ).size ;
172 * 指定の書き込み可能かどうかを返します。
174 * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。
176 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字)
179 public boolean isWritable( final int clmNo ) {
180 return clmInfos.get( clmNo ).isWrit ;
184 * カーソルを現在の位置から順方向に1行移動します。
186 * ResultSet#next() を呼び出しています。
187 * 結果は、すべて文字列に変換されて格納されます。
189 * @return 新しい現在の行が有効である場合はtrue、行がそれ以上存在しない場合はfalse
190 * @see java.sql.ResultSet#next()
191 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合、またはこのメソッドがクローズされた結果セットで呼び出された場合
193 public boolean next() throws SQLException {
194 if( skipNext ) { skipNext = false; return firstNext; } // STRUCTタイプ取得時に、一度 next() している。
195 return resultSet.next();
199 * 現在のカーソル位置にあるレコードのカラム番号のデータを取得します。
201 * ResultSet#getObject( clmNo+1 ) を呼び出しています。
202 * 引数のカラム番号は、0から始まりますが、ResultSet のカラム順は、1から始まります。
204 * 結果は、すべて文字列に変換されて返されます。
205 * また、null オブジェクトの場合も、ゼロ文字列に変換されて返されます。
207 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字)
208 * @return 現在行のカラム番号のデータ(文字列)
209 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合
212 public String getValue( final int clmNo ) throws SQLException {
213 final ColumnInfo clmInfo = clmInfos.get( clmNo ) ; // 内部カラム番号に対応したObject
214 final int dbClmNo = clmInfo.clmNo ; // データベース上のカラム番号(+1済み)
217 final Object obj = resultSet.getObject( dbClmNo );
222 else if( clmInfo.isStruct ) {
223 final Object[] attrs = ((Struct)obj).getAttributes();
224 final int no = clmInfo.objNo;
225 val = no < attrs.length ? String.valueOf( attrs[no] ) : "" ; // 配列オーバーする場合は、""(ゼロ文字列)
227 else if( clmInfo.isObject ) {
228 switch( clmInfo.type ) {
229 case Types.CLOB : val = getClobData( (Clob)obj ) ;
231 case Types.ROWID: val = resultSet.getString( dbClmNo );
233 case Types.TIMESTAMP : val = StringUtil.getTimeFormat( ((Timestamp)obj).getTime() , "yyyyMMddHHmmss" );
235 default : val = String.valueOf( obj );
240 val = String.valueOf( obj );
247 * 現在のカーソル位置にあるレコードの全カラムデータを取得します。
249 * 個々のカラムの値も、null を含みません。(ゼロ文字列になっています)
251 * #getValue( clmNo ) を、0から、カラム数-1 まで呼び出して求めた文字列配列を返します。
253 * @return 現在行の全カラムデータの文字列配列
254 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合
257 public String[] getValues() throws SQLException {
258 final String[] vals = new String[clmInfos.size()];
260 for( int i=0; i<vals.length; i++ ) {
261 vals[i] = getValue( i );
268 * タイプに応じて変換された、Numberオブジェクトを返します。
270 * 条件に当てはまらない場合は、null を返します。
271 * org.opengion.hayabusa.io.HybsJDBCCategoryDataset2 から移動してきました。
272 * これは、検索結果をグラフ化する為の 値を取得する為のメソッドですので、
273 * 数値に変換できない場合は、エラーになります。
275 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字)
276 * @return Numberオブジェクト(条件に当てはまらない場合は、null)
277 * @see java.sql.Types
278 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合
279 * @throws RuntimeException 数字変換できなかった場合。
281 public Number getNumber( final int clmNo ) throws SQLException {
282 final ColumnInfo clmInfo = clmInfos.get( clmNo ) ; // 内部カラム番号に対応したObject
283 final int dbClmNo = clmInfo.clmNo ; // データベース上のカラム番号(+1済み)
287 Object obj = resultSet.getObject( dbClmNo );
289 if( clmInfo.isStruct ) {
290 final Object[] attrs = ((Struct)obj).getAttributes();
291 final int no = clmInfo.objNo;
292 obj = no < attrs.length ? attrs[no] : null ; // 配列オーバーする場合は、null
293 if( obj == null ) { return value; } // 配列外 or 取出した結果が null の場合、処理を中止。
296 switch( clmInfo.type ) {
311 value = Long.valueOf( ((Date)obj).getTime() );
314 // 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。
315 case Types.TIMESTAMP: {
316 value = Long.valueOf( ((Timestamp)obj).getTime() );
321 case Types.LONGVARCHAR: {
322 final String str = (String)obj;
324 value = Double.valueOf(str);
326 catch ( final NumberFormatException ex ) {
327 // final String errMsg = "数字変換できませんでした。in=" + str
328 // + CR + ex.getMessage() ;
329 // throw new RuntimeException( errMsg,ex );
330 // // suppress (value defaults to null)
331 // MSG0031 = 数字変換できませんでした。\n\tメッセージ=[{0}]
332 final String errMsg = "in=" + str + " , clmNo=" + dbClmNo + " , CLM=" + clmInfo.getName() ; // 1.4.0 (2019/10/01)
333 throw MsgUtil.throwException( ex , "MSG0031" , errMsg );
338 // not a value, can't use it (defaults to null)
346 * カラムのタイプを表現する文字列値を返します。
348 * この文字列を用いて、CCSファイルでタイプごとの表示方法を
350 * 現時点では、VARCHAR2,LONG,NUMBER,DATE,CLOB,NONE のどれかにあてはめます。
352 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字)
353 * @return カラムのタイプを表現する文字列値
354 * @see java.sql.Types
357 public String getClassName( final int clmNo ) {
360 switch( clmInfos.get( clmNo ).type ) {
364 rtn = "VARCHAR2"; break;
365 case Types.LONGVARCHAR:
376 rtn = "NUMBER"; break;
379 case Types.TIMESTAMP:
383 case Types.STRUCT: // 6.3.3.0 (2015/07/25) 内部分解されない2レベル以上の場合のみ
384 rtn = "STRUCT"; break;
393 * try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
395 * コンストラクタで渡された ResultSet を close() します。
397 * @og.rev 6.4.2.1 (2016/02/05) 新規作成。try-with-resourcesブロックで、自動的に呼ばれる AutoCloseable の実装。
399 * @see java.lang.AutoCloseable#close()
401 @Override // AutoCloseable
402 public void close() {
404 if( resultSet != null ) { resultSet.close(); }
406 catch( final SQLException ex ) {
407 // MSG0020 = ResultSet を close することが出来ませんでした。{0} : {1}
408 // MsgUtil.errPrintln( ex , "MSG0020" , ex.getSQLState() , ex.getMessage() );
409 final String errMsg = "ResultSetValue#close : " + ex.getErrorCode() ;
410 LOGGER.warning( ex , "MSG0020" , ex.getSQLState() , errMsg );
412 catch( final RuntimeException ex ) {
413 // MSG0021 = 予期せぬエラーが発生しました。\n\tメッセージ=[{0}]
414 // MsgUtil.errPrintln( ex , "MSG0021" , ex.getMessage() );
415 LOGGER.warning( ex , "MSG0021" , "ResultSetValue#close" );
420 * Clob オブジェクトから文字列を取り出します。
422 * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getClobData( Clob ) から移動
424 * @param clobData Clobオブジェクト
425 * @return Clobオブジェクトから取り出した文字列
426 * @throws SQLException データベースアクセスエラー
427 * @throws RuntimeException 入出力エラーが発生した場合
430 private String getClobData( final Clob clobData ) throws SQLException {
431 if( clobData == null ) { return ""; }
433 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
435 Reader reader = null;
437 reader = clobData.getCharacterStream();
438 final char[] ch = new char[BUFFER_MIDDLE]; // char配列とBuilderの初期値は無関係。
440 while( (len = reader.read( ch )) >= 0 ) {
441 buf.append( ch,0,len );
444 catch( final IOException ex ) {
445 // MSG0022 = CLOBデータの読み込みに失敗しました。メッセージ=[{0}]
446 throw MsgUtil.throwException( ex , "MSG0022" , ex.getMessage() );
450 if( reader != null ) { reader.close(); }
452 catch( final IOException ex ) {
453 // MSG0023 = ストリーム close 処理でエラーが発生しました。メッセージ=[{0}]
454 // MsgUtil.errPrintln( ex , "MSG0023" , ex.getMessage() );
455 LOGGER.warning( ex , "MSG0023" , "" );
457 catch( final RuntimeException ex ) {
458 // MSG0021 = 予期せぬエラーが発生しました。\n\tメッセージ=[{0}]
459 // MsgUtil.errPrintln( ex , "MSG0021" , ex.getMessage() );
460 LOGGER.warning( ex , "MSG0021" , "ResultSetValue#getClobData" );
464 return buf.toString();
468 * 各種カラム属性の管理を、クラスで行うようにします。
470 * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
472 * @param clobData Clobオブジェクト
473 * @return Clobオブジェクトから取り出した文字列
475 private static final class ColumnInfo {
476 private final String name ; // カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
477 private final int type ; // java.sql.Types の定数定義
478 private final int size ; // カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
479 private final boolean isWrit ; // 書き込み許可(ResultSetMetaData#isWritable(int))
480 private final int clmNo ; // ResultSet での元のカラムNo( 1から始まる番号 )
481 private final int objNo ; // STRUCT での配列番号( 0から始まる番号 )
482 private final boolean isStruct ; // オリジナルのタイプが、Struct型 かどうか。
483 private final boolean isObject ; // タイプが、CLOB,ROWID,TIMESTAMP かどうか。
488 * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
490 * @param name カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase)
491 * @param type java.sql.Types の定数定義
492 * @param size カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int))
493 * @param isWrit 書き込み許可(ResultSetMetaData#isWritable(int))
494 * @param clmNo ResultSet での元のカラムNo( 1から始まる番号 )
495 * @param objNo STRUCT での配列番号( 0から始まる番号 )
497 ColumnInfo( final String name , final int type , final int size , final boolean isWrit , final int clmNo , final int objNo ) {
501 this.isWrit = isWrit;
504 isStruct = objNo >= 0; // Struct型かどうかは、配列番号で判定する。
505 isObject = type == Types.CLOB || type == Types.ROWID || type == Types.TIMESTAMP ;
511 * @og.rev 6.3.3.0 (2015/07/25) STRUCTタイプの対応
515 public String getName() { return name; }