SqlSelectExecutor.java

package com.github.mygreen.sqlmapper.core.query.sql;

import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;

import com.github.mygreen.splate.ProcessResult;
import com.github.mygreen.sqlmapper.core.SqlMapperContext;
import com.github.mygreen.sqlmapper.core.dialect.Dialect;
import com.github.mygreen.sqlmapper.core.mapper.EntityMappingCallback;
import com.github.mygreen.sqlmapper.core.mapper.SqlEntityRowMapper;
import com.github.mygreen.sqlmapper.core.query.JdbcTemplateBuilder;


/**
 * SQLテンプレートによる抽出を行うクエリを実行します。
 * {@link SqlSelectImpl}のクエリ実行処理の移譲先です。
 *
 * @author T.TSUCHIE
 *
 * @param <T> 処理対象のエンティティの型
 */
public class SqlSelectExecutor<T> {

    /**
     * クエリ情報
     */
    private final SqlSelectImpl<T> query;

    /**
     * 設定情報
     */
    private final SqlMapperContext context;

    /**
     * 実行するSQLです
     */
    private String executedSql;

    /**
     * クエリのパラメータ
     */
    private Object[] paramValues;

    /**
     * 組み立てたクエリ情報を指定するコンストラクタ。
     * @param query クエリ情報
     */
    public SqlSelectExecutor(SqlSelectImpl<T> query) {
        this.query = query;
        this.context = query.getContext();
    }

    /**
     * クエリ実行の準備を行います。
     */
    private void prepare() {
        prepareSql();
    }

    /**
     * 実行するSQLを準備します。
     */
    private void prepareSql() {

        final ProcessResult result = query.getTemplate().process(query.getParameter());
        final Dialect dialect = context.getDialect();

        String sql = result.getSql();
        if(query.getLimit() > 0 || query.getOffset() >= 0) {
            sql = dialect.convertLimitSql(sql, query.getOffset(), query.getLimit());
        }
        this.executedSql = sql;

        this.paramValues = result.getParameters().toArray();

    }

    /**
     * 1件だけヒットすることを前提として検索クエリを実行します。
     *
     * @param callback エンティティマッピング後のコールバック処理
     * @return エンティティのベースオブジェクト。
     * @throws IncorrectResultSizeDataAccessException 1件も見つからない場合、2件以上見つかった場合にスローされます。
     */
    public T getSingleResult(EntityMappingCallback<T> callback) {
        prepare();

        SqlEntityRowMapper<T> rowMapper = new SqlEntityRowMapper<T>(query.getEntityMeta(), Optional.ofNullable(callback));
        return getJdbcTemplate().queryForObject(executedSql, rowMapper, paramValues);
    }

    /**
     * 1件だけヒットすることを前提として検索クエリを実行します。
     *
     * @param callback エンティティマッピング後のコールバック処理。
     * @return エンティティのベースオブジェクト。1件も対象がないときは空を返します。
     * @throws IncorrectResultSizeDataAccessException 2件以上見つかった場合にスローされます。
     */
    public Optional<T> getOptionalResult(EntityMappingCallback<T> callback) {
        prepare();

        SqlEntityRowMapper<T> rowMapper = new SqlEntityRowMapper<T>(query.getEntityMeta(), Optional.ofNullable(callback));
        final List<T> ret = getJdbcTemplate().query(executedSql, rowMapper, paramValues);
        if(ret.isEmpty()) {
            return Optional.empty();
        } else if(ret.size() > 1) {
            throw new IncorrectResultSizeDataAccessException(1, ret.size());
        } else {
            return Optional.of(ret.get(0));
        }
    }

    /**
     * 検索クエリを実行します。
     *
     * @param callback エンティティマッピング後のコールバック処理。
     * @return 検索してヒットした複数のベースオブジェクト。1件も対象がないときは空のリストを返します。
     */
    public List<T> getResultList(EntityMappingCallback<T> callback) {
        prepare();

        SqlEntityRowMapper<T> rowMapper = new SqlEntityRowMapper<T>(query.getEntityMeta(), Optional.ofNullable(callback));
        return getJdbcTemplate().query(executedSql, rowMapper, paramValues);
    }

    /**
     * 結果を {@link Stream} で返す検索クエリを実行します。
     * @param callback エンティティマッピング後のコールバック処理。
     * @return 問い合わせの結果
     */
    public Stream<T> getResultStream(EntityMappingCallback<T> callback) {
        prepare();

        SqlEntityRowMapper<T> rowMapper = new SqlEntityRowMapper<T>(query.getEntityMeta(), Optional.ofNullable(callback));
        return getJdbcTemplate().queryForStream(executedSql, rowMapper, paramValues);
    }

    /**
     * {@link JdbcTemplate}を取得します。
     * @return {@link JdbcTemplate}のインスタンス。
     */
    private JdbcTemplate getJdbcTemplate() {
        return JdbcTemplateBuilder.create(context.getDataSource(), context.getJdbcTemplateProperties())
                .queryTimeout(query.getQueryTimeout())
                .fetchSize(query.getFetchSize())
                .maxRows(query.getMaxRows())
                .build();
    }

}