    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) コネクションの獲得まで、２秒待つ
    064        /** データベースリトライ回数 {@value} */
    065        public static final int CONN_RETRY_COUNT = 10 ;         // 6.8.2.2 (2017/11/02) コネクションの獲得まで、１０回リトライする。
    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 は、必ず、４つ必要です。
    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処理の実行に失敗のエラーは、３回までは、何も出さない。
    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 ) {  // とりあえず３回までは、何も出さない
    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 のカラム番号は、１から始まる。
    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 のカラム番号は、１から始まる。
    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         * ここでは、１行だけ処理するための簡易メソッドを提供します。
    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         * ここでは、１行だけ処理するための簡易メソッドを提供します。
    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         * データ配列は、１行分のデータに対する設定値の配列です。
    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         * データ配列は、１行分のデータに対する設定値の配列です。
    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で、更新してみて、０件の場合、insQuery で追加処理を行います。
    447         *
    448         * データ配列は、１行分のデータに対する設定値の配列です。
    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で、更新してみて、０件の場合、insQuery で追加処理を行います。
    472         *
    473         * データ配列は、１行分のデータに対する設定値の配列です。
    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}