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.xml; 017 018import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 019import org.opengion.fukurou.system.Closer; 020import org.opengion.fukurou.system.LogWriter; 021import static org.opengion.fukurou.system.HybsConst.CR; // 6.1.0.0 (2014/12/26) refactoring 022import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 023 024import java.io.Reader; 025import java.io.BufferedReader; 026import java.io.InputStreamReader; 027import java.io.FileInputStream; 028import java.util.Map; 029import java.util.List; 030import java.util.ArrayList; 031import java.util.regex.Pattern; 032import java.util.regex.Matcher; 033import java.util.Arrays; 034import java.util.Locale; 035 036import java.sql.DriverManager; 037import java.sql.Connection; 038import java.sql.Statement; 039import java.sql.PreparedStatement; 040import java.sql.ParameterMetaData; 041import java.sql.SQLException; 042 043/** 044 * このクラスは、オラクル XDKの oracle.xml.sql.dml.OracleXMLSave クラスと 045 * ほぼ同様の目的で使用できるクラスです。 046 * 拡張XDK形式のXMLファイルを読み込み、データベースに INSERT します。 047 * 048 * 拡張XDK形式の元となる オラクル XDK(Oracle XML Developer's Kit)については、以下の 049 * リンクを参照願います。 050 * <a href="http://otn.oracle.co.jp/software/tech/xml/xdk/index.html" target="_blank" > 051 * XDK(Oracle XML Developer's Kit)</a> 052 * 053 * このクラスでは、MAP を登録する[ setDefaultMap( Map ) ]ことにより、 054 * XMLファイルに存在しないカラムを初期値として設定することが可能になります。 055 * 例えば、登録日や、登録者、または、テンプレートより各システムID毎に 056 * 登録するなどです。 057 * 同様に、読み取った XMLファイルの情報を書き換える機能[ setAfterMap( Map ) ]メソッド 058 * により、カラムの値の置き換えも可能です。 059 * 060 * 拡張XDK形式の元となる オラクル XDK(Oracle XML Developer's Kit)については、以下の 061 * リンクを参照願います。 062 * <a href="http://otn.oracle.co.jp/software/tech/xml/xdk/index.html" target="_blank" > 063 * XDK(Oracle XML Developer's Kit)</a> 064 * 065 * 拡張XDK形式とは、ROW 以外に、SQL処理用タグ(EXEC_SQL)を持つ XML ファイルです。 066 * また、登録するテーブル(table)を ROWSETタグの属性情報として付与することができます。 067 * (大文字小文字に注意) 068 * これは、オラクルXDKで処理する場合、無視されますので、同様に扱うことが出来ます。 069 * この、EXEC_SQL は、それそれの XMLデータをデータベースに登録する際に、 070 * SQL処理を自動的に流す為の、SQL文を記載します。 071 * この処理は、イベント毎に実行される為、その配置順は重要です。 072 * このタグは、複数記述することも出来ますが、BODY部には、1つのSQL文のみ記述します。 073 * 074 * <ROWSET tableName="XX" > 075 * <EXEC_SQL> 最初に記載して、初期処理(データクリア等)を実行させる。 076 * delete from GEXX where YYYYY 077 * </EXEC_SQL> 078 * <MERGE_SQL> このSQL文で UPDATEして、結果が0件ならINSERTを行います。 079 * update GEXX set AA=[AA] , BB=[BB] where CC=[CC] 080 * </MERGE_SQL> 081 * <ROW num="1"> 082 * <カラム1>値1</カラム1> 083 * ・・・ 084 * <カラムn>値n</カラムn> 085 * </ROW> 086 * ・・・ 087 * <ROW num="n"> 088 * ・・・ 089 * </ROW> 090 * <EXEC_SQL> 最後に記載して、項目の設定(整合性登録)を行う。 091 * update GEXX set AA='XX' , BB='XX' where YYYYY 092 * </EXEC_SQL> 093 * <ROWSET> 094 * 095 * @og.rev 7.0.1.3 (2018/11/12) EXEC_SQLで、";" で複数のSQL文に分割、実行します。 096 * 097 * @version 7.0 098 * @author Kazuhiko Hasegawa 099 * @since JDK9.0, 100 */ 101public class HybsXMLSave implements TagElementListener { 102 103 private String tableName ; 104 // private String[] keyColumns ; //6.3.9.0 (2015/11/06) 現時点で使われていないため、一旦取り消しておきます。 105 private Connection connection ; 106 private PreparedStatement insPstmt ; // INSERT用の PreparedStatement 107 private PreparedStatement updPstmt ; // UPDATE用の PreparedStatement 108 private ParameterMetaData insMeta ; 109 private ParameterMetaData updMeta ; 110 private int insCnt ; 111 private int updCnt ; 112 private int delCnt ; 113 private int ddlCnt ; // 5.6.7.0 (2013/07/27) DDL文のカウンター 114 /** 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。 */ 115 private Map<String,String> defaultMap ; 116 /** 6.4.3.1 (2016/02/12) 作成元のMapを、HashMap から ConcurrentHashMap に置き換え。 */ 117 private Map<String,String> afterMap ; 118 private List<String> updClms ; 119 private String[] insClms ; 120 private String lastSQL ; // 5.6.6.1 (2013/07/12) デバッグ用。最後に使用したSQL文 121 122 private final boolean useParamMetaData ; // 4.0.0.0 (2007/09/25) 123 124 // UPDATE時の [XXX] を取り出します。\w は、単語構成文字: [a-zA-Z_0-9]と同じ 125 private static final Pattern PATTERN = Pattern.compile( "\\[\\w*\\]" ); // 6.4.1.1 (2016/01/16) pattern → PATTERN refactoring 126 127 // 5.6.9.2 (2013/10/18) EXEC_SQL のエラーを無視するかどうかを指定できます。 128 private boolean isExecErr = true; // 6.0.2.5 (2014/10/31) true は、エラー時に Exception を発行します。 129 130 // 7.3.2.0 (2021/03/19) エラーは無視するが、履歴は返します。 131 private final StringBuilder errBuf = new StringBuilder(); 132 133 /** 134 * コネクションを指定して、オブジェクトを構築します。 135 * テーブル名は、拡張XDK形式のROWSETタグのtableName属性に 136 * 記述しておく必要があります。 137 * 138 * @param conn データベース接続 139 */ 140 public HybsXMLSave( final Connection conn ) { 141 this( conn,null ); 142 } 143 144 /** 145 * コネクションとテーブル名を指定して、オブジェクトを構築します。 146 * ここで指定するテーブル名は、デフォルトテーブルという扱いです。 147 * 拡張XDK形式のROWSETタグのtableName属性にテーブル名が記述されている場合は、 148 * そちらが優先されます。 149 * 150 * @og.rev 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加。 151 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を このクラスで直接取得する。(PostgreSQL対応) 152 * 153 * @param conn データベース接続 154 * @param table テーブル名(ROWSETタグのtable属性が未設定時に使用) 155 */ 156 public HybsXMLSave( final Connection conn,final String table ) { 157 connection = conn; 158 tableName = table; 159 useParamMetaData = useParameterMetaData( connection ); // 5.3.8.0 (2011/08/01) 160 } 161 162 /** 163 * EXEC_SQL のエラー時に Exception を発行するかどうかを指定できます(初期値:true)。 164 * true を指定すると、エラー時には、 RuntimeException を throw します。 165 * false にすると、標準エラー出力にのみ、出力します。 166 * このフラグは、EXEC_SQL のみ有効です。それ以外のタブの処理では、エラーが発生すると 167 * その時点で、Exception を発行して、処理を終了します。 168 * 初期値は、true(Exception を発行する) です。 169 * 170 * @og.rev 5.6.9.2 (2013/10/18) 新規追加 171 * 172 * @param flag true:Exception を発行する/false:標準エラー出力に出力する 173 */ 174 public void onExecErrException( final boolean flag ) { 175 isExecErr = flag; // 6.0.2.5 (2014/10/31) refactoring 176 } 177 178 /** 179 * <ROWSET> タグの一番最初に呼び出されます。 180 * ROWSET の属性である、table 属性と、dbid 属性 を、TagElement の 181 * get メソッドで取得できます。 182 * 取得時のキーは、それぞれ、"TABLE" と "DBID" です。 183 * 184 * @param tag タグエレメント 185 * @see org.opengion.fukurou.xml.TagElement 186 * @see HybsXMLHandler#setTagElementListener( TagElementListener ) 187 */ 188 @Override 189 public void actionInit( final TagElement tag ) { 190 final String table = tag.get( "tableName" ); 191 if( table != null ) { tableName = table; } 192 } 193 194 /** 195 * <ROW> タグの endElement 処理毎に呼び出されます。 196 * この Listener をセットすることにより、行データを取得都度、 197 * TagElement オブジェクトを作成し、このメソッドが呼び出されます。 198 * 199 * @og.rev 4.0.0.0 (2007/05/09) ParameterMetaData を使用したパラメータ設定追加。 200 * @og.rev 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更 201 * @og.rev 4.3.7.0 (2009/06/01) HSQLDB対応 202 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData setNull 対応(PostgreSQL対応) 203 * @og.rev 5.6.6.1 (2013/07/12) lastSQL 対応。デバッグ用に、最後に使用したSQL文を残します。 204 * 205 * @param tag タグエレメント 206 * @see org.opengion.fukurou.xml.TagElement 207 * @see HybsXMLHandler#setTagElementListener( TagElementListener ) 208 */ 209 @Override 210 public void actionRow( final TagElement tag ) { 211 tag.setAfterMap( afterMap ); 212 213 String[] vals = null; // 5.6.6.1 (2013/07/12) デバッグ用 214 try { 215 // 更新SQL(MERGE_SQLタグ)が存在する場合の処理 216 int tempCnt = 0; 217 if( updPstmt != null ) { 218 vals = tag.getValues( updClms ); // 5.6.6.1 (2013/07/12) デバッグ用 219 for( int j=0; j<vals.length; j++ ) { 220 // 4.3.7.0 (2009/06/01) HSQLDB対応。空文字の場合nullに置換え 221 if( vals[j] != null && vals[j].isEmpty() ){ 222 vals[j] = null; 223 } 224 225 // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加 226 if( useParamMetaData ) { 227 final int type = updMeta.getParameterType( j+1 ); 228 // 5.3.8.0 (2011/08/01) setNull 対応 229 final String val = vals[j]; 230 if( val == null || val.isEmpty() ) { 231 updPstmt.setNull( j+1, type ); 232 } 233 else { 234 updPstmt.setObject( j+1, val, type ); 235 } 236 } 237 else { 238 updPstmt.setObject( j+1,vals[j] ); 239 } 240 } 241 tempCnt = updPstmt.executeUpdate(); 242 if( tempCnt > 1 ) { 243 final String errMsg = "Update キーが重複しています。" 244 + "TABLE=[" + tableName + "] ROW=[" 245 + tag.getRowNo() + "]" + CR 246 + " SQL=[" + lastSQL + "]" + CR // 5.6.6.1 (2013/07/12) デバッグ用 247 + tag.toString() + CR 248 + Arrays.toString( vals ) + CR ; // 5.6.6.1 (2013/07/12) デバッグ用 249 throw new OgRuntimeException( errMsg ); 250 } 251 updCnt += tempCnt; 252 } 253 // 更新が 0件の場合は、INSERT処理を行います。 254 if( tempCnt == 0 ) { 255 // 初回INSERT時のタグより、DB登録SQL文を構築します。 256 if( insPstmt == null ) { 257 insClms = tag.getKeys(); 258 lastSQL = insertSQL( insClms,tableName ); // 5.6.6.1 (2013/07/12) デバッグ用 259 insPstmt = connection.prepareStatement( lastSQL ); 260 // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加 261 if( useParamMetaData ) { insMeta = insPstmt.getParameterMetaData(); } 262 } 263 vals = tag.getValues( insClms ); // 5.6.6.1 (2013/07/12) デバッグ用 264 for( int j=0; j<vals.length; j++ ) { 265 // 4.3.7.0 (2009/06/01) HSQLDB対応。空文字の場合nullに置換え 266 if( vals[j] != null && vals[j].isEmpty() ){ 267 vals[j] = null; 268 } 269 270 // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加 271 if( useParamMetaData ) { 272 final int type = insMeta.getParameterType( j+1 ); 273 // 5.3.8.0 (2011/08/01) setNull 対応 274 final String val = vals[j]; 275 if( val == null || val.isEmpty() ) { 276 insPstmt.setNull( j+1, type ); 277 } 278 else { 279 insPstmt.setObject( j+1, val, type ); 280 } 281 } 282 else { 283 insPstmt.setObject( j+1,vals[j] ); 284 } 285 } 286 insCnt += insPstmt.executeUpdate(); 287 } 288 } 289 catch( final SQLException ex ) { 290 final String errMsg = "DB登録エラーが発生しました。" 291 + "TABLE=[" + tableName + "] ROW=[" 292 + tag.getRowNo() + "]" + CR 293 + " SQL=[" + lastSQL + "]" + CR // 5.6.6.1 (2013/07/12) デバッグ用 294 + tag.toString() + CR 295 + Arrays.toString( vals ) + CR // 5.6.6.1 (2013/07/12) デバッグ用 296 + ex.getMessage() + ":" + ex.getSQLState() + CR ; 297 throw new OgRuntimeException( errMsg,ex ); 298 } 299 } 300 301 /** 302 * <EXEC_SQL> タグの endElement 処理毎に呼び出されます。 303 * getBody メソッドを使用して、このタグのBODY部の文字列を取得します。 304 * この Listener をセットすることにより、EXEC_SQL データを取得都度、 305 * TagElement オブジェクトを作成し、このメソッドが呼び出されます。 306 * EXEC_SQL タグでは、delete文やupdate文など、特殊な前処理や後処理用の SQLと 307 * DDL(データ定義言語:Data Definition Language)の処理なども記述できます。 308 * ここでは簡易的に、何か実行された場合は、delete 処理と考え、削除カウントを加算し、 309 * 0件で帰ってきた場合に、DDLが実行されたと考え、DDLカウントを+1します。 310 * ただし、0件 delete も考えられるため、SQL文の先頭文字によるチェックは入れておきます。 311 * 312 * @og.rev 5.6.6.1 (2013/07/12) lastSQL 対応。デバッグ用に、最後に使用したSQL文を残します。 313 * @og.rev 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加 314 * @og.rev 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行するかどうかを指定 315 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。 316 * @og.rev 7.0.1.3 (2018/11/12) EXEC_SQLで、";" で複数のSQL文に分割、実行します。 317 * 318 * @param tag タグエレメント 319 * @see org.opengion.fukurou.xml.TagElement 320 * @see HybsXMLHandler#setTagElementListener( TagElementListener ) 321 */ 322 @Override 323 public void actionExecSQL( final TagElement tag ) { 324 // 6.4.2.1 (2016/02/05) try-with-resources 文 325 lastSQL = tag.getBody(); // 5.6.6.1 (2013/07/12) デバッグ用 6.4.2.1 (2016/02/05) try の前に出します。 326 try( Statement execSQL = connection.createStatement() ) { 327// lastSQL = tag.getBody(); // 5.6.6.1 (2013/07/12) デバッグ用 328 // 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加 329 // 7.0.1.3 (2018/11/12) EXEC_SQLで、";" で複数のSQL文に分割、実行します。 330 final String[] sqls = getExecSQLs( lastSQL ) ; 331 for( final String sql : sqls ) { 332 final int cnt = execSQL.executeUpdate( sql ) ; 333 334 // 件数カウント用 335 final String upSQL = sql.trim().toUpperCase( Locale.JAPAN ); 336 if( upSQL.startsWith( "DELETE" ) ) { delCnt += cnt; } 337 else if( upSQL.startsWith( "INSERT" ) ) { insCnt += cnt; } 338 else if( upSQL.startsWith( "UPDATE" ) ) { updCnt += cnt; } 339 else { ddlCnt ++ ; } // DLLの場合は、件数=0が返される。 340 } 341 342// final int cnt = execSQL.executeUpdate( lastSQL ) ; 343// if( cnt > 0 ) { delCnt += cnt; } // 件数が返れば、DDLでないため、削除数を加算 344// else { 345// final String sql = lastSQL.trim().toUpperCase( Locale.JAPAN ); 346// if( !sql.startsWith( "DELETE" ) && !sql.startsWith( "INSERT" ) && !sql.startsWith( "UPDATE" ) ) { 347// ddlCnt ++ ; 348// } 349// } 350 } 351 catch( final SQLException ex ) { // catch は、close() されてから呼ばれます。 352 final String errMsg = "DB登録エラーが発生しました。" 353 + "TABLE=[" + tableName + "] ROW=[" 354 + tag.getRowNo() + "]" + CR 355 + " SQL=[" + lastSQL + "]" + CR // 5.6.6.1 (2013/07/12) デバッグ用 356 + tag.toString() + CR 357 + ex.getMessage() + ":" + ex.getSQLState() + CR ; 358 359 // 5.6.9.2 (2013/10/18) EXEC_SQL のエラー時に Exception を発行するかどうかを指定 360 if( isExecErr ) { // 6.0.2.5 (2014/10/31) refactoring 361 throw new OgRuntimeException( errMsg,ex ); 362 } 363 else { 364 System.err.println( errMsg ); 365 errBuf.append( errMsg ); 366 } 367 } 368 } 369 370 /** 371 * EXEC_SQLで、";" で複数のSQL文に分割します。 372 * 373 * 厳密に処理していません。 374 * SQL文の中に文字として";"が使われている場合の考慮がされていません。 375 * 376 * 7.3.2.0 (2021/03/19) 377 * 暫定的に、BEGIN~END 構文(大文字のみ)を持つ場合は、";" で複数のSQL文に分割しません。 378 * 379 * @og.rev 7.0.1.3 (2018/11/12) EXEC_SQLで、";" で複数のSQL文に分割、実行します。 380 * @og.rev 7.3.2.0 (2021/03/19) TRIGGER など、BEGIN~END 構文を持つ場合は、";" で複数のSQL文に分割しません。 381 * 382 * @param sqlText EXEC_SQL内部に書かれたSQL文 383 * 384 * @return 分割されたSQL文の配列 385 */ 386 private String[] getExecSQLs( final String sqlText ) { 387 if( sqlText.contains( "BEGIN" ) && sqlText.contains( "END" ) ) { // 7.3.2.0 (2021/03/19) 388 return new String[] { sqlText }; 389 } 390 else { 391 return sqlText.split( ";" ); 392 } 393 } 394 395 /** 396 * <MERGE_SQL> タグの endElement 処理時に呼び出されます。 397 * getBody メソッドを使用して、このタグのBODY部の文字列を取得します。 398 * MERGE_SQLタグは、マージ処理したいデータ部よりも上位に記述しておく 399 * 必要がありますが、中間部に複数回記述しても構いません。 400 * このタグが現れるまでは、INSERT のみ実行されます。このタグ以降は、 401 * 一旦 UPDATE し、結果が 0件の場合は、INSERTする流れになります。 402 * 完全に INSERT のみであるデータを前半に、UPDATE/INSERTを行う 403 * データを後半に、その間に、MERGE_SQL タグを入れることで、無意味な 404 * UPDATE を避けることが可能です。 405 * この Listener をセットすることにより、MERGE_SQL データを取得都度、 406 * TagElement オブジェクトを作成し、このメソッドが呼び出されます。 407 * 408 * @og.rev 4.0.0.0 (2007/05/09) ParameterMetaData を使用したパラメータ設定追加。 409 * @og.rev 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更 410 * @og.rev 5.6.6.1 (2013/07/12) lastSQL 対応。デバッグ用に、最後に使用したSQL文を残します。 411 * 412 * @param tag タグエレメント 413 * @see org.opengion.fukurou.xml.TagElement 414 * @see HybsXMLHandler#setTagElementListener( TagElementListener ) 415 */ 416 @Override 417 public void actionMergeSQL( final TagElement tag ) { 418 if( updPstmt != null ) { 419 final String errMsg = "MERGE_SQLタグが、複数回記述されています。" 420 + "TABLE=[" + tableName + "] ROW=[" 421 + tag.getRowNo() + "]" + CR 422 + " SQL=[" + lastSQL + "]" + CR // 5.6.6.1 (2013/07/12) デバッグ用 423 + tag.toString() + CR; 424 throw new OgRuntimeException( errMsg ); 425 } 426 427 final String orgSql = tag.getBody(); 428 final Matcher matcher = PATTERN.matcher( orgSql ); 429 updClms = new ArrayList<>(); 430 while( matcher.find() ) { 431 // ここでは、[XXX]にマッチする為、前後の[]を取り除きます。 432 updClms.add( orgSql.substring( matcher.start()+1,matcher.end()-1 ) ); 433 } 434 lastSQL = matcher.replaceAll( "?" ); // 5.6.6.1 (2013/07/12) デバッグ用 435 436 try { 437 updPstmt = connection.prepareStatement( lastSQL ); 438 // 4.0.0.0 (2007/09/25) ParameterMetaData を使用したパラメータ設定追加 439 if( useParamMetaData ) { updMeta = updPstmt.getParameterMetaData(); } 440 } 441 catch( final SQLException ex ) { 442 final String errMsg = "Statement作成時にエラーが発生しました。" 443 + "TABLE=[" + tableName + "] ROW=[" 444 + tag.getRowNo() + "]" + CR 445 + " SQL=[" + lastSQL + "]" + CR // 5.6.6.1 (2013/07/12) デバッグ用 446 + tag.toString() + CR 447 + ex.getMessage() + ":" + ex.getSQLState() + CR ; 448 throw new OgRuntimeException( errMsg,ex ); 449 } 450 } 451 452 // /** 453 // * UPDATE,DELETE を行う場合の WHERE 条件になるキー配列 454 // * このキーの AND 条件でカラムを特定し、UPDATE,DELETE などの処理を 455 // * 行います。 456 // * 457 // * @og.rev 6.3.9.0 (2015/11/06) 現時点で使われていないため、一旦取り消しておきます。 458 // * 459 // * @param keyCols WHERE条件になるキー配列(可変長引数) 460 // */ 461 // public void setKeyColumns( final String... keyCols ) { 462 // keyColumns = new String[keyCols.length]; 463 // System.arraycopy( keyCols,0,keyColumns,0,keyColumns.length ); 464 // } 465 466 /** 467 * XMLファイルを読み取る前に指定するカラムと値のペア(マップ)情報をセットします。 468 * 469 * このカラムと値のペアのマップは、オブジェクト構築前に設定される為、 470 * XMLファイルにキーが存在している場合は、値が書き変わります。(XML優先) 471 * XMLファイルにキーが存在していない場合は、ここで指定するMapの値が 472 * 初期設定値として使用されます。 473 * ここで指定する Map に LinkedHashMap を使用する場合、カラム順も 474 * 指定することが出来ます。 475 * 476 * @param map 初期設定するカラムデータマップ 477 * @see #setAfterMap( Map ) 478 */ 479 public void setDefaultMap( final Map<String,String> map ) { defaultMap = map; } 480 481 /** 482 * XMLファイルを読み取った後で指定するカラムと値のペア(マップ)情報をセットします。 483 * 484 * このカラムと値のペアのマップは、オブジェクト構築後に設定される為、 485 * XMLファイルのキーの存在に関係なく、Mapのキーと値が使用されます。(Map優先) 486 * null を設定した場合は、なにも処理されません。 487 * 488 * @param map 後設定するカラムデータマップ 489 * @see #setDefaultMap( Map ) 490 */ 491 public void setAfterMap( final Map<String,String> map ) { afterMap = map; } 492 493 /** 494 * データベースに追加処理(INSERT)を行います。 495 * 496 * 先に指定されたコネクションを用いて、指定のテーブルに INSERT します。 497 * 引数には、XMLファイルを指定したリーダーをセットします。 498 * コネクションは、終了後、コミットされます。(close されません。) 499 * リーダーのクローズは、ここでは行っていません。 500 * 501 * @og.rev 5.1.1.0 (2009/11/11) insMeta , updMeta のクリア(気休め) 502 * 503 * @param reader XMLファイルを指定するリーダー 504 */ 505 public void insertXML( final Reader reader ) { 506 try { 507 final HybsXMLHandler handler = new HybsXMLHandler(); 508 handler.setTagElementListener( this ); 509 handler.setDefaultMap( defaultMap ); 510 511 handler.parse( reader ); 512 } 513 finally { 514 Closer.stmtClose( insPstmt ); 515 Closer.stmtClose( updPstmt ); 516 insPstmt = null; 517 updPstmt = null; 518 insMeta = null; // 5.1.1.0 (2009/11/11) 519 updMeta = null; // 5.1.1.0 (2009/11/11) 520 } 521 } 522 523 /** 524 * インサート用のSQL文を作成します。 525 * 526 * @og.rev 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。 527 * 528 * @param columns インサートするカラム名 529 * @param tableName インサートするテーブル名 530 * 531 * @return インサート用のSQL文 532 * @og.rtnNotNull 533 */ 534 private String insertSQL( final String[] columns,final String tableName ) { 535 if( tableName == null ) { 536 final String errMsg = "tableName がセットされていません。" + CR 537 + "tableName は、コンストラクタで指定するか、ROWSETのtableName属性で" 538 + "指定しておく必要があります" + CR ; 539 throw new OgRuntimeException( errMsg ); 540 } 541 542 // 6.0.2.5 (2014/10/31) char を append する。 543 // 6.2.3.0 (2015/05/01) CSV形式の作成を、String#join( CharSequence , CharSequence... )を使用。 544 final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE ) 545 .append( "INSERT INTO " ).append( tableName ) 546 .append( " ( " ) 547 .append( String.join( "," , columns ) ) // 6.2.3.0 (2015/05/01) 548 .append( " ) VALUES ( ?" ); 549 for( int i=1; i<columns.length; i++ ) { 550 sql.append( ",?" ); 551 } 552 sql.append( " )" ); 553 554 return sql.toString(); 555 } 556 557 /** 558 * データベースに追加した件数を返します。 559 * 560 * @return 登録件数 561 */ 562 public int getInsertCount() { return insCnt; } 563 564 /** 565 * データベースを更新した件数を返します。 566 * これは、拡張XDK形式で、MERGE_SQL タグを使用した場合の更新処理件数を 567 * 合計した値を返します。 568 * 569 * @return 更新件数 570 */ 571 public int getUpdateCount() { return updCnt; } 572 573 /** 574 * データベースに変更(更新、削除を含む)した件数を返します。 575 * これは、拡張XDK形式で、EXEC_SQL タグを使用した場合の実行件数を合計した 576 * 値を返します。 577 * よって、更新か、追加か、削除かは、判りませんが、通常 登録前に削除する 578 * ケースで使われることから、deleteCount としています。 579 * 580 * @return 変更件数(主に、削除件数) 581 */ 582 public int getDeleteCount() { return delCnt; } 583 584 /** 585 * データベースにDDL(データ定義言語:Data Definition Language)処理した件数を返します。 586 * これは、拡張XDK形式で、EXEC_SQL タグを使用した場合の実行件数を合計した 587 * 値を返します。 588 * EXEC_SQL では、登録前に削除する delete 処理も、EXEC_SQL タグを使用して実行しますが 589 * その処理と分けてカウントします。 590 * 591 * @og.rev 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加 592 * 593 * @return DDL(データ定義言語:Data Definition Language)処理した件数 594 */ 595 public int getDDLCount() { return ddlCnt; } 596 597 /** 598 * 実際に登録された テーブル名を返します。 599 * 600 * テーブル名は、拡張XDK形式のROWSETタグのtableName属性に 601 * 記述しておくか、コンストラクターで引数として渡します。 602 * 両方指定された場合は、ROWSETタグのtableName属性が優先されます。 603 * ここでの返り値は、実際に使用された テーブル名です。 604 * 605 * @return テーブル名 606 */ 607 public String getTableName() { return tableName; } 608 609 /** 610 * isExecErr でfalseを指定した場合に、エラー内容の文字列を取り出します。 611 * エラーが発生しなかった場合は、ゼロ文字列が返ります。 612 * 613 * @og.rev 7.3.2.0 (2021/03/19) isExecErr でfalseを指定した場合に、エラー内容の文字列を取り出します。 614 * 615 * @return エラー内容の文字列 616 */ 617 public String getErrorMessage() { return errBuf.toString(); } 618 619 /** 620 * この接続が、PreparedStatement#getParameterMetaData() を使用するかどうかを判定します。 621 * 本来は、ConnectionFactory#useParameterMetaData(String)を使うべきだが、dbid が無いため、直接取得します。 622 * 623 * ※ 6.1.0.0 (2014/12/26) で、直接取得に変更します。DBUtil 経由で取得する方が、ソースコードレベルでの 624 * 共通化になるので良いのですが、org.opengion.fukurou.db と、org.opengion.fukurou.xml パッケージが 625 * 循環参照(相互参照)になるため、どちらかを切り離す必要があります。 626 * db パッケージ側では、DBConfig.xml の処理の関係で、org.opengion.fukurou.xml.DomParser を 627 * 使っているため、こちらの処理を、内部処理に変更することで、対応します。 628 * 629 * @og.rev 5.3.8.0 (2011/08/01) 新規作成 ( ApplicationInfo#useParameterMetaData(Connection) からコピー ) 630 * @og.rev 5.6.7.0 (2013/07/27) dbProductName は、DBUtil 経由で取得する。 631 * @og.rev 6.1.0.0 (2014/12/26) dbProductName は、DBUtil 経由ではなく、直接取得する。 632 * 633 * @param conn 接続先(コネクション) 634 * 635 * @return 使用する場合:true / その他:false 636 */ 637 private static boolean useParameterMetaData( final Connection conn ) { 638 639 String dbName ; 640 try { 641 dbName = conn.getMetaData().getDatabaseProductName().toLowerCase( Locale.JAPAN ); 642 } 643 catch( final SQLException ex ) { 644 dbName = "none"; 645 } 646 647 return "PostgreSQL".equalsIgnoreCase( dbName ) ; 648 } 649 650 /** 651 * テスト用のメインメソッド 652 * 653 * Usage: java org.opengion.fukurou.xml.HybsXMLSave USER PASSWD URL TABLE FILE [ENCODE] [DRIVER] 654 * USER : DB接続ユーザー(GE) 655 * PASSWD : DB接続パスワード(GE) 656 * URL : DB接続JDBCドライバURL(jdbc:oracle:thin:@localhost:1521:HYBS 657 * TABLE : 登録するテーブルID(GE21) 658 * FILE : 登録するORACLE XDK 形式 XMLファイル(GE21.xml) 659 * [ENCODE]: ファイルのエンコード 初期値:UTF-8 660 * [DRIVER]: JDBCドライバー 初期値:oracle.jdbc.OracleDriver 661 * 662 * ※ ファイルが存在しなかった場合、FileNotFoundException を RuntimeException に変換して、throw します。 663 * ※ 指定のエンコードが存在しなかった場合、UnsupportedEncodingException を RuntimeException に変換して、throw します。 664 * 665 * @og.rev 5.1.1.0 (2009/12/01) MySQL対応 明示的に、TRANSACTION_READ_COMMITTED を指定する。 666 * @og.rev 5.6.7.0 (2013/07/27) DDL(データ定義言語:Data Definition Language)の処理件数追加 667 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。 668 * 669 * @param args コマンド引数配列 670 * @throws ClassNotFoundException クラスを見つけることができなかった場合。 671 * @throws SQLException データベース接続エラーが発生した場合。 672 */ 673 public static void main( final String[] args ) 674 throws ClassNotFoundException , SQLException { 675 if( args.length < 5 ) { 676 LogWriter.log( "Usage: java org.opengion.fukurou.xml.HybsXMLSave USER PASSWD URL TABLE FILE [ENCODE] [DRIVER]" ); 677 LogWriter.log( " USER : DB接続ユーザー(GE)" ); 678 LogWriter.log( " PASSWD: DB接続パスワード(GE)" ); 679 LogWriter.log( " URL : DB接続JDBCドライバURL(jdbc:oracle:thin:@localhost:1521:HYBS)" ); 680 LogWriter.log( " TABLE : 登録するテーブルID(GE21)" ); 681 LogWriter.log( " FILE : 登録するORACLE XDK 形式 XMLファイル(GE21.xml)" ); 682 LogWriter.log( " [ ENCODE: ファイルのエンコード 初期値:UTF-8 ]" ); 683 LogWriter.log( " [ DRIVER: JDBCドライバー 初期値:oracle.jdbc.OracleDriver ]" ); 684 return ; 685 } 686 687 final String user = args[0] ; 688 final String passwd = args[1] ; 689 final String url = args[2] ; 690 final String table = args[3] ; 691 final String file = args[4] ; 692 final String encode = ( args.length == 6 ) ? args[5] : "UTF-8" ; 693 final String driver = ( args.length == 7 ) ? args[6] : "oracle.jdbc.OracleDriver" ; 694 695 Class.forName(driver); 696 697 int insCnt; 698 int updCnt; 699 int delCnt; 700 int ddlCnt; // 5.6.7.0 (2013/07/27) DDL処理件数追加 701 // 6.4.2.1 (2016/02/05) try-with-resources 文 702 try( Connection conn = DriverManager.getConnection( url,user,passwd ) ) { 703 conn.setAutoCommit( false ); 704 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 5.1.1.0 (2009/12/01) 705 final HybsXMLSave save = new HybsXMLSave( conn,table ); 706 707 // 6.4.2.1 (2016/02/05) try-with-resources 文 708 try( Reader reader = new BufferedReader(new InputStreamReader(new FileInputStream( file ) ,encode ) ) ) { 709 save.insertXML( reader ); 710 insCnt = save.getInsertCount(); 711 updCnt = save.getUpdateCount(); 712 delCnt = save.getDeleteCount(); 713 ddlCnt = save.getDDLCount(); // 5.6.7.0 (2013/07/27) DDL処理件数追加 714 } 715 // FileNotFoundException , UnsupportedEncodingException 716 catch( final java.io.FileNotFoundException ex ) { // catch は、close() されてから呼ばれます。 717 final String errMsg = "ファイルが存在しません。" + ex.getMessage() 718 + CR + "Table=[" + table + "] File =[" + file + "]" ; 719 throw new OgRuntimeException( errMsg,ex ); 720 } 721 catch( final java.io.UnsupportedEncodingException ex ) { // catch は、close() されてから呼ばれます。 722 final String errMsg = "指定のエンコードが存在しません。" + ex.getMessage() 723 + CR + "Table=[" + table + "] Encode =[" + encode + "]" ; 724 throw new OgRuntimeException( errMsg,ex ); 725 } 726 catch( final java.io.IOException ex ) { // catch は、close() されてから呼ばれます。 727 final String errMsg = "ファイル読み込み処理でエラーが発生しました。" + ex.getMessage() 728 + CR + "Table=[" + table + "] File =[" + file + "]" ; 729 throw new OgRuntimeException( errMsg,ex ); 730 } 731 Closer.commit( conn ); 732 } 733 734 System.out.println( "XML File[" + file + "] Into [" + table + "] Table" ); 735 System.out.println( " Insert Count : [" + insCnt + "]" ); 736 System.out.println( " Update Count : [" + updCnt + "]" ); 737 System.out.println( " Delete Count : [" + delCnt + "]" ); 738 System.out.println( " DDL Count : [" + ddlCnt + "]" ); 739 } 740}