/* * 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; } }