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.util.List; 019import java.util.ArrayList; 020import java.util.Locale ; 021import java.util.Arrays ; 022import java.util.Set ; 023import java.util.HashSet ; 024import java.util.LinkedHashSet ; 025import java.util.StringJoiner ; 026 027import org.opengion.fukurou.util.StringUtil; 028import org.opengion.fukurou.system.OgBuilder ; 029import org.opengion.fukurou.system.OgRuntimeException ; 030import static org.opengion.fukurou.system.HybsConst.CR; 031import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; 032 033/** 034 * QueryMaker は、カラム名などから、SELECT,INSERT,UPDATE,DALETE 文字列を作成するクラスです。 035 * 036 * 基本的には、カラム名と、それに対応する値のセットで、QUERY文を作成します。 037 * 値には、[カラム名] が使用でき、出力される値として、? が使われます。 038 * これは、PreparedStatement に対する引数で、処理を行うためです。 039 * この[カラム名]のカラム名は、検索された側のカラム名で、INSERT/UPDATE/DELETE等が実行される 040 * データベース(テーブル)のカラム名ではありません。(偶然、一致しているかどうかは別として) 041 * 042 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 043 * 044 * @version 6.8.6.0 (2018/01/19) 045 * @author Kazuhiko Hasegawa 046 * @since JDK6.0, 047 */ 048public class QueryMaker { 049 private static final String QUERY_TYPE = "SELECT,INSERT,UPDATE,DELETE,MERGE" ; 050 051 private final List<String> whrList = new ArrayList<>() ; // where条件に含まれる [カラム名] のリスト(パラメータ一覧) 052 053 private String queryType ; // QUERYタイプ(SELECT,INSERT,UPDATE,DELETE,MERGE) を指定します。 054 private String table ; 055 private String names ; 056 private String omitNames ; 057 private String where ; 058 private String whrNames ; 059 private String orderBy ; 060 private String cnstKeys ; 061 private String cnstVals ; 062 063 private int clmLen; // names カラムの "?" に置き換えられる個数 064 private boolean isSetup ; // セットアップ済みを管理しておきます。 065 private String[] nameAry; 066 067 /** 068 * デフォルトコンストラクター 069 * 070 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 071 */ 072 public QueryMaker() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 073 074 /** 075 * 処理の前に、入力データの整合性チェックや、初期設定を行います。 076 * 077 * あまり、何度も実行したくないので、フラグ管理しておきます。 078 * 079 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 080 * @og.rev 6.9.0.2 (2018/02/13) omitNamesの対応 081 */ 082 public void setup() { 083 if( isSetup ) { return; } // セットアップ済み 084 085 if( StringUtil.isNull( table ) ) { 086 final String errMsg = "指定の table に、null、ゼロ文字列は指定できません。" 087 + " table=" + table ; 088 throw new OgRuntimeException( errMsg ); 089 } 090 091 if( StringUtil.isNull( names ) ) { 092 final String errMsg = "指定の names に、null、ゼロ文字列は指定できません。" 093 + " names=" + names ; 094 throw new OgRuntimeException( errMsg ); 095 } 096 097 // 6.9.0.2 (2018/02/13) omitNamesの対応 098 final String[] nmAry = StringUtil.csv2Array( names ); 099 final Set<String> nmSet = new LinkedHashSet<>( Arrays.asList( nmAry ) ); // names の順番は、キープします。 100 final String[] omtAry = StringUtil.csv2Array( omitNames ); 101 final Set<String> omtSet = new HashSet<>( Arrays.asList( omtAry ) ); // 除外する順番は、問いません。 102 nmSet.removeAll( omtSet ); 103 104 // 初期設定 105 clmLen = nmSet.size(); 106 nameAry = nmSet.toArray( new String[clmLen] ); 107 108// // 初期設定 109// nameAry = StringUtil.csv2Array( names ); 110// clmLen = nameAry.length; 111 112 // [カラム名] List は、whereNames + where の順番です。(whrListの登録順を守る必要がある) 113 // where条件も、この順番に連結しなければなりません。 114 where = StringUtil.join( " AND " , whrNames , formatSplit( where ) ); // formatSplit で、whrListの登録を行っている。 115 116 isSetup = true; 117 } 118 119 /** 120 * データを検索する場合に使用するSQL文を作成します。 121 * 122 * SELECT names FROM table WHERE where ORDER BY orderBy ; 123 * 124 * cnstKeys,cnstVals は、使いません。 125 * where,orderBy は、それぞれ、値が存在しない場合は、設定されません。 126 * 127 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 128 * @og.rev 6.9.0.2 (2018/02/13) omitNamesの対応 129 * 130 * @return 検索SQL 131 * @og.rtnNotNull 132 */ 133 public String getSelectSQL() { 134 if( !"SELECT".equals( queryType ) ) { 135 final String errMsg = "指定のQUERYタイプと異なるSQL文を要求しています。" + CR 136 + " 要求SQL=SELECT queryType=" + queryType ; 137 throw new OgRuntimeException( errMsg ); 138 } 139 140 setup(); 141 142 return new OgBuilder() 143// .append( "SELECT " , names ) 144 .append( "SELECT " ) 145 .join( "," , nameAry ) // 6.9.0.2 (2018/02/13) names ではなく、omitNames後のカラム配列を使用します。 146 .append( " FROM " , table ) 147 .appendNN( " WHERE " , where ) // nullなら、追加しない。where + whereNames 148 .appendNN( " ORDER BY " , orderBy ) // nullなら、追加しない。 149 .toString(); 150 } 151 152 /** 153 * データを追加する場合に使用するSQL文を作成します。 154 * 155 * INSERT INTO table ( names,cnstKeys ) VALUES ( values,cnstVals ) ; 156 * 157 * cnstKeys,cnstVals は、INSERTカラムとして使います。 158 * where,orderBy は、使いません。 159 * 160 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 161 * @og.rev 6.9.0.2 (2018/02/13) omitNamesの対応 162 * 163 * @return 追加SQL 164 * @og.rtnNotNull 165 */ 166 public String getInsertSQL() { 167 if( !"INSERT".equals( queryType ) && !"MERGE".equals( queryType ) ) { 168 final String errMsg = "指定のQUERYタイプと異なるSQL文を要求しています。" + CR 169 + " 要求SQL=INSERT queryType=" + queryType ; 170 throw new OgRuntimeException( errMsg ); 171 } 172 173 setup(); 174 175 return new OgBuilder() 176 .append( "INSERT INTO " ).append( table ) 177// .append( " ( " ).append( names ) 178 .append( " ( " ) 179 .join( "," , nameAry ) // 6.9.0.2 (2018/02/13) names ではなく、omitNames後のカラム配列を使用します。 180 .appendNN( "," , cnstKeys ) 181 .append( " ) VALUES ( " ) 182 .appendRoop( 0,clmLen,",",i -> "?" ) 183 .appendNN( "," , cnstVals ) 184 .append( " )" ) 185 .toString(); 186 } 187 188 /** 189 * データを更新する場合に使用するSQL文を作成します。 190 * 191 * UPDATE table SET names[i]=values[i], ・・・cnstKeys[i]=cnstVals[i], ・・・ WHERE where; 192 * 193 * cnstKeys,cnstVals は、UPDATEカラムとして使います。 194 * orderBy は、使いません。 195 * 196 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 197 * 198 * @return 更新SQL 199 * @og.rtnNotNull 200 */ 201 public String getUpdateSQL() { 202 if( !"UPDATE".equals( queryType ) && !"MERGE".equals( queryType ) ) { 203 final String errMsg = "指定のQUERYタイプと異なるSQL文を要求しています。" + CR 204 + " 要求SQL=UPDATE queryType=" + queryType ; 205 throw new OgRuntimeException( errMsg ); 206 } 207 208 setup(); 209 210 final String[] cnKey = StringUtil.csv2Array( cnstKeys ); 211 final String[] cnVal = StringUtil.csv2Array( cnstVals ); 212 213 // 整合性チェック 214 if( cnKey != null && cnVal == null || 215 cnKey == null && cnVal != null || 216 cnKey != null && cnVal != null && cnKey.length != cnVal.length ) { 217 final String errMsg = "指定の keys,vals には、null、ゼロ件配列、または、個数違いの配列は指定できません。" 218 + " keys=" + cnstKeys 219 + " vals=" + cnstVals ; 220 throw new OgRuntimeException( errMsg ); 221 } 222 223 return new OgBuilder() 224 .append( "UPDATE " ).append( table ) 225 .append( " SET " ) 226 .appendRoop( 0,clmLen ,",",i -> nameAry[i] + "=?" ) 227 .appendRoop( 0,cnVal.length,",",i -> cnKey[i] + "=" + cnVal[i] ) 228 .appendNN( " WHERE " , where ) // nullなら、追加しない。where + whereNames 229 .toString(); 230 } 231 232 /** 233 * データを削除する場合に使用するSQL文を作成します。 234 * 235 * DELETE FROM table WHERE where; 236 * 237 * cnstKeys,cnstVal,orderBys は、使いません。 238 * where は、値が存在しない場合は、設定されません。 239 * orderBy は、使いません。 240 * 241 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 242 * 243 * @return 削除SQL 244 * @og.rtnNotNull 245 */ 246 public String getDeleteSQL() { 247 if( !"DELETE".equals( queryType ) ) { 248 final String errMsg = "指定のQUERYタイプと異なるSQL文を要求しています。" + CR 249 + " 要求SQL=DELETE queryType=" + queryType ; 250 throw new OgRuntimeException( errMsg ); 251 } 252 253 setup(); 254 255 return new OgBuilder() 256 .append( "DELETE FROM " ).append( table ) 257 .appendNN( " WHERE " , where ) // nullなら、追加しない。where + whereNames 258 .toString(); 259 } 260 261 /** 262 * [カラム名]を含む文字列を分解し、Map に登録します。 263 * 264 * これは、[カラム名]を含む文字列を分解し、カラム名 を取り出し、whrList に 265 * 追加していきます。 266 * 戻り値は、[XXXX] を、? に置換済みの文字列になります。 267 * 268 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 269 * 270 * @param fmt [カラム名]を含む文字列 271 * @return PreparedStatementに対応した変換後の文字列 272 */ 273 private String formatSplit( final String fmt ) { 274 if( StringUtil.isNull( fmt ) ) { return fmt; } // null,ゼロ文字列チェック 275 276 final StringBuilder rtnStr = new StringBuilder( BUFFER_MIDDLE ); 277 278 int start = 0; 279 int index = fmt.indexOf( '[' ); 280 while( index >= 0 ) { 281 final int end = fmt.indexOf( ']',index ); 282 if( end < 0 ) { 283 final String errMsg = "[ と ] との対応関係がずれています。" 284 + "format=[" + fmt + "] : index=" + index ; 285 throw new OgRuntimeException( errMsg ); 286 } 287 288 // [ より前方の文字列は、rtnStr へ追加する。 289 if( index > 0 ) { rtnStr.append( fmt.substring( start,index ) ); } 290 // index == 0 は、][ と連続しているケース 291 292 // [XXXX] の XXXX部分と、位置(?の位置になる)を、Listに登録 293 whrList.add( fmt.substring( index+1,end ) ); 294 295 rtnStr.append( '?' ); // [XXXX] を、? に置換する。 296 297 start = end+1 ; 298 index = fmt.indexOf( '[',start ); 299 } 300 // ] の後方部分は、rtnStr へ追加する。 301 rtnStr.append( fmt.substring( start ) ); // '[' が見つからなかった場合は、この処理で、すべての fmt データが、append される。 302 303 return rtnStr.toString(); 304 } 305 306 /** 307 * QUERYタイプ(SELECT,INSERT,UPDATE,DELETE,MERGE) を指定します。 308 * 309 * 引数が nullか、ゼロ文字列の場合は、登録しません。 310 * 311 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 312 * 313 * @param queryType QUERYタイプ 314 */ 315 public void setQueryType( final String queryType ) { 316 if( !StringUtil.isNull( queryType ) ) { 317 if( QUERY_TYPE.contains( queryType ) ) { 318 this.queryType = queryType; 319 } 320 else { 321 final String errMsg = "queryType は、" + QUERY_TYPE + " から、指定してください。"; 322 throw new OgRuntimeException( errMsg ); 323 } 324 } 325 } 326 327 /** 328 * テーブル名をセットします。 329 * 330 * 引数が nullか、ゼロ文字列の場合は、登録しません。 331 * 332 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 333 * 334 * @param table テーブル名 335 */ 336 public void setTable( final String table ) { 337 if( !StringUtil.isNull( table ) ) { 338 this.table = table; 339 } 340 } 341 342 /** 343 * テーブル名を取得します。 344 * 345 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 346 * 347 * @return テーブル名 348 */ 349 public String getTable() { 350 return table; 351 } 352 353 /** 354 * カラム名をセットします。 355 * 356 * カラム名は、登録時に、大文字に変換しておきます。 357 * カラム名は、CSV形式でもかまいません。 358 * 引数が nullか、ゼロ文字列の場合は、登録しません。 359 * 360 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 361 * 362 * @param names キー(大文字のみ。内部で変換しておきます。) 363 */ 364 public void setNames( final String names ) { 365 if( !StringUtil.isNull( names ) ) { 366 this.names = names.toUpperCase(Locale.JAPAN); 367 } 368 } 369 370 /** 371 * カラム名を取得します。 372 * 373 * 登録時に、すでに、大文字に変換していますので、 374 * ここで取得するカラム名も、大文字に変換されています。 375 * 376 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 377 * 378 * @return カラム名(大文字に変換済み) 379 */ 380 public String getNames() { 381 return names; 382 } 383 384 /** 385 * 除外するカラム名をセットします。 386 * 387 * カラム名は、登録時に、大文字に変換しておきます。 388 * カラム名は、CSV形式でもかまいません。 389 * 引数が nullか、ゼロ文字列の場合は、登録しません。 390 * 391 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 392 * 393 * @param omitNames キー(大文字のみ。内部で変換しておきます。) 394 */ 395 public void setOmitNames( final String omitNames ) { 396 if( !StringUtil.isNull( omitNames ) ) { 397 this.omitNames = omitNames.toUpperCase(Locale.JAPAN); 398 } 399 } 400 401 /** 402 * WHERE条件をセットします。 403 * 404 * whereNames属性と同時に使用する場合は、"AND" で、処理します。 405 * 引数が nullか、ゼロ文字列の場合は、登録しません。 406 * 407 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 408 * 409 * @param where WHERE条件 410 */ 411 public void setWhere( final String where ) { 412 if( !StringUtil.isNull( where ) ) { 413 this.where = where; 414 } 415 } 416 417 /** 418 * WHERE条件となるカラム名をCSV形式でセットします。 419 * 420 * カラム名配列より、WHERE条件を、KEY=[KEY] 文字列で作成します。 421 * where属性と同時に使用する場合は、"AND" で、処理します。 422 * 引数が nullか、ゼロ件配列の場合は、登録しません。 423 * 424 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 425 * 426 * @param whNames WHERE句作成のためのカラム名 427 */ 428 public void setWhereNames( final String whNames ) { 429 if( !StringUtil.isNull( whNames ) ) { 430 final String[] whAry = StringUtil.csv2Array( whNames ); 431 432 final StringJoiner sj = new StringJoiner( " AND " ); // 区切り文字 433 for( final String whName : whAry ) { 434 whrList.add( whName ); 435 sj.add( whName + "=?" ); 436 } 437 whrNames = sj.toString(); 438 } 439 } 440 441 /** 442 * orderBy条件をセットします。 443 * 444 * 引数が nullか、ゼロ文字列の場合は、登録しません。 445 * 446 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 447 * 448 * @param orderBy orderBy条件 449 */ 450 public void setOrderBy( final String orderBy ) { 451 if( !StringUtil.isNull( orderBy ) ) { 452 this.orderBy = orderBy; 453 } 454 } 455 456 /** 457 * 固定値のカラム名をセットします。 458 * 459 * nullでなく、ゼロ文字列でない場合のみセットします。 460 * カラム名は、CSV形式でもかまいません。 461 * 引数が nullか、ゼロ文字列の場合は、登録しません。 462 * 463 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 464 * 465 * @param keys 固定値のカラム名 466 */ 467 public void setConstKeys( final String keys ) { 468 if( !StringUtil.isNull( keys ) ) { 469 this.cnstKeys = keys; 470 } 471 } 472 473 /** 474 * 固定値のカラム名に対応した、固定値文字列をセットします。 475 * 476 * nullでなく、ゼロ文字列でない場合のみセットします。 477 * 固定値は、CSV形式でもかまいません。 478 * 引数が nullか、ゼロ文字列の場合は、登録しません。 479 * 480 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 481 * 482 * @param vals 固定値 483 */ 484 public void setConstVals( final String vals ) { 485 if( !StringUtil.isNull( vals ) ) { 486 this.cnstVals = vals; 487 } 488 } 489 490 /** 491 * PreparedStatement で、パラメータとなるカラム名の配列を返します。 492 * 493 * これは、QUERYの変数部分 "[カラム名]" を、"?" に置き換えており、 494 * この、カラム名の現れた順番に、配列として返します。 495 * データベース処理では、パラメータを設定する場合に、このカラム名を取得し、 496 * オリジナル(SELECT)のカラム番号から、その値を取得しなければなりません。 497 * 498 * カラム名配列は、QUERYタイプ(queryType)に応じて作成されます。 499 * SELECT : パラメータ は使わないので、長さゼロの配列 500 * INSERT : where条件は使わず、names部分のみなので、0 ~ clmLen までの配列 501 * UPDATE : names も、where条件も使うため、すべての配列 502 * DELETE : names条件は使わず、where部分のみなので、clmLen ~ clmLen+whrLen までの配列(clmLen以降の配列) 503 * 504 * @og.rev 6.8.6.0 (2018/01/19) 新規作成 505 * 506 * @param useInsert queryType="MERGE" の場合に、false:UPDATE , true:INSERT のパラメータのカラム名配列を返します。 507 * @return パラメータとなるカラム名の配列 508 * @og.rtnNotNull 509 */ 510 public String[] getParamNames( final boolean useInsert ) { 511 final String[] whrAry = whrList.toArray( new String[whrList.size()] ); 512 final String[] allAry = Arrays.copyOf( nameAry , nameAry.length + whrList.size() ); 513 System.arraycopy( whrAry , 0 , allAry , nameAry.length , whrAry.length ); // allAry = nameAry + whrAry の作成 514 515 String[] rtnClms = null; 516 switch( queryType ) { 517 case "SELECT" : rtnClms = new String[0]; break; // パラメータはない。 518 case "INSERT" : rtnClms = nameAry; break; // names指定の分だけ、パラメータセット 519 case "UPDATE" : rtnClms = allAry; break; // names+whereの分だけ、パラメータセット 520 case "DELETE" : rtnClms = whrAry; break; // whereの分だけ、パラメータセット 521 case "MERGE" : rtnClms = allAry; break; // useInsert=false は、UPDATEと同じ 522 default : break; 523 } 524 525 if( useInsert && "MERGE".equals( queryType ) ) { 526 rtnClms = nameAry; // MERGEで、useInsert=true は、INSERTと同じ 527 } 528 529 return rtnClms; 530 } 531}