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;
023import 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;
029import 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.4.6.0 (2016/05/27)" ;
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         *
100         * @param   rowNo 選択された行番号配列(登録する対象行)
101         * @param   table DBTableModelオブジェクト(登録する元データ)
102         */
103        @Override
104        public void execute( final int[] rowNo, final DBTableModel table ) {
105
106                int row = 0;                    // エラー時に表示するエラー行番号
107                int executeCount = 0;   // 処理件数
108                final Formatter form = new Formatter( table,getStatement() );   // 6.4.3.4 (2016/03/11)
109                final int[] clmNos = form.getClmNos();          // 引数の個数分の配列。カラム番号を保存
110                final String query = form.getQueryFormatString();
111                final int    cnt   = clmNos.length;                     // 引数の個数(カラムの個数ではありません。)
112
113                // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録できるようにする。
114                boolean useTimeStamp = false;
115                // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
116                final boolean[] isTime = new boolean[cnt];
117                for( int j=0; j<cnt; j++ ) {
118                        // 5.5.8.5 (2012/11/27) TIMESTAMP型でも処理できるようにします。
119        //              final String clsName = table.getDBColumn( clmNos[j] ).getClassName();
120                        // 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
121                        isTime[j] = table.getDBColumn( clmNos[j] ).isDateType();        // 6.4.6.0 (2016/05/27)
122                        if( !useTimeStamp && isTime[j] ) { useTimeStamp = true; }       // isTime[j] == true 時に、一度だけ実行される。
123                }
124
125                final Connection conn = getConnection();
126
127                // 6.4.2.1 (2016/02/05) try-with-resources 文
128                try( final PreparedStatement pstmt = conn.prepareStatement( query ) ) {         // 更新系なので、setFetchSize は不要。
129                        pstmt.setQueryTimeout( DB_MAX_QUERY_TIMEOUT );
130                //      ((oracle.jdbc.OraclePreparedStatement)pstmt).setExecuteBatch(50);
131                        // 4.0.0.0 (2007/09/25) isOracle から useParamMetaData に変更
132                        final boolean useParamMetaData = useParameterMetaData();        // 5.3.8.0 (2011/08/01)
133
134                        final DBUpdater dbUpper = new DBUpdater( cnt,pstmt,useParamMetaData );          // 6.9.3.0 (2018/03/26) DBUpdaterクラスを利用したバッチ更新を追加します。
135
136                        // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合
137                        if( useTimeStamp ) {
138                                for( int i=0; i<rowNo.length; i++ ) {
139                                        row = rowNo[i];
140                                        final String[] vals = new String[cnt];
141                                        for( int j=0; j<cnt; j++ ) {
142                                                vals[j] = StringUtil.rTrim( table.getValue( row,clmNos[j] ) );
143                                        }
144
145                                        dbUpper.execute( vals,isTime );
146                                }
147                        }
148                        else {
149                                for( int i=0; i<rowNo.length; i++ ) {
150                                        row = rowNo[i];
151                                        final String[] vals = new String[cnt];
152                                        for( int j=0; j<cnt; j++ ) {
153                                                vals[j] = StringUtil.rTrim( table.getValue( row,clmNos[j] ) );
154                                        }
155
156                                        dbUpper.execute( vals );
157                                }
158                        }
159                        executeCount = dbUpper.execEnd();
160
161//                      // 5.5.5.4 (2012/08/18) 以下、useParamMetaData、useTimeStamp、通常の3種類を、行列ループの外に出す。
162//                      // 5.5.5.4 (2012/08/18) useParamMetaData 処理を、ループの外に出す。(PostgreSQL対応)
163//                      if( useParamMetaData ) {
164//                              // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
165//                              final int[] types = new int[cnt];
166//                              final ParameterMetaData pMeta = pstmt.getParameterMetaData();
167//                              for( int j=0; j<cnt; j++ ) {
168//                                      types[j] = pMeta.getParameterType( j+1 );       // ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。
169//                              }
170//                              for( int i=0; i<rowNo.length; i++ ) {
171//                                      row = rowNo[i];
172//                                      for( int j=0; j<cnt; j++ ) {
173//                                              final String val = StringUtil.rTrim( table.getValue( row,clmNos[j] ) );
174//                                              if( val == null || val.isEmpty() ) {
175//                                                      pstmt.setNull( j+1, types[j] );
176//                                              }
177//                                              else {
178//                                                      pstmt.setObject( j+1, val, types[j] );
179//                                              }
180//                                      }
181//                                      executeCount += pstmt.executeUpdate();
182//                              }
183//                      }
184//                      // 5.5.5.4 (2012/08/18) PostgreSQL対応 以外のDBの場合
185//                      else {
186//                              // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合
187//                              if( useTimeStamp ) {
188//                                      for( int i=0; i<rowNo.length; i++ ) {
189//                                              row = rowNo[i];
190//                                              for( int j=0; j<cnt; j++ ) {
191//                                                      final String val = StringUtil.rTrim( table.getValue( row,clmNos[j] ) );
192//                                                      if( isTime[j] && val != null && !val.isEmpty() ) {
193//                                                              // 5.5.8.5 (2012/11/27) val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
194//                                                              final java.sql.Timestamp time = java.sql.Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
195//                                                              pstmt.setObject( j+1,time );
196//                                                      }
197//                                                      else {
198//                                                              pstmt.setObject( j+1,val );
199//                                                      }
200//                                              }
201//                                              executeCount += pstmt.executeUpdate();
202//                                      }
203//                              }
204//                              // 5.5.5.4 (2012/08/18) その他:つまり、これが通常の処理
205//                              else {
206//                                      for( int i=0; i<rowNo.length; i++ ) {
207//                                              row = rowNo[i];
208//                                              for( int j=0; j<cnt; j++ ) {
209//                                                      final String val = StringUtil.rTrim( table.getValue( row,clmNos[j] ) );
210//                                                      pstmt.setObject( j+1,val );
211//                                              }
212//                                              executeCount += pstmt.executeUpdate();
213//                                      }
214//                              }
215//                      }
216                        setExecuteCount( executeCount );
217                        setErrorCode( ErrorMessage.OK );
218                }
219                catch( final SQLException ex) {         // catch は、close() されてから呼ばれます。
220                        setErrorCode( ErrorMessage.EXCEPTION );
221                        final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE )
222                                .append( ex.getMessage() ).append( ':' ).append( ex.getSQLState() ).append( CR )
223                                .append( "  QUERY=" ).append( getStatement() ).append( CR )
224                                .append( "  ROW =[" ).append( (row+1) ).append( ']' ).append( CR )
225                                .append( "  VALS=[" ).append( StringUtil.array2csv( table.getValues(row) )).append( ']' )
226                                .append( CR ) ;
227                        // 5.6.9.4 (2013/10/31)
228                        if( row > 0 ) {
229                                errMsg.append("  ROW(-1) =[" ).append( row ).append( ']' ).append( CR )         // 6.3.9.0 (2015/11/06) This statement may have some unnecessary parentheses(PMD)
230                                        .append(  "  VALS(-1)=[" ).append( StringUtil.array2csv( table.getValues(row-1) )).append( ']' )
231                                        .append( CR ) ;
232                        }
233                        throw new HybsSystemException( errMsg.toString(),ex );          // 3.5.5.4 (2004/04/15) 引数の並び順変更
234                }
235        }
236}