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 org.opengion.hayabusa.db.AbstractQuery; 019import org.opengion.hayabusa.db.DBTableModel; 020import org.opengion.hayabusa.common.HybsSystemException; 021import org.opengion.fukurou.util.ErrorMessage; 022import org.opengion.fukurou.util.StringUtil; 023// import org.opengion.fukurou.util.HybsDateUtil; // 5.5.8.5 (2012/11/27) 024import org.opengion.fukurou.model.Formatter; 025import org.opengion.fukurou.db.DBUpdater; // 6.9.3.0 (2018/03/26) 026 027import java.sql.Connection; 028import java.sql.PreparedStatement; 029// import java.sql.ParameterMetaData; 030import java.sql.SQLException; 031 032/** 033 * 引数引き当て(PreparedStatement) を利用した登録系Queryです。 034 * 035 * java.sql.PreparedStatement を用いて、データベース検索処理を行います。 036 * 引数の指定方法は、DBTableModele のカラム名に対応する名称を、SQL文の[カラム名]形式で 037 * 記述します。これを解析して、実際に実行する PreparedStatement に対応する文字列を 038 * 作成します。 039 * たとえば、INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES ([CLM],[NAME_JA],[LABEL_NAME] ) 040 * と記述すれば、内部で、DBTableModele のカラム名に対応する値を取り出し、SQL文として、 041 * INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES (?,?,? ) を実行します。 042 * 043 * @og.formSample 044 * ●使用例 045 * 046 * ・QUERYを直接書く場合 047 * 【entry.jsp】 048 * <og:tableUpdate 049 * command = "{@command}" 050 * queryType = "JDBCTableUpdate" 051 * > 052 * INSERT INTO GE41 053 * (CLM,NAME_JA,LABEL_NAME,KBSAKU,SYSTEM_ID,LANG, 054 * FGJ,DYSET,DYUPD,USRSET,USRUPD,PGUPD) 055 * VALUES 056 * ([CLM],[NAME_JA],[LABEL_NAME],[KBSAKU],[SYSTEM_ID],[LANG], 057 * '1','{@USER.YMDH}','{@USER.YMDH}','{@USER.ID}','{@USER.ID}','{@GUI.KEY}') 058 * </og:tableUpdate> 059 * 060 * @og.rev 4.0.0.0 (2005/01/31) 新規作成 061 * @og.group データ編集 062 * 063 * @version 4.0 064 * @author Kazuhiko Hasegawa 065 * @since JDK5.0, 066 */ 067public class Query_JDBCTableUpdate extends AbstractQuery { 068 /** このプログラムのVERSION文字列を設定します。 {@value} */ 069 private static final String VERSION = "6.9.8.0 (2018/05/28)" ; 070 071 /** 072 * デフォルトコンストラクター 073 * 074 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 075 */ 076 public Query_JDBCTableUpdate() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 077 078 /** 079 * 引数配列付のクエリーを実行します。 080 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。 081 * これは、PreparedQuery で使用する引数を配列でセットするものです。 082 * select * from emp where deptno = ? and job = ? などの PreparedQuery の 083 * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。 084 * 085 * @og.rev 3.8.0.8 (2005/10/03) エラーメッセージの出力順をメッセージ+Queryに変更します。 086 * @og.rev 4.0.0.0 (2007/05/09) ParameterMetaData を使用したパラメータ設定追加。 087 * @og.rev 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更 088 * @og.rev 5.3.8.0 (2011/08/01) useParamMetaData を ConnectionFactory経由で取得。(PostgreSQL対応)、setNull 対応 089 * @og.rev 5.5.5.4 (2012/08/18) useParamMetaData 処理を、ループの外に出す。(PostgreSQL対応) 090 * @og.rev 5.5.5.4 (2012/08/18) DATE オブジェクトを登録できるようにする。 091 * @og.rev 5.5.8.5 (2012/11/27) TIMESTAMP型でも処理できるようにします。 092 * @og.rev 5.6.9.4 (2013/10/31) エラーメッセージに1行前の情報も出力します。 093 * @og.rev 6.3.6.1 (2015/08/28) close(),realClose() 廃止。Queryはキャッシュしません。 094 * @og.rev 6.4.2.1 (2016/02/05) try-with-resources 文で記述。 095 * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。 096 * @og.rev 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。 097 * @og.rev 6.4.6.0 (2016/05/27) isNumber , isDate 追加。 098 * @og.rev 6.9.3.0 (2018/03/26) DBUpdaterクラスを利用したバッチ更新を追加します。 099 * @og.rev 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。 100 * 101 * @param rowNo 選択された行番号配列(登録する対象行) 102 * @param table DBTableModelオブジェクト(登録する元データ) 103 */ 104 @Override 105 public void execute( final int[] rowNo, final DBTableModel table ) { 106 107 int row = 0; // エラー時に表示するエラー行番号 108 int executeCount = 0; // 処理件数 109 final Formatter form = new Formatter( table,getStatement() ); // 6.4.3.4 (2016/03/11) 110 final int[] clmNos = form.getClmNos(); // 引数の個数分の配列。カラム番号を保存 111 final String query = form.getQueryFormatString(); 112 final int cnt = clmNos.length; // 引数の個数(カラムの個数ではありません。) 113 114 // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録できるようにする。 115 boolean useTimeStamp = false; 116 // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 117 final boolean[] isTime = new boolean[cnt]; 118 for( int j=0; j<cnt; j++ ) { 119 // 5.5.8.5 (2012/11/27) TIMESTAMP型でも処理できるようにします。 120 // final String clsName = table.getDBColumn( clmNos[j] ).getClassName(); 121 // 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。 122 isTime[j] = table.getDBColumn( clmNos[j] ).isDateType(); // 6.4.6.0 (2016/05/27) 123 if( !useTimeStamp && isTime[j] ) { useTimeStamp = true; } // isTime[j] == true 時に、一度だけ実行される。 124 } 125 126 final Connection conn = getConnection(); 127 // 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。 128 final String[] vals = new String[cnt]; 129 130 // 6.4.2.1 (2016/02/05) try-with-resources 文 131 try( final PreparedStatement pstmt = conn.prepareStatement( query ) ) { // 更新系なので、setFetchSize は不要。 132 pstmt.setQueryTimeout( DB_MAX_QUERY_TIMEOUT ); 133 // ((oracle.jdbc.OraclePreparedStatement)pstmt).setExecuteBatch(50); 134 // 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更 135 final boolean useParamMetaData = useParameterMetaData(); // 5.3.8.0 (2011/08/01) 136 137 final DBUpdater dbUpper = new DBUpdater( cnt,pstmt,useParamMetaData ); // 6.9.3.0 (2018/03/26) DBUpdaterクラスを利用したバッチ更新を追加します。 138 139 // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合 140 if( useTimeStamp ) { 141 for( int i=0; i<rowNo.length; i++ ) { 142 row = rowNo[i]; 143// final String[] vals = new String[cnt]; 144 for( int j=0; j<cnt; j++ ) { 145 vals[j] = StringUtil.rTrim( table.getValue( row,clmNos[j] ) ); 146 } 147 148 dbUpper.execute( vals,isTime ); 149 } 150 } 151 else { 152 for( int i=0; i<rowNo.length; i++ ) { 153 row = rowNo[i]; 154// final String[] vals = new String[cnt]; 155 for( int j=0; j<cnt; j++ ) { 156 vals[j] = StringUtil.rTrim( table.getValue( row,clmNos[j] ) ); 157 } 158 159 dbUpper.execute( vals ); 160 } 161 } 162 executeCount = dbUpper.execEnd(); 163 164// // 5.5.5.4 (2012/08/18) 以下、useParamMetaData、useTimeStamp、通常の3種類を、行列ループの外に出す。 165// // 5.5.5.4 (2012/08/18) useParamMetaData 処理を、ループの外に出す。(PostgreSQL対応) 166// if( useParamMetaData ) { 167// // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD) 168// final int[] types = new int[cnt]; 169// final ParameterMetaData pMeta = pstmt.getParameterMetaData(); 170// for( int j=0; j<cnt; j++ ) { 171// types[j] = pMeta.getParameterType( j+1 ); // ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。 172// } 173// for( int i=0; i<rowNo.length; i++ ) { 174// row = rowNo[i]; 175// for( int j=0; j<cnt; j++ ) { 176// final String val = StringUtil.rTrim( table.getValue( row,clmNos[j] ) ); 177// if( val == null || val.isEmpty() ) { 178// pstmt.setNull( j+1, types[j] ); 179// } 180// else { 181// pstmt.setObject( j+1, val, types[j] ); 182// } 183// } 184// executeCount += pstmt.executeUpdate(); 185// } 186// } 187// // 5.5.5.4 (2012/08/18) PostgreSQL対応 以外のDBの場合 188// else { 189// // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合 190// if( useTimeStamp ) { 191// for( int i=0; i<rowNo.length; i++ ) { 192// row = rowNo[i]; 193// for( int j=0; j<cnt; j++ ) { 194// final String val = StringUtil.rTrim( table.getValue( row,clmNos[j] ) ); 195// if( isTime[j] && val != null && !val.isEmpty() ) { 196// // 5.5.8.5 (2012/11/27) val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。 197// final java.sql.Timestamp time = java.sql.Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) ); 198// pstmt.setObject( j+1,time ); 199// } 200// else { 201// pstmt.setObject( j+1,val ); 202// } 203// } 204// executeCount += pstmt.executeUpdate(); 205// } 206// } 207// // 5.5.5.4 (2012/08/18) その他:つまり、これが通常の処理 208// else { 209// for( int i=0; i<rowNo.length; i++ ) { 210// row = rowNo[i]; 211// for( int j=0; j<cnt; j++ ) { 212// final String val = StringUtil.rTrim( table.getValue( row,clmNos[j] ) ); 213// pstmt.setObject( j+1,val ); 214// } 215// executeCount += pstmt.executeUpdate(); 216// } 217// } 218// } 219 setExecuteCount( executeCount ); 220 setErrorCode( ErrorMessage.OK ); 221 } 222 catch( final SQLException ex) { // catch は、close() されてから呼ばれます。 223 setErrorCode( ErrorMessage.EXCEPTION ); 224 final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE ) 225 .append( ex.getMessage() ).append( ':' ).append( ex.getSQLState() ).append( CR ) 226 .append( " QUERY=" ).append( getStatement() ).append( CR ) 227 .append( " ROW =[" ).append( (row+1) ).append( ']' ).append( CR ) 228 // 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。 229// .append( " VALS=[" ).append( StringUtil.array2csv( table.getValues(row) )).append( ']' ) 230 .append( " VALS=[" ).append( StringUtil.array2csv( vals )).append( ']' ) // 6.9.8.0 (2018/05/28) 231 .append( CR ) ; 232 // 5.6.9.4 (2013/10/31) 233 if( row > 0 ) { 234 // 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。 235 for( int j=0; j<cnt; j++ ) { 236 vals[j] = StringUtil.rTrim( table.getValue( row-1,clmNos[j] ) ); 237 } 238 239 errMsg.append(" ROW(-1) =[" ).append( row ).append( ']' ).append( CR ) // 6.3.9.0 (2015/11/06) This statement may have some unnecessary parentheses(PMD) 240// .append( " VALS(-1)=[" ).append( StringUtil.array2csv( table.getValues(row-1) )).append( ']' ) 241 .append( " VALS(-1)=[" ).append( StringUtil.array2csv( vals )).append( ']' ) // 6.9.8.0 (2018/05/28) 242 .append( CR ) ; 243 } 244 throw new HybsSystemException( errMsg.toString(),ex ); // 3.5.5.4 (2004/04/15) 引数の並び順変更 245 } 246 } 247}