/*
 * MosP - Mind Open Source Project    http://www.mosp.jp/
 * Copyright (C) MIND Co., Ltd.       http://www.e-mind.co.jp/
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jp.mosp.framework.base;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTransientException;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import jp.mosp.framework.constant.ExceptionConst;
import jp.mosp.framework.constant.MospConst;
import jp.mosp.framework.utils.DatabaseUtility;
import jp.mosp.framework.utils.LogUtility;

/**
 *  DAOの基本機能を提供する。<br><br>
 *  Statement取得やSQL実行等、DB操作関連メソッドを有する。<br>
 */
public abstract class BaseDao implements BaseDaoInterface {
	
	// 定数
	
	/**
	 * 削除フラグ列名<br>
	 * {@link #setCommonParams(BaseDtoInterface, boolean)}、
	 * {@link #mappingCommonInfo(BaseDto)}で用いられる。<br>
	 */
	protected String			colDeleteFlag	= "delete_flag";
	
	/**
	 * デフォルト列名(作成日)。<br>
	 * {@link #setCommonParams(BaseDtoInterface, boolean)}、
	 * {@link #mappingCommonInfo(BaseDto)}で用いられる。<br>
	 * 不要な場合は、""(空文字列)を設定する。
	 */
	protected String			colInsertDate	= "insert_date";
	
	/**
	 * デフォルト列名(作成者)。<br>
	 * {@link #setCommonParams(BaseDtoInterface, boolean)}、
	 * {@link #mappingCommonInfo(BaseDto)}で用いられる。<br>
	 * 不要な場合は、""(空文字列)を設定する。
	 */
	protected String			colInsertUser	= "insert_user";
	
	/**
	 * デフォルト列名(更新日)。<br>
	 * {@link #setCommonParams(BaseDtoInterface, boolean)}、
	 * {@link #mappingCommonInfo(BaseDto)}で用いられる。<br>
	 * 不要な場合は、""(空文字列)を設定する。
	 */
	protected String			colUpdateDate	= "update_date";
	
	/**
	 * デフォルト列名(更新者)。<br>
	 * {@link #setCommonParams(BaseDtoInterface, boolean)}、
	 * {@link #mappingCommonInfo(BaseDto)}で用いられる。<br>
	 * 不要な場合は、""(空文字列)を設定する。
	 */
	protected String			colUpdateUser	= "update_user";
	
	// フィールド
	
	/**
	 * ログインユーザーID。<br>
	 */
	protected String			userId;
	
	/**
	 * MosPパラメータクラス。
	 */
	protected MospParams		mospParams;
	
	/**
	 * DBコネクション。<br>
	 */
	protected Connection		connection;
	
	/**
	 * 各種SQL実行の際に利用するステートメント。<br>
	 */
	protected PreparedStatement	ps;
	
	/**
	 * 各種検索SQL実行結果。<br>
	 */
	protected ResultSet			rs;
	
	/**
	 * 各種更新SQL実行件数。<br>
	 */
	protected int				cnt;
	
	/**
	 * パラメーターインデックス。<br>
	 */
	protected int				index;
	

	/**
	 * コンストラクタ。<br>
	 */
	protected BaseDao() {
		// 処理無し
	}
	
	public void setInitParams(MospParams mospParams, Connection connection) {
		this.mospParams = mospParams;
		MospUser user = mospParams.getUser();
		if (user != null) {
			userId = user.getUserId();
		}
		this.connection = connection;
	}
	
	/**
	 * ResultSet開放。<br>
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void releaseResultSet() throws MospException {
		try {
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * PreparedStatement取得。<br>
	 * @param sql 実行SQL
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void prepareStatement(String sql) throws MospException {
		try {
			ps = connection.prepareStatement(sql);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * PreparedStatement開放。<br>
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void releasePreparedStatement() throws MospException {
		try {
			if (ps != null) {
				ps.close();
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータを設定する(String)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @param ps    ステートメント
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, String param, PreparedStatement ps) throws MospException {
		try {
			ps.setString(index, param);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータを設定する。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @param ps    ステートメント
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, int param, PreparedStatement ps) throws MospException {
		try {
			ps.setInt(index, param);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータを設定する(String)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, String param) throws MospException {
		setParam(index, param, ps);
	}
	
	/**
	 * パラメータ設定(int)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, int param) throws MospException {
		try {
			if (ps != null) {
				ps.setInt(index, param);
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータ設定(long)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, long param) throws MospException {
		try {
			if (ps != null) {
				ps.setLong(index, param);
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータ設定(float)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, float param) throws MospException {
		try {
			if (ps != null) {
				ps.setFloat(index, param);
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータ設定(double)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, double param) throws MospException {
		try {
			if (ps != null) {
				ps.setDouble(index, param);
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータ設定(InputStream)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, byte[] param) throws MospException {
		try {
			if (ps != null) {
				ps.setBytes(index, param);
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータを設定する(Date)。<br>
	 * @param index       インデックス
	 * @param param       パラメータ
	 * @param isTimeStamp 設定オブジェクトフラグ(true：java.sql.Timestamp、false：java.sql.Date)
	 * @param ps          ステートメント
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, Date param, boolean isTimeStamp, PreparedStatement ps) throws MospException {
		try {
			if (ps != null) {
				if (param == null) {
					if (isTimeStamp) {
						ps.setTimestamp(index, null);
					} else {
						ps.setDate(index, null);
					}
					
				} else {
					if (isTimeStamp) {
						ps.setTimestamp(index, new Timestamp(param.getTime()));
					} else {
						Calendar cal = Calendar.getInstance();
						cal.setTime(param);
						cal.set(Calendar.HOUR_OF_DAY, 0);
						cal.set(Calendar.MINUTE, 0);
						cal.set(Calendar.SECOND, 0);
						cal.set(Calendar.MILLISECOND, 0);
						ps.setDate(index, new java.sql.Date(cal.getTimeInMillis()));
					}
				}
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータを設定する(Date)。<br>
	 * @param index       インデックス
	 * @param param       パラメータ
	 * @param isTimeStamp 設定オブジェクトフラグ(true：java.sql.Timestamp、false：java.sql.Date)
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, Date param, boolean isTimeStamp) throws MospException {
		setParam(index, param, isTimeStamp, ps);
	}
	
	/**
	 * パラメータ設定(Date)。<br>
	 * java.sql.Dateとして設定する。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, Date param) throws MospException {
		setParam(index, param, false);
	}
	
	/**
	 * パラメータ設定(Time)。<br>
	 * @param index インデックス
	 * @param param パラメータ
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setParam(int index, Time param) throws MospException {
		try {
			if (ps != null) {
				ps.setTime(index, param);
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * パラメータ消去。<br>
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void clearParams() throws MospException {
		try {
			if (ps != null) {
				ps.clearParameters();
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値をStringとして取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected String getString(String columnLabel) throws MospException {
		try {
			return rs.getString(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値を数値として取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected int getInt(String columnLabel) throws MospException {
		try {
			return rs.getInt(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値を数値として取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected long getLong(String columnLabel) throws MospException {
		try {
			return rs.getLong(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値を数値として取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected double getDouble(String columnLabel) throws MospException {
		try {
			return rs.getDouble(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetカーソルを現在の位置から1行順方向に移動する。
	 * @return 行の有無(true：新しい現在の行が有効、false：それ以上行がない)
	 * @throws MospException SQL例外が発生した場合
	 */
	protected boolean next() throws MospException {
		try {
			return rs.next();
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値をDateとして取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected Date getDate(String columnLabel) throws MospException {
		try {
			return rs.getDate(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値をTimeとして取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected Date getTime(String columnLabel) throws MospException {
		try {
			return rs.getTime(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値をTimestampとして取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected Date getTimestamp(String columnLabel) throws MospException {
		try {
			return rs.getTimestamp(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * ResultSetの現在行にある指定された列の値をbyteの配列として取得する。
	 * @param columnLabel 取得対象列名
	 * @return 列値
	 * @throws MospException SQL例外が発生した場合
	 */
	protected byte[] getBytes(String columnLabel) throws MospException {
		try {
			return rs.getBytes(columnLabel);
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * 検索系SQL実行。<br>
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void executeQuery() throws MospException {
		try {
			if (ps != null) {
				rs = ps.executeQuery();
				// ログ出力
				LogUtility.sqlSelect(mospParams, ps.toString());
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * 更新系SQLを実行する。<br>
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void executeUpdate() throws MospException {
		executeUpdate(true);
	}
	
	/**
	 * 更新系SQL実行(ログ出力制御付)。<br>
	 * @param needLog ログ出力要否
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void executeUpdate(boolean needLog) throws MospException {
		if (ps != null) {
			try {
				cnt = ps.executeUpdate();
				if (needLog) {
					// ログ出力
					LogUtility.sqlRegist(mospParams, ps.toString());
				}
			} catch (SQLException e) {
				// 一時的な例外の場合
				if (e instanceof SQLTransientException) {
					throw new MospException(e);
				}
				throw new MospException(e);
			}
		}
	}
	
	/**
	 * 更新系SQLを実行する。<br>
	 * 処理インデックスが挿入レコード上限数か挿入対象件数に達した場合、
	 * 挿入処理を行う。<br>
	 * {@link #getInsertQuery(Class)}と併せて用いる。
	 * 大量のデータを挿入する場合、パフォーマンスの向上が見込める。<br>
	 * @param cls   DTOクラス
	 * @param size  挿入対象件数
	 * @param max   挿入レコード上限数(一度に挿入する上限)
	 * @param idx   処理インデックス
	 * @throws MospException SQLの作成に失敗した場合、或いはSQL例外が発生した場合
	 */
	protected void executeUpdate(Class<?> cls, int size, int max, int idx) throws MospException {
		// インデックスを実際の件数に合わせる
		int executeIndex = idx + 1;
		// 挿入レコード上限数か挿入対象件数に達した場合
		if (executeIndex % max == 0 || executeIndex == size) {
			executeUpdate(false);
			clearParams();
			index = 1;
			if (size - executeIndex < max && executeIndex != size) {
				releasePreparedStatement();
				prepareStatement(getInsertQuery(cls, size - executeIndex, max));
			}
		} else {
			cnt = 0;
		}
	}
	
	/**
	 * 全レコードを取得する。<br>
	 * 但し、削除フラグが立っているものは対象外。<br>
	 * @return DTOリスト
	 * @throws MospException SQLの作成に失敗した場合、或いはSQL例外が発生した場合
	 */
	public List<?> findAll() throws MospException {
		try {
			StringBuffer sb = getSelectQuery(getClass());
			if (!colDeleteFlag.isEmpty()) {
				sb.append(where());
				sb.append(deleteFlagOff());
			}
			prepareStatement(sb.toString());
			executeQuery();
			return mappingAll();
		} catch (Throwable e) {
			throw new MospException(e);
		} finally {
			releaseResultSet();
			releasePreparedStatement();
		}
	}
	
	/**
	 * レコードIDでレコードを取得する。<br>
	 * @param isUpdate アップデートフラグ(true：for update有り、false：for update無し)
	 * @return 取得レコードDTO
	 * @throws MospException SQLの作成に失敗した場合、或いはSQL例外が発生した場合
	 */
	public BaseDto findForKey(long id, boolean isUpdate) throws MospException {
		try {
			index = 1;
			StringBuffer sb = new StringBuffer();
			sb.append(getSelectQuery(getClass()));
			sb.append(getConditionForKey(getClass()));
			if (isUpdate) {
				sb.append(getForUpdate());
			}
			prepareStatement(sb.toString());
			setParam(index++, id);
			executeQuery();
			BaseDto dto = null;
			if (rs.next()) {
				dto = mapping();
			}
			return dto;
		} catch (SQLException e) {
			throw new MospException(e);
		} finally {
			releaseResultSet();
			releasePreparedStatement();
		}
	}
	
	/**
	 * 挿入SQLを実行する。
	 * @param baseDto 挿入対象DTO
	 * @throws MospException SQLの作成に失敗した場合、或いはSQL例外が発生した場合
	 */
	@Override
	public int insert(BaseDtoInterface baseDto) throws MospException {
		index = 1;
		prepareStatement(getInsertQuery(getClass()));
		setParams(baseDto, true);
		executeUpdate();
		chkInsert(1);
		return cnt;
	}
	
	/**
	 * レコードIDの最大値を取得する。
	 * @param cls DAOクラス
	 * @return レコードID最大値
	 * @throws MospException SQLの作成に失敗した場合、或いはSQL例外が発生した場合
	 */
	@Override
	public String findForMaxId(Class<?> cls) throws MospException {
		try {
			String max = "";
			index = 1;
			StringBuffer sb = new StringBuffer();
			String column = "";
			Field[] fields = cls.getFields();
			for (Field field : fields) {
				if (field.getName().indexOf("KEY_1") == 0) {
					column = String.valueOf(getFieldValue(field));
				}
			}
			sb.append(selectMax(column));
			sb.append(from(getTable(cls)));
			prepareStatement(sb.toString());
			executeQuery();
			if (rs.next()) {
				max = String.valueOf(rs.getLong(1));
			}
			return max;
		} catch (SQLException e) {
			throw new MospException(e);
		} finally {
			releaseResultSet();
			releasePreparedStatement();
		}
	}
	
	/**
	 * 全件検索SQLを取得する。<br>
	 * @param cls DAOクラス
	 * @return 全件検索SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected StringBuffer getSelectQuery(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append(getSelectStatement(cls));
		query.append("FROM ");
		query.append(getTable(cls));
		query.append(" ");
		return query;
	}
	
	/**
	 * 検索SQLを取得する。<br>
	 * @param cls DAOクラス
	 * @return 全件検索SQL文字列
	 * @throws MospException フィールド値の取得に失敗した場合
	 */
	protected String getSelectStatement(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append("SELECT ");
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("COL_") == 0) {
				query.append(getFieldValue(field));
				query.append(", ");
			}
		}
		query.append(getCommonColumn());
		query.delete(query.length() - 2, query.length() - 1);
		return query.toString();
	}
	
	/**
	 * 検索SQL文を取得する。<br>
	 * @param cls DAOクラス
	 * @param needTableName テーブル名追加フラグ
	 * @return 全件検索SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected String getSelectStatement(Class<?> cls, boolean needTableName) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append("SELECT ");
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("COL_") == 0) {
				if (needTableName) {
					query.append(getTable(cls) + ".");
				}
				query.append(getFieldValue(field));
				query.append(", ");
			}
		}
		query.append(getCommonColumn(cls, needTableName));
		query.delete(query.length() - 2, query.length() - 1);
		return query.toString();
	}
	
	/**
	 * 件数検索SQLを取得する。<br>
	 * @param cls DAOクラス
	 * @return 全件検索SQL文字列
	 * @throws MospException テーブル名の取得に失敗した場合
	 */
	protected String getSelectCountQuery(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append(getSelectCountStatement());
		query.append(" FROM ");
		query.append(getTable(cls));
		query.append(" ");
		return query.toString();
	}
	
	/**
	 * 件数検索SQLを取得する。<br>
	 * @return 件数検索SQL文字列(SELECT COUNT(*))
	 */
	protected String getSelectCountStatement() {
		StringBuffer query = new StringBuffer();
		query.append("SELECT COUNT(*) ");
		return query.toString();
	}
	
	/**
	 * 挿入SQLを取得する。<br>
	 * @param cls DTOクラス
	 * @return 挿入SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected String getInsertQuery(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append(getInsertQueryBase(cls));
		query.append("(");
		Field[] fields = getClass().getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("COL_") == 0) {
				query.append("?, ");
			}
		}
		query.append(getCommonParams());
		query.delete(query.length() - 2, query.length());
		query.append(")");
		return query.toString();
	}
	
	/**
	 * 挿入SQLのベースを取得する。<br>
	 * @param cls DTOクラス
	 * @return 挿入SQLベース文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	private String getInsertQueryBase(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append("INSERT INTO ");
		query.append(getTable(cls));
		query.append("(");
		Field[] fields = getClass().getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("COL_") == 0) {
				query.append(getFieldValue(field));
				query.append(", ");
			}
		}
		query.append(getCommonColumn());
		query.delete(query.length() - 2, query.length() - 1);
		query.append(") VALUES");
		return query.toString();
	}
	
	/**
	 * 更新SQLを取得する。<br>
	 * @param cls DTOクラス
	 * @return 更新SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected String getUpdateQuery(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append("UPDATE ");
		query.append(getTable(cls));
		query.append(" SET ");
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("COL_") == 0) {
				query.append(getFieldValue(field));
				query.append(" = ?, ");
			}
		}
		if (!colDeleteFlag.isEmpty()) {
			query.append(colDeleteFlag + " = ?, ");
		}
		if (!colUpdateDate.isEmpty()) {
			query.append(colUpdateDate + " = ?, ");
		}
		if (!colUpdateUser.isEmpty()) {
			query.append(colUpdateUser + " = ?, ");
		}
		query.delete(query.length() - 2, query.length() - 1);
		query.append(getConditionForKey(cls));
		return query.toString();
	}
	
	/**
	 * 削除SQLを取得する。<br>
	 * @param cls DTOクラス
	 * @return 削除SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected String getDeleteQuery(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append("DELETE FROM ");
		query.append(getTable(cls));
		query.append(" ");
		query.append(getConditionForKey(cls));
		return query.toString();
	}
	
	/**
	 * キーによる条件SQLを取得する。<br>
	 * @param cls DTOクラス
	 * @return キーによる条件SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected String getConditionForKey(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append("WHERE ");
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("KEY_") == 0) {
				query.append(getFieldValue(field));
				query.append(" = ? AND ");
			}
		}
		query.delete(query.length() - 4, query.length());
		query.append(" ");
		return query.toString();
	}
	
	/**
	 * キーによる条件SQL取得(追加用)。<br>
	 * @param cls DTOクラス
	 * @return キーによる条件SQL文字列
	 * @throws MospException フィールド値の取得に失敗した場合
	 */
	protected String getAddConditionForKey(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append(" AND ");
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("KEY_") == 0) {
				query.append(getFieldValue(field));
				query.append(" = ? AND ");
			}
		}
		query.delete(query.length() - 4, query.length());
		query.append(" ");
		return query.toString();
	}
	
	/**
	 * キーによるソートSQL取得。<br>
	 * @param cls DAOクラス
	 * @return キーによるソートSQL文字列
	 * @throws MospException フィールド値の取得に失敗した場合
	 */
	protected String getOrderForKey(Class<?> cls) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append(getOrderBy());
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("KEY_") == 0) {
				query.append(getFieldValue(field));
				query.append(", ");
			}
		}
		query.delete(query.length() - 2, query.length());
		query.append(" ");
		return query.toString();
	}
	
	/**
	 * 挿入SQLを取得する。<br>
	 * 挿入レコード数分を一度に挿入するSQLを作成する。<br>
	 * 但し、挿入レコード最大数以上はSQLを作成しない。<br>
	 * @param cls  DAOクラス
	 * @param size 挿入レコード数
	 * @param max  挿入レコード最大数
	 * @return 挿入SQL文字列
	 * @throws MospException テーブル名、フィールド値の取得に失敗した場合
	 */
	protected String getInsertQuery(Class<?> cls, int size, int max) throws MospException {
		StringBuffer query = new StringBuffer();
		query.append(getInsertQueryBase(cls));
		// パラメータ個数
		Field[] fields = getClass().getFields();
		// 1レコード分SQL作成
		StringBuffer sb = new StringBuffer();
		sb.append("(");
		for (Field field : fields) {
			String name = field.getName();
			if (name.indexOf("COL_") == 0) {
				sb.append("?, ");
			}
		}
		sb.append(getCommonParams());
		sb.delete(sb.length() - 2, sb.length());
		sb.append("), ");
		// 件数分作成(但しmaxを上限とする)
		for (int i = 0; i < size; i++) {
			if (i == max) {
				break;
			}
			query.append(sb);
		}
		query.delete(query.length() - 2, query.length());
		return query.toString();
	}
	
	/**
	 * {@link #colInsertUser}、{@link #colInsertDate}、
	 * {@link #colUpdateUser}、{@link #colUpdateDate}
	 * のSQL文字列を取得する。<br>
	 * @return 作成者、作成日、更新者、更新日列SQL文字列
	 */
	private String getCommonColumn() {
		StringBuffer query = new StringBuffer();
		if (!colDeleteFlag.isEmpty()) {
			query.append(colDeleteFlag + ", ");
		}
		if (!colInsertDate.isEmpty()) {
			query.append(colInsertDate + ", ");
		}
		if (!colInsertUser.isEmpty()) {
			query.append(colInsertUser + ", ");
		}
		if (!colUpdateDate.isEmpty()) {
			query.append(colUpdateDate + ", ");
		}
		if (!colUpdateUser.isEmpty()) {
			query.append(colUpdateUser + ", ");
		}
		return query.toString();
	}
	
	/**
	 * {@link #colInsertUser}、{@link #colInsertDate}、
	 * {@link #colUpdateUser}、{@link #colUpdateDate}
	 * のSQL文字列を取得する。<br>
	 * @param cls DAOクラス
	 * @param needTableName テーブル名追加フラグ
	 * @return 作成者、作成日、更新者、更新日列SQL文字列
	 * @throws MospException テーブル名の取得に失敗した場合
	 */
	private String getCommonColumn(Class<?> cls, boolean needTableName) throws MospException {
		StringBuffer query = new StringBuffer();
		if (!colDeleteFlag.isEmpty()) {
			if (needTableName) {
				query.append(getTable(cls) + ".");
			}
			query.append(colDeleteFlag + ", ");
		}
		if (!colInsertDate.isEmpty()) {
			if (needTableName) {
				query.append(getTable(cls) + ".");
			}
			query.append(colInsertDate + ", ");
		}
		if (!colInsertUser.isEmpty()) {
			if (needTableName) {
				query.append(getTable(cls) + ".");
			}
			query.append(colInsertUser + ", ");
		}
		if (!colUpdateDate.isEmpty()) {
			if (needTableName) {
				query.append(getTable(cls) + ".");
			}
			query.append(colUpdateDate + ", ");
		}
		if (!colUpdateUser.isEmpty()) {
			if (needTableName) {
				query.append(getTable(cls) + ".");
			}
			query.append(colUpdateUser + ", ");
		}
		return query.toString();
	}
	
	/**
	 * {@link #colInsertUser}、{@link #colInsertDate}、
	 * {@link #colUpdateUser}、{@link #colUpdateDate}
	 * のパラメータ設定SQL文字列を取得する。<br>
	 * @return 作成者、作成日、更新者、更新日列パラメータ設定SQL文字列
	 */
	private String getCommonParams() {
		StringBuffer query = new StringBuffer();
		if (!colDeleteFlag.isEmpty()) {
			query.append("?, ");
		}
		if (!colInsertDate.isEmpty()) {
			query.append("?, ");
		}
		if (!colInsertUser.isEmpty()) {
			query.append("?, ");
		}
		if (!colUpdateDate.isEmpty()) {
			query.append("?, ");
		}
		if (!colUpdateUser.isEmpty()) {
			query.append("?, ");
		}
		return query.toString();
	}
	
	/**
	 * テーブル名取得。<br>
	 * @param cls DAOクラス
	 * @return テーブル名文字列
	 * @throws MospException テーブル名の取得に失敗した場合
	 */
	public String getTable(Class<?> cls) throws MospException {
		try {
			Field table = cls.getField("TABLE");
			return (String)table.get(null);
		} catch (IllegalAccessException e) {
			throw new MospException(e);
		} catch (NoSuchFieldException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * フィールド値を取得する。<br>
	 * @param field フィールド
	 * @return フィールド値
	 * @throws MospException フィールド値の取得に失敗した場合
	 */
	protected String getFieldValue(Field field) throws MospException {
		try {
			return (String)field.get(null);
		} catch (IllegalAccessException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * 頁操作用SQL取得。<br>
	 * @param sortKey     ソートキー
	 * @param isAscending 昇順フラグ
	 * @return 頁操作用SQL文字列
	 */
	protected String getPageStatement(String sortKey, boolean isAscending) {
		StringBuffer query = new StringBuffer();
		query.append(getOrderBy() + sortKey + " ");
		if (!isAscending) {
			query.append(getDesc());
		}
		query.append(getLimit() + "? ");
		query.append(getOffset() + "? ");
		return query.toString();
	}
	
	/**
	 * 明示的にテーブルカラム名を取得。<br>
	 * @param tableName テーブル名
	 * @param column 対象カラム
	 * @return テーブル名.対象カラム
	 */
	protected static String getExplicitTableColumn(String tableName, String column) {
		return tableName + "." + column;
	}
	
	/**
	 * 一時テーブル名宣言。<br>
	 * @param tableName テーブル名
	 * @return as tmp_テーブル名
	 */
	protected static String asTmpTable(String tableName) {
		return " AS " + getTmpTable(tableName);
	}
	
	/**
	 * 一時テーブル名取得。<br>
	 * @param tableName テーブル名
	 * @return tmp_テーブル名
	 */
	protected static String getTmpTable(String tableName) {
		return "tmp_" + tableName;
	}
	
	/**
	 * 一時テーブルカラム名取得。
	 * @param tableName テーブル名
	 * @param column 対象カラム
	 * @return tmp_テーブル名.対象カラム
	 */
	protected static String getTmpTableColumn(String tableName, String column) {
		return getExplicitTableColumn(getTmpTable(tableName), column);
	}
	
	/**
	 * 一時テーブルカラムとテーブルカラムの一致。
	 * @param tableName テーブル名
	 * @param column 対象カラム
	 * @return テーブル名.対象カラム = tmp_テーブル名.対象カラム
	 */
	protected static String equalTmpColumn(String tableName, String column) {
		StringBuffer sb = new StringBuffer();
		sb.append(getExplicitTableColumn(tableName, column));
		sb.append(" = ");
		sb.append(getTmpTableColumn(tableName, column));
		return sb.toString();
	}
	
	/**
	 * FOR UPDATE SQL取得。<br>
	 * @return FOR UPDATE 文字列
	 */
	protected static String getForUpdate() {
		return " FOR UPDATE";
	}
	
	/**
	 * ORDER BY SQL取得。<br>
	 * @return ORDER BY 文字列
	 */
	protected static String getOrderBy() {
		return " ORDER BY ";
	}
	
	/**
	 * DESC SQL取得。<br>
	 * @return DESC 文字列
	 */
	protected static String getDesc() {
		return " DESC ";
	}
	
	/**
	 * @return	DESC LIMIT 1
	 */
	protected static String getDescLimit1() {
		return " DESC LIMIT 1";
	}
	
	/**
	 * LIMIT SQL取得。<br>
	 * @return LIMIT 文字列
	 */
	protected static String getLimit() {
		return " LIMIT ";
	}
	
	/**
	 * OFFSET SQL取得。<br>
	 * @return OFFSET 文字列
	 */
	protected static String getOffset() {
		return " OFFSET ";
	}
	
	/**
	 * @return WHERE 文字列
	 */
	protected static String where() {
		return " WHERE ";
	}
	
	/**
	 * 前方一致
	 * @param param 対象パラメータ
	 * @return param + %
	 */
	protected static String startWithParam(String param) {
		return param + "%";
	}
	
	/**
	 * 後方一致
	 * @param param 対象パラメータ
	 * @return % + param
	 */
	protected static String endWithParam(String param) {
		return "%" + param;
	}
	
	/**
	 * 部分一致
	 * @param param 対象パラメータ
	 * @return % + param + %
	 */
	protected static String containsParam(String param) {
		return "%" + param + "%";
	}
	
	/**
	 * カンマ区切り<br>
	 * @param param パラメータ
	 * @param isContain 部分一致フラグ
	 * @return , + param + , 部分一致の場合、%, + param + ,%
	 * @throws MospException SQL例外が発生した場合
	 */
	protected static String boxedByCommaParam(String param, boolean isContain) throws MospException {
		StringBuffer sb = new StringBuffer();
		if (param != null) {
			if (!param.startsWith(",")) {
				sb.append(",");
			}
			sb.append(param);
			if (!param.endsWith(",")) {
				sb.append(",");
			}
			if (isContain) {
				return containsParam(sb.toString());
			}
		}
		return sb.toString();
	}
	
	/**
	 * @param column 対象カラム
	 * @return column IS NULL 
	 */
	protected static String isNull(String column) {
		return " " + column + " IS NULL ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column IS NOT NULL 
	 */
	protected static String isNotNull(String column) {
		return " " + column + " IS NOT NULL ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return SELECT MAX(column) 
	 */
	protected static String selectMax(String column) {
		StringBuffer sb = new StringBuffer();
		sb.append("SELECT MAX(");
		sb.append(column);
		sb.append(") ");
		return sb.toString();
	}
	
	/**
	 * @param tableName テーブル名
	 * @return FROM テーブル名
	 */
	protected static String from(String tableName) {
		StringBuffer sb = new StringBuffer();
		sb.append(" FROM ");
		sb.append(tableName);
		sb.append(" ");
		return sb.toString();
	}
	
	/**
	 * @return AND
	 */
	protected static String and() {
		return " AND ";
	}
	
	/**
	 * @return OR
	 */
	protected static String or() {
		return " OR ";
	}
	
	/**
	 * @return IN
	 */
	protected static String in() {
		return " IN ";
	}
	
	/**
	 * @return NOT IN
	 */
	protected static String notIn() {
		return " NOT IN ";
	}
	
	/**
	 * @return (
	 */
	protected static String leftParenthesis() {
		return " ( ";
	}
	
	/**
	 * @return )
	 */
	protected static String rightParenthesis() {
		return " ) ";
	}
	
	/**
	 * @return SELECT
	 */
	protected static String select() {
		return " SELECT ";
	}
	
	/**
	 * @return ' '
	 */
	protected static String blank() {
		return "' '";
	}
	
	/**
	 * @param column1 対象カラム1
	 * @param column2 対象カラム2
	 * @return column1 || column2
	 */
	protected static String concat(String column1, String column2) {
		return column1 + " || " + column2;
	}
	
	/**
	 * @param column1 対象カラム1
	 * @param column2 対象カラム2
	 * @param column3 対象カラム3
	 * @return column1 || column2 || column3
	 */
	protected static String concat(String column1, String column2, String column3) {
		return column1 + " || " + column2 + " || " + column3;
	}
	
	/**
	 * @param column 対象カラム
	 * @return column = ?
	 */
	protected static String equal(String column) {
		return column + " = ? ";
	}
	
	/**
	 * @param column 対象カラム
	 * @param value  対象値
	 * @return column = value
	 */
	protected static String equal(String column, int value) {
		return column + " = " + value + " ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column LIKE ?
	 */
	protected static String like(String column) {
		return column + " LIKE ? ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column > ?
	 */
	protected static String greater(String column) {
		return column + greater() + " ? ";
	}
	
	/**
	 * @return >
	 */
	protected static String greater() {
		return " > ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column >= ?
	 */
	protected static String greaterEqual(String column) {
		return column + " >= ? ";
	}
	
	/**
	 * @return <
	 */
	protected static String less() {
		return " < ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column < ?
	 */
	protected static String less(String column) {
		return column + less() + " ? ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column <= ?
	 */
	protected static String lessEqual(String column) {
		return column + " <= ? ";
	}
	
	/**
	 * @param column 対象カラム
	 * @return column <> ?
	 */
	protected static String notEqual(String column) {
		return column + " <> ? ";
	}
	
	/**
	 * @param column 対象カラム
	 * @param value  対象値
	 * @return column = value
	 */
	protected static String notEqual(String column, String value) {
		return column + " <> '" + value + "' ";
	}
	
	/**
	 * @param colDeleteFlag 対象削除フラグカラム
	 * @return colDeleteFlag = {@link MospConst#DELETE_FLAG_OFF}
	 */
	protected static String deleteFlagOff(String colDeleteFlag) {
		if (colDeleteFlag.isEmpty()) {
			return "";
		}
		StringBuffer sb = new StringBuffer();
		sb.append(colDeleteFlag);
		sb.append(" = ");
		sb.append(MospConst.DELETE_FLAG_OFF);
		sb.append(" ");
		return sb.toString();
	}
	
	/**
	 * @return delete_flag = {@link MospConst#DELETE_FLAG_OFF}
	 */
	protected String deleteFlagOff() {
		if (colDeleteFlag.isEmpty()) {
			return "";
		}
		StringBuffer sb = new StringBuffer();
		sb.append(colDeleteFlag);
		sb.append(" = ");
		sb.append(MospConst.DELETE_FLAG_OFF);
		sb.append(" ");
		return sb.toString();
	}
	
	/**
	 * @param column 対象カラム
	 * @return ORDER BY column 
	 */
	protected static String getOrderByColumn(String column) {
		StringBuffer sb = new StringBuffer();
		sb.append(getOrderBy());
		sb.append(column);
		sb.append(" ");
		return sb.toString();
	}
	
	/**
	 * @param column1 対象カラム1
	 * @param column2 対象カラム2
	 * @return ORDER BY column 
	 */
	protected static String getOrderByColumn(String column1, String column2) {
		StringBuffer sb = new StringBuffer();
		sb.append(getOrderBy());
		sb.append(column1);
		sb.append(", ");
		sb.append(column2);
		sb.append(" ");
		return sb.toString();
	}
	
	/**
	 * @param columns 対象カラム
	 * @return ORDER BY columns
	 */
	protected static String getOrderByColumns(String... columns) {
		if (columns.length == 0) {
			return "";
		}
		StringBuffer sb = new StringBuffer();
		sb.append(getOrderBy());
		for (String column : columns) {
			if (column.isEmpty()) {
				continue;
			}
			sb.append(column);
			sb.append(", ");
		}
		if (sb.length() > 0) {
			sb.delete(sb.length() - 2, sb.length() - 1);
		}
		return sb.toString();
	}
	
	/**
	 * @param column 対象カラム
	 * @return ORDER BY column DESC LIMIT 1
	 */
	protected static String getOrderByColumnDescLimit1(String column) {
		StringBuffer sb = new StringBuffer();
		sb.append(getOrderByColumn(column));
		sb.append(getDescLimit1());
		return sb.toString();
	}
	
	/**
	 * 共通情報取得。<br>
	 * @param dto 対象DTO
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void mappingCommonInfo(BaseDto dto) throws MospException {
		try {
			if (!colDeleteFlag.isEmpty()) {
				dto.setDeleteFlag(rs.getInt(colDeleteFlag));
			}
			if (!colInsertDate.isEmpty()) {
				Date insertDate = new Date(rs.getTimestamp(colInsertDate).getTime());
				dto.setInsertDate(insertDate);
			}
			if (!colInsertUser.isEmpty()) {
				dto.setInsertUser(rs.getString(colInsertUser));
			}
			if (!colUpdateDate.isEmpty()) {
				Date updateDate = new Date(rs.getTimestamp(colUpdateDate).getTime());
				dto.setUpdateDate(updateDate);
			}
			if (!colUpdateUser.isEmpty()) {
				dto.setUpdateUser(rs.getString(colUpdateUser));
			}
		} catch (SQLException e) {
			throw new MospException(e);
		}
	}
	
	/**
	 * 共通情報設定。<br>
	 * @param baseDto 対象DTO
	 * @param isInsert 挿入文フラグ(true：挿入文、false：挿入文)
	 * @throws MospException SQL例外が発生した場合
	 */
	protected void setCommonParams(BaseDtoInterface baseDto, boolean isInsert) throws MospException {
		Date date = new Date();
		if (!colDeleteFlag.isEmpty()) {
			setParam(index++, baseDto.getDeleteFlag());
		}
		if (isInsert) {
			if (!colInsertDate.isEmpty()) {
				setParam(index++, date, true);
			}
			if (!colInsertUser.isEmpty()) {
				setParam(index++, userId);
			}
		}
		if (!colUpdateDate.isEmpty()) {
			setParam(index++, date, true);
		}
		if (!colUpdateUser.isEmpty()) {
			setParam(index++, userId);
		}
	}
	
	/**
	 * 挿入件数確認。<br>
	 * @param expectedCount 想定される挿入件数
	 * @throws MospException 想定される件数と異なる場合
	 */
	protected void chkInsert(int expectedCount) throws MospException {
		if (cnt != expectedCount) {
			mospParams.setErrorViewUrl();
			throw new MospException(ExceptionConst.EX_FAIL_INSERT);
		}
	}
	
	/**
	 * 更新件数確認。<br>
	 * @param expectedCount 想定される更新件数
	 * @throws MospException 想定される件数と異なる場合
	 */
	protected void chkUpdate(int expectedCount) throws MospException {
		if (cnt != expectedCount) {
			mospParams.setErrorViewUrl();
			throw new MospException(ExceptionConst.EX_FAIL_UPDATE);
		}
	}
	
	/**
	 * 削除件数確認。<br>
	 * @param expectedCount 想定される削除件数
	 * @throws MospException 想定される件数と異なる場合
	 */
	protected void chkDelete(int expectedCount) throws MospException {
		if (cnt != expectedCount) {
			mospParams.setErrorViewUrl();
			throw new MospException(ExceptionConst.EX_FAIL_DELETE);
		}
	}
	
	/**
	 * RDBMS種類を取得する。<br>
	 * {@link #connection}の情報からRDBMSの種類を特定する。<br>
	 * @return RDBMS種類
	 * @throws MospException SQL例外が発生した場合、或いはMosPが扱えないRDBMSを取得した場合
	 */
	protected RDBMSType getRdbmsType() throws MospException {
		RDBMSType type = DatabaseUtility.getRDBMS(connection);
		if (type != null) {
			return type;
		}
		// 例外発行
		mospParams.setErrorViewUrl();
		throw new MospException(ExceptionConst.EX_UNKNOWN_RDBMS);
	}
	
}
