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.process; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import org.opengion.fukurou.util.Argument; 020import org.opengion.fukurou.util.SystemParameter; 021import org.opengion.fukurou.system.LogWriter; 022 023import org.opengion.fukurou.util.HybsEntry ; 024import org.opengion.fukurou.db.ConnectionFactory; 025 026import java.util.Set ; 027import java.util.HashSet ; 028import java.util.Map ; 029import java.util.LinkedHashMap ; 030 031import java.sql.Connection; 032import java.sql.Statement; 033import java.sql.ResultSet; 034import java.sql.SQLException; 035 036/** 037 * Process_BulkQueryは、データベースから読み取った内容を、一括処理する、 038 * FirstProcess と、ChainProcess のインターフェースを両方持った、実装クラスです。 039 * ParamProcess のサブクラス(Process_DBParam)にセットしたり、加工したりします。 040 * 041 * このクラスは、上流から、下流への処理は、1度しか実行されません。 042 * FirstProcess の検索結果は、Set オブジェクトとして、Process_DBParam に渡します。 043 * ChainProcess は、その結果を取り出し、自分自身の処理結果と合せて加工します。 044 * 045 * FirstProcess では、-action は、query のみです。 046 * query は、指定のSQL文を実行し、結果のSetをParamProcessに設定します。 047 * ChainProcess では、-action は、query、bulkSet、minus、intersect が指定できます。 048 * query は、上記と同じです。 049 * minus は、先のSetから、SQL文の実行結果を引き算し、結果Setを再設定します。 050 * intersect は、先のSetから、SQL文の実行結果と重複する結果Setを再設定します。 051 * bulkSet は、先のSetを取り出し、SQL文に加味して処理します。 052 * 流れ的には、query で検索し、minusまたはintersect でSetオブジェクトを加工し、bulkSet で 053 * 利用します。例えば、ORACLEから、ユニークキーのSetを作成し、SQLServerのユニークキーを 054 * minusした結果を、ORACLEからDELETEすれば、不要なデータを削除するなどの処理が実行可能になります。 055 * また、単純に、query だけを、チェインすれば、単発のUPDATE文を実行することが可能です。 056 * 057 * データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に 058 * 設定された接続(Connection)を使用します。 059 * DBID は、Process_DBParam の -configFile で指定する DBConfig.xml ファイルを使用します。 060 * 061 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。 062 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に 063 * 繋げてください。 064 * 065 * SQL文には、{@DATE.YMDH}等のシステム変数が使用できます。 066 * 067 * @og.formSample 068 * Process_BulkQuery -action=query -dbid=DBGE -sql="select KEY from TABLE_X" 069 * 070 * -action=処理方法(必須) : 実行する処理方法を指定します 071 * -action=query 単なるSQL文を実行します。 072 * -action=bulkSet 実行したSQL文の結果を、Set<String> オブジェクトに設定します。 073 * -action=minus Set<String> オブジェクトと、ここでの実行結果の差分をとります。 074 * -action=plus Set<String> オブジェクトと、ここでの実行結果の加算をします。 075 * -action=intersect Set<String> オブジェクトと、ここでの実行結果の積分をとります。 076 * [ -dbid=DB接続ID ] : -dbid=DBGE (例: Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定) 077 * [ -sql=検索SQL文 ] : -sql="select * from GEA08" 078 * [ -sqlFile=検索SQLファイル ] : -sqlFile=select.sql 079 * -sql= を指定しない場合は、ファイルで必ず指定してください。 080 * [ -sql_XXXX=固定値 ] : -sql_SYSTEM_ID=GE 081 * SQL文中の{@XXXX}文字列を指定の固定値で置き換えます。 082 * WHERE SYSTEM_ID='{@SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE' 083 * [ -bulkKey=XXXX ] : -bulkKey=XXXX 084 * SQL文中の{@XXXX}文字列をProcess_BulkQuery等で取得した値で置き換えます。 085 * WHERE SYSTEM_ID IN ( {@XXXX} ) ⇒ WHERE SYSTEM_ID IN ( 'AA','BB','CC' ) 086 * [ -bulkType=NUM|STR ] : -bulkType=STR 087 * Bulkの値を文字列に変換する場合に、数字型か、文字型を指定します。 088 * 数字型では、AA,BB,CC とし、文字型では、'AA','BB','CC' に変換します(初期値:STR)。 089 * [ -fetchSize=1000 ] :フェッチする行数(初期値:1000) 090 * [ -display=[false/true] ] :結果を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 091 * [ -debug=[false/true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 092 * 093 * @og.rev 5.3.4.0 (2011/04/01) 新規追加 094 * @version 4.0 095 * @author Kazuhiko Hasegawa 096 * @since JDK5.0, 097 */ 098public class Process_BulkQuery extends AbstractProcess implements FirstProcess , ChainProcess { 099 private static final int MAX_BULK_SET = 500 ; // ORACLE の制約が 1000 なので。 100 101 private static final String ACT_QUERY = "query" ; 102 private static final String ACT_BULKSET = "bulkSet" ; 103 private static final String ACT_MINUS = "minus" ; 104 private static final String ACT_PLUS = "plus" ; // 6.3.1.1 (2015/07/10) 追加 105 private static final String ACT_INTERSECT = "intersect" ; 106 107 private static final String[] ACTION_LST = { ACT_QUERY,ACT_BULKSET,ACT_MINUS,ACT_PLUS,ACT_INTERSECT }; 108 109 /** 6.9.3.0 (2018/03/26) データ検索時のフェッチサイズ {@value} */ 110 private static final int DB_FETCH_SIZE = 1001 ; 111 112 private String actionCmd ; // SQL結果を加工(query:実行、minus:引き算、intersect:重複分) 113 private String dbid ; // メインDB接続ID 114 115 private String bulkKey ; 116 private boolean bulkType = true; // true:STR , false:NUM 117 118 private int sqlCount ; // SQL文の処理件数 119 private int setCount ; // 取り出したSetの件数 120 private int outCount ; // マージ後のSetの件数 121 122 private int fetchSize = DB_FETCH_SIZE; // 6.9.3.0 (2018/03/26) 初期値を100→1000 に変更 123 private boolean display ; // 表示しない 124 private boolean debug ; // デバッグ情報 125 private boolean firstTime = true; // 最初の一回目 126 127 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 128 private static final Map<String,String> MUST_PROPARTY ; // [プロパティ]必須チェック用 Map 129 /** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */ 130 private static final Map<String,String> USABLE_PROPARTY ; // [プロパティ]整合性チェック Map 131 132 static { 133 MUST_PROPARTY = new LinkedHashMap<>(); 134 MUST_PROPARTY.put( "action", "実行する処理方法を指定します。(query|minus|plus|intersect)" ); 135 136 USABLE_PROPARTY = new LinkedHashMap<>(); 137 USABLE_PROPARTY.put( "dbid", "Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定" ); 138 USABLE_PROPARTY.put( "sql", "検索SQL文(sql or sqlFile 必須)例: \"select * from GEA08\"" ); 139 USABLE_PROPARTY.put( "sqlFile", "検索SQLファイル(sql or sqlFile 必須)例: select.sql" ); 140 USABLE_PROPARTY.put( "sql_", "SQL文中の{@XXXX}文字列を指定の固定値で置き換えます。" + 141 CR + "WHERE SYSTEM_ID='{@SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE'" ); 142 USABLE_PROPARTY.put( "dbid2", "DB接続ID2 例: Process_DBParam の -configFile で指定する DBConfig.xml ファイルで規定" ); 143 USABLE_PROPARTY.put( "sql2", "検索SQL文2(sql or sqlFile 必須)例: \"select * from GEA08\"" ); 144 USABLE_PROPARTY.put( "sqlFile2", "検索SQLファイル2(sql or sqlFile 必須)例: select.sql" ); 145 USABLE_PROPARTY.put( "sql2_", "SQL文2中の{@XXXX}文字列を指定の固定値で置き換えます。" + 146 CR + "WHERE SYSTEM_ID='{@SYSTEM_ID}' ⇒ WHERE SYSTEM_ID='GE'" ); 147 USABLE_PROPARTY.put( "bulkKey", "SQL文中の{@XXXX}文字列をProcess_BulkQuery等で取得した値で置き換えます。" + 148 CR + "WHERE SYSTEM_ID IN ( {@XXXX} ) ⇒ WHERE SYSTEM_ID IN ( 'AA','BB','CC' )" ); 149 USABLE_PROPARTY.put( "bulkType", "Bulkの値を文字列に変換する場合に、文字型か、数字型を指定します。" + 150 CR + "数字型では、AA,BB,CC とし、文字型では、'AA','BB','CC' に変換します。(初期値:STR)" ); 151 USABLE_PROPARTY.put( "fetchSize","フェッチする行数 (初期値:1000)" ); 152 USABLE_PROPARTY.put( "display", "結果を標準出力に表示する(true)かしない(false)か" + 153 CR + "(初期値:false:表示しない)" ); 154 USABLE_PROPARTY.put( "debug", "デバッグ情報を標準出力に表示する(true)かしない(false)か" + 155 CR + "(初期値:false:表示しない)" ); 156 } 157 158 /** 159 * デフォルトコンストラクター。 160 * このクラスは、動的作成されます。デフォルトコンストラクターで、 161 * super クラスに対して、必要な初期化を行っておきます。 162 * 163 */ 164 public Process_BulkQuery() { 165 super( "org.opengion.fukurou.process.Process_BulkQuery",MUST_PROPARTY,USABLE_PROPARTY ); 166 } 167 168 /** 169 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。 170 * 初期処理(ファイルオープン、DBオープン等)に使用します。 171 * 172 * @og.rev 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加 173 * @og.rev 6.3.1.1 (2015/07/10) plus アクションの追加 174 * 175 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 176 */ 177 public void init( final ParamProcess paramProcess ) { 178 final Argument arg = getArgument(); 179 180 actionCmd = arg.getProparty( "action" , null , ACTION_LST ); 181 182 fetchSize = arg.getProparty( "fetchSize" , fetchSize ); 183 display = arg.getProparty( "display" , display ); 184 debug = arg.getProparty( "debug" , debug ); 185 186 dbid = arg.getProparty( "dbid"); 187 String sql = arg.getFileProparty( "sql" , "sqlFile",true ); 188 if( debug ) { println( "入力SQL:" + sql ); } 189 190 final HybsEntry[] entry =arg.getEntrys( "sql_" ); //配列 191 final SystemParameter sysParam = new SystemParameter( sql ); 192 sql = sysParam.replace( entry ); 193 if( debug ) { println( "変換SQL:" + sql ); } 194 195 if( ACT_BULKSET.equalsIgnoreCase( actionCmd ) ) { 196 bulkKey = arg.getProparty( "bulkKey" ); 197 final String bkType = arg.getProparty( "bulkType" ); 198 if( bkType != null ) { bulkType = "STR".equalsIgnoreCase( bkType ); } // 初期値が true なので、null チャックは外せません。 199 200 final Set<String> setData = paramProcess.getBulkData(); 201 if( debug ) { println( setData.toString() ); } 202 setCount = setData.size(); 203 204 if( setCount > 0 ) { 205 // 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加 206 final String[] sqls = makeBulkQuery( sql,bulkKey,bulkType,setData ); 207 for( int i=0; i<sqls.length; i++ ) { 208 if( debug ) { println( "BulkSQL:" + sqls[i] ); } 209 createSetData( paramProcess, dbid, sqls[i] ); 210 } 211 } 212 } 213 else if( ACT_QUERY.equalsIgnoreCase( actionCmd ) ) { 214 final Set<String> setData2 = createSetData( paramProcess, dbid, sql ); 215 if( debug ) { println( setData2.toString() ); } 216 setCount = setData2.size(); 217 outCount = setCount; 218 paramProcess.setBulkData( setData2 ); 219 } 220 else { 221 final Set<String> setData = paramProcess.getBulkData(); 222 final Set<String> setData2 = createSetData( paramProcess, dbid, sql ); 223 setCount = setData2.size(); 224 225 if( ACT_MINUS.equalsIgnoreCase( actionCmd ) ) { 226 setData.removeAll( setData2 ); 227 } 228 else if( ACT_PLUS.equalsIgnoreCase( actionCmd ) ) { // 6.3.1.1 (2015/07/10) 229 setData.addAll( setData2 ); 230 } 231 else if( ACT_INTERSECT.equalsIgnoreCase( actionCmd ) ) { 232 setData.retainAll( setData2 ); 233 } 234 outCount = setData.size(); 235 if( debug ) { println( setData.toString() ); } 236 paramProcess.setBulkData( setData ); 237 } 238 } 239 240 /** 241 * プロセスの終了を行います。最後に一度だけ、呼び出されます。 242 * 終了処理(ファイルクローズ、DBクローズ等)に使用します。 243 * 244 * @param isOK トータルで、OKだったかどうか [true:成功/false:失敗] 245 */ 246 public void end( final boolean isOK ) { 247 // 何もありません。 248 } 249 250 /** 251 * このデータの処理において、次の処理が出来るかどうかを問い合わせます。 252 * この呼び出し1回毎に、次のデータを取得する準備を行います。 253 * 254 * @return 処理できる:true / 処理できない:false 255 */ 256 public boolean next() { 257 return firstTime; 258 } 259 260 /** 261 * 引数の LineModel を処理するメソッドです。 262 * 変換処理後の LineModel を返します。 263 * 後続処理を行わない場合(データのフィルタリングを行う場合)は、 264 * null データを返します。つまり、null データは、後続処理を行わない 265 * フラグの代わりにも使用しています。 266 * なお、変換処理後の LineModel と、オリジナルの LineModel が、 267 * 同一か、コピー(クローン)かは、各処理メソッド内で決めています。 268 * ドキュメントに明記されていない場合は、副作用が問題になる場合は、 269 * 各処理ごとに自分でコピー(クローン)して下さい。 270 * 271 * @param data オリジナルのLineModel 272 * 273 * @return 処理変換後のLineModel 274 */ 275 @SuppressWarnings(value={"unchecked"}) 276 public LineModel action( final LineModel data ) { 277 return data ; 278 } 279 280 /** 281 * 最初に、 行データである LineModel を作成します 282 * FirstProcess は、次々と処理をチェインしていく最初の行データを 283 * 作成して、後続の ChainProcess クラスに処理データを渡します。 284 * 285 * @param rowNo 処理中の行番号 286 * 287 * @return 処理変換後のLineModel 288 */ 289 public LineModel makeLineModel( final int rowNo ) { 290 firstTime = false; // 一度しか処理しないため、false を設定する。 291 292 final LineModel model = new LineModel(); 293 294 model.setRowNo( rowNo ); 295 296 return model; 297 } 298 299 /** 300 * 内部で使用する Set オブジェクトを作成します。 301 * Exception 以外では、必ず Set<String> オブジェクトを返します。 302 * 303 * @og.rev 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加 304 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。 305 * 306 * @param paramProcess データベースの接続先情報などを持っているオブジェクト 307 * @param dbid 接続先ID 308 * @param sql 実行するSQL文(検索系) 309 * 310 * @return 実行結果から取り出した、最初のカラムのみを集めた Setオブジェクト 311 * @throws RuntimeException データベース処理ができなかった場合。 312 */ 313 private Set<String> createSetData( final ParamProcess paramProcess, final String dbid, final String sql ) { 314 final Set<String> data = new HashSet<>(); 315 316 Connection connection = null; 317 318 try { 319 connection = paramProcess.getConnection( dbid ); 320 // 6.4.2.1 (2016/02/05) try-with-resources 文 321 try( final Statement stmt = connection.createStatement() ) { 322 if( fetchSize > 0 ) { stmt.setFetchSize( fetchSize ); } 323 if( stmt.execute( sql ) ) { // true:検索系 , false:更新系 324 try( final ResultSet resultSet = stmt.getResultSet() ) { 325 while( resultSet.next() ) { 326 sqlCount++ ; 327 final String str = resultSet.getString(1); 328 if( display ) { println( str ); } 329 data.add( str ); 330 } 331 } 332 } 333 else { 334 sqlCount += stmt.getUpdateCount(); 335 } 336 } 337 } 338 catch( final SQLException ex) { 339 final String errMsg = "SQL を実行できませんでした。" + CR 340 + "errMsg=[" + ex.getMessage() + "]" + CR 341 + "errorCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" + CR 342 + "DBID=" + dbid + CR 343 + "SQL =" + sql ; 344 345 throw new OgRuntimeException( errMsg,ex ); 346 } 347 finally { 348 349 ConnectionFactory.remove( connection,dbid ); 350 } 351 return data; 352 } 353 354 /** 355 * 内部で使用する Set オブジェクトを作成します。 356 * Exception 以外では、必ず Set<String[]> オブジェクトを返します。 357 * 358 * @og.rev 5.3.9.0 (2011/09/01) 1000件を超えた場合の処理を追加 359 * 360 * @param sql オリジナルのSQL文 361 * @param bulkKey 一括処理で置き換えるキー文字列 362 * @param bulkType 文字型(true)か、数字型(false)を指定 363 * @param setData 一括処理の元となるSetオブジェクト 364 * 365 * @return オリジナルのSQL文 に 一括処理の文字列と置換したSQL文の配列 366 */ 367 private String[] makeBulkQuery( final String sql, final String bulkKey, final boolean bulkType,final Set<String> setData ) { 368 String[] sqls = new String[ setData.size()/MAX_BULK_SET + 1 ]; 369 int idx = 0; 370 int cnt = 0; 371 372 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ); 373 String bulkVal = null; 374 if( bulkType ) { // 文字列の場合 375 for( final String key : setData ) { 376 cnt++; 377 buf.append( ",'" ).append( key ).append( '\'' ); 378 if( cnt >= MAX_BULK_SET ) { 379 bulkVal = buf.substring( 1 ); // 先頭のコロンをはずす 380 sqls[idx++] = sql.replace( "{@" + bulkKey + "}" ,bulkVal ); 381 cnt = 0; 382 buf.setLength(0); // 6.1.0.0 (2014/12/26) refactoring 383 } 384 } 385 if( cnt > 0 ) { // きっちりで終わらない場合 386 bulkVal = buf.substring( 1 ); // 先頭のコロンをはずす 387 sqls[idx] = sql.replace( "{@" + bulkKey + "}" ,bulkVal ); 388 } 389 } 390 else { // 数字の場合 391 for( final String key : setData ) { 392 cnt++; 393 buf.append( ',' ).append( key ); // 6.0.2.5 (2014/10/31) char を append する。 394 if( cnt >= MAX_BULK_SET ) { 395 bulkVal = buf.substring( 1 ); // 先頭のコロンをはずす 396 sqls[idx++] = sql.replace( "{@" + bulkKey + "}" ,bulkVal ); 397 cnt = 0; 398 buf.setLength(0); // 6.1.0.0 (2014/12/26) refactoring 399 } 400 } 401 if( cnt > 0 ) { // きっちりで終わらない場合 402 bulkVal = buf.substring( 1 ); // 先頭のコロンをはずす 403 sqls[idx] = sql.replace( "{@" + bulkKey + "}" ,bulkVal ); 404 } 405 } 406 407 return sqls; 408 } 409 410 /** 411 * プロセスの処理結果のレポート表現を返します。 412 * 処理プログラム名、入力件数、出力件数などの情報です。 413 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような 414 * 形式で出してください。 415 * 416 * @return 処理結果のレポート 417 */ 418 public String report() { 419 final String report = "[" + getClass().getName() + "]" + CR 420 + TAB + "Action : " + actionCmd + CR 421 + TAB + "DBID : " + dbid + CR 422 + TAB + "sqlCount : " + sqlCount + CR 423 + TAB + "setCount : " + setCount + CR 424 + TAB + "outCount : " + outCount ; 425 426 return report ; 427 } 428 429 /** 430 * このクラスの使用方法を返します。 431 * 432 * @return このクラスの使用方法 433 * @og.rtnNotNull 434 */ 435 public String usage() { 436 final StringBuilder buf = new StringBuilder( 1200 ) 437 .append( "Process_BulkQueryは、データベースから読み取った内容を、一括処理するために、" ).append( CR ) 438 .append( "ParamProcess のサブクラス(Process_DBParam)にセットしたり、加工したりする" ).append( CR ) 439 .append( "FirstProcess と、ChainProcess のインターフェースを両方持った、実装クラスです。" ).append( CR ) 440 .append( CR ) 441 .append( "このクラスは、上流から、下流への処理は、1度しか実行されません。" ).append( CR ) 442 .append( "FirstProcess の検索結果は、Set オブジェクトとして、Process_DBParam に渡します。" ).append( CR ) 443 .append( "ChainProcess は、その結果を取り出し、自分自身の処理結果と合せて加工します。" ).append( CR ) 444 .append( CR ) 445 .append( "FirstProcess では、-action は、query のみです。" ).append( CR ) 446 .append( " query は、指定のSQL文を実行し、結果のSetをParamProcessに設定します。" ).append( CR ) 447 .append( "ChainProcess では、-action は、query、bulkSet、minus、intersect が指定できます。" ).append( CR ) 448 .append( " query は、上記と同じです。" ).append( CR ) 449 .append( " minus は、先のSetから、SQL文の実行結果を引き算し、結果Setを再設定します。" ).append( CR ) 450 .append( " intersect は、先のSetから、SQL文の実行結果と重複する結果Setを再設定します。" ).append( CR ) 451 .append( " bulkSet は、先のSetを取り出し、SQL文に加味して処理します。" ).append( CR ) 452 .append( CR ) 453 .append( "流れ的には、query で検索し、minusまたはintersect でSetオブジェクトを加工し、" ).append( CR ) 454 .append( "bulkSet で利用します。例えば、ORACLEから、ユニークキーのSetを作成し、" ).append( CR ) 455 .append( "SQLServerのユニークキーをminusした結果を、ORACLEからDELETEすれば、不要な" ).append( CR ) 456 .append( "データを削除するなどの処理が実行可能になります。また、単純に、query だけを、" ).append( CR ) 457 .append( "チェインすれば、単発のUPDATE文を実行することが可能です。" ).append( CR ) 458 .append( CR ) 459 .append( "データベース接続先等は、ParamProcess のサブクラス(Process_DBParam)に" ).append( CR ) 460 .append( "設定された接続(Connection)を使用します。" ).append( CR ) 461 .append( CR ) 462 .append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。" ).append( CR ) 463 .append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に" ).append( CR ) 464 .append( "繋げてください。" ).append( CR ) 465 .append( CR ) 466 .append( "SQL文には、{@DATE.YMDH}等のシステム変数が使用できます。" ).append( CR ) 467 .append( CR ).append( CR ) 468 .append( getArgument().usage() ).append( CR ); 469 470 return buf.toString(); 471 } 472 473 /** 474 * このクラスは、main メソッドから実行できません。 475 * 476 * @param args コマンド引数配列 477 */ 478 public static void main( final String[] args ) { 479 LogWriter.log( new Process_BulkQuery().usage() ); 480 } 481}