/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.fileexec;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.PreparedStatement;
import java.sql.ParameterMetaData;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;		// 7.4.1.0 (2021/04/23)

import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;

import org.opengion.fukurou.system.HybsConst;					// 7.2.3.1 (2020/04/17)
import org.opengion.fukurou.db.Transaction;						// 7.4.2.0 (2021/05/14)

/**
 * データベース処理を行う、簡易的なユーティリティークラスです。
 * staticメソッドしか持っていません。
 * sql文を execute( query ) する事により,データベースに書き込みます。
 *
 * このクラスは、マルチスレッドに対して、安全です。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class DBUtil {
	private static final XLogger LOGGER= XLogger.getLogger( DBUtil.class.getSimpleName() );	// ログ出力

	/** ﾃﾞｰﾀﾍﾞｰｽのｷｰﾜｰﾄﾞ {@value}	*/
	public static final String DATABASE_KEY	= "DATABASE";

	/** 接続先URL	{@value}	*/
	public static final String URL_KEY		= "REALM_URL";
	/** ﾄﾞﾗｲﾊﾞｰ 	{@value}	*/
	public static final String DRIVER_KEY	= "REALM_DRIVER";
	/** ﾕｰｻﾞｰID 	{@value}	*/
	public static final String NAME_KEY		= "REALM_NAME";
	/** ﾊﾟｽﾜｰﾄﾞ 	{@value}	*/
	public static final String PASSWORD_KEY	= "REALM_PASSWORD";

	/** データベースリトライの待ち時間(ミリ秒) {@value} */
	public static final int CONN_SLEEP_TIME  = 2000 ;	// 6.8.2.2 (2017/11/02) コネクションの獲得まで、２秒待つ
	/** データベースリトライ回数 {@value} */
	public static final int CONN_RETRY_COUNT = 10 ;		// 6.8.2.2 (2017/11/02) コネクションの獲得まで、１０回リトライする。
	/** データベースValid タイムアウト時間(秒) {@value} */
	public static final int CONN_VALID_TIMEOUT = 10 ;	// 6.8.2.2 (2017/11/02) コネクションのValidチェックのタイムアウト時間。

	/** データ検索時のフェッチサイズ  {@value} */
	public static final int DB_FETCH_SIZE = 251 ;

	private static final DataSource DATA_SOURCE = new DataSource();

	private static boolean readyFlag ;		// 準備が出来た場合は、true
	private static boolean oracleFlag ;		// 接続先がORACLEの場合は、true

	private static final int	BUFFER_MIDDLE = 200 ;

	/**
	 * インスタンスを作成させないため、private 化します。
	 */
	private DBUtil() {}
	/**
	 * 引数を指定せず、オブジェクトを作成します。
	 *
	 * System.getProperty より取得し、さらに、そこから取得できなかった
	 * 場合は、環境変数から、取得します。
	 *
	 * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
	 *
	 * @see		#URL_KEY
	 */
	public static void init() {
//		init(	System.getProperty( URL_KEY			, System.getenv( URL_KEY		) ) ,
//				System.getProperty( DRIVER_KEY		, System.getenv( DRIVER_KEY		) ) ,
//				System.getProperty( NAME_KEY		, System.getenv( NAME_KEY		) ) ,
//				System.getProperty( PASSWORD_KEY	, System.getenv( PASSWORD_KEY	) )
//		);
		init(	HybsConst.getenv( URL_KEY		) ,
				HybsConst.getenv( DRIVER_KEY	) ,
				HybsConst.getenv( NAME_KEY		) ,
				HybsConst.getenv( PASSWORD_KEY	)
		);
	}

	/**
	 * 接続先URL、ドライバー、ユーザーID、パスワードなどを含んだMapを指定して、オブジェクトを作成します。
	 *
	 * Mapに指定のキーが含まれない場合は、System.getProperty より取得し、さらに、そこから取得できなかった
	 * 場合は、環境変数から、取得します。
	 *
	 * @og.rev 7.2.3.1 (2020/04/17) System.getenv → HybsConst.getenv 変更(サービス化対応)
	 *
	 * @param	prmMap	必要情報を含んだMapオブジェクト
	 * @see		#URL_KEY
	 */
	public static void init( final Map<String,String> prmMap ) {
//		init(	prmMap.getOrDefault( URL_KEY		, System.getProperty( URL_KEY			, System.getenv( URL_KEY		) ) ) ,
//				prmMap.getOrDefault( DRIVER_KEY		, System.getProperty( DRIVER_KEY		, System.getenv( DRIVER_KEY		) ) ) ,
//				prmMap.getOrDefault( NAME_KEY		, System.getProperty( NAME_KEY			, System.getenv( NAME_KEY		) ) ) ,
//				prmMap.getOrDefault( PASSWORD_KEY	, System.getProperty( PASSWORD_KEY		, System.getenv( PASSWORD_KEY	) ) )
//		);
		init(	prmMap.getOrDefault( URL_KEY		, HybsConst.getenv( URL_KEY			) ) ,
				prmMap.getOrDefault( DRIVER_KEY		, HybsConst.getenv( DRIVER_KEY		) ) ,
				prmMap.getOrDefault( NAME_KEY		, HybsConst.getenv( NAME_KEY		) ) ,
				prmMap.getOrDefault( PASSWORD_KEY	, HybsConst.getenv( PASSWORD_KEY	) )
		);
	}

	/**
	 * 接続先URL、ドライバー、ユーザーID、パスワードを指定して、オブジェクトを作成します。
	 *
	 * params は、必ず、４つ必要です。
	 *
	 * @param	params		接続先URL、ドライバー、ユーザーID、パスワード
	 * @see		#isReady()
	 */
	public static void init( final String... params ) {
		if( readyFlag ) {
			// MSG0024 = すでに、接続先設定は完了しています。[{0}]
			throw MsgUtil.throwException( "MSG0024" , DATA_SOURCE );
		}

		if( params == null || params.length != 4 ) {
			// MSG0027 = 接続先設定情報が不足しています。[{0}]
			throw MsgUtil.throwException( "MSG0027" , Arrays.toString( params ) );
		}

		final PoolProperties pp = new PoolProperties();
		pp.setUrl(				params[0] );
		pp.setDriverClassName(	params[1] );
		pp.setUsername(			params[2] );
		pp.setPassword(			params[3] );

		DATA_SOURCE.setPoolProperties( pp );
		readyFlag = true;

		oracleFlag = params[0] != null && params[0].startsWith( "jdbc:oracle" );
	}

	/**
	 * DataSourceの初期化が完了していれば、true を返します。
	 *
	 * 初期化は、#init(String...) メソッドの呼び出して、完了します。
	 * #crear() で、未完了に戻ります。
	 *
	 * @return	初期化が完了しているかどうか
	 * @see		#init(String...)
	 */
	public static boolean isReady() { return readyFlag; }

	/**
	 * 接続先がORACLEかどうかを返します。
	 *
	 * ORACLE の場合は、true を返します。
	 *
	 * @return	接続先がORACLEかどうか[true:ORACLE false:その他]
	 */
	public static boolean isOracle() { return oracleFlag; }

	/**
	 * DataSource から、Connectionを取得して、返します。
	 *
	 * @og.rev 6.8.2.2 (2017/11/02) コネクションの再取得をリトライします。
	 * @og.rev 7.2.5.0 (2020/06/01) DB処理の実行に失敗のエラーは、３回までは、何も出さない。
	 *
	 * @return	DataSourceから、Connectionを取得して、返します。
	 * @throws	SQLException SQLエラーが発生した場合
	 */
	public static Connection getConnection() throws SQLException {
		if( !readyFlag ) {
	//		// MSG0025 = 接続先設定が完了していません。
	//		throw MsgUtil.throwException( "MSG0025" , "getConnection() Error!!" );
			init();
		}

		SQLException errEX = null;
		for( int i=0; i<CONN_RETRY_COUNT; i++ ) {
			try {
				final Connection conn = DATA_SOURCE.getConnection();
				conn.setAutoCommit( false );

				if( conn.isValid( CONN_VALID_TIMEOUT ) ) { return conn; }
			}
			catch( final SQLException ex ) {
				if( i >= 3 ) {	// とりあえず３回までは、何も出さない
//					// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
//					MsgUtil.errPrintln( "MSG0019" , ex.getMessage() );
					// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
					MsgUtil.errPrintln( "MSG0019" , ex.getMessage() , "" );
				}

				errEX = ex ;
				try{ Thread.sleep( CONN_SLEEP_TIME ); } catch( final InterruptedException ex2 ){}
			}
		}

		final String errMsg = errEX == null ? "COUNT Over" : errEX.getMessage() ;
//		// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
//		throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" , "" );
		// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
		throw MsgUtil.throwException( errEX , "MSG0019" , errMsg , "getConnection" );
	}

	/**
	 * データ配列を渡してPreparedStatementの引数に、値をセットします。
	 *
	 * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
	 * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
	 * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
	 *
	 * @param	pstmt	PreparedStatementオブジェクト
	 * @param	values	？に割り当てる設定値
	 * @param	pMeta	オラクル系以外のDBに対して、type指定する場合に使用する ParameterMetaDataオブジェクト
	 *
	 * @throws SQLException DB処理の実行に失敗した場合
	 */
	private static void setObject( final PreparedStatement pstmt , final String[] values , final ParameterMetaData pMeta ) throws SQLException {
		if( values != null && values.length > 0 ) {
			// ORACLE では、ParameterMetaDataは、使わない。
			if( pMeta == null ) {
				int clmNo = 1;	// JDBC のカラム番号は、１から始まる。
				for( int i=0; i<values.length; i++ ) {
					final String val = values[i];
					pstmt.setObject( clmNo++,val );
				}
			}
			else {
				int clmNo = 1;	// JDBC のカラム番号は、１から始まる。
				for( int i=0; i<values.length; i++ ) {
					final int type = pMeta.getParameterType( clmNo );
					final String val = values[i];
					if( val == null || val.isEmpty() ) {
						pstmt.setNull( clmNo++, type );
					}
					else {
						pstmt.setObject( clmNo++,val,type );
					}
				}
			}
		}
	}

	/**
	 * データ配列を渡して実際のDB処理を実行します。
	 *
	 * ここでは、１行だけ処理するための簡易メソッドを提供します。
	 *
	 * @param	query	実行するSQL文
	 * @param	values	？に割り当てる設定値
	 * @return	ここでの処理件数
	 *
	 * @throws RuntimeException Connection DB処理の実行に失敗した場合
	 */
	public static int execute( final String query , final String... values ) {
//		final List<String[]> list = new ArrayList<>();
//		list.add( values );
//
//		return execute( query,list );

		int	execCnt = 0;

		// try-with-resources 文 (AutoCloseable)
		try( Connection conn = getConnection() ) {
			// try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
			try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
				// ORACLE では、ParameterMetaDataは、使わない。
				final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();

				setObject( pstmt , values , pMeta );
				execCnt = pstmt.executeUpdate();			// 1回なので、+= の必要性は無い。

				conn.commit();
			}
			catch( final SQLException ex ) {
				conn.rollback();
				conn.setAutoCommit(true);
				throw ex;
			}
		}
		catch( final SQLException ex ) {
			// MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
			throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
		}

		return execCnt;
	}

	/**
	 * データ配列を渡して実際のDB処理を実行します。
	 *
	 * ここでは、１行だけ処理するための簡易メソッドを提供します。
	 *
	 * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionｵﾌﾞｼﾞｪｸﾄ 対応
	 *
	 * @param	tarn	外部から指定するTransactionｵﾌﾞｼﾞｪｸﾄ
	 * @param	query	実行するSQL文
	 * @param	values	？に割り当てる設定値
	 * @return	ここでの処理件数
	 *
	 * @throws RuntimeException Connection DB処理の実行に失敗した場合
	 */
	public static int execute( final Transaction tarn , final String query , final String... values ) {
		int	execCnt = 0;

//		// try-with-resources 文 (AutoCloseable)
//		try( Connection conn = tarn.getConnection( null ) ) {
		final Connection conn = tarn.getConnection( null );
//		try {
			// try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
			try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
				// ORACLE では、ParameterMetaDataは、使わない。
				final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();

				setObject( pstmt , values , pMeta );
				execCnt = pstmt.executeUpdate();			// 1回なので、+= の必要性は無い。

				tarn.commit();
			}
			catch( final SQLException ex ) {
				tarn.rollback();
	//			conn.setAutoCommit(true);
	//			throw ex;
				// MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
				throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
			}
	//	}
	//	catch( final SQLException ex ) {
	//		// MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
	//		throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( values ) );
	//	}

		return execCnt;
	}

	/**
	 * データ配列のListを渡して実際のDB処理を実行します。
	 *
	 * データ配列は、１行分のデータに対する設定値の配列です。
	 * これは、keys で指定した並び順と一致している必要があります。
	 *
	 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
	 *
	 * @param	query	実行するSQL文
	 * @param	list	？に割り当てる設定値
	 * @return	ここでの処理件数
	 *
	 * @throws RuntimeException Connection DB処理の実行に失敗した場合
	 */
	public static int execute( final String query , final List<String[]> list ) {
		return execute( query,list,true );			// 互換性確保。true でｴﾗｰ発生時に、Exception を throw する。
	}

	/**
	 * データ配列のListを渡して実際のDB処理を実行します。
	 *
	 * データ配列は、１行分のデータに対する設定値の配列です。
	 * これは、keys で指定した並び順と一致している必要があります。
	 *
	 * 処理の途中で整合性制約違反が発生した場合に継続するかどうかを指定できるﾌﾗｸﾞを追加しています。
	 * false に設定した場合は、ｴﾗｰが発生しても処理を継続して、commit します。(7.4.1.0 (2021/04/23) )
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
	 *
	 * @param	query	実行するSQL文
	 * @param	list	？に割り当てる設定値
	 * @param	useErrro	false に設定すると、途中で整合性制約違反が発生しても処理を継続する。
	 * @return	ここでの処理件数
	 *
	 * @throws RuntimeException Connection DB処理の実行に失敗した場合
	 */
	public static int execute( final String query , final List<String[]> list , final boolean useErrro ) {
		LOGGER.debug( () -> "execute query=" + query );

		String[] debugLine	= null;
		int		 execCnt	= 0;

		// try-with-resources 文 (AutoCloseable)
		try( Connection conn = getConnection() ) {
			// try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
			try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {		// 更新系なので、setFetchSize は不要。

				// ORACLE では、ParameterMetaDataは、使わない。
				final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();

				for( final String[] values : list ) {
					debugLine = values;
					LOGGER.debug( () -> "execute values=" + Arrays.toString( values ) );
					setObject( pstmt , values , pMeta );

					// 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
					try {
						execCnt += pstmt.executeUpdate();
					}
					catch( final SQLIntegrityConstraintViolationException ex ) {
						if( useErrro ) { throw ex; }
						else {
							// MSG0033 = 整合性制約違反が発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]\n\tquery=[{1}]\n\tvalues={2}
							MsgUtil.errPrintln( "MSG0033" , ex.getMessage() , query , Arrays.toString( debugLine ) );
						}
					}
				}

				conn.commit();
			}
			catch( final SQLException ex ) {
				conn.rollback();
				conn.setAutoCommit(true);
				throw ex;
			}
		}
		catch( final SQLException ex ) {
//			// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
//			throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) );
			// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
			throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) );
		}

		return execCnt;
	}

	/**
	 * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド)
	 *
	 * これは、updQueryで、更新してみて、０件の場合、insQuery で追加処理を行います。
	 *
	 * データ配列は、１行分のデータに対する設定値の配列です。
	 * これは、keys で指定した並び順と一致している必要があります。
	 *
	 * 処理の途中で整合性制約違反が発生した場合に継続するかどうかを指定できるﾌﾗｸﾞを追加しています。
	 * false に設定した場合は、ｴﾗｰが発生しても処理を継続して、commit します。 (7.4.1.0 (2021/04/23) )
	 *
	 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
	 *
	 * @param	insQuery	追加するSQL文
	 * @param	updQuery	更新するSQL文
	 * @param	insList	？に割り当てる設定値
	 * @param	updList	？に割り当てる設定値
	 * @return	ここでの処理件数
	 *
	 * @throws RuntimeException Connection DB処理の実行に失敗した場合
	 */
	public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList ) {
		return execute( insQuery,updQuery,insList,updList,true );		// 互換性確保。true でｴﾗｰ発生時に、Exception を throw する。
	}

	/**
	 * データ配列のListを渡して実際のDB処理を実行します。(暫定メソッド)
	 *
	 * これは、updQueryで、更新してみて、０件の場合、insQuery で追加処理を行います。
	 *
	 * データ配列は、１行分のデータに対する設定値の配列です。
	 * これは、keys で指定した並び順と一致している必要があります。
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
	 *
	 * @param	insQuery	追加するSQL文
	 * @param	updQuery	更新するSQL文
	 * @param	insList	？に割り当てる設定値
	 * @param	updList	？に割り当てる設定値
	 * @param	useErrro	false に設定すると、途中で整合性制約違反が発生しても処理を継続する。
	 * @return	ここでの処理件数
	 *
	 * @throws RuntimeException Connection DB処理の実行に失敗した場合
	 */
	public static int execute( final String insQuery , final String updQuery , final List<String[]> insList , final List<String[]> updList , final boolean useErrro ) {
		LOGGER.debug( () -> "execute insQuery=" + insQuery + " , updQuery=" + updQuery );

		String[] debugLine = null;
		String   query     = null;

		int	execCnt = 0;

		// try-with-resources 文 (AutoCloseable)
		try( Connection conn = getConnection() ) {
			// try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
			try( PreparedStatement inPstmt = conn.prepareStatement( insQuery );
				 PreparedStatement upPstmt = conn.prepareStatement( updQuery ) ) {

				// ORACLE では、ParameterMetaDataは、使わない。
				final ParameterMetaData	inpMeta = oracleFlag ? null : inPstmt.getParameterMetaData();
				final ParameterMetaData	uppMeta = oracleFlag ? null : upPstmt.getParameterMetaData();

				for( int i=0; i<updList.size(); i++ ) {			// 更新処理と、挿入処理は、同じ数のListを用意する。
					query = updQuery;
					// 更新処理を行う。
					final String[] upVals = updList.get(i);
					debugLine = upVals;
					setObject( upPstmt , upVals , uppMeta );

					int cnt = upPstmt.executeUpdate();

					if( cnt <= 0 ) {	// 更新が無い、つまり、追加対象
						query = insQuery;
						// 挿入処理を行う。
						final String[] inVals = insList.get(i);
						debugLine = inVals;
						setObject( inPstmt , inVals , inpMeta );

						LOGGER.debug( () -> "execute INSERT=" + Arrays.toString( inVals ) );

						// 7.4.1.0 (2021/04/23) 途中で整合性制約違反が発生した場合に継続するかどうかを指定できるようにします。
						try {
							cnt = inPstmt.executeUpdate();
						}
						catch( final SQLIntegrityConstraintViolationException ex ) {
							if( useErrro ) { throw ex; }
							else {
								cnt = 0;	// ｴﾗｰ時に設定されないと、-1 のままなので。
								// MSG0033 = 整合性制約違反が発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]\n\tquery=[{1}]\n\tvalues={2}
								MsgUtil.errPrintln( "MSG0033" , ex.getMessage() , insQuery , Arrays.toString( debugLine ) );
							}
						}
					}
					else {		// 元々、このelse は必要ない。UPDATE は、先に処理済
						LOGGER.debug( () -> "execute UPDATE=" + Arrays.toString( upVals ) );
					}

					execCnt += cnt;
				}
				conn.commit();
			}
			catch( final SQLException ex ) {
				conn.rollback();
				conn.setAutoCommit(true);
				throw ex;
			}
		}
		catch( final SQLException ex ) {
//			// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
//			throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( debugLine ) );
			// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
			throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( debugLine ) );
		}

		return execCnt;
	}

	/**
	 * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。
	 *
	 * ステートメントと引数により、Prepared クエリーの検索のみ実行します。
	 * 結果は,すべて文字列に変換されて格納されます。
	 *
	 * @param   query ステートメント文字列
	 * @param   args オブジェクトの引数配列
	 *
	 * @return  検索結果のリスト配列(結果が無ければ、サイズゼロのリスト)
	 * @throws RuntimeException DB検索処理の実行に失敗した場合
	 * @og.rtnNotNull
	 */
	public static List<String[]> dbQuery( final String query , final String... args ) {
		// try-with-resources 文 (AutoCloseable)
		try( Connection conn = getConnection() ) {
			// try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
			try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
				// ORACLE では、ParameterMetaDataは、使わない。
				final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
				// 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。
				setObject( pstmt , args , pMeta );

				if( pstmt.execute() ) {
					try( ResultSet resultSet = pstmt.getResultSet() ) {
						return resultToArray( resultSet );
					}
				}
				conn.commit();
			}
			catch ( final SQLException ex ) {
				conn.rollback();
				conn.setAutoCommit(true);
				throw ex;
			}
		}
		catch ( final SQLException ex ) {
//			// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
//			throw MsgUtil.throwException( ex , "MSG0019" , ex.getMessage() , query , Arrays.toString( args ) );
			// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
			throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
		}

		return new ArrayList<String[]>();
	}

	/**
	 * 検索するデータベースを指定して、Queryを実行します(Transaction 対応)。
	 *
	 * ステートメントと引数により、Prepared クエリーの検索のみ実行します。
	 * 結果は,すべて文字列に変換されて格納されます。
	 *
	 * @og.rev 7.4.2.0 (2021/05/14) 外部から指定するTransactionｵﾌﾞｼﾞｪｸﾄ 対応
	 *
	 * @param	tarn	外部から指定するTransactionｵﾌﾞｼﾞｪｸﾄ
	 * @param   query ステートメント文字列
	 * @param   args オブジェクトの引数配列
	 *
	 * @return  検索結果のリスト配列(結果が無ければ、サイズゼロのリスト)
	 * @throws RuntimeException DB検索処理の実行に失敗した場合
	 * @og.rtnNotNull
	 */
	public static List<String[]> dbQuery( final Transaction tarn , final String query , final String... args ) {
		final Connection conn = tarn.getConnection( null );

		// try-with-resources 文 (AutoCloseable)
	//	try( Connection conn = tarn.getConnection( null ) ) {
			// try-with-resources 文 は、close()してから、commit()後に、catch節が呼ばれる。
			try( PreparedStatement pstmt = conn.prepareStatement( query ) ) {
				// ORACLE では、ParameterMetaDataは、使わない。
				final ParameterMetaData pMeta = oracleFlag ? null : pstmt.getParameterMetaData();
				// 6.4.3.2 (2016/02/19) args が null でなく、length==0 でない場合のみ、処理する。
				setObject( pstmt , args , pMeta );

				if( pstmt.execute() ) {
					try( ResultSet resultSet = pstmt.getResultSet() ) {
						return resultToArray( resultSet );
					}
				}
				tarn.commit();
			}
			catch ( final SQLException ex ) {
				tarn.rollback();
		//		conn.setAutoCommit(true);
		//		throw ex;
				// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
				throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
			}
	//	}
	//	catch ( final SQLException ex ) {
	//		// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
	//		throw MsgUtil.throwException( ex , "MSG0019" , query , Arrays.toString( args ) );
	//	}

		return new ArrayList<String[]>();
	}

	/**
	 * ResultSet より、結果の文字列配列を作成します。
	 *
	 * 結果は,すべて文字列に変換されて格納されます。
	 * 移動したメソッドで使われているのでこれも移動
	 *
	 * @param   resultSet ResultSetオブジェクト
	 *
	 * @return  ResultSetの検索結果リスト配列
	 * @throws	java.sql.SQLException データベース・アクセス・エラーが発生した場合
	 * @og.rtnNotNull
	 */
	public static List<String[]> resultToArray( final ResultSet resultSet ) throws SQLException {
		final ArrayList<String[]> data = new ArrayList<>();

		final ResultSetValue rsv = new ResultSetValue( resultSet );

		while( rsv.next() ) {
			data.add( rsv.getValues() );
		}

		return data;
	}

	/**
	 * データをインサートする場合に使用するSQL文を作成します。
	 *
	 * これは、key に対応した ？ 文字列で、SQL文を作成します。
	 * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
	 * conKeysとconValsは、固定値のキーと値です。
	 * conKeys,conVals がnullの場合は、これらの値を使用しません。
	 *
	 * @param	table	テーブルID
	 * @param	keys	設定値に対応するキー配列
	 * @param	conKeys	固定値の設定値に対応するキー配列
	 * @param	conVals	固定値に対応する値配列
	 * @return  インサートSQL
	 * @og.rtnNotNull
	 */
	public static String getInsertSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals ) {
		final String[] vals = new String[keys.length];
		Arrays.fill( vals , "?" );

		final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ;

		final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
			.append( "INSERT INTO " ).append( table )
			.append( " ( " )
			.append( String.join( "," , keys ) );

		if( useConst ) {
			sql.append( ',' ).append( String.join( "," , conKeys ) );
		}

		sql.append( " ) VALUES ( " )
			.append( String.join( "," , vals ) );

		if( useConst ) {
			sql.append( ",'" ).append( String.join( "','" , conVals ) ).append( '\'' );
		}

		return sql.append( " )" ).toString();
	}

	/**
	 * データをアップデートする場合に使用するSQL文を作成します。
	 *
	 * これは、key に対応した ？ 文字列で、SQL文を作成します。
	 * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
	 * WHERE 文字列は、この、？ も含めたWHERE条件の文字列を渡します。
	 * WHERE条件の場合は、この、？に、関数を設定したり、条件を指定したり、
	 * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が
	 * 限られるためです。
	 * conKeysとconValsは、固定値のキーと値です。
	 * conKeys,conVals,where がnullの場合は、これらの値を使用しません。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) UPDATEで、? を含むｷｰﾜｰﾄﾞを、処理できるようにします。
	 *
	 * @param	table	テーブルID
	 * @param	keys	設定値に対応するキー配列
	 * @param	conKeys	固定値の設定値に対応するキー配列
	 * @param	conVals	固定値に対応する値配列(VARCHARのみ)
	 * @param	where	WHERE条件式
	 * @return  アップデートSQL
	 * @og.rtnNotNull
	 */
	public static String getUpdateSQL( final String table , final String[] keys , final String[] conKeys , final String[] conVals , final String where ) {
		final boolean useConst = conKeys != null && conVals != null && conKeys.length == conVals.length && conKeys.length > 0 ;

		final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
			.append( "UPDATE " ).append( table ).append( " SET " );
//			.append( String.join( " = ? ," , keys ) )					// key[0] = ? , ･･･  = ? , key[n-1] という文字列が作成されます。
//			.append( " = ? " );											// 最後の key[n-1] の後ろに、 = ? という文字列を追加します。
		for( final String key : keys ) {
			sql.append( key );
			if( ! key.contains( "?" ) ) {
				sql.append( " = ? " );				// key = ? という文字列が作成されます。
			}
			sql.append( ',' );
		}
		sql.deleteCharAt( sql.length() - 1 );		// 最後の一文字(,)を削除します。

		if( useConst ) {
			for( int i=0; i<conKeys.length; i++ ) {
				sql.append( ',' ).append( conKeys[i] ).append( " = '" ).append( conVals[i] ).append( "' " );
			}
		}

		if( where != null && !where.isEmpty() ) {
			sql.append( " WHERE " ).append( where );			// WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。
		}

		return sql.toString();
	}

	/**
	 * データをデリートする場合に使用するSQL文を作成します。
	 *
	 * これは、key に対応した ？ 文字列で、SQL文を作成します。
	 * 実際の値設定は、この、キーの並び順に応じた値を設定することになります。
	 * WHERE 文字列は、この、？ も含めたWHERE条件の文字列を渡します。
	 * WHERE条件の場合は、この、？に、関数を設定したり、条件を指定したり、
	 * 色々なケースがあるため、単純にキーだけ指定する方法では、対応範囲が
	 * 限られるためです。
	 *
	 * @param	table	テーブルID
	 * @param	where	設定値に対応するキー配列(可変長引数)
	 * @return  デリートSQL
	 * @og.rtnNotNull
	 */
	public static String getDeleteSQL( final String table , final String where ) {
		final StringBuilder sql = new StringBuilder( BUFFER_MIDDLE )
			.append( "DELETE FROM " ).append( table );

		if( where != null && !where.isEmpty() ) {
			sql.append( " WHERE " ).append( where );			// WHERE条件は、? に関数が入ったりするため、予め文字列を作成しておいてもらう。
		}

		return sql.toString();
	}
}
