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.fukurou.fileexec;
017
018import java.sql.Connection;
019import java.sql.ResultSet;
020import java.sql.PreparedStatement;
021import java.sql.ParameterMetaData;
022import java.sql.SQLException;
023
024import java.util.Map;
025import java.util.List;
026import java.util.ArrayList;
027import java.util.Arrays;
028
029import org.apache.tomcat.jdbc.pool.DataSource;
030import org.apache.tomcat.jdbc.pool.PoolProperties;
031
032import org.opengion.fukurou.system.HybsConst;                           // 7.2.3.1 (2020/04/17)
033
034/**
035 * データベース処理を行う、簡易的なユーティリティークラスです。
036 * staticメソッドしか持っていません。
037 * sql文を execute( query ) する事により,データベースに書き込みます。
038 *
039 * このクラスは、マルチスレッドに対して、安全です。
040 *
041 * @version  4.0
042 * @author   Kazuhiko Hasegawa
043 * @since    JDK5.0,
044 */
045public final class DBUtil {
046        private static final XLogger LOGGER= XLogger.getLogger( DBUtil.class.getSimpleName() ); // ログ出力
047
048        /** データベースのキーワード {@value}    */
049        public static final String DATABASE_KEY = "DATABASE";
050
051        /** 接続先URL      {@value}        */
052        public static final String URL_KEY              = "REALM_URL";
053        /** ドライバー     {@value}        */
054        public static final String DRIVER_KEY   = "REALM_DRIVER";
055        /** ユーザーID     {@value}        */
056        public static final String NAME_KEY             = "REALM_NAME";
057        /** パスワード     {@value}        */
058        public static final String PASSWORD_KEY = "REALM_PASSWORD";
059
060        /** データベースリトライの待ち時間(ミリ秒) {@value} */
061        public static final int CONN_SLEEP_TIME  = 2000 ;       // 6.8.2.2 (2017/11/02) コネクションの獲得まで、2秒待つ
062        /** データベースリトライ回数 {@value} */
063        public static final int CONN_RETRY_COUNT = 10 ;         // 6.8.2.2 (2017/11/02) コネクションの獲得まで、10回リトライする。
064        /** データベースValid タイムアウト時間(秒) {@value} */
065        public static final int CONN_VALID_TIMEOUT = 10 ;       // 6.8.2.2 (2017/11/02) コネクションのValidチェックのタイムアウト時間。
066
067        /** データ検索時のフェッチサイズ  {@value} */
068        public static final int DB_FETCH_SIZE = 251 ;
069
070        private static final DataSource DATA_SOURCE = new DataSource();
071
072        private static boolean readyFlag ;              // 準備が出来た場合は、true
073        private static boolean oracleFlag ;             // 接続先がORACLEの場合は、true
074
075        private static final int        BUFFER_MIDDLE = 200 ;
076
077        /**
078         * インスタンスを作成させないため、private 化します。
079         */
080        private DBUtil() {}
081        /**
082         * 引数を指定せず、オブジェクトを作成します。
083         *
084         * System.getProperty より取得し、さらに、そこから取得できなかった
085         * 場合は、環境変数から、取得します。
086         *
087         * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
088         *
089         * @see         #URL_KEY
090         */
091        public static void init() {
092//              init(   System.getProperty( URL_KEY                     , System.getenv( URL_KEY                ) ) ,
093//                              System.getProperty( DRIVER_KEY          , System.getenv( DRIVER_KEY             ) ) ,
094//                              System.getProperty( NAME_KEY            , System.getenv( NAME_KEY               ) ) ,
095//                              System.getProperty( PASSWORD_KEY        , System.getenv( PASSWORD_KEY   ) )
096//              );
097                init(   HybsConst.getenv( URL_KEY               ) ,
098                                HybsConst.getenv( DRIVER_KEY    ) ,
099                                HybsConst.getenv( NAME_KEY              ) ,
100                                HybsConst.getenv( PASSWORD_KEY  )
101                );
102        }
103
104        /**
105         * 接続先URL、ドライバー、ユーザーID、パスワードなどを含んだMapを指定して、オブジェクトを作成します。
106         *
107         * Mapに指定のキーが含まれない場合は、System.getProperty より取得し、さらに、そこから取得できなかった
108         * 場合は、環境変数から、取得します。
109         *
110         * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
111         *
112         * @param       prmMap  必要情報を含んだMapオブジェクト
113         * @see         #URL_KEY
114         */
115        public static void init( final Map<String,String> prmMap ) {
116//              init(   prmMap.getOrDefault( URL_KEY            , System.getProperty( URL_KEY                   , System.getenv( URL_KEY                ) ) ) ,
117//                              prmMap.getOrDefault( DRIVER_KEY         , System.getProperty( DRIVER_KEY                , System.getenv( DRIVER_KEY             ) ) ) ,
118//                              prmMap.getOrDefault( NAME_KEY           , System.getProperty( NAME_KEY                  , System.getenv( NAME_KEY               ) ) ) ,
119//                              prmMap.getOrDefault( PASSWORD_KEY       , System.getProperty( PASSWORD_KEY              , System.getenv( PASSWORD_KEY   ) ) )
120//              );
121                init(   prmMap.getOrDefault( URL_KEY            , HybsConst.getenv( URL_KEY                     ) ) ,
122                                prmMap.getOrDefault( DRIVER_KEY         , HybsConst.getenv( DRIVER_KEY          ) ) ,
123                                prmMap.getOrDefault( NAME_KEY           , HybsConst.getenv( NAME_KEY            ) ) ,
124                                prmMap.getOrDefault( PASSWORD_KEY       , HybsConst.getenv( PASSWORD_KEY        ) )
125                );
126        }
127
128        /**
129         * 接続先URL、ドライバー、ユーザーID、パスワードを指定して、オブジェクトを作成します。
130         *
131         * params は、必ず、4つ必要です。
132         *
133         * @param       params          接続先URL、ドライバー、ユーザーID、パスワード
134         * @see         #isReady()
135         */
136        public static void init( final String... params ) {
137                if( readyFlag ) {
138                        // MSG0024 = すでに、接続先設定は完了しています。[{0}]
139                        throw MsgUtil.throwException( "MSG0024" , DATA_SOURCE );
140                }
141
142                if( params == null || params.length != 4 ) {
143                        // MSG0027 = 接続先設定情報が不足しています。[{0}]
144                        throw MsgUtil.throwException( "MSG0027" , Arrays.toString( params ) );
145                }
146
147                final PoolProperties pp = new PoolProperties();
148                pp.setUrl(                              params[0] );
149                pp.setDriverClassName(  params[1] );
150                pp.setUsername(                 params[2] );
151                pp.setPassword(                 params[3] );
152
153                DATA_SOURCE.setPoolProperties( pp );
154                readyFlag = true;
155
156                oracleFlag = params[0] != null && params[0].startsWith( "jdbc:oracle" );
157        }
158
159        /**
160         * DataSourceの初期化が完了していれば、true を返します。
161         *
162         * 初期化は、#init(String...) メソッドの呼び出して、完了します。
163         * #crear() で、未完了に戻ります。
164         *
165         * @return      初期化が完了しているかどうか
166         * @see         #init(String...)
167         */
168        public static boolean isReady() { return readyFlag; }
169
170        /**
171         * 接続先がORACLEかどうかを返します。
172         *
173         * ORACLE の場合は、true を返します。
174         *
175         * @return      接続先がORACLEかどうか[true:ORACLE false:その他]
176         */
177        public static boolean isOracle() { return oracleFlag; }
178
179        /**
180         * DataSource から、Connectionを取得して、返します。
181         *
182         * @og.rev 6.8.2.2 (2017/11/02) コネクションの再取得をリトライします。
183         * @og.rev 7.2.5.0 (2020/06/01) DB処理の実行に失敗のエラーは、3回までは、何も出さない。
184         *
185         * @return      DataSourceから、Connectionを取得して、返します。
186         * @throws      SQLException SQLエラーが発生した場合
187         */
188        public static Connection getConnection() throws SQLException {
189                if( !readyFlag ) {
190        //              // MSG0025 = 接続先設定が完了していません。
191        //              throw MsgUtil.throwException( "MSG0025" , "getConnection() Error!!" );
192                        init();
193                }
194
195                SQLException errEX = null;
196                for( int i=0; i<CONN_RETRY_COUNT; i++ ) {
197                        try {
198                                final Connection conn = DATA_SOURCE.getConnection();
199                                conn.setAutoCommit( false );
200
201                                if( conn.isValid( CONN_VALID_TIMEOUT ) ) { return conn; }
202                        }
203                        catch( final SQLException ex ) {
204                                if( i >= 3 ) {  // とりあえず3回までは、何も出さない
205//                                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
206//                                      MsgUtil.errPrintln( "MSG0019" , ex.getMessage() );
207                                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
208                                        MsgUtil.errPrintln( "MSG0019" , ex.getMessage() , "" );
209                                }
210
211                                errEX = ex ;
212                                try{ Thread.sleep( CONN_SLEEP_TIME ); } catch( final InterruptedException ex2 ){}
213                        }
214                }
215
216                final String errMsg = errEX == null ? "COUNT Over" : errEX.getMessage() ;
217//              // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
218//              throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" , "" );
219                // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
220                throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" );
221        }
222
223        /**
224         * データ配列を渡してPreparedStatementの引数に、値をセットします。
225         *
226         * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
227         * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
228         * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
229         *
230         * @param       pstmt   PreparedStatementオブジェクト
231         * @param       values  ?に割り当てる設定値
232         * @param       pMeta   オラクル系以外のDBに対して、type指定する場合に使用する ParameterMetaDataオブジェクト
233         *
234         * @throws SQLException DB処理の実行に失敗した場合
235         */
236        private static void setObject( final PreparedStatement pstmt , final String[] values , final ParameterMetaData pMeta ) throws SQLException {
237                if( values != null && values.length > 0 ) {
238                        // ORACLE では、ParameterMetaDataは、使わない。
239                        if( pMeta == null ) {
240                                int clmNo = 1;  // JDBC のカラム番号は、1から始まる。
241                                for( int i=0; i<values.length; i++ ) {
242                                        final String val = values[i];
243                                        pstmt.setObject( clmNo++,val );
244                                }
245                        }
246                        else {
247                                int clmNo = 1;  // JDBC のカラム番号は、1から始まる。
248                                for( int i=0; i<values.length; i++ ) {
249                                        final int type = pMeta.getParameterType( clmNo );
250                                        final String val = values[i];
251                                        if( val == null || val.isEmpty() ) {
252                                                pstmt.setNull( clmNo++, type );
253                                        }
254                                        else {
255                                                pstmt.setObject( clmNo++,val,type );
256                                        }
257                                }
258                        }
259                }
260        }
261
262        /**
263         * データ配列を渡して実際のDB処理を実行します。
264         *
265         * ここでは、1行だけ処理するための簡易メソッドを提供します。
266         *
267         * @param       query   実行するSQL文
268         * @param       values  ?に割り当てる設定値
269         * @return      ここでの処理件数
270         *
271         * @throws RuntimeException Connection DB処理の実行に失敗した場合
272         */
273        public static int execute( final String query , final String... values ) {
274//              final List<String[]> list = new ArrayList<>();
275//              list.add( values );
276//
277//              return execute( query,list );
278
279                int     execCnt = 0;
280
281                // try-with-resources 文 (AutoCloseable)
282                try( Connection conn = getConnection() ) {
283                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
284                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
285                                // ORACLE では、ParameterMetaDataは、使わない。
286                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
287
288                                setObject( pstmt , values , pMeta );
289                                execCnt = pstmt.executeUpdate();                        // 1回なので、+= の必要性は無い。
290
291                                conn.commit();
292                        }
293                        catch( final SQLException ex ) {
294                                conn.rollback();
295                                conn.setAutoCommit(true);
296                                throw ex;
297                        }
298                }
299                catch( final SQLException ex ) {
300                        // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
301                        throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
302                }
303
304                return execCnt;
305        }
306
307        /**
308         * データ配列のListを渡して実際のDB処理を実行します。
309         *
310         * データ配列は、1行分のデータに対する設定値の配列です。
311         * これは、keys で指定した並び順と一致している必要があります。
312         *
313         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
314         *
315         * @param       query   実行するSQL文
316         * @param       list    ?に割り当てる設定値
317         * @return      ここでの処理件数
318         *
319         * @throws RuntimeException Connection DB処理の実行に失敗した場合
320         */
321        public static int execute( final String query , final List<String[]> list ) {
322                LOGGER.debug( () -> "execute query=" + query );
323
324                String[] debugLine      = null;
325                int              execCnt        = 0;
326
327                // try-with-resources 文 (AutoCloseable)
328                try( Connection conn = getConnection() ) {
329                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
330                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {               // 更新系なので、setFetchSize は不要。
331
332                                // ORACLE では、ParameterMetaDataは、使わない。
333                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
334
335                                for( final String[] values : list ) {
336                                        debugLine = values;
337                                        LOGGER.debug( () -> "execute values=" + Arrays.toString( values ) );
338                                        setObject( pstmt , values , pMeta );
339                                        execCnt += pstmt.executeUpdate();
340                                }
341
342                                conn.commit();
343                        }
344                        catch( final SQLException ex ) {
345                                conn.rollback();
346                                conn.setAutoCommit(true);
347                                throw ex;
348                        }
349                }
350                catch( final SQLException ex ) {
351//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
352//                      throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) );
353                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
354                        throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) );
355                }
356
357                return execCnt;
358        }
359
360        /**
361         * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド)
362         *
363         * これは、updQueryで、更新してみて、0件の場合、insQuery で追加処理を行います。
364         *
365         * データ配列は、1行分のデータに対する設定値の配列です。
366         * これは、keys で指定した並び順と一致している必要があります。
367         *
368         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
369         *
370         * @param       insQuery        追加するSQL文
371         * @param       updQuery        更新するSQL文
372         * @param       insList ?に割り当てる設定値
373         * @param       updList ?に割り当てる設定値
374         * @return      ここでの処理件数
375         *
376         * @throws RuntimeException Connection DB処理の実行に失敗した場合
377         */
378        public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList ) {
379                LOGGER.debug( () -> "execute insQuery=" + insQuery + " , updQuery=" + updQuery );
380
381                String[] debugLine = null;
382                String   query     = null;
383
384                int     execCnt = 0;
385
386                // try-with-resources 文 (AutoCloseable)
387                try( Connection conn = getConnection() ) {
388                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
389                        try( PreparedStatement inPstmt = conn.prepareStatement( insQuery );
390                                 PreparedStatement upPstmt = conn.prepareStatement( updQuery ) ) {
391
392                                // ORACLE では、ParameterMetaDataは、使わない。
393                                final ParameterMetaData inpMeta = oracleFlag ? null : inPstmt.getParameterMetaData();
394                                final ParameterMetaData uppMeta = oracleFlag ? null : upPstmt.getParameterMetaData();
395
396                                for( int i=0; i<updList.size(); i++ ) {                 // 更新処理と、挿入処理は、同じ数のListを用意する。
397                                        query = updQuery;
398                                        // 更新処理を行う。
399                                        final String[] upVals = updList.get(i);
400                                        debugLine = upVals;
401                                        setObject( upPstmt , upVals , uppMeta );
402
403                                        int cnt = upPstmt.executeUpdate();
404
405                                        if( cnt <= 0 ) {        // 更新が無い、つまり、追加対象
406                                                query = insQuery;
407                                                // 挿入処理を行う。
408                                                final String[] inVals = insList.get(i);
409                                                debugLine = inVals;
410                                                setObject( inPstmt , inVals , inpMeta );
411
412                                                LOGGER.debug( () -> "execute INSERT=" + Arrays.toString( inVals ) );
413
414                                                cnt = inPstmt.executeUpdate();
415                                        }
416                                        else {          // 元々、このelse は必要ない。UPDATE は、先に処理済
417                                                LOGGER.debug( () -> "execute UPDATE=" + Arrays.toString( upVals ) );
418                                        }
419
420                                        execCnt += cnt;
421                                }
422                                conn.commit();
423                        }
424                        catch( final SQLException ex ) {
425                                conn.rollback();
426                                conn.setAutoCommit(true);
427                                throw ex;
428                        }
429                }
430                catch( final SQLException ex ) {
431//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
432//                      throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) );
433                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
434                        throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) );
435                }
436
437                return execCnt;
438        }
439
440        /**
441         * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。
442         *
443         * ステートメントと引数により、Prepared クエリーの検索のみ実行します。
444         * 結果は,すべて文字列に変換されて格納されます。
445         *
446         * @param   query ステートメント文字列
447         * @param   args オブジェクトの引数配列
448         *
449         * @return  検索結果のリスト配列(結果が無ければ、サイズゼロのリスト)
450         * @throws RuntimeException DB検索処理の実行に失敗した場合
451         * @og.rtnNotNull
452         */
453        public static List<String[]> dbQuery( final String query , final String... args ) {
454                // try-with-resources 文 (AutoCloseable)
455                try( Connection conn = getConnection() ) {
456                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
457                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
458                                // ORACLE では、ParameterMetaDataは、使わない。
459                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
460                                // 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。
461                                setObject( pstmt , args , pMeta );
462
463                                if( pstmt.execute() ) {
464                                        try( ResultSet resultSet = pstmt.getResultSet() ) {
465                                                return resultToArray( resultSet );
466                                        }
467                                }
468                                conn.commit();
469                        }
470                        catch ( final SQLException ex ) {
471                                conn.rollback();
472                                conn.setAutoCommit(true);
473                                throw ex;
474                        }
475                }
476                catch ( final SQLException ex ) {
477//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
478//                      throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( args ) );
479                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
480                        throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
481                }
482
483                return new ArrayList<String[]>();
484        }
485
486        /**
487         * ResultSet より、結果の文字列配列を作成します。
488         *
489         * 結果は,すべて文字列に変換されて格納されます。
490         * 移動したメソッドで使われているのでこれも移動
491         *
492         * @param   resultSet ResultSetオブジェクト
493         *
494         * @return  ResultSetの検索結果リスト配列
495         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
496         * @og.rtnNotNull
497         */
498        public static List<String[]> resultToArray( final ResultSet resultSet ) throws SQLException {
499                final ArrayList<String[]> data = new ArrayList<>();
500
501                final ResultSetValue rsv = new ResultSetValue( resultSet );
502
503                while( rsv.next() ) {
504                        data.add( rsv.getValues() );
505                }
506
507                return data;
508        }
509
510        /**
511         * データをインサートする場合に使用するSQL文を作成します。
512         *
513         * これは、key に対応した ? 文字列で、SQL文を作成します。
514         * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
515         * conKeysとconValsは、固定値のキーと値です。
516         * conKeys,conVals がnullの場合は、これらの値を使用しません。
517         *
518         * @param       table   テーブルID
519         * @param       keys    設定値に対応するキー配列
520         * @param       conKeys 固定値の設定値に対応するキー配列
521         * @param       conVals 固定値に対応する値配列
522         * @return  インサートSQL
523         * @og.rtnNotNull
524         */
525        public static String getInsertSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals ) {
526                final String[] vals = new String[keys.length];
527                Arrays.fill( vals , "?" );
528
529                final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ;
530
531                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
532                        .append( "INSERT INTO " ).append( table )
533                        .append( " ( " )
534                        .append( String.join( "," , keys ) );
535
536                if( useConst ) {
537                        sql.append( ',' ).append( String.join( "," , conKeys ) );
538                }
539
540                sql.append( " ) VALUES ( " )
541                        .append( String.join( "," , vals ) );
542
543                if( useConst ) {
544                        sql.append( ",'" ).append( String.join( "','" , conVals ) ).append( '\'' );
545                }
546
547                return sql.append( " )" ).toString();
548        }
549
550        /**
551         * データをアップデートする場合に使用するSQL文を作成します。
552         *
553         * これは、key に対応した ? 文字列で、SQL文を作成します。
554         * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
555         * WHERE 文字列は、この、? も含めたWHERE条件の文字列を渡します。
556         * WHERE条件の場合は、この、?に、関数を設定したり、条件を指定したり、
557         * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が
558         * 限られるためです。
559         * conKeysとconValsは、固定値のキーと値です。
560         * conKeys,conVals,where がnullの場合は、これらの値を使用しません。
561         *
562         * @og.rev 7.2.5.0 (2020/06/01) UPDATEで、? を含むキーワードを、処理できるようにします。
563         *
564         * @param       table   テーブルID
565         * @param       keys    設定値に対応するキー配列
566         * @param       conKeys 固定値の設定値に対応するキー配列
567         * @param       conVals 固定値に対応する値配列(VARCHARのみ)
568         * @param       where   WHERE条件式
569         * @return  アップデートSQL
570         * @og.rtnNotNull
571         */
572        public static String getUpdateSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals , final String where ) {
573                final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ;
574
575                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
576                        .append( "UPDATE " ).append( table ).append( " SET " );
577//                      .append( String.join( " = ? ," , keys ) )                                       // key[0] = ? , ・・・  = ? , key[n-1] という文字列が作成されます。
578//                      .append( " = ? " );                                                                                     // 最後の key[n-1] の後ろに、 = ? という文字列を追加します。
579                for( final String key : keys ) {
580                        sql.append( key );
581                        if( ! key.contains( "?" ) ) {
582                                sql.append( " = ? " );                          // key = ? という文字列が作成されます。
583                        }
584                        sql.append( ',' );
585                }
586                sql.deleteCharAt( sql.length() - 1 );           // 最後の一文字(,)を削除します。
587
588                if( useConst ) {
589                        for( int i=0; i<conKeys.length; i++ ) {
590                                sql.append( ',' ).append( conKeys[i] ).append( " = '" ).append( conVals[i] ).append( "' " );
591                        }
592                }
593
594                if( where != null && !where.isEmpty() ) {
595                        sql.append( " WHERE " ).append( where );                        // WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。
596                }
597
598                return sql.toString();
599        }
600
601        /**
602         * データをデリートする場合に使用するSQL文を作成します。
603         *
604         * これは、key に対応した ? 文字列で、SQL文を作成します。
605         * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
606         * WHERE 文字列は、この、? も含めたWHERE条件の文字列を渡します。
607         * WHERE条件の場合は、この、?に、関数を設定したり、条件を指定したり、
608         * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が
609         * 限られるためです。
610         *
611         * @param       table   テーブルID
612         * @param       where   設定値に対応するキー配列(可変長引数)
613         * @return  デリートSQL
614         * @og.rtnNotNull
615         */
616        public static String getDeleteSQL( final String table , final String where ) {
617                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
618                        .append( "DELETE FROM " ).append( table );
619
620                if( where != null && !where.isEmpty() ) {
621                        sql.append( " WHERE " ).append( where );                        // WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。
622                }
623
624                return sql.toString();
625        }
626}