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}