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