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;
023import java.sql.SQLIntegrityConstraintViolationException;               // 7.4.1.0 (2021/04/23)
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)
033import org.opengion.fukurou.db.Transaction;                                             // 7.4.2.0 (2021/05/14)
034import org.opengion.fukurou.db.ResultSetValue;                                  // 8.5.6.1 (2024/03/29) org.opengion.fukurou.db.ResultSetValue を使用する
035
036/**
037 * データベース処理を行う、簡易的なユーティリティークラスです。
038 * staticメソッドしか持っていません。
039 * sql文を execute( query ) する事により、データベースに書き込みます。
040 *
041 * このクラスは、マルチスレッドに対して、安全です。
042 *
043 * @version  4.0
044 * @author   Kazuhiko Hasegawa
045 * @since    JDK5.0,
046 */
047public final class DBUtil {
048        private static final XLogger LOGGER= XLogger.getLogger( DBUtil.class.getSimpleName() ); // ログ出力
049
050        /** データベースのキーワード {@value}    */
051        public static final String DATABASE_KEY = "DATABASE";
052
053        /** 接続先URL {@value} */
054        public static final String URL_KEY              = "REALM_URL";
055        /** ドライバー {@value} */
056        public static final String DRIVER_KEY   = "REALM_DRIVER";
057        /** ユーザーID {@value} */
058        public static final String NAME_KEY             = "REALM_NAME";
059        /** パスワード {@value} */
060        public static final String PASSWORD_KEY = "REALM_PASSWORD";
061
062        /** データベースリトライの待ち時間(ミリ秒) {@value} */
063        public static final int CONN_SLEEP_TIME  = 2000 ;       // 6.8.2.2 (2017/11/02) コネクションの獲得まで、2秒待つ
064        /** データベースリトライ回数 {@value} */
065        public static final int CONN_RETRY_COUNT = 10 ;         // 6.8.2.2 (2017/11/02) コネクションの獲得まで、10回リトライする。
066        /** データベースValid タイムアウト時間(秒) {@value} */
067        public static final int CONN_VALID_TIMEOUT = 10 ;       // 6.8.2.2 (2017/11/02) コネクションのValidチェックのタイムアウト時間。
068
069        /** データ検索時のフェッチサイズ  {@value} */
070        public static final int DB_FETCH_SIZE = 251 ;
071
072        private static final DataSource DATA_SOURCE = new DataSource();
073
074        private static boolean readyFlag ;              // 準備が出来た場合は、true
075        private static boolean oracleFlag ;             // 接続先がORACLEの場合は、true
076
077        private static final int        BUFFER_MIDDLE = 200 ;
078
079        /**
080         * デフォルトコンストラクターをprivateにして、
081         * オブジェクトの生成をさせないようにする。
082         */
083        private DBUtil() {}
084
085        /**
086         * 引数を指定せず、オブジェクトを作成します。
087         *
088         * System.getProperty より取得し、さらに、そこから取得できなかった
089         * 場合は、環境変数から、取得します。
090         *
091         * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
092         *
093         * @see         #URL_KEY
094         */
095        public static void init() {
096//              init(   System.getProperty( URL_KEY                     , System.getenv( URL_KEY                ) ) ,
097//                              System.getProperty( DRIVER_KEY          , System.getenv( DRIVER_KEY             ) ) ,
098//                              System.getProperty( NAME_KEY            , System.getenv( NAME_KEY               ) ) ,
099//                              System.getProperty( PASSWORD_KEY        , System.getenv( PASSWORD_KEY   ) )
100//              );
101                init(   HybsConst.getenv( URL_KEY               ) ,
102                                HybsConst.getenv( DRIVER_KEY    ) ,
103                                HybsConst.getenv( NAME_KEY              ) ,
104                                HybsConst.getenv( PASSWORD_KEY  )
105                );
106        }
107
108        /**
109         * 接続先URL、ドライバー、ユーザーID、パスワードなどを含んだMapを指定して、オブジェクトを作成します。
110         *
111         * Mapに指定のキーが含まれない場合は、System.getProperty より取得し、さらに、そこから取得できなかった
112         * 場合は、環境変数から、取得します。
113         *
114         * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
115         *
116         * @param       prmMap  必要情報を含んだMapオブジェクト
117         * @see         #URL_KEY
118         */
119        public static void init( final Map<String,String> prmMap ) {
120//              init(   prmMap.getOrDefault( URL_KEY            , System.getProperty( URL_KEY                   , System.getenv( URL_KEY                ) ) ) ,
121//                              prmMap.getOrDefault( DRIVER_KEY         , System.getProperty( DRIVER_KEY                , System.getenv( DRIVER_KEY             ) ) ) ,
122//                              prmMap.getOrDefault( NAME_KEY           , System.getProperty( NAME_KEY                  , System.getenv( NAME_KEY               ) ) ) ,
123//                              prmMap.getOrDefault( PASSWORD_KEY       , System.getProperty( PASSWORD_KEY              , System.getenv( PASSWORD_KEY   ) ) )
124//              );
125                init(   prmMap.getOrDefault( URL_KEY            , HybsConst.getenv( URL_KEY                     ) ) ,
126                                prmMap.getOrDefault( DRIVER_KEY         , HybsConst.getenv( DRIVER_KEY          ) ) ,
127                                prmMap.getOrDefault( NAME_KEY           , HybsConst.getenv( NAME_KEY            ) ) ,
128                                prmMap.getOrDefault( PASSWORD_KEY       , HybsConst.getenv( PASSWORD_KEY        ) )
129                );
130        }
131
132        /**
133         * 接続先URL、ドライバー、ユーザーID、パスワードを指定して、オブジェクトを作成します。
134         *
135         * params は、必ず、4つ必要です。
136         *
137         * @param       params          接続先URL、ドライバー、ユーザーID、パスワード
138         * @see         #isReady()
139         */
140        public static void init( final String... params ) {
141                if( readyFlag ) {
142                        // MSG0024 = すでに、接続先設定は完了しています。[{0}]
143                        throw MsgUtil.throwException( "MSG0024" , DATA_SOURCE );
144                }
145
146                if( params == null || params.length != 4 ) {
147                        // MSG0027 = 接続先設定情報が不足しています。[{0}]
148                        throw MsgUtil.throwException( "MSG0027" , Arrays.toString( params ) );
149                }
150
151                final PoolProperties pp = new PoolProperties();
152                pp.setUrl(                              params[0] );
153                pp.setDriverClassName(  params[1] );
154                pp.setUsername(                 params[2] );
155                pp.setPassword(                 params[3] );
156
157                DATA_SOURCE.setPoolProperties( pp );
158                readyFlag = true;
159
160                oracleFlag = params[0] != null && params[0].startsWith( "jdbc:oracle" );
161        }
162
163        /**
164         * DataSourceの初期化が完了していれば、true を返します。
165         *
166         * 初期化は、#init(String...) メソッドの呼び出して、完了します。
167         * #crear() で、未完了に戻ります。
168         *
169         * @return      初期化が完了しているかどうか
170         * @see         #init(String...)
171         */
172        public static boolean isReady() { return readyFlag; }
173
174        /**
175         * 接続先がORACLEかどうかを返します。
176         *
177         * ORACLE の場合は、true を返します。
178         *
179         * @return      接続先がORACLEかどうか[true:ORACLE false:その他]
180         */
181        public static boolean isOracle() { return oracleFlag; }
182
183        /**
184         * DataSource から、Connectionを取得して、返します。
185         *
186         * @og.rev 6.8.2.2 (2017/11/02) コネクションの再取得をリトライします。
187         * @og.rev 7.2.5.0 (2020/06/01) DB処理の実行に失敗のエラーは、3回までは、何も出さない。
188         *
189         * @return      DataSourceから、Connectionを取得して、返します。
190         * @throws      SQLException SQLエラーが発生した場合
191         */
192        public static Connection getConnection() throws SQLException {
193                if( !readyFlag ) {
194        //              // MSG0025 = 接続先設定が完了していません。
195        //              throw MsgUtil.throwException( "MSG0025" , "getConnection() Error!!" );
196                        init();
197                }
198
199                SQLException errEX = null;
200                for( int i=0; i<CONN_RETRY_COUNT; i++ ) {
201                        try {
202                                final Connection conn = DATA_SOURCE.getConnection();
203                                conn.setAutoCommit( false );
204
205                                if( conn.isValid( CONN_VALID_TIMEOUT ) ) { return conn; }
206                        }
207                        catch( final SQLException ex ) {
208                                if( i >= 3 ) {  // とりあえず3回までは、何も出さない
209//                                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
210//                                      MsgUtil.errPrintln( "MSG0019" , ex.getMessage() );
211                                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
212                                        MsgUtil.errPrintln( "MSG0019" , ex.getMessage() , "" );
213                                }
214
215                                errEX = ex ;
216//                              try{ Thread.sleep( CONN_SLEEP_TIME ); } catch( final InterruptedException ex2 ){}
217                                try { Thread.sleep( CONN_SLEEP_TIME ); } catch( final InterruptedException ignored ) {} // 8.5.4.2 (2024/01/12) PMD 7.0.0 EmptyCatchBlock
218                        }
219                }
220
221                final String errMsg = errEX == null ? "COUNT Over" : errEX.getMessage() ;
222//              // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
223//              throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" , "" );
224                // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
225                throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" );
226        }
227
228        /**
229         * データ配列を渡してPreparedStatementの引数に、値をセットします。
230         *
231         * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
232         * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
233         * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
234         *
235         * @param       pstmt   PreparedStatementオブジェクト
236         * @param       values  ?に割り当てる設定値
237         * @param       pMeta   オラクル系以外のDBに対して、type指定する場合に使用する ParameterMetaDataオブジェクト
238         *
239         * @throws SQLException DB処理の実行に失敗した場合
240         */
241        private static void setObject( final PreparedStatement pstmt , final String[] values , final ParameterMetaData pMeta ) throws SQLException {
242                if( values != null && values.length > 0 ) {
243                        // ORACLE では、ParameterMetaDataは、使わない。
244                        if( pMeta == null ) {
245                                int clmNo = 1;  // JDBC のカラム番号は、1から始まる。
246                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
247//                              for( int i=0; i<values.length; i++ ) {
248//                                      final String val = values[i];
249//                                      pstmt.setObject( clmNo++,val );
250//                              }
251                                for( final String val : values ) {
252                                        pstmt.setObject( clmNo++,val );
253                                }
254                        }
255                        else {
256                                int clmNo = 1;  // JDBC のカラム番号は、1から始まる。
257                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ForLoopCanBeForeach
258//                              for( int i=0; i<values.length; i++ ) {
259//                                      final String val = values[i];
260                                for( final String val : values ) {
261                                        final int type = pMeta.getParameterType( clmNo );
262                                        if( val == null || val.isEmpty() ) {
263                                                pstmt.setNull( clmNo++, type );
264                                        }
265                                        else {
266                                                pstmt.setObject( clmNo++,val,type );
267                                        }
268                                }
269                        }
270                }
271        }
272
273        /**
274         * データ配列を渡して実際のDB処理を実行します。
275         *
276         * ここでは、1行だけ処理するための簡易メソッドを提供します。
277         *
278         * @param       query   実行するSQL文
279         * @param       values  ?に割り当てる設定値
280         * @return      ここでの処理件数
281         *
282         * @throws RuntimeException Connection DB処理の実行に失敗した場合
283         */
284        public static int execute( final String query , final String... values ) {
285//              final List<String[]> list = new ArrayList<>();
286//              list.add( values );
287//
288//              return execute( query,list );
289
290                int     execCnt = 0;
291
292                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
293                SQLException throwEx = null;
294                // try-with-resources æ–‡ (AutoCloseable)
295                try( Connection conn = getConnection() ) {
296                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
297                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
298                                // ORACLE では、ParameterMetaDataは、使わない。
299                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
300
301                                setObject( pstmt , values , pMeta );
302                                execCnt = pstmt.executeUpdate();                        // 1回なので、+= の必要性は無い。
303
304                                conn.commit();
305                        }
306                        catch( final SQLException ex ) {
307                                conn.rollback();
308                                conn.setAutoCommit(true);
309                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
310//                              throw ex;
311                                throwEx = ex;
312                        }
313                }
314                catch( final SQLException ex ) {
315                        // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
316                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
317//                      throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
318                        throwEx = ex;
319                }
320                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
321                // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally
322//              finally {
323                        if( throwEx != null ) {
324                                // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
325                                throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( values ) );
326                        }
327//              }
328
329                return execCnt;
330        }
331
332        /**
333         * データ配列を渡して実際のDB処理を実行します。
334         *
335         * ここでは、1行だけ処理するための簡易メソッドを提供します。
336         *
337         * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionオブジェクト 対応
338         *
339         * @param       tarn    外部から指定するTransactionオブジェクト
340         * @param       query   実行するSQL文
341         * @param       values  ?に割り当てる設定値
342         * @return      ここでの処理件数
343         *
344         * @throws RuntimeException Connection DB処理の実行に失敗した場合
345         */
346        public static int execute( final Transaction tarn , final String query , final String... values ) {
347                // 8.5.4.2 (2024/01/12) PMD 7.0.0 UnusedAssignment
348//              int     execCnt = 0;
349                final int execCnt ;
350
351//              // try-with-resources æ–‡ (AutoCloseable)
352//              try( Connection conn = tarn.getConnection( null ) ) {
353                final Connection conn = tarn.getConnection( null );
354//              try {
355                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
356                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
357                                // ORACLE では、ParameterMetaDataは、使わない。
358                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
359
360                                setObject( pstmt , values , pMeta );
361                                execCnt = pstmt.executeUpdate();                        // 1回なので、+= の必要性は無い。
362
363                                tarn.commit();
364                        }
365                        catch( final SQLException ex ) {
366                                tarn.rollback();
367        //                      conn.setAutoCommit(true);
368        //                      throw ex;
369                                // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
370                                throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
371                        }
372        //      }
373        //      catch( final SQLException ex ) {
374        //              // MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
375        //              throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
376        //      }
377
378                return execCnt;
379        }
380
381        /**
382         * データ配列のListを渡して実際のDB処理を実行します。
383         *
384         * データ配列は、1行分のデータに対する設定値の配列です。
385         * これは、keys で指定した並び順と一致している必要があります。
386         *
387         * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
388         *
389         * @param       query   実行するSQL文
390         * @param       list    ?に割り当てる設定値
391         * @return      ここでの処理件数
392         *
393         * @throws RuntimeException Connection DB処理の実行に失敗した場合
394         */
395        public static int execute( final String query , final List<String[]> list ) {
396                return execute( query,list,true );                      // 互換性確保。true でエラー発生時に、Exception を throw する。
397        }
398
399        /**
400         * データ配列のListを渡して実際のDB処理を実行します。
401         *
402         * データ配列は、1行分のデータに対する設定値の配列です。
403         * これは、keys で指定した並び順と一致している必要があります。
404         *
405         * 処理の途中で整合性制約違反が発生した場合に継続するかどうかを指定できるフラグを追加しています。
406         * false に設定した場合は、エラーが発生しても処理を継続して、commit します。(7.4.1.0 (2021/04/23) )
407         *
408         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
409         * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
410         *
411         * @param       query   実行するSQL文
412         * @param       list    ?に割り当てる設定値
413         * @param       useErrro        false に設定すると、途中で整合性制約違反が発生しても処理を継続する。
414         * @return      ここでの処理件数
415         *
416         * @throws RuntimeException Connection DB処理の実行に失敗した場合
417         */
418        public static int execute( final String query , final List<String[]> list , final boolean useErrro ) {
419                LOGGER.debug( () -> "execute query=" + query );
420
421                String[] debugLine      = null;
422                int              execCnt        = 0;
423
424                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
425                SQLException throwEx = null;
426                // try-with-resources æ–‡ (AutoCloseable)
427                try( Connection conn = getConnection() ) {
428                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
429                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {               // 更新系なので、setFetchSize は不要。
430
431                                // ORACLE では、ParameterMetaDataは、使わない。
432                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
433
434                                for( final String[] values : list ) {
435                                        debugLine = values;
436                                        LOGGER.debug( () -> "execute values=" + Arrays.toString( values ) );
437                                        setObject( pstmt , values , pMeta );
438
439                                        // 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
440                                        try {
441                                                execCnt += pstmt.executeUpdate();
442                                        }
443                                        catch( final SQLIntegrityConstraintViolationException ex ) {
444                                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
445//                                              if( useErrro ) { throw ex; }
446                                                if( useErrro ) { throwEx = ex; break; }
447                                                else {
448                                                        // MSG0033 = 整合性制約違反が発生しました。\n\tメッセージ=[{0}]\n\tquery=[{1}]\n\tvalues={2}
449                                                        MsgUtil.errPrintln( "MSG0033" , ex.getMessage() , query , Arrays.toString( debugLine ) );
450                                                }
451                                        }
452                                }
453
454                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
455//                              conn.commit();
456                                if( throwEx == null ) { conn.commit(); }
457                        }
458                        catch( final SQLException ex ) {
459                                conn.rollback();
460                                conn.setAutoCommit(true);
461                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
462//                              throw ex;
463                                throwEx = ex;
464                        }
465                }
466                catch( final SQLException ex ) {
467//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
468//                      throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) );
469                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
470                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
471//                      throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) );
472                        throwEx = ex;
473                }
474                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
475                // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally
476//              finally {
477                        if( throwEx != null ) {
478                                // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
479                                throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( debugLine ) );
480                        }
481//              }
482
483                return execCnt;
484        }
485
486        /**
487         * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド)
488         *
489         * これは、updQueryで、更新してみて、0件の場合、insQuery で追加処理を行います。
490         *
491         * データ配列は、1行分のデータに対する設定値の配列です。
492         * これは、keys で指定した並び順と一致している必要があります。
493         *
494         * 処理の途中で整合性制約違反が発生した場合に継続するかどうかを指定できるフラグを追加しています。
495         * false に設定した場合は、エラーが発生しても処理を継続して、commit します。 (7.4.1.0 (2021/04/23) )
496         *
497         * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
498         *
499         * @param       insQuery        追加するSQL文
500         * @param       updQuery        更新するSQL文
501         * @param       insList ?に割り当てる設定値
502         * @param       updList ?に割り当てる設定値
503         * @return      ここでの処理件数
504         *
505         * @throws RuntimeException Connection DB処理の実行に失敗した場合
506         */
507        public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList ) {
508                return execute( insQuery,updQuery,insList,updList,true );               // 互換性確保。true でエラー発生時に、Exception を throw する。
509        }
510
511        /**
512         * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド)
513         *
514         * これは、updQueryで、更新してみて、0件の場合、insQuery で追加処理を行います。
515         *
516         * データ配列は、1行分のデータに対する設定値の配列です。
517         * これは、keys で指定した並び順と一致している必要があります。
518         *
519         * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
520         * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
521         *
522         * @param       insQuery        追加するSQL文
523         * @param       updQuery        更新するSQL文
524         * @param       insList ?に割り当てる設定値
525         * @param       updList ?に割り当てる設定値
526         * @param       useErrro        false に設定すると、途中で整合性制約違反が発生しても処理を継続する。
527         * @return      ここでの処理件数
528         *
529         * @throws RuntimeException Connection DB処理の実行に失敗した場合
530         */
531        public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList , final boolean useErrro ) {
532                LOGGER.debug( () -> "execute insQuery=" + insQuery + " , updQuery=" + updQuery );
533
534                String[] debugLine = null;
535                String   query     = null;
536
537                int     execCnt = 0;
538
539                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
540                SQLException throwEx = null;
541                // try-with-resources æ–‡ (AutoCloseable)
542                try( Connection conn = getConnection() ) {
543                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
544                        try( PreparedStatement inPstmt = conn.prepareStatement( insQuery );
545                                 PreparedStatement upPstmt = conn.prepareStatement( updQuery ) ) {
546
547                                // ORACLE では、ParameterMetaDataは、使わない。
548                                final ParameterMetaData inpMeta = oracleFlag ? null : inPstmt.getParameterMetaData();
549                                final ParameterMetaData uppMeta = oracleFlag ? null : upPstmt.getParameterMetaData();
550
551                                for( int i=0; i<updList.size(); i++ ) {                 // 更新処理と、挿入処理は、同じ数のListを用意する。
552                                        query = updQuery;
553                                        // 更新処理を行う。
554                                        final String[] upVals = updList.get(i);
555                                        debugLine = upVals;
556                                        setObject( upPstmt , upVals , uppMeta );
557
558                                        int cnt = upPstmt.executeUpdate();
559
560                                        if( cnt <= 0 ) {        // 更新が無い、つまり、追加対象
561                                                query = insQuery;
562                                                // 挿入処理を行う。
563                                                final String[] inVals = insList.get(i);
564                                                debugLine = inVals;
565                                                setObject( inPstmt , inVals , inpMeta );
566
567                                                LOGGER.debug( () -> "execute INSERT=" + Arrays.toString( inVals ) );
568
569                                                // 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
570                                                try {
571                                                        cnt = inPstmt.executeUpdate();
572                                                }
573                                                catch( final SQLIntegrityConstraintViolationException ex ) {
574                                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
575//                                                      if( useErrro ) { throw ex; }
576                                                        if( useErrro ) { throwEx = ex; break; }
577                                                        else {
578                                                                cnt = 0;        // エラー時に設定されないと、-1 のままなので。
579                                                                // MSG0033 = 整合性制約違反が発生しました。\n\tメッセージ=[{0}]\n\tquery=[{1}]\n\tvalues={2}
580                                                                MsgUtil.errPrintln( "MSG0033" , ex.getMessage() , insQuery , Arrays.toString( debugLine ) );
581                                                        }
582                                                }
583                                        }
584                                        else {          // 元々、このelse は必要ない。UPDATE は、先に処理済
585                                                LOGGER.debug( () -> "execute UPDATE=" + Arrays.toString( upVals ) );
586                                        }
587
588                                        execCnt += cnt;
589                                }
590                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
591//                              conn.commit();
592                                if( throwEx == null ) { conn.commit(); }
593                        }
594                        catch( final SQLException ex ) {
595                                conn.rollback();
596                                conn.setAutoCommit(true);
597                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
598//                              throw ex;
599                                throwEx = ex;
600                        }
601                }
602                catch( final SQLException ex ) {
603//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
604//                      throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) );
605                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
606                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
607//                      throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) );
608                        throwEx = ex;
609                }
610                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
611                // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally
612//              finally {
613                        if( throwEx != null ) {
614                                // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
615                                throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( debugLine ) );
616                        }
617//              }
618
619                return execCnt;
620        }
621
622        /**
623         * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。
624         *
625         * ステートメントと引数により、Prepared クエリーの検索のみ実行します。
626         * 結果は、すべて文字列に変換されて格納されます。
627         *
628         * @param   query ステートメント文字列
629         * @param   args オブジェクトの引数配列
630         *
631         * @return  検索結果のリスト配列(結果が無ければ、サイズゼロのリスト)
632         * @throws RuntimeException DB検索処理の実行に失敗した場合
633         * @og.rtnNotNull
634         */
635        public static List<String[]> dbQuery( final String query , final String... args ) {
636                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
637                SQLException throwEx = null;
638                // try-with-resources æ–‡ (AutoCloseable)
639                try( Connection conn = getConnection() ) {
640                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
641                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
642                                // ORACLE では、ParameterMetaDataは、使わない。
643                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
644                                // 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。
645                                setObject( pstmt , args , pMeta );
646
647                                if( pstmt.execute() ) {
648                                        try( ResultSet resultSet = pstmt.getResultSet() ) {
649                                                return resultToArray( resultSet );
650                                        }
651                                }
652                                conn.commit();
653                        }
654                        catch ( final SQLException ex ) {
655                                conn.rollback();
656                                conn.setAutoCommit(true);
657                                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
658//                              throw ex;
659                                throwEx = ex;
660                        }
661                }
662                catch ( final SQLException ex ) {
663//                      // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
664//                      throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( args ) );
665                        // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
666                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
667//                      throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
668                        throwEx = ex;
669                }
670                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
671                // 8.5.5.1 (2024/02/29) PMD 7.0.0 DoNotThrowExceptionInFinally (return で抜けない)
672//              finally {
673                        if( throwEx != null ) {
674                                // MSG0019 = DB処理の実行に失敗しました。メッセージ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
675                                throw MsgUtil.throwException( throwEx , "MSG0019" , query , Arrays.toString( args ) );
676                        }
677//              }
678
679                // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseDiamondOperator 対応
680//              return new ArrayList<String[]>();
681                return new ArrayList<>();
682        }
683
684        /**
685         * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。
686         *
687         * ステートメントと引数により、Prepared クエリーの検索のみ実行します。
688         * 結果は、すべて文字列に変換されて格納されます。
689         *
690         * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionオブジェクト 対応
691         *
692         * @param       tarn    外部から指定するTransactionオブジェクト
693         * @param   query ステートメント文字列
694         * @param   args オブジェクトの引数配列
695         *
696         * @return  検索結果のリスト配列(結果が無ければ、サイズゼロのリスト)
697         * @throws RuntimeException DB検索処理の実行に失敗した場合
698         * @og.rtnNotNull
699         */
700        public static List<String[]> dbQuery( final Transaction tarn , final String query , final String... args ) {
701                final Connection conn = tarn.getConnection( null );
702
703                // try-with-resources æ–‡ (AutoCloseable)
704        //      try( Connection conn = tarn.getConnection( null ) ) {
705                        // try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
706                        try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
707                                // ORACLE では、ParameterMetaDataは、使わない。
708                                final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
709                                // 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。
710                                setObject( pstmt , args , pMeta );
711
712                                if( pstmt.execute() ) {
713                                        try( ResultSet resultSet = pstmt.getResultSet() ) {
714                                                return resultToArray( resultSet );
715                                        }
716                                }
717                                tarn.commit();
718                        }
719                        catch ( final SQLException ex ) {
720                                tarn.rollback();
721                //              conn.setAutoCommit(true);
722                //              throw ex;
723                                // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
724                                throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
725                        }
726        //      }
727        //      catch ( final SQLException ex ) {
728        //              // 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
729        //              throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
730        //      }
731
732                // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseDiamondOperator 対応
733//              return new ArrayList<String[]>();
734                return new ArrayList<>();
735        }
736
737        /**
738         * ResultSet より、結果の文字列配列を作成します。
739         *
740         * 結果は、すべて文字列に変換されて格納されます。
741         * 移動したメソッドで使われているのでこれも移動
742         *
743         * @param   resultSet ResultSetオブジェクト
744         *
745         * @return  ResultSetの検索結果リスト配列
746         * @throws      java.sql.SQLException データベース・アクセス・エラーが発生した場合
747         * @og.rtnNotNull
748         */
749        public static List<String[]> resultToArray( final ResultSet resultSet ) throws SQLException {
750//              final ArrayList<String[]> data = new ArrayList<>();
751                final List<String[]> data = new ArrayList<>();                  // 8.5.4.2 (2024/01/12) PMD 7.0.0 LooseCoupling
752
753                final ResultSetValue rsv = new ResultSetValue( resultSet );
754
755                while( rsv.next() ) {
756                        data.add( rsv.getValues() );
757                }
758
759                return data;
760        }
761
762        /**
763         * データをインサートする場合に使用するSQL文を作成します。
764         *
765         * これは、key に対応した ? 文字列で、SQL文を作成します。
766         * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
767         * conKeysとconValsは、固定値のキーと値です。
768         * conKeys,conVals がnullの場合は、これらの値を使用しません。
769         *
770         * @param       table   テーブルID
771         * @param       keys    設定値に対応するキー配列
772         * @param       conKeys 固定値の設定値に対応するキー配列
773         * @param       conVals 固定値に対応する値配列
774         * @return  インサートSQL
775         * @og.rtnNotNull
776         */
777        public static String getInsertSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals ) {
778                final String[] vals = new String[keys.length];
779                Arrays.fill( vals , "?" );
780
781                final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ;
782
783                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
784                        .append( "INSERT INTO " ).append( table )
785                        .append( " ( " )
786                        .append( String.join( "," , keys ) );
787
788                if( useConst ) {
789                        sql.append( ',' ).append( String.join( "," , conKeys ) );
790                }
791
792                sql.append( " ) VALUES ( " )
793                        .append( String.join( "," , vals ) );
794
795                if( useConst ) {
796                        sql.append( ",'" ).append( String.join( "','" , conVals ) ).append( '\'' );
797                }
798
799                return sql.append( " )" ).toString();
800        }
801
802        /**
803         * データをアップデートする場合に使用するSQL文を作成します。
804         *
805         * これは、key に対応した ? 文字列で、SQL文を作成します。
806         * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
807         * WHERE 文字列は、この、? も含めたWHERE条件の文字列を渡します。
808         * WHERE条件の場合は、この、?に、関数を設定したり、条件を指定したり、
809         * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が
810         * 限られるためです。
811         * conKeysとconValsは、固定値のキーと値です。
812         * conKeys,conVals,where がnullの場合は、これらの値を使用しません。
813         *
814         * @og.rev 7.2.5.0 (2020/06/01) UPDATEで、? を含むキーワードを、処理できるようにします。
815         *
816         * @param       table   テーブルID
817         * @param       keys    設定値に対応するキー配列
818         * @param       conKeys 固定値の設定値に対応するキー配列
819         * @param       conVals 固定値に対応する値配列(VARCHARのみ)
820         * @param       where   WHERE条件式
821         * @return  アップデートSQL
822         * @og.rtnNotNull
823         */
824        public static String getUpdateSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals , final String where ) {
825                final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ;
826
827                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
828                        .append( "UPDATE " ).append( table ).append( " SET " );
829//                      .append( String.join( " = ? ," , keys ) )                                       // key[0] = ? , ・・・  = ? , key[n-1] という文字列が作成されます。
830//                      .append( " = ? " );                                                                                     // 最後の key[n-1] の後ろに = ? という文字列を追加します。
831                for( final String key : keys ) {
832                        sql.append( key );
833                        if( ! key.contains( "?" ) ) {
834                                sql.append( " = ? " );                          // key = ? という文字列が作成されます。
835                        }
836                        sql.append( ',' );
837                }
838                sql.deleteCharAt( sql.length() - 1 );           // 最後の一文字(,)を削除します。
839
840                if( useConst ) {
841                        for( int i=0; i<conKeys.length; i++ ) {
842                                sql.append( ',' ).append( conKeys[i] ).append( " = '" ).append( conVals[i] ).append( "' " );
843                        }
844                }
845
846                if( where != null && !where.isEmpty() ) {
847                        sql.append( " WHERE " ).append( where );                        // WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。
848                }
849
850                return sql.toString();
851        }
852
853        /**
854         * データをデリートする場合に使用するSQL文を作成します。
855         *
856         * これは、key に対応した ? 文字列で、SQL文を作成します。
857         * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
858         * WHERE 文字列は、この、? も含めたWHERE条件の文字列を渡します。
859         * WHERE条件の場合は、この、?に、関数を設定したり、条件を指定したり、
860         * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が
861         * 限られるためです。
862         *
863         * @param       table   テーブルID
864         * @param       where   設定値に対応するキー配列(可変長引数)
865         * @return  デリートSQL
866         * @og.rtnNotNull
867         */
868        public static String getDeleteSQL( final String table , final String where ) {
869                final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
870                        .append( "DELETE FROM " ).append( table );
871
872                if( where != null && !where.isEmpty() ) {
873                        sql.append( " WHERE " ).append( where );                        // WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。
874                }
875
876                return sql.toString();
877        }
878}