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.plugin.query; 017 018import java.sql.Connection; 019import java.sql.PreparedStatement; 020// import java.sql.ParameterMetaData; 021import java.sql.SQLException; 022 023import org.opengion.hayabusa.db.AbstractQuery; 024import org.opengion.hayabusa.db.DBTableModel; 025import org.opengion.hayabusa.common.HybsSystemException; 026import org.opengion.fukurou.util.ErrorMessage; 027import org.opengion.fukurou.util.StringUtil; 028// import org.opengion.fukurou.util.HybsDateUtil; // 5.5.8.5 (2012/11/27) 029import org.opengion.fukurou.model.Formatter; 030import org.opengion.fukurou.db.DBUpdater; // 6.9.3.0 (2018/03/26) 031import org.opengion.fukurou.system.Closer; // 7.3.0.0 (2021/01/06) 032 033/** 034 * 引数引き当て(PreparedStatement) を利用した登録系Queryです。 035 * 036 * java.sql.PreparedStatement を用いて、データベース登録処理を行います。 037 * 引数の指定方法は、DBTableModele のカラム名に対応する名称を、SQL文の[カラム名]形式で 038 * 記述します。これを解析して、実際に実行する PreparedStatement に対応する文字列を 039 * 作成します。 040 * たとえば、INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES ([CLM],[NAME_JA],[LABEL_NAME] ) 041 * と記述すれば、内部で、DBTableModele のカラム名に対応する値を取り出し、SQL文として、 042 * INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES (?,?,? ) を実行します。 043 * 044 * Query_JDBCTableUpdate との違いは、INSERT文とUPDATE文を渡しておき、 045 * UPDATEの処理結果が、0件の場合は、INSERTを行います。 046 * そのため、tableUpdateタグのBODY部に直接SQLを書くのではなく、tableUpdateParam タグを2個書くことになります。 047 * 048 * 基本的に、tableUpdateタグのqueryTypeにJDBCTableUpdateと記述しておき、tableUpdateParam の 049 * sqlType が MERGE の場合は、2種類のSQL文が作成され、自動的に、JDBCTableMerge が呼ばれます。 050 * ※ つまり、通常は、queryType="JDBCTableUpdate" のままで、sqlType="MERGE" を指定すればよい。 051 * 052 * @og.formSample 053 * ●使用例 054 * 055 * ・JDBCTableUpdate のまま、sqlType="MERGE" を指定する場合。 056 * 【entry.jsp】 057 * <og:tableUpdate 058 * command = "{@command}" 059 * queryType = "JDBCTableUpdate" 060 * <og:tableUpdateParam 061 * sqlType = "MERGE" // INSERT or UPDATE 062 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 063 * names = "{@names}" // 処理対象のカラム名 064 * omitNames = "{@omitNames}" // 処理対象外のカラム名 065 * where = "{@where}" // 処理対象を特定するキー(INSERT時には使われず、UPDAET時に使われる。) 066 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 067 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 068 * /> 069 * </og:tableUpdate> 070 * 071 * ・JDBCTableMerge を直接的に指定する場合。 072 * 【entry.jsp】 073 * <og:tableUpdate 074 * command = "{@command}" 075 * queryType = "JDBCTableMerge" 076 * <og:tableUpdateParam 077 * sqlType = "INSERT" // INSERT or UPDATE 078 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 079 * names = "{@names}" // 処理対象のカラム名 080 * omitNames = "{@omitNames}" // 処理対象外のカラム名 081 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 082 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 083 * /> 084 * <og:tableUpdateParam 085 * sqlType = "UPDATE" // INSERT or UPDATE 086 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 087 * names = "{@names}" // 処理対象のカラム名 088 * omitNames = "{@omitNames}" // 処理対象外のカラム名 089 * where = "{@where}" // 処理対象を特定するキー 090 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 091 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 092 * /> 093 * </og:tableUpdate> 094 * 095 * @og.rev 7.2.9.1 (2020/10/23) 新規作成 096 * @og.group データ編集 097 * 098 * @version 7.2 099 * @author Kazuhiko Hasegawa 100 * @since JDK11.0, 101 */ 102public class Query_JDBCTableMerge extends AbstractQuery { 103 /** このプログラムのVERSION文字列を設定します。 {@value} */ 104 private static final String VERSION = "7.4.1.0 (2021/04/23)" ; 105 106 /** 107 * デフォルトコンストラクター 108 * 109 * @og.rev 7.2.9.1 (2020/10/23) 新規作成 110 */ 111 public Query_JDBCTableMerge() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 112 113 /** 114 * 引数配列付のクエリーを実行します。 115 * 処理自体は、#execute() と同様に、各サブクラスの実装に依存します。 116 * これは、PreparedQuery で使用する引数を配列でセットするものです。 117 * select * from emp where deptno = ? and job = ? などの PreparedQuery の 118 * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。 119 * 120 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 121 * @og.rev 7.3.0.0 (2021/01/06) SpotBugs:チェック例外でストリームやリソースのクリーンアップに失敗するかもしれないメソッド 122 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 123 * 124 * @param rowNo 選択された行番号配列(登録する対象行) 125 * @param table DBTableModelオブジェクト(登録する元データ) 126 */ 127 @Override 128 public void execute( final int[] rowNo, final DBTableModel table ) { 129 130 int row = 0; // エラー時に表示するエラー行番号 131 int executeCount = 0; // 処理件数 132 133 // 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 134// final String[] sqls = getMergeStatement(); // UPDATE,INSERTの順番 135 final String[] sqls = getMergeStatement(); // UPDATE,INSERT,SELECTの順番(UPDATE か SELECT はnull の可能性がある) 136 137 // 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。 138 String[] vals = null; 139 boolean isUpdate = false; // update/insert の区別が分かるように 140 final Connection conn = getConnection(); 141 142 // 7.3.0.0 (2021/01/06) SpotBugs:チェック例外でストリームやリソースのクリーンアップに失敗するかもしれないメソッド 143 // 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 144 // ※ トリッキー:updQuery は、selectの場合もあり得る。 145// final DBquery updQuery = new DBquery( table,sqls[0] ); 146 final DBquery updQuery = new DBquery( table,sqls[0] == null ? sqls[2] : sqls[0] ); 147 final DBquery insQuery = new DBquery( table,sqls[1] ); 148 149 // 7.3.0.0 (2021/01/06) SpotBugs:チェック例外でストリームやリソースのクリーンアップに失敗するかもしれないメソッド 150 try { 151 final boolean usePMeta = useParameterMetaData(); 152 153 final DBUpdater updDB = updQuery.getDBUpdater( conn,usePMeta ); 154 final DBUpdater insDB = insQuery.getDBUpdater( conn,usePMeta ); 155 156 // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合 157 // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach 158// for( int i=0; i<rowNo.length; i++ ) { 159// row = rowNo[i]; 160 for( final int tmp : rowNo ) { 161 row = tmp; 162 vals = updQuery.getVals( row ); 163 164 isUpdate = true; 165 int cnt = updDB.update( vals ) ; 166 if( cnt == 0 ) { // UPDATE を実行して、結果が 0 件なら 167 vals = insQuery.getVals( row ); 168 isUpdate = false; 169 cnt = insDB.update( vals ); // INSERT 処理を行う。 170 } 171 executeCount += cnt ; 172 } 173 174 setExecuteCount( executeCount ); 175 setErrorCode( ErrorMessage.OK ); 176 } 177 catch( final SQLException ex) { // catch は、close() されてから呼ばれます。 178 setErrorCode( ErrorMessage.EXCEPTION ); 179 final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE ) 180 .append( ex.getMessage() ).append( ':' ).append( ex.getSQLState() ).append( CR ) 181 .append( isUpdate ? "UPDATE" : "INSERT" ).append( CR ) 182 .append( " UPDATE=" ).append( sqls[0] ).append( CR ) 183 .append( " INSERT=" ).append( sqls[1] ).append( CR ) 184 .append( " ROW =[" ).append( row+1 ).append( ']' ).append( CR ) 185 .append( " VALS=[" ).append( StringUtil.array2csv( vals )).append( ']' ) // 6.9.8.0 (2018/05/28) 186 .append( CR ) ; 187 188 throw new HybsSystemException( errMsg.toString(),ex ); // 3.5.5.4 (2004/04/15) 引数の並び順変更 189 } 190 // 7.3.0.0 (2021/01/06) SpotBugs:チェック例外でストリームやリソースのクリーンアップに失敗するかもしれないメソッド 191 finally { 192 updQuery.close(); 193 insQuery.close(); 194 } 195 } 196 197 /** 198 * DBUpdater で処理するにあたり、 199 * 処理自体は、#execute() と同様に、各サブクラスの実装に依存します。 200 * これは、PreparedQuery で使用する引数を配列でセットするものです。 201 * select * from emp where deptno = ? and job = ? などの PreparedQuery の 202 * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。 203 * 204 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 205 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 206 */ 207 private static final class DBquery { 208 private final DBTableModel table; 209 private final int[] clmNos; 210 private final String query ; 211 private final int cnt ; 212 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 213// private final boolean[] isTime; 214 private final boolean[] timeClms; 215 private final boolean useTime; 216 private final boolean useSelect; // 7.4.1.0 (2021/04/23) 217 218 private PreparedStatement pstmt; // close用に持っておく 219 220 /** 221 * DBTableModelとQUERY文を指定した、コンストラクター 222 * 223 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 224 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 225 * 226 * @param table DBTableModelオブジェクト(登録する元データ) 227 * @param sql 実行するQUERY文 228 */ 229 public DBquery( final DBTableModel table , final String sql ) { 230 useSelect =sql.startsWith( "SELECT" ); // 7.4.1.0 (2021/04/23) 231 232 this.table = table; 233 final Formatter form = new Formatter( table,sql ); 234 clmNos = form.getClmNos(); // 引数の個数分の配列。カラム番号を保存 235 query = form.getQueryFormatString(); 236 cnt = clmNos.length; // 引数の個数(カラムの個数ではありません。) 237 238 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 239// isTime = new boolean[cnt]; 240 timeClms = new boolean[cnt]; 241 242 boolean useTimeStamp = false; 243 for( int j=0; j<cnt; j++ ) { 244 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 245// isTime[j] = table.getDBColumn( clmNos[j] ).isDateType(); // 6.4.6.0 (2016/05/27) 246// if( !useTimeStamp && isTime[j] ) { useTimeStamp = true; } // isTime[j] == true 時に、一度だけ実行される。 247 timeClms[j] = table.getDBColumn( clmNos[j] ).isDateType(); // 6.4.6.0 (2016/05/27) 248 if( !useTimeStamp && timeClms[j] ) { useTimeStamp = true; } // timeClms[j] == true 時に、一度だけ実行される。 249 } 250 useTime = useTimeStamp; 251 } 252 253 /** 254 * Connection から、DBUpdater を作成して返します。 255 * 256 * @og.rev 7.3.0.0 (2021/01/06) PreparedStatement 関連の処理の見直し 257 * @og.rev 7.4.1.0 (2021/04/23) sqlType="MERGE" 時のみ有効で、where 条件で存在すれば何もしない 258 * 259 * @param conn Connectionオブジェクト 260 * @param usePMeta Meta情報を使用する場合は、true 261 * @return DBUpdaterオブジェクト 262 * @throws SQLException データベース実行エラー 263 */ 264 public DBUpdater getDBUpdater( final Connection conn,final boolean usePMeta ) throws SQLException { 265 pstmt = conn.prepareStatement( query ); 266 pstmt.setQueryTimeout( DB_MAX_QUERY_TIMEOUT ); 267 268// return new DBUpdater( cnt,pstmt,usePMeta,useTime ? isTime : null ); 269 // 8.5.4.2 (2024/01/12) PMD 7.0.0 LinguisticNaming 270// return new DBUpdater( cnt,pstmt,usePMeta,useTime ? isTime : null,useSelect ); // 7.4.1.0 (2021/04/23) 271 return new DBUpdater( cnt,pstmt,usePMeta,useTime ? timeClms : null,useSelect ); // 7.4.1.0 (2021/04/23) 272 } 273 274 /** 275 * パラメータの個数を返します。 276 * 277 * @og.rev 7.3.0.0 (2021/01/06) PreparedStatement 関連の処理の見直し 278 */ 279 public void close() { 280 Closer.stmtClose( pstmt ); 281 } 282 283 /** 284 * 指定行のデータのうち、パラメータカラムの値の配列を返します。 285 * 286 * これは、[カラム]を? に置き換えた処理の、? の順番にDBTableModelの値を再設定します。 287 * 取り出した行データは、rTrim して、右側の空白文字を削除しています。 288 * 289 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 290 * 291 * @param row 指定行 292 * @return 指定行のデータ配列 293 */ 294 public String[] getVals( final int row ) { 295 final String[] vals = new String[cnt]; 296 final String[] data = table.getValues( row ); 297 for( int j=0; j<cnt; j++ ) { 298 vals[j] = StringUtil.rTrim( data[ clmNos[j] ] ); 299 } 300 return vals ; 301 } 302 } 303}