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.io.IOException; 019import java.io.Reader; 020import java.sql.Clob; 021import java.sql.ResultSet; 022import java.sql.ResultSetMetaData; 023import java.sql.SQLException; 024import java.sql.Types; 025import java.sql.Date; 026import java.sql.Timestamp; 027import java.util.Locale; 028 029import org.opengion.fukurou.util.Closer; 030import org.opengion.fukurou.util.HybsDateUtil; 031import static org.opengion.fukurou.util.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 032 033/** 034 * ResultSet のデータ処理をまとめたクラスです。 035 * ここでは、ResultSetMetaData から、カラム数、カラム名(NAME列)、 036 * Type属性を取得し、ResultSet で、値を求める時に、Object型の 037 * 処理を行います。 038 * Object型としては、CLOB、ROWID、TIMESTAMP 型のみ取り扱っています。 039 * 040 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 041 * @og.group DB制御 042 * 043 * @version 6.0 044 * @author Kazuhiko Hasegawa 045 * @since JDK6.0, 046 */ 047public class ResultSetValue { 048 049// /** システム依存の改行記号をセットします。4.0.0.0(2007/10/17) */ 050// private static final String CR = System.getProperty( "line.separator" ); 051 052 private final ResultSet resultSet ; // 内部で管理する ResultSet オブジェクト 053 private final int clmSize ; // カラムサイズ 054 private final String[] names ; // カラム名(ResultSetMetaData#getColumnLabel(int) の toUpperCase) 055 private final int[] type ; // java.sql.Types の定数定義 056 private final boolean useObj ; // オブジェクト型の Type が存在するかどうか。 057 058 private final int[] size ; // カラムサイズ(ResultSetMetaData#getColumnDisplaySize(int)) 059 private final boolean[] isWrit ; // 書き込み許可(ResultSetMetaData#isWritable(int)) 060 061 /** 062 * ResultSet を引数にとるコンストラクタ 063 * 064 * ここで、カラムサイズ、カラム名、java.sql.Types の定数定義 を取得します。 065 * 066 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 067 * 068 * @param res 内部で管理するResultSetオブジェクト 069 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合 070 */ 071 public ResultSetValue( final ResultSet res ) throws SQLException { 072 resultSet = res; 073 074 final ResultSetMetaData metaData = resultSet.getMetaData(); 075 clmSize = metaData.getColumnCount(); 076 names = new String[clmSize]; 077 type = new int[clmSize]; 078 size = new int[clmSize]; 079 isWrit = new boolean[clmSize]; 080 081 boolean tempUseObj = false; // そもそも、オブジェクト系のカラムがあるかどうか 082 for( int i=0; i<clmSize; i++ ) { 083 final int tp = metaData.getColumnType( i+1 ); 084 type[i] = tp ; 085 if( tp == Types.CLOB || tp == Types.ROWID || tp == Types.TIMESTAMP ) { tempUseObj = true; } 086 names[i] = metaData.getColumnLabel(i+1).toUpperCase(Locale.JAPAN) ; 087 size[i] = metaData.getColumnDisplaySize(i+1) ; 088 isWrit[i] = metaData.isWritable(i+1) ; 089 } 090 useObj = tempUseObj ; 091 } 092 093 /** 094 * ResultSetMetaData で求めた、カラム数を返します。 095 * 096 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 097 * 098 * @return カラム数(データの列数) 099 */ 100 public int getColumnCount() { return clmSize ; } 101 102 /** 103 * カラム名配列を返します。 104 * 105 * 配列は、0から始まり、カラム数-1 までの文字型配列に設定されます。 106 * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した 107 * 大文字が返されます。 108 * 109 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 110 * 111 * @return カラム名配列 112 * @og.rtnNotNull 113 */ 114 public String[] getNames() { 115 return names.clone(); 116 } 117 118 /** 119 * 指定のカラム番号のカラム名を返します。 120 * 121 * カラム名を取得する、カラム番号は、0から始まり、カラム数-1 までの数字で指定します。 122 * データベース上の、1から始まる番号とは、異なります。 123 * カラム名は、ResultSetMetaData#getColumnLabel(int) を toUpperCase した 124 * 大文字が返されます。 125 * 126 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 127 * 128 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 129 * @return 指定のカラム番号のカラム名 130 */ 131 public String getColumnName( final int clmNo ) { 132 return names[clmNo]; 133 } 134 135// /** 136// * java.sql.Types の定数定義配列を返します。 137// * 138// * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。 139// * Types の定数定義は、ResultSetMetaData#getColumnType(int) の値です。 140// * 141// * @og.rev 6.0.4.0 (2014/11/28) 新規作成 142// * 143// * @return カラム(0〜カラム数-1)に対応した、Types定数定義配列 144// */ 145// public int[] getSqlTypes() { 146// return type.clone(); 147// } 148 149 /** 150 * 指定のカラム番号のjava.sql.Types の定数定義を返します。 151 * 152 * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。 153 * Types の定数定義は、ResultSetMetaData#getColumnType(int) の値です。 154 * 155 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 156 * 157 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 158 * @return 指定のカラム番号のTypes定数 159 */ 160 public int getSqlType( final int clmNo ) { 161 return type[clmNo]; 162 } 163 164// /** 165// * オブジェクト型の Type が存在するかどうか[true:存在する/false:存在しない]を返します。 166// * 167// * 基本的には、値を求める処理の内部ロジックでのみ使用します。 168// * Types.CLOB 、Types.ROWID、Types.TIMESTAMP が、Types定義に含まれている場合、 169// * true を返します。 170// * 含まれない場合は、false です。 171// * オブジェクト型 が存在する場合、値を求める処理に、専用のメソッドが必要になります。 172// * 含まれない場合は、String.valueOf( Object ) で求めることが可能です。 173// * この値を参考に、呼び出すメソッドを変えることで、処理の高速化を図ろうと 174// * 考えています。(効果があるかどうかは、判りません。) 175// * 176// * @og.rev 6.0.4.0 (2014/11/28) 新規作成 177// * 178// * @return オブジェクト型の Type が存在するかどうか[true:存在する/false:存在しない] 179// */ 180// public boolean useObjectType() { 181// return useObj; 182// } 183 184 /** 185 * カラムのサイズのint配列を返します。 186 * 187 * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。 188 * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。 189 * 190 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 191 * 192 * @return カラムのサイズのint配列 193 * @og.rtnNotNull 194 */ 195 public int[] getColumnDisplaySizes() { 196 return size.clone(); 197 } 198 199 /** 200 * 指定のカラム番号のサイズを返します。 201 * 202 * カラムのサイズは、ResultSetMetaData#getColumnDisplaySize(int) の値です。 203 * 204 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 205 * 206 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 207 * @return 指定のカラム番号のサイズ 208 */ 209 public int getColumnDisplaySize( final int clmNo ) { 210 return size[clmNo]; 211 } 212 213 /** 214 * カラムの書き込み可能かどうかのboolean配列を返します。 215 * 216 * 配列は、0から始まり、カラム数-1 までのint型配列に設定されます。 217 * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。 218 * 219 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 220 * 221 * @return 書き込み可能かどうかのboolean配列 222 * @og.rtnNotNull 223 */ 224 public boolean[] isWritable() { 225 return isWrit.clone(); 226 } 227 228 /** 229 * 指定の書き込み可能かどうかを返します。 230 * 231 * カラムの書き込み可能かどうかは、ResultSetMetaData#isWritable(int) の値です。 232 * 233 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 234 * 235 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 236 * @return 書き込み可能かどうか 237 */ 238 public boolean isWritable( final int clmNo ) { 239 return isWrit[clmNo]; 240 } 241 242 /** 243 * カーソルを現在の位置から順方向に1行移動します。 244 * 245 * ResultSet#next() を呼び出しています。 246 * 結果は,すべて文字列に変換されて格納されます。 247 * 248 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 249 * 250 * @return 新しい現在の行が有効である場合はtrue、行がそれ以上存在しない場合はfalse 251 * @see java.sql.ResultSet#next() 252 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合、またはこのメソッドがクローズされた結果セットで呼び出された場合 253 */ 254 public boolean next() throws SQLException { 255 return resultSet.next() ; 256 } 257 258 /** 259 * 現在のカーソル位置にあるレコードのカラム番号のデータを取得します。 260 * 261 * ResultSet#getObject( clmNo+1 ) を呼び出しています。 262 * 引数のカラム番号は、0から始まりますが、ResultSet のカラム順は、1から始まります。 263 * 指定は、0から始まるカラム番号です。 264 * 結果は,すべて文字列に変換されて返されます。 265 * また、null オブジェクトの場合も、ゼロ文字列に変換されて返されます。 266 * 267 * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getValue( ResultSet , int , int ) から移動 268 * 269 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 270 * @return 現在行のカラム番号のデータ(文字列) 271 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合 272 */ 273 public String getValue( final int clmNo ) throws SQLException { 274 final String val ; 275 final Object obj = resultSet.getObject( clmNo+1 ); 276 if( obj == null ) { 277 val = ""; 278 } 279 else if( useObj ) { 280 switch( type[clmNo] ) { 281 case Types.CLOB : val = getClobData( (Clob)obj ) ; 282 break; 283 case Types.ROWID: val = resultSet.getString(clmNo+1); 284 break; 285 case Types.TIMESTAMP : val = HybsDateUtil.getDate( ((Timestamp)obj).getTime() , "yyyyMMddHHmmss" ); 286 break; 287 default : val = String.valueOf( obj ); 288 break; 289 } 290 } 291 else { 292 val = String.valueOf( obj ); 293 } 294 295 return val ; 296 } 297 298 /** 299 * 現在のカーソル位置にあるレコードの全カラムデータを取得します。 300 * 301 * #getValue( clmNo ) を、0から、カラム数-1 まで呼び出して求めた文字列配列を返します。 302 * 303 * @og.rev 6.0.4.0 (2014/11/28) 新規作成 304 * 305 * @return 現在行の全カラムデータの文字列配列 306 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合 307 */ 308 public String[] getValues() throws SQLException { 309 final String[] vals = new String[clmSize]; 310 311 for( int i=0; i<clmSize; i++ ) { 312 vals[i] = getValue( i ); 313 } 314 315 return vals ; 316 } 317 318 /** 319 * タイプに応じて変換された、Numberオブジェクトを返します。 320 * 321 * 条件に当てはまらない場合は、null を返します。 322 * org.opengion.hayabusa.io.HybsJDBCCategoryDataset2 から移動してきました。 323 * これは、検索結果をグラフ化する為の 値を取得する為のメソッドですので、 324 * 数値に変換できない場合は、エラーになります。 325 * 326 * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getNumber( int , Object ) から移動 327 * 328 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 329 * @return Numberオブジェクト(条件に当てはまらない場合は、null) 330 * @see java.sql.Types 331 * @throws java.sql.SQLException データベース・アクセス・エラーが発生した場合 332 * @throws RuntimeException 数字変換できなかった場合。 333 */ 334 public Number getNumber( final int clmNo ) throws SQLException { 335 final Object obj = resultSet.getObject( clmNo+1 ); 336 Number value = null; 337 338 switch( type[clmNo] ) { 339 case Types.TINYINT: 340 case Types.SMALLINT: 341 case Types.INTEGER: 342 case Types.BIGINT: 343 case Types.FLOAT: 344 case Types.DOUBLE: 345 case Types.DECIMAL: 346 case Types.NUMERIC: 347 case Types.REAL: { 348 value = (Number)obj; 349 break; 350 } 351 case Types.DATE: 352 case Types.TIME: { 353 value = Long.valueOf( ((Date)obj).getTime() ); 354 break; 355 } 356 // 5.6.2.1 (2013/03/08) Types.DATE と Types.TIMESTAMP で処理を分けます。 357 case Types.TIMESTAMP: { 358 value = Long.valueOf( ((Timestamp)obj).getTime() ); 359 break; 360 } 361 case Types.CHAR: 362 case Types.VARCHAR: 363 case Types.LONGVARCHAR: { 364 final String str = (String)obj; 365 try { 366 value = Double.valueOf(str); 367 } 368 catch (NumberFormatException ex) { 369 final String errMsg = "数字変換できませんでした。in=" + str 370 + CR + ex.getMessage() ; 371 throw new RuntimeException( errMsg,ex ); 372 // suppress (value defaults to null) 373 } 374 break; 375 } 376 default: 377 // not a value, can't use it (defaults to null) 378 break; 379 } 380 381 return value; 382 } 383 384 /** 385 * カラムのタイプを表現する文字列値を返します。 386 * 387 * この文字列を用いて、CCSファイルでタイプごとの表示方法を 388 * 指定することができます。 389 * 現時点では、VARCHAR2,LONG,NUMBER,DATE,CLOB,NONE のどれかにあてはめます。 390 * 391 * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#type2ClassName( int ) から移動 392 * 393 * @param clmNo カラム番号 (0から始まり、カラム数-1までの数字) 394 * @return カラムのタイプを表現する文字列値 395 * @see java.sql.Types 396 */ 397 public String getClassName( final int clmNo ) { 398 final String rtn ; 399 400 switch( type[clmNo] ) { 401 case Types.CHAR: 402 case Types.VARCHAR: 403 case Types.BIT: 404 rtn = "VARCHAR2"; break; 405 case Types.LONGVARCHAR: 406 rtn = "LONG"; break; 407 case Types.TINYINT: 408 case Types.SMALLINT: 409 case Types.INTEGER: 410 case Types.NUMERIC: 411 case Types.BIGINT: 412 case Types.FLOAT: 413 case Types.DOUBLE: 414 case Types.REAL: 415 case Types.DECIMAL: 416 rtn = "NUMBER"; break; 417 case Types.DATE: 418 case Types.TIME: 419 case Types.TIMESTAMP: 420 rtn = "DATE"; break; 421 case Types.CLOB: 422 rtn = "CLOB"; break; 423 default: 424 rtn = "NONE"; break; 425 } 426 427 return rtn; 428 } 429 430 /** 431 * Clob オブジェクトから文字列を取り出します。 432 * 433 * @og.rev 6.0.4.0 (2014/11/28) 新規作成: org.opengion.hayabusa.db.DBUtil#getClobData( Clob ) から移動 434 * 435 * @param clobData Clobオブジェクト 436 * @return Clobオブジェクトから取り出した文字列 437 * @throws SQLException データベースアクセスエラー 438 * @throws RuntimeException 入出力エラーが発生した場合 439 * @og.rtnNotNull 440 */ 441 private String getClobData( final Clob clobData ) throws SQLException { 442 if( clobData == null ) { return ""; } 443 444 final StringBuilder buf = new StringBuilder( 10000 ); 445 446 Reader reader = null; 447 try { 448 reader = clobData.getCharacterStream(); 449 final char[] ch = new char[10000]; 450 int len ; 451 while( (len = reader.read( ch )) >= 0 ) { 452 buf.append( ch,0,len ); 453 } 454 } 455 catch( IOException ex ) { 456 final String errMsg = "CLOBデータの読み込みに失敗しました。" 457 + ex.getMessage() ; 458 throw new RuntimeException( errMsg,ex ); 459 } 460 finally { 461 Closer.ioClose( reader ); 462 } 463 return buf.toString(); 464 } 465}