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