/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* 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.seasar.extension.jdbc.query;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import javax.persistence.NoResultException;
import javax.persistence.TemporalType;
import org.seasar.extension.jdbc.DbmsDialect;
import org.seasar.extension.jdbc.IterationCallback;
import org.seasar.extension.jdbc.JdbcContext;
import org.seasar.extension.jdbc.ResultSetHandler;
import org.seasar.extension.jdbc.Select;
import org.seasar.extension.jdbc.StatementHandler;
import org.seasar.extension.jdbc.exception.SNoResultException;
import org.seasar.extension.jdbc.exception.SNonUniqueResultException;
import org.seasar.extension.jdbc.manager.JdbcManagerImplementor;
import org.seasar.framework.util.PreparedStatementUtil;
import org.seasar.framework.util.ResultSetUtil;
import org.seasar.framework.util.StatementUtil;
/**
* 検索の抽象クラスです。
*
* @author higa
* @param <T>
* 戻り値のベースの型です。
* @param <S>
* <code>Select</code>のサブタイプです。
*/
public abstract class AbstractSelect<T, S extends Select<T, S>> extends
AbstractQuery<S> implements Select<T, S> {
/**
* ベースクラスです。
*/
protected Class<T> baseClass;
/**
* 最大行数です。
*/
protected int maxRows;
/**
* フェッチ数です。
*/
protected int fetchSize;
/**
* オフセットです。
*/
protected int offset;
/**
* リミットです。
*/
protected int limit;
/**
* 検索結果がなかった場合に{@link NoResultException}をスローするなら<code>true</code>です。
*/
protected boolean disallowNoResult;
/**
* 戻り値型がLOBなら<code>true</code>です。
*/
protected boolean resultLob;
/**
* 戻り値の時制の種別です。
*/
protected TemporalType resultTemporalType;
/**
* SELECT COUNT(*)~で行数を取得する場合に<code>true</code>
*/
protected boolean count;
/**
* {@link AbstractSelect}を作成します。
*
* @param jdbcManager
* 内部的なJDBCマネージャ
* @param baseClass
* ベースクラス
*/
public AbstractSelect(JdbcManagerImplementor jdbcManager, Class<T> baseClass) {
super(jdbcManager);
this.baseClass = baseClass;
}
@SuppressWarnings("unchecked")
public S maxRows(int maxRows) {
this.maxRows = maxRows;
return (S) this;
}
@SuppressWarnings("unchecked")
public S fetchSize(int fetchSize) {
this.fetchSize = fetchSize;
return (S) this;
}
@SuppressWarnings("unchecked")
public S limit(int limit) {
this.limit = limit;
return (S) this;
}
@SuppressWarnings("unchecked")
public S offset(int offset) {
this.offset = offset;
return (S) this;
}
@SuppressWarnings("unchecked")
public S disallowNoResult() {
this.disallowNoResult = true;
return (S) this;
}
@SuppressWarnings("unchecked")
public S lob() {
resultLob = true;
return (S) this;
}
@SuppressWarnings("unchecked")
public S temporal(TemporalType temporalType) {
this.resultTemporalType = temporalType;
return (S) this;
}
/**
* 必要ならページング用にSQLを変換します。
*
* @param sql
* SQL
* @return 変換後のSQL
*/
protected String convertLimitSql(String sql) {
DbmsDialect dialect = jdbcManager.getDialect();
if (dialect.supportsLimit()
&& (limit > 0 || limit == 0 && offset > 0
&& dialect.supportsOffsetWithoutLimit())) {
return dialect.convertLimitSql(sql, offset, limit);
}
return sql;
}
public List<T> getResultList() {
prepare("getResultList");
logSql();
try {
return getResultListInternal();
} finally {
completed();
}
}
public T getSingleResult() throws SNonUniqueResultException {
prepare("getSingleResult");
logSql();
try {
return getSingleResultInternal();
} finally {
completed();
}
}
public <RESULT> RESULT iterate(IterationCallback<T, RESULT> callback) {
prepare("iterate");
logSql();
try {
return iterateInternal(callback);
} finally {
completed();
}
}
/**
* SQLが返す結果セットの行数を返します。
*
* @return SQLが返す結果セットの行数
*/
public long getCount() {
count = true;
prepare("getCount");
logSql();
try {
return Long.class.cast(getSingleResultInternal());
} finally {
completed();
}
}
/**
* 検索してベースオブジェクトのリストを返します。
*
* @return ベースオブジェクトのリスト
*/
@SuppressWarnings("unchecked")
protected List<T> getResultListInternal() {
ResultSetHandler handler = createResultListResultSetHandler();
List<T> ret = null;
JdbcContext jdbcContext = jdbcManager.getJdbcContext();
try {
ret = (List<T>) processResultSet(jdbcContext, handler);
if (disallowNoResult && ret.isEmpty()) {
throw new SNoResultException(executedSql);
}
} finally {
if (!jdbcContext.isTransactional()) {
jdbcContext.destroy();
}
}
return ret;
}
/**
* 検索してベースオブジェクトを返します。
*
* @return ベースオブジェクト
*/
@SuppressWarnings("unchecked")
protected T getSingleResultInternal() {
ResultSetHandler handler = createSingleResultResultSetHandler();
T ret = null;
JdbcContext jdbcContext = jdbcManager.getJdbcContext();
try {
ret = (T) processResultSet(jdbcContext, handler);
if (disallowNoResult && ret == null) {
throw new SNoResultException(executedSql);
}
} finally {
if (!jdbcContext.isTransactional()) {
jdbcContext.destroy();
}
}
return ret;
}
/**
* 検索して反復した結果を返します。
*
* @param <RESULT>
* 反復コールバックの戻り値の型
* @param callback
* 反復コールバック
* @return 反復コールバックの戻り値
*/
@SuppressWarnings("unchecked")
protected <RESULT> RESULT iterateInternal(
final IterationCallback<T, RESULT> callback) {
final ResultSetHandler handler = createIterateResultSetHandler(callback);
final JdbcContext jdbcContext = jdbcManager.getJdbcContext();
try {
return (RESULT) processResultSet(jdbcContext, handler);
} finally {
if (!jdbcContext.isTransactional()) {
jdbcContext.destroy();
}
}
}
/**
* 準備されたステートメントを処理します。
*
* @param jdbcContext
* JDBCコンテキスト
* @param handler
* 準備されたステートメントを処理するハンドラ
* @return 準備されたステートメントを処理した結果
*/
protected Object processPreparedStatement(final JdbcContext jdbcContext,
final StatementHandler<Object, PreparedStatement> handler) {
return jdbcContext.usingPreparedStatement(executedSql,
new StatementHandler<Object, PreparedStatement>() {
public Object handle(final PreparedStatement ps) {
setupPreparedStatement(ps);
return handler.handle(ps);
}
});
}
/**
* カーソルつきの準備されたステートメントを処理します。
*
* @param jdbcContext
* JDBCコンテキスト
* @param handler
* 準備されたステートメントを処理するハンドラ
* @return 準備されたステートメントを処理した結果
*/
protected Object processCursorPreparedStatement(
final JdbcContext jdbcContext,
final StatementHandler<Object, PreparedStatement> handler) {
return jdbcContext.usingCursorPreparedStatement(executedSql,
new StatementHandler<Object, PreparedStatement>() {
public Object handle(final PreparedStatement ps) {
setupPreparedStatement(ps);
return handler.handle(ps);
}
});
}
/**
* 準備されたステートメントをセットアップします。
*
* @param ps
* 準備されたステートメント
*
*/
protected void setupPreparedStatement(PreparedStatement ps) {
if (maxRows > 0) {
StatementUtil.setMaxRows(ps, maxRows);
}
if (fetchSize > 0) {
StatementUtil.setFetchSize(ps, fetchSize);
}
if (queryTimeout > 0) {
StatementUtil.setQueryTimeout(ps, queryTimeout);
}
prepareInParams(ps);
}
/**
* リストを返す結果セットハンドラを作成します。
*
* @return 結果セットハンドラ
*/
protected abstract ResultSetHandler createResultListResultSetHandler();
/**
* 単独の値を返す結果セットハンドラを作成します。
*
* @return 結果セットハンドラ
*/
protected abstract ResultSetHandler createSingleResultResultSetHandler();
/**
* 反復する結果セットハンドラを作成します。
*
* @param callback
* 反復コールバック
* @return 結果セットハンドラ
*/
protected abstract ResultSetHandler createIterateResultSetHandler(
final IterationCallback<T, ?> callback);
/**
* 結果セットを処理します。
*
* @param jdbcContext
* JDBCコンテキスト
* @param handler
* 結果セットを処理するハンドラ
* @return 結果セットを処理した結果
*/
protected Object processResultSet(final JdbcContext jdbcContext,
final ResultSetHandler handler) {
final DbmsDialect dialect = jdbcManager.getDialect();
if (offset > 0) {
if (dialect.supportsOffset()
&& (limit > 0 || limit == 0
&& dialect.supportsOffsetWithoutLimit())) {
return processPreparedStatement(jdbcContext,
new StatementHandler<Object, PreparedStatement>() {
public Object handle(final PreparedStatement ps) {
final ResultSet rs = PreparedStatementUtil
.executeQuery(ps);
return handleResultSet(handler, rs);
}
});
} else if (dialect.supportsCursor()) {
return processCursorPreparedStatement(jdbcContext,
new StatementHandler<Object, PreparedStatement>() {
public Object handle(final PreparedStatement ps) {
final ResultSet rs = PreparedStatementUtil
.executeQuery(ps);
ResultSetUtil.absolute(rs, offset);
return handleResultSet(handler, rs);
}
});
} else {
return processPreparedStatement(jdbcContext,
new StatementHandler<Object, PreparedStatement>() {
public Object handle(final PreparedStatement ps) {
final ResultSet rs = PreparedStatementUtil
.executeQuery(ps);
for (int i = 0; i < offset; i++) {
if (!ResultSetUtil.next(rs)) {
break;
}
}
return handleResultSet(handler, rs);
}
});
}
}
return processPreparedStatement(jdbcContext,
new StatementHandler<Object, PreparedStatement>() {
public Object handle(final PreparedStatement ps) {
final ResultSet rs = PreparedStatementUtil
.executeQuery(ps);
return handleResultSet(handler, rs);
}
});
}
/**
* ベースクラスを返します。
*
* @return ベースクラス
*/
public Class<T> getBaseClass() {
return baseClass;
}
/**
* フェッチ数を返します。
*
* @return フェッチ数
*/
public int getFetchSize() {
return fetchSize;
}
/**
* リミットを返します。
*
* @return リミット
*/
public int getLimit() {
return limit;
}
/**
* 最大行数を返します。
*
* @return 最大行数
*/
public int getMaxRows() {
return maxRows;
}
/**
* オフセットを返します。
*
* @return オフセット
*/
public int getOffset() {
return offset;
}
}