text:
package com.landawn.abacus.util;

import java.io.Closeable;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.Driver;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import com.landawn.abacus.DataSet;
import com.landawn.abacus.DataSourceManager;
import com.landawn.abacus.DataSourceSelector;
import com.landawn.abacus.DirtyMarker;
import com.landawn.abacus.IsolationLevel;
import com.landawn.abacus.annotation.Beta;
import com.landawn.abacus.condition.And;
import com.landawn.abacus.condition.Condition;
import com.landawn.abacus.condition.ConditionFactory.CF;
import com.landawn.abacus.condition.Equal;
import com.landawn.abacus.dataSource.SQLDataSource;
import com.landawn.abacus.exception.AbacusException;
import com.landawn.abacus.exception.DuplicatedResultException;
import com.landawn.abacus.exception.UncheckedSQLException;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.parser.ParserUtil;
import com.landawn.abacus.parser.ParserUtil.EntityInfo;
import com.landawn.abacus.parser.ParserUtil.PropInfo;
import com.landawn.abacus.type.Type;
import com.landawn.abacus.util.Fn.FN;
import com.landawn.abacus.util.Fn.Suppliers;
import com.landawn.abacus.util.JdbcUtil.BiRowMapper;
import com.landawn.abacus.util.JdbcUtil.RowMapper;
import com.landawn.abacus.util.SQLBuilder.NAC;
import com.landawn.abacus.util.SQLBuilder.NLC;
import com.landawn.abacus.util.SQLBuilder.NSC;
import com.landawn.abacus.util.SQLBuilder.SP;
import com.landawn.abacus.util.SQLTransaction.CreatedBy;
import com.landawn.abacus.util.StringUtil.Strings;
import com.landawn.abacus.util.u.Nullable;
import com.landawn.abacus.util.u.Optional;
import com.landawn.abacus.util.u.OptionalBoolean;
import com.landawn.abacus.util.u.OptionalByte;
import com.landawn.abacus.util.u.OptionalChar;
import com.landawn.abacus.util.u.OptionalDouble;
import com.landawn.abacus.util.u.OptionalFloat;
import com.landawn.abacus.util.u.OptionalInt;
import com.landawn.abacus.util.u.OptionalLong;
import com.landawn.abacus.util.u.OptionalShort;
import com.landawn.abacus.util.function.BiConsumer;
import com.landawn.abacus.util.function.Function;
import com.landawn.abacus.util.function.Supplier;
import com.landawn.abacus.util.stream.Collector;
import com.landawn.abacus.util.stream.ObjIteratorEx;
import com.landawn.abacus.util.stream.Stream;

/**
 * SQLExecutor is a simple sql/jdbc utility class. SQL is supported with different format:

 *
 *

 *
 *
 INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (?,  ?,  ?,  ?,  ?)

 *
 INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (#{firstName}, #{lastName}, #{gui}, #{lastUpdateTime}, #{createTime})

 *
 INSERT INTO account (first_name, last_name, gui, last_update_time, create_time) VALUES (:firstName, :lastName, :gui, :lastUpdateTime, :createTime)

 *
 * All these kinds of SQLs can be generated by SQLBuilder conveniently. Parameters with format of Object[]/List parameters are supported for parameterized SQL({@code id = ?}).
 * Parameters with format of Object[]/List/Map/Entity are supported for named parameterized SQL({@code id = :id}).
 * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
 *

 *
 * Here is sample of CRUD(create/read/update/delete):
 *
========================================================================
 *

 *
 * static final DataSource dataSource = JdbcUtil.createDataSource(...);
 * static final SQLExecutor sqlExecutor = new SQLExecutor(dataSource);
 * ...
 * Account account = createAccount();
 *
 * // create
 * String sql_insert = NE.insert(GUI, FIRST_NAME, LAST_NAME, LAST_UPDATE_TIME, CREATE_TIME).into(Account.class).sql();
 * N.println(sql_insert);
 * sqlExecutor.insert(sql_insert, account);
 *
 * // read
 * String sql_selectByGUI = NE.selectFrom(Account.class, N.asSet(DEVICES)).where(L.eq(GUI, L.QME)).sql();
 * N.println(sql_selectByGUI);
 * Account dbAccount = sqlExecutor.findFirst(Account.class, sql_selectByGUI, account);
 * assertEquals(account.getFirstName(), dbAccount.getFirstName());
 *
 * // update
 * String sql_updateByLastName = NE.update(Account.class).set(FIRST_NAME).where(L.eq(LAST_NAME, L.QME)).sql();
 * N.println(sql_updateByLastName);
 * dbAccount.setFirstName("newFirstName");
 * sqlExecutor.update(sql_updateByLastName, dbAccount);
 *
 * // delete
 * String sql_deleteByFirstName = NE.deleteFrom(Account.class).where(L.eq(FIRST_NAME, L.QME)).sql();
 * N.println(sql_deleteByFirstName);
 * sqlExecutor.update(sql_deleteByFirstName, dbAccount);
 *
 * dbAccount = sqlExecutor.findFirst(Account.class, sql_selectByGUI, account);
 * assertNull(dbAccount);
 *
 *

 * ========================================================================
 *

 *

 * If {@code conn} argument is null or not specified, {@code SQLExecutor} is responsible to get the connection from the
 * internal {@code DataSource}, start and commit/roll back transaction for batch operations if needed, and close the
 * connection finally. otherwise it's user's responsibility to do such jobs if {@code conn} is specified and not null.

 *

 *
 * The general programming way with SQLExeucte is to execute sql scripts(generated by SQLBuilder) with array/list/map/entity by calling (batch)insert/update/delete/query/... methods.
 * if Transaction is required. it can be started:
 *

 *
 * final SQLTransaction tran = sqlExecutor.beginTransaction(IsolationLevel.READ_COMMITTED);
 *
 * try {
 *     // sqlExecutor.insert(...);
 *     // sqlExecutor.update(...);
 *     // sqlExecutor.query(...);
 *
 *     tran.commit();
 * } finally {
 *     // The connection will be automatically closed after the transaction is committed or rolled back.
 *     tran.rollbackIfNotCommitted();
 * }
 *
 *

 *
 *
 * Spring Transaction is also supported and Integrated.
 * If a method of this class is called where a Spring transaction is started with the {@code DataSource} inside this {@code SQLExecutor}, without {@code Connection} parameter specified,
 * the {@code Connection} started the Spring Transaction will be used. Otherwise a {@code Connection} directly from the inside {@code DataSource}(Connection pool) will be borrowed and used.
 *
 *
 * SQLExecutor is tread-safe.

 *
 * @since 0.8
 *
 * @author Haiyang Li
 *
 * @see JdbcUtil
 *
 * @see {@link com.landawn.abacus.annotation.ReadOnly}
 * @see {@link com.landawn.abacus.annotation.ReadOnlyId}
 * @see {@link com.landawn.abacus.annotation.NonUpdatable}
 * @see {@link com.landawn.abacus.annotation.Transient}
 * @see {@link com.landawn.abacus.annotation.Table}
 * @see {@link com.landawn.abacus.annotation.Column}
 *
 * @see http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html
 * @see http://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html
 * @see http://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html
 * @see http://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html
 */
public class SQLExecutor implements Closeable {
    private static final Logger logger = LoggerFactory.getLogger(SQLExecutor.class);

    static final String ID = "id";

    static final String QUERY_WITH_DATA_SOURCE = "queryWithDataSource";

    private static final ResultExtractor<Boolean> EXISTS_RESULT_SET_EXTRACTOR = new ResultExtractor<Boolean>() {
        @Override
        public Boolean extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            return rs.next();
        }
    };

    private static final ResultExtractor<Integer> COUNT_RESULT_SET_EXTRACTOR = new ResultExtractor<Integer>() {
        @Override
        public Integer extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            int cnt = 0;

            while (rs.next()) {
                cnt++;
            }

            return cnt;
        }
    };

    private static final ResultExtractor<OptionalBoolean> SINGLE_BOOLEAN_EXTRACTOR = new ResultExtractor<OptionalBoolean>() {
        @Override
        public OptionalBoolean extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalBoolean.of(rs.getBoolean(1));
            }

            return OptionalBoolean.empty();
        }
    };

    private static final ResultExtractor<OptionalChar> SINGLE_CHAR_EXTRACTOR = new ResultExtractor<OptionalChar>() {
        @Override
        public OptionalChar extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                final String str = rs.getString(1);

                return OptionalChar.of(str == null || str.length() == 0 ? N.CHAR_0 : str.charAt(0));
            }

            return OptionalChar.empty();
        }
    };

    private static final ResultExtractor<OptionalByte> SINGLE_BYTE_EXTRACTOR = new ResultExtractor<OptionalByte>() {
        @Override
        public OptionalByte extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalByte.of(rs.getByte(1));
            }

            return OptionalByte.empty();
        }
    };

    private static final ResultExtractor<OptionalShort> SINGLE_SHORT_EXTRACTOR = new ResultExtractor<OptionalShort>() {
        @Override
        public OptionalShort extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalShort.of(rs.getShort(1));
            }

            return OptionalShort.empty();
        }
    };

    private static final ResultExtractor<OptionalInt> SINGLE_INT_EXTRACTOR = new ResultExtractor<OptionalInt>() {
        @Override
        public OptionalInt extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalInt.of(rs.getInt(1));
            }

            return OptionalInt.empty();
        }
    };

    private static final ResultExtractor<OptionalLong> SINGLE_LONG_EXTRACTOR = new ResultExtractor<OptionalLong>() {
        @Override
        public OptionalLong extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalLong.of(rs.getLong(1));
            }

            return OptionalLong.empty();
        }
    };

    private static final ResultExtractor<OptionalFloat> SINGLE_FLOAT_EXTRACTOR = new ResultExtractor<OptionalFloat>() {
        @Override
        public OptionalFloat extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalFloat.of(rs.getFloat(1));
            }

            return OptionalFloat.empty();
        }
    };

    private static final ResultExtractor<OptionalDouble> SINGLE_DOUBLE_EXTRACTOR = new ResultExtractor<OptionalDouble>() {
        @Override
        public OptionalDouble extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return OptionalDouble.of(rs.getDouble(1));
            }

            return OptionalDouble.empty();
        }
    };

    private static final ResultExtractor<Nullable<BigDecimal>> SINGLE_BIG_DECIMAL_EXTRACTOR = new ResultExtractor<Nullable<BigDecimal>>() {
        @Override
        public Nullable<BigDecimal> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return Nullable.of(rs.getBigDecimal(1));
            }

            return Nullable.empty();
        }
    };

    private static final ResultExtractor<Nullable<String>> SINGLE_STRING_EXTRACTOR = new ResultExtractor<Nullable<String>>() {
        @Override
        public Nullable<String> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return Nullable.of(rs.getString(1));
            }

            return Nullable.empty();
        }
    };

    private static final ResultExtractor<Nullable<Date>> SINGLE_DATE_EXTRACTOR = new ResultExtractor<Nullable<Date>>() {
        @Override
        public Nullable<Date> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return Nullable.of(rs.getDate(1));
            }

            return Nullable.empty();
        }
    };

    private static final ResultExtractor<Nullable<Time>> SINGLE_TIME_EXTRACTOR = new ResultExtractor<Nullable<Time>>() {
        @Override
        public Nullable<Time> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return Nullable.of(rs.getTime(1));
            }

            return Nullable.empty();
        }
    };

    private static final ResultExtractor<Nullable<Timestamp>> SINGLE_TIMESTAMP_EXTRACTOR = new ResultExtractor<Nullable<Timestamp>>() {
        @Override
        public Nullable<Timestamp> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            JdbcUtil.skip(rs, jdbcSettings.getOffset());

            if (rs.next()) {
                return Nullable.of(rs.getTimestamp(1));
            }

            return Nullable.empty();
        }
    };

    private static final JdbcUtil.RowMapper<Object> NO_GENERATED_KEY_EXTRACTOR = new JdbcUtil.RowMapper<Object>() {
        @Override
        public Object apply(final ResultSet rs) throws SQLException {
            return null;
        }
    };

    private static final JdbcUtil.RowMapper<Object> SINGLE_GENERATED_KEY_EXTRACTOR = new JdbcUtil.RowMapper<Object>() {
        @Override
        public Object apply(final ResultSet rs) throws SQLException {
            return JdbcUtil.getColumnValue(rs, 1);
        }
    };

    private static final JdbcUtil.RowMapper<Object> MULTI_GENERATED_KEY_EXTRACTOR = new JdbcUtil.RowMapper<Object>() {
        @Override
        public Object apply(final ResultSet rs) throws SQLException {
            final List<String> columnLabelList = JdbcUtil.getColumnLabelList(rs);

            if (columnLabelList.size() == 1) {
                return JdbcUtil.getColumnValue(rs, 1);
            } else {
                return JdbcUtil.BiRowMapper.TO_LINKED_HASH_MAP.apply(rs, columnLabelList);
            }
        }
    };

    private static final JdbcUtil.BiRowMapper<Object> NO_BI_GENERATED_KEY_EXTRACTOR = new JdbcUtil.BiRowMapper<Object>() {
        @Override
        public Object apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
            return null;
        }
    };

    private static final JdbcUtil.BiRowMapper<Object> SINGLE_BI_GENERATED_KEY_EXTRACTOR = new JdbcUtil.BiRowMapper<Object>() {
        @Override
        public Object apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
            return JdbcUtil.getColumnValue(rs, 1);
        }
    };

    private static final JdbcUtil.BiRowMapper<Object> MULTI_BI_GENERATED_KEY_EXTRACTOR = new JdbcUtil.BiRowMapper<Object>() {
        @Override
        public Object apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
            final List<String> columnLabelList = JdbcUtil.getColumnLabelList(rs);

            if (columnLabelList.size() == 1) {
                return JdbcUtil.getColumnValue(rs, 1);
            } else {
                return JdbcUtil.BiRowMapper.TO_LINKED_HASH_MAP.apply(rs, columnLabelList);
            }
        }
    };

    private static final ResultExtractor<ResultSet> RESULT_SET_EXTRACTOR = new ResultExtractor<ResultSet>() {
        @Override
        public ResultSet extractData(ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            return rs;
        }
    };

    private static final int factor = Math.min(Math.max(1, IOUtil.MAX_MEMORY_IN_MB / 1024), 8);
    private static final int CACHED_SQL_LENGTH = 1024 * factor;
    private static final int SQL_CACHE_SIZE = 1000 * factor;

    private static final Map<String, List<String>> _sqlColumnLabelPool = new ConcurrentHashMap<>();
    private final Map<String, List<String>> _tableColumnNamePool = new ConcurrentHashMap<>();

    private final DataSource _ds;
    private final DataSourceManager _dsm;
    private final DataSourceSelector _dss;
    private final JdbcSettings _jdbcSettings;
    private final SQLMapper _sqlMapper;
    private final NamingPolicy _namingPolicy;
    private final AsyncExecutor _asyncExecutor;
    private final boolean _isReadOnly;
    private final String _dbProudctName;
    private final String _dbProudctVersion;
    private final DBVersion _dbVersion;
    private final IsolationLevel _defaultIsolationLevel;
    private final AsyncSQLExecutor _asyncSQLExecutor;

    private final Map<Class<?>, Mapper<?, ?>> mapperPool = new ConcurrentHashMap<>();

    /**
     *
     * @param dataSource
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource) {
        this(dataSource, null);
    }

    /**
     *
     * @param dataSource
     * @param jdbcSettings
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource, final JdbcSettings jdbcSettings) {
        this(dataSource, jdbcSettings, null);
    }

    /**
     *
     * @param dataSource
     * @param jdbcSettings
     * @param sqlMapper
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper) {
        this(dataSource, jdbcSettings, sqlMapper, null);
    }

    /**
     *
     * @param dataSource
     * @param jdbcSettings
     * @param sqlMapper
     * @param namingPolicy
     * @see JdbcUtil#createDataSourceManager(String)
     * @see JdbcUtil#createDataSourceManager(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper, final NamingPolicy namingPolicy) {
        this(dataSource, jdbcSettings, sqlMapper, namingPolicy, null);
    }

    /**
     *
     * @param dataSource
     * @param jdbcSettings
     * @param sqlMapper
     * @param asyncExecutor
     * @see JdbcUtil#createDataSource(String)
     * @see JdbcUtil#createDataSource(java.io.InputStream)
     */
    public SQLExecutor(final DataSource dataSource, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper, final NamingPolicy namingPolicy,
            final AsyncExecutor asyncExecutor) {
        this(null, dataSource, jdbcSettings, sqlMapper, namingPolicy, asyncExecutor, false);
    }

    /**
     *
     * @param dataSourceManager
     * @see JdbcUtil#createDataSourceManager(String)
     * @see JdbcUtil#createDataSourceManager(java.io.InputStream)
     */
    public SQLExecutor(final DataSourceManager dataSourceManager) {
        this(dataSourceManager, null);
    }

    /**
     *
     * @param dataSourceManager
     * @param jdbcSettings
     * @see JdbcUtil#createDataSourceManager(String)
     * @see JdbcUtil#createDataSourceManager(java.io.InputStream)
     */
    public SQLExecutor(final DataSourceManager dataSourceManager, final JdbcSettings jdbcSettings) {
        this(dataSourceManager, jdbcSettings, null);
    }

    /**
     *
     * @param dataSourceManager
     * @param jdbcSettings
     * @param sqlMapper
     * @see JdbcUtil#createDataSourceManager(String)
     * @see JdbcUtil#createDataSourceManager(java.io.InputStream)
     */
    public SQLExecutor(final DataSourceManager dataSourceManager, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper) {
        this(dataSourceManager, jdbcSettings, sqlMapper, null);
    }

    /**
     *
     * @param dataSourceManager
     * @param jdbcSettings
     * @param sqlMapper
     * @param namingPolicy
     * @see JdbcUtil#createDataSourceManager(String)
     * @see JdbcUtil#createDataSourceManager(java.io.InputStream)
     */
    public SQLExecutor(final DataSourceManager dataSourceManager, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper, final NamingPolicy namingPolicy) {
        this(dataSourceManager, jdbcSettings, sqlMapper, namingPolicy, null);
    }

    /**
     *
     * @param dataSourceManager
     * @param jdbcSettings
     * @param sqlMapper
     * @param asyncExecutor
     * @see JdbcUtil#createDataSourceManager(String)
     * @see JdbcUtil#createDataSourceManager(java.io.InputStream)
     */
    public SQLExecutor(final DataSourceManager dataSourceManager, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper, final NamingPolicy namingPolicy,
            final AsyncExecutor asyncExecutor) {
        this(dataSourceManager, null, jdbcSettings, sqlMapper, namingPolicy, asyncExecutor, false);
    }

    protected SQLExecutor(final DataSourceManager dataSourceManager, final DataSource dataSource, final JdbcSettings jdbcSettings, final SQLMapper sqlMapper,
            final NamingPolicy namingPolicy, final AsyncExecutor asyncExecutor, final boolean isReadOnly) {

        if (dataSourceManager == null) {
            this._ds = dataSource;
            this._dsm = null;
            this._dss = null;
        } else {
            this._ds = dataSourceManager.getPrimaryDataSource();
            this._dsm = dataSourceManager;
            this._dss = dataSourceManager.getDataSourceSelector();
        }

        this._jdbcSettings = (jdbcSettings == null) ? JdbcSettings.create() : jdbcSettings.copy();

        if (_jdbcSettings.getBatchSize() == 0) {
            _jdbcSettings.setBatchSize(JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        _jdbcSettings.freeze();

        this._sqlMapper = sqlMapper == null ? new SQLMapper() : sqlMapper;
        this._namingPolicy = namingPolicy == null ? NamingPolicy.LOWER_CASE_WITH_UNDERSCORE : namingPolicy;
        this._asyncExecutor = asyncExecutor == null ? new AsyncExecutor(64, 300, TimeUnit.SECONDS) : asyncExecutor;
        this._isReadOnly = isReadOnly;

        int originalIsolationLevel;
        Connection conn = getConnection();

        try {
            _dbProudctName = conn.getMetaData().getDatabaseProductName();
            _dbProudctVersion = conn.getMetaData().getDatabaseProductVersion();
            _dbVersion = JdbcUtil.getDBVersion(conn);
            originalIsolationLevel = conn.getTransactionIsolation();
        } catch (SQLException e) {
            throw new UncheckedSQLException(e);
        } finally {
            closeConnection(conn);
        }

        final IsolationLevel tmp = this._ds instanceof SQLDataSource ? ((SQLDataSource) this._ds).getDefaultIsolationLevel() : IsolationLevel.DEFAULT;
        _defaultIsolationLevel = tmp == IsolationLevel.DEFAULT ? IsolationLevel.valueOf(originalIsolationLevel) : tmp;

        this._asyncSQLExecutor = new AsyncSQLExecutor(this, _asyncExecutor);
    }

    //    public static SQLExecutor create(final String dataSourceFile) {
    //        return new SQLExecutor(JdbcUtil.createDataSourceManager(dataSourceFile));
    //    }
    //
    //    public static SQLExecutor create(final InputStream dataSourceInputStream) {
    //        return new SQLExecutor(JdbcUtil.createDataSourceManager(dataSourceInputStream));
    //    }
    //
    //    public static SQLExecutor create(final String url, final String user, final String password) {
    //        return new SQLExecutor(JdbcUtil.createDataSource(url, user, password));
    //    }
    //
    //    public static SQLExecutor create(final String driver, final String url, final String user, final String password) {
    //        return new SQLExecutor(JdbcUtil.createDataSource(driver, url, user, password));
    //    }
    //
    //    public static SQLExecutor create(final Class<? extends Driver> driverClass, final String url, final String user, final String password) {
    //        return new SQLExecutor(JdbcUtil.createDataSource(driverClass, url, user, password));
    //    }
    //
    //    /**
    //     *
    //     * @param props refer to Connection.xsd for the supported properties.
    //     * @return
    //     */
    //    public static SQLExecutor create(final Map<String, ?> props) {
    //        return new SQLExecutor(JdbcUtil.createDataSource(props));
    //    }
    //
    //    public static SQLExecutor create(final DataSource sqlDataSource) {
    //        return new SQLExecutor(JdbcUtil.wrap(sqlDataSource));
    //    }

    //
    //    public SQLMapper sqlMapper() {
    //        return _sqlMapper;
    //    }

    @Beta
    public static SQLExecutor w(final String url, final String user, final String password) {
        return new SQLExecutor(JdbcUtil.createDataSource(url, user, password));
    }

    @Beta
    public static SQLExecutor w(final String driver, final String url, final String user, final String password) {
        return new SQLExecutor(JdbcUtil.createDataSource(driver, url, user, password));
    }

    @Beta
    public static SQLExecutor w(final Class<? extends Driver> driverClass, final String url, final String user, final String password) {
        return new SQLExecutor(JdbcUtil.createDataSource(driverClass, url, user, password));
    }

    /**
     *
     * @param targetClass
     * @return
     */
    public <T, ID> Mapper<T, ID> mapper(final Class<T> targetClass) {
        Mapper<T, ID> mapper = (Mapper<T, ID>) mapperPool.get(targetClass);

        if (mapper == null) {
            N.checkArgument(ClassUtil.isEntity(targetClass),
                    ClassUtil.getCanonicalClassName(targetClass) + " is not an entity class with getter/setter methods");

            SQLBuilder.registerEntityPropColumnNameMap(targetClass);

            mapper = new Mapper<T, ID>(targetClass, this, this._namingPolicy);
            mapperPool.put(targetClass, mapper);
        }

        return mapper;
    }

    public AsyncSQLExecutor async() {
        return _asyncSQLExecutor;
    }

    public DataSource dataSource() {
        return _ds;
    }

    public JdbcSettings jdbcSettings() {
        return _jdbcSettings;
    }

    public String dbProudctName() {
        return _dbProudctName;
    }

    public String dbProudctVersion() {
        return _dbProudctVersion;
    }

    public DBVersion dbVersion() {
        return _dbVersion;
    }

    @SafeVarargs
    public final <ID> ID insert(final String sql, final Object... parameters) throws UncheckedSQLException {
        return insert(sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final String sql, final StatementSetter statementSetter, final Object... parameters) throws UncheckedSQLException {
        return insert(sql, statementSetter, null, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return insert(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return insert(sql, statementSetter, (JdbcUtil.RowMapper<ID>) getGeneratedKeyExtractor(jdbcSettings), jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<ID> autoGeneratedKeyExtractor,
            final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return insert(null, sql, statementSetter, autoGeneratedKeyExtractor, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, final Object... parameters) throws UncheckedSQLException {
        return insert(conn, sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws UncheckedSQLException {
        return insert(conn, sql, statementSetter, null, parameters);
    }

    public final <ID> ID insert(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return insert(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return insert(conn, sql, statementSetter, (JdbcUtil.RowMapper<ID>) getGeneratedKeyExtractor(jdbcSettings), jdbcSettings, parameters);
    }

    /**
     * @see #batchInsert(Connection, String, StatementSetter, JdbcSettings, String, Object[])
     */
    @SuppressWarnings({ "unchecked", "deprecation" })
    @SafeVarargs
    public final <ID> ID insert(final Connection conn, final String sql, StatementSetter statementSetter, JdbcUtil.RowMapper<ID> autoGeneratedKeyExtractor,
            JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        final NamedSQL namedSQL = getNamedSQL(sql);
        final boolean isEntityOrMapParameter = isEntityOrMapParameter(namedSQL, parameters);
        final boolean isEntity = isEntityOrMapParameter && ClassUtil.isEntity(parameters[0].getClass());
        final Collection<String> idPropNames = isEntity ? ClassUtil.getIdFieldNames(parameters[0].getClass()) : null;
        final boolean autoGeneratedKeys = isEntity == false || (N.notNullOrEmpty(idPropNames) && !namedSQL.getNamedParameters().containsAll(idPropNames));

        statementSetter = checkStatementSetter(namedSQL, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, namedSQL, _sqlMapper.getAttrs(sql));
        autoGeneratedKeyExtractor = checkGeneratedKeysExtractor(autoGeneratedKeyExtractor, namedSQL, parameters);

        DataSource ds = null;
        Connection localConn = null;
        Object result = null;
        PreparedStatement stmt = null;

        try {
            ds = getDataSource(namedSQL.getParameterizedSQL(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.INSERT);

            stmt = prepareStatement(ds, localConn, namedSQL, statementSetter, jdbcSettings, autoGeneratedKeys, false, parameters);

            result = executeInsert(namedSQL, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
        } catch (SQLException e) {
            String msg = AbacusException.getErrorMsg(e) + ". [SQL] " + namedSQL.getNamedSQL();
            throw new UncheckedSQLException(msg, e);
        } finally {
            close(stmt);
            close(localConn, conn, ds);
        }

        if (isEntityOrMapParameter) {
            if (result != null) {
                Object parameter_0 = parameters[0];

                if (parameter_0 instanceof Map) {
                    // Don't update input map ?
                } else if (autoGeneratedKeys) {
                    setIds(result, parameter_0, namedSQL);
                }
            }

            if (parameters[0] instanceof DirtyMarker) {
                ((DirtyMarker) parameters[0]).dirtyPropNames().clear();
            }
        }

        return (ID) result;
    }

    private <ID> JdbcUtil.RowMapper<ID> getGeneratedKeyExtractor(JdbcSettings jdbcSettings) {
        if (jdbcSettings != null && ((N.notNullOrEmpty(jdbcSettings.getReturnedColumnIndexes()) && jdbcSettings.getReturnedColumnIndexes().length > 1)
                || (N.notNullOrEmpty(jdbcSettings.getReturnedColumnNames()) && jdbcSettings.getReturnedColumnNames().length > 1))) {
            return (JdbcUtil.RowMapper<ID>) MULTI_GENERATED_KEY_EXTRACTOR;
        } else {
            return (JdbcUtil.RowMapper<ID>) SINGLE_GENERATED_KEY_EXTRACTOR;
        }
    }

    private <ID> JdbcUtil.BiRowMapper<ID> getBiGeneratedKeyExtractor(JdbcSettings jdbcSettings) {
        if (jdbcSettings != null && ((N.notNullOrEmpty(jdbcSettings.getReturnedColumnIndexes()) && jdbcSettings.getReturnedColumnIndexes().length > 1)
                || (N.notNullOrEmpty(jdbcSettings.getReturnedColumnNames()) && jdbcSettings.getReturnedColumnNames().length > 1))) {
            return (JdbcUtil.BiRowMapper<ID>) MULTI_BI_GENERATED_KEY_EXTRACTOR;
        } else {
            return (JdbcUtil.BiRowMapper<ID>) SINGLE_BI_GENERATED_KEY_EXTRACTOR;
        }
    }

    @SuppressWarnings("rawtypes")
    static <ID> JdbcUtil.RowMapper<ID> checkGeneratedKeysExtractor(JdbcUtil.RowMapper<ID> autoGeneratedKeyExtractor, final NamedSQL namedSQL,
            final Object[] parameters) {
        if ((autoGeneratedKeyExtractor == null || autoGeneratedKeyExtractor == SINGLE_GENERATED_KEY_EXTRACTOR
                || autoGeneratedKeyExtractor == MULTI_GENERATED_KEY_EXTRACTOR) //
                && isEntityOrMapParameter(namedSQL, parameters) && ClassUtil.isEntity(parameters[0].getClass())) {
            final Class<?> cls = parameters[0].getClass();
            @SuppressWarnings("deprecation")
            final List<String> idPropNames = ClassUtil.getIdFieldNames(cls);

            if (idPropNames.size() == 0) {
                return (JdbcUtil.RowMapper) NO_GENERATED_KEY_EXTRACTOR;
            } else if (idPropNames.size() == 1) {
                if (namedSQL.getNamedParameters().contains(idPropNames.get(0))) {
                    return (JdbcUtil.RowMapper) NO_GENERATED_KEY_EXTRACTOR;
                } else {
                    return (JdbcUtil.RowMapper) SINGLE_GENERATED_KEY_EXTRACTOR;
                }
            } else {
                List<String> generatedIdPropNames = null;

                for (String idPropName : idPropNames) {
                    if (!namedSQL.getNamedParameters().contains(idPropName)) {
                        if (generatedIdPropNames == null) {
                            generatedIdPropNames = new ArrayList<>();
                        }

                        generatedIdPropNames.add(idPropName);
                    }
                }

                if (N.isNullOrEmpty(generatedIdPropNames)) {
                    return (JdbcUtil.RowMapper) NO_GENERATED_KEY_EXTRACTOR;
                } else if (generatedIdPropNames.size() == 1) {
                    return (JdbcUtil.RowMapper) SINGLE_GENERATED_KEY_EXTRACTOR;
                } else {
                    return (JdbcUtil.RowMapper) MULTI_GENERATED_KEY_EXTRACTOR;
                }
            }
        }

        return autoGeneratedKeyExtractor;
    }

    @SuppressWarnings("rawtypes")
    static <ID> JdbcUtil.BiRowMapper<ID> checkBiGeneratedKeysExtractor(JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, final NamedSQL namedSQL,
            final Object... parameters) {
        if ((autoGeneratedKeyExtractor == null || autoGeneratedKeyExtractor == SINGLE_BI_GENERATED_KEY_EXTRACTOR
                || autoGeneratedKeyExtractor == MULTI_BI_GENERATED_KEY_EXTRACTOR) //
                && isEntityOrMapParameter(namedSQL, parameters) && ClassUtil.isEntity(parameters[0].getClass())) {
            final Class<?> cls = parameters[0].getClass();
            @SuppressWarnings("deprecation")
            final List<String> idPropNames = ClassUtil.getIdFieldNames(cls);

            if (idPropNames.size() == 0) {
                return (JdbcUtil.BiRowMapper) NO_BI_GENERATED_KEY_EXTRACTOR;
            } else if (idPropNames.size() == 1) {
                if (namedSQL.getNamedParameters().contains(idPropNames.get(0))) {
                    return (JdbcUtil.BiRowMapper) NO_BI_GENERATED_KEY_EXTRACTOR;
                } else {
                    return (JdbcUtil.BiRowMapper) SINGLE_BI_GENERATED_KEY_EXTRACTOR;
                }
            } else {
                List<String> generatedIdPropNames = null;

                for (String idPropName : idPropNames) {
                    if (!namedSQL.getNamedParameters().contains(idPropName)) {
                        if (generatedIdPropNames == null) {
                            generatedIdPropNames = new ArrayList<>();
                        }

                        generatedIdPropNames.add(idPropName);
                    }
                }

                if (N.isNullOrEmpty(generatedIdPropNames)) {
                    return (JdbcUtil.BiRowMapper) NO_BI_GENERATED_KEY_EXTRACTOR;
                } else if (generatedIdPropNames.size() == 1) {
                    return (JdbcUtil.BiRowMapper) SINGLE_BI_GENERATED_KEY_EXTRACTOR;
                } else {
                    return (JdbcUtil.BiRowMapper) MULTI_BI_GENERATED_KEY_EXTRACTOR;
                }
            }
        }

        return autoGeneratedKeyExtractor;
    }

    @SuppressWarnings("deprecation")
    static void setIds(final Object ids, final Object entity, final NamedSQL namedSQL) {
        final Class<?> entityClass = entity.getClass();
        final EntityInfo entityInfo = ParserUtil.getEntityInfo(entityClass);
        final List<String> idPropNameSet = ClassUtil.getIdFieldNames(entityClass);
        final Collection<String> idPropNames = idPropNameSet.size() <= 1 ? idPropNameSet : N.difference(idPropNameSet, namedSQL.getNamedParameters());
        PropInfo propInfo = null;

        try {
            for (String idPropName : idPropNames) {
                if (namedSQL.getNamedParameters().contains(idPropName)) {
                    continue;
                }

                propInfo = entityInfo.getPropInfo(idPropName);

                if (propInfo != null && propInfo.getMethod != null && propInfo.setMethod != null) {
                    final Object idPropValue = propInfo.getPropValue(entity);

                    if (JdbcUtil.isDefaultIdPropValue(idPropValue)) {
                        if (idPropNames.size() > 1) {
                            if (ids instanceof Map && ((Map<String, Object>) ids).containsKey(idPropName)) {
                                propInfo.setPropValue(entity, ((Map<String, Object>) ids).get(idPropName));
                            }
                        } else {
                            if (ids instanceof Map && !propInfo.setMethod.getParameterTypes()[0].isAssignableFrom(ids.getClass())) {
                                if (((Map<String, Object>) ids).containsKey(idPropName)) {
                                    propInfo.setPropValue(entity, ((Map<String, Object>) ids).get(idPropName));
                                } else {
                                    propInfo.setPropValue(entity, ((Map<String, Object>) ids).values().iterator().next());
                                }
                            } else {
                                propInfo.setPropValue(entity, ids);
                            }
                        }
                    }
                } else {
                    if (logger.isWarnEnabled()) {
                        logger.warn("Failed to set the returned id property to entity. no get/set method for id property (" + idPropName + ") found. ");
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Failed to set the returned id property to entity", e);
        }
    }

    protected <ID> ID executeInsert(final NamedSQL namedSQL, final PreparedStatement stmt, final JdbcUtil.RowMapper<ID> autoGeneratedKeyExtractor,
            final boolean autoGeneratedKeys) throws SQLException {
        if (_isReadOnly) {
            throw new AbacusException("This SQL Executor is configured for read-only");
        }

        stmt.executeUpdate();

        ID id = null;

        if (autoGeneratedKeys) {
            ResultSet rs = null;

            try {
                rs = stmt.getGeneratedKeys();
                id = rs.next() ? autoGeneratedKeyExtractor.apply(rs) : null;
            } catch (SQLException e) {
                logger.error("Failed to retrieve the auto-generated Ids", e);
            } finally {
                close(rs);
            }
        }

        return id;
    }

    public <ID> List<ID> batchInsert(final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(sql, StatementSetter.DEFAULT, parametersList);
    }

    public <ID> List<ID> batchInsert(final String sql, final StatementSetter statementSetter, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(sql, statementSetter, null, parametersList);
    }

    public <ID> List<ID> batchInsert(final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    public <ID> List<ID> batchInsert(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchInsert(sql, statementSetter, (JdbcUtil.BiRowMapper<ID>) getBiGeneratedKeyExtractor(jdbcSettings), jdbcSettings, parametersList);
    }

    public <ID> List<ID> batchInsert(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor,
            final JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(null, sql, statementSetter, autoGeneratedKeyExtractor, jdbcSettings, parametersList);
    }

    public <ID> List<ID> batchInsert(final Connection conn, final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(conn, sql, StatementSetter.DEFAULT, parametersList);
    }

    public <ID> List<ID> batchInsert(final Connection conn, final String sql, final StatementSetter statementSetter, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchInsert(conn, sql, statementSetter, null, parametersList);
    }

    public <ID> List<ID> batchInsert(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchInsert(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    public <ID> List<ID> batchInsert(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings,
            final List<?> parametersList) throws UncheckedSQLException {
        return batchInsert(conn, sql, statementSetter, (JdbcUtil.BiRowMapper<ID>) getBiGeneratedKeyExtractor(jdbcSettings), jdbcSettings, parametersList);
    }

    @SuppressWarnings("deprecation")
    public <ID> List<ID> batchInsert(final Connection conn, final String sql, StatementSetter statementSetter,
            JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        N.checkArgNotNullOrEmpty(parametersList, "parametersList");

        final NamedSQL namedSQL = getNamedSQL(sql);
        final Object parameters_0 = parametersList.get(0);
        final boolean isEntityOrMapParameter = isEntityOrMapParameter(namedSQL, parameters_0);
        final boolean isEntity = isEntityOrMapParameter && ClassUtil.isEntity(parameters_0.getClass());
        final Collection<String> idPropNames = isEntity ? ClassUtil.getIdFieldNames(parameters_0.getClass()) : null;
        final boolean autoGeneratedKeys = isEntity == false || (N.notNullOrEmpty(idPropNames) && !namedSQL.getNamedParameters().containsAll(idPropNames));

        statementSetter = checkStatementSetter(namedSQL, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, namedSQL, _sqlMapper.getAttrs(sql));
        autoGeneratedKeyExtractor = checkBiGeneratedKeysExtractor(autoGeneratedKeyExtractor, namedSQL, parametersList.get(0));

        final int len = parametersList.size();
        final int batchSize = getBatchSize(jdbcSettings);

        List<ID> resultIdList = new ArrayList<>(len);

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;
        int originalIsolationLevel = 0;
        boolean autoCommit = true;

        try {
            ds = getDataSource(namedSQL.getParameterizedSQL(), parametersList, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.INSERT);

            try {
                originalIsolationLevel = localConn.getTransactionIsolation();
                autoCommit = localConn.getAutoCommit();
            } catch (SQLException e) {
                close(localConn, conn, ds);
                throw new UncheckedSQLException(e);
            }

            if ((conn == null) && (len > batchSize)) {
                localConn.setAutoCommit(false);

                setIsolationLevel(jdbcSettings, localConn);
            }

            stmt = prepareStatement(ds, localConn, namedSQL, statementSetter, jdbcSettings, autoGeneratedKeys, true, parametersList);

            if (len <= batchSize) {
                for (int i = 0; i < len; i++) {
                    statementSetter.setParameters(namedSQL, stmt, N.asArray(parametersList.get(i)));
                    stmt.addBatch();
                }

                executeBatchInsert(resultIdList, namedSQL, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
            } else {
                int num = 0;

                for (int i = 0; i < len; i++) {
                    statementSetter.setParameters(namedSQL, stmt, N.asArray(parametersList.get(i)));
                    stmt.addBatch();
                    num++;

                    if ((num % batchSize) == 0) {
                        executeBatchInsert(resultIdList, namedSQL, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
                    }
                }

                if ((num % batchSize) > 0) {
                    executeBatchInsert(resultIdList, namedSQL, stmt, autoGeneratedKeyExtractor, autoGeneratedKeys);
                }
            }

            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                localConn.commit();
            }
        } catch (SQLException e) {
            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Trying to roll back ...");
                }

                try {
                    localConn.rollback();

                    if (logger.isWarnEnabled()) {
                        logger.warn("succeeded to roll back");
                    }
                } catch (SQLException e1) {
                    logger.error("Failed to roll back", e1);
                }
            }

            String msg = AbacusException.getErrorMsg(e) + ". [SQL] " + namedSQL.getNamedSQL();
            throw new UncheckedSQLException(msg, e);
        } finally {
            if ((conn == null) && (len > batchSize)) {
                try {
                    localConn.setAutoCommit(autoCommit);
                    localConn.setTransactionIsolation(originalIsolationLevel);
                } catch (SQLException e) {
                    logger.error("Failed to reset AutoCommit", e);
                }
            }

            close(stmt);
            close(localConn, conn, ds);
        }

        if (isEntityOrMapParameter(namedSQL, parametersList.get(0))) {
            if (N.notNullOrEmpty(resultIdList)) {
                if (resultIdList.size() == len && ClassUtil.isEntity(parametersList.get(0).getClass())) {
                    for (int i = 0; i < len; i++) {
                        setIds(resultIdList.get(i), parametersList.get(i), namedSQL);
                    }
                } else {
                    if (logger.isWarnEnabled()) {
                        logger.warn(
                                "Failed to set the returned id property to entity/map. because the size of returned key not equals the lenght of the input arrray");
                    }
                }
            }

            if (N.firstNonNull(parametersList).orNull() instanceof DirtyMarker) {
                for (Object parameters : parametersList) {
                    ((DirtyMarker) parameters).dirtyPropNames().clear();
                }
            }
        }

        return resultIdList;
    }

    private void setIsolationLevel(JdbcSettings jdbcSettings, Connection localConn) throws SQLException {
        final int isolationLevel = jdbcSettings.getIsolationLevel() == null || jdbcSettings.getIsolationLevel() == IsolationLevel.DEFAULT
                ? _defaultIsolationLevel.intValue()
                : jdbcSettings.getIsolationLevel().intValue();

        if (isolationLevel == localConn.getTransactionIsolation()) {
            // ignore.
        } else {
            localConn.setTransactionIsolation(isolationLevel);
        }
    }

    protected <ID> void executeBatchInsert(final List<ID> resultIdList, final NamedSQL namedSQL, final PreparedStatement stmt,
            final JdbcUtil.BiRowMapper<ID> autoGeneratedKeyExtractor, final boolean autoGeneratedKeys) throws SQLException {
        if (_isReadOnly) {
            throw new AbacusException("This SQL Executor is configured for read-only");
        }

        stmt.executeBatch();

        if (autoGeneratedKeys) {
            ResultSet rs = null;

            try {
                rs = stmt.getGeneratedKeys();
                final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);

                while (rs.next()) {
                    resultIdList.add(autoGeneratedKeyExtractor.apply(rs, columnLabels));
                }
            } catch (SQLException e) {
                logger.error("Failed to retrieve the auto-generated Ids", e);
            } finally {
                close(rs);
            }
        }

        stmt.clearBatch();
    }

    @SafeVarargs
    public final int update(final String sql, final Object... parameters) throws UncheckedSQLException {
        return update(sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final int update(final String sql, final StatementSetter statementSetter, final Object... parameters) throws UncheckedSQLException {
        return update(sql, statementSetter, null, parameters);
    }

    @SafeVarargs
    public final int update(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return update(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final int update(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        return update(null, sql, statementSetter, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final int update(final Connection conn, final String sql, final Object... parameters) throws UncheckedSQLException {
        return update(conn, sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final int update(final Connection conn, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws UncheckedSQLException {
        return update(conn, sql, statementSetter, null, parameters);
    }

    @SafeVarargs
    public final int update(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) throws UncheckedSQLException {
        return update(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * @see #batchUpdate(Connection, String, StatementSetter, JdbcSettings, Object[])
     */
    @SuppressWarnings("deprecation")
    @SafeVarargs
    public final int update(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings, final Object... parameters)
            throws UncheckedSQLException {
        final NamedSQL namedSQL = getNamedSQL(sql);
        statementSetter = checkStatementSetter(namedSQL, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, namedSQL, _sqlMapper.getAttrs(sql));

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;

        try {
            ds = getDataSource(namedSQL.getParameterizedSQL(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.UPDATE);

            stmt = prepareStatement(ds, localConn, namedSQL, statementSetter, jdbcSettings, false, false, parameters);

            final int result = executeUpdate(namedSQL, stmt);

            if (isEntityOrMapParameter(namedSQL, parameters)) {
                if (parameters[0] instanceof DirtyMarker) {
                    ((DirtyMarker) parameters[0]).markDirty(namedSQL.getNamedParameters(), false);
                }
            }

            return result;
        } catch (SQLException e) {
            String msg = AbacusException.getErrorMsg(e) + ". [SQL] " + namedSQL.getNamedSQL();
            throw new UncheckedSQLException(msg, e);
        } finally {
            close(stmt);
            close(localConn, conn, ds);
        }
    }

    protected int executeUpdate(final NamedSQL namedSQL, final PreparedStatement stmt) throws SQLException {
        if (_isReadOnly) {
            throw new AbacusException("This SQL Executor is configured for read-only");
        }

        return stmt.executeUpdate();
    }

    public int batchUpdate(final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(sql, StatementSetter.DEFAULT, parametersList);
    }

    public int batchUpdate(final String sql, final StatementSetter statementSetter, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(sql, statementSetter, null, parametersList);
    }

    public int batchUpdate(final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    public int batchUpdate(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchUpdate(null, sql, statementSetter, jdbcSettings, parametersList);
    }

    public int batchUpdate(final Connection conn, final String sql, final List<?> parametersList) throws UncheckedSQLException {
        return batchUpdate(conn, sql, StatementSetter.DEFAULT, parametersList);
    }

    public int batchUpdate(final Connection conn, final String sql, final StatementSetter statementSetter, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchUpdate(conn, sql, statementSetter, null, parametersList);
    }

    public int batchUpdate(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        return batchUpdate(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
    }

    /**
     * @see #batchUpdate(Connection, String, StatementSetter, JdbcSettings, Object[])
     */
    @SuppressWarnings("deprecation")
    public int batchUpdate(final Connection conn, final String sql, StatementSetter statementSetter, JdbcSettings jdbcSettings, final List<?> parametersList)
            throws UncheckedSQLException {
        final NamedSQL namedSQL = getNamedSQL(sql);
        statementSetter = checkStatementSetter(namedSQL, statementSetter);
        jdbcSettings = checkJdbcSettings(jdbcSettings, namedSQL, _sqlMapper.getAttrs(sql));

        final int len = parametersList.size();
        final int batchSize = getBatchSize(jdbcSettings);

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;
        int originalIsolationLevel = 0;
        boolean autoCommit = true;

        try {
            ds = getDataSource(namedSQL.getParameterizedSQL(), parametersList, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.UPDATE);

            try {
                originalIsolationLevel = localConn.getTransactionIsolation();
                autoCommit = localConn.getAutoCommit();
            } catch (SQLException e) {
                close(localConn, conn, ds);
                throw new UncheckedSQLException(e);
            }

            if ((conn == null) && (len > batchSize)) {
                localConn.setAutoCommit(false);

                setIsolationLevel(jdbcSettings, localConn);
            }

            stmt = prepareStatement(ds, localConn, namedSQL, statementSetter, jdbcSettings, false, true, parametersList);

            int result = 0;

            if (len <= batchSize) {
                for (int i = 0; i < len; i++) {
                    statementSetter.setParameters(namedSQL, stmt, N.asArray(parametersList.get(i)));
                    stmt.addBatch();
                }

                result += executeBatchUpdate(namedSQL, stmt);
            } else {
                int num = 0;

                for (int i = 0; i < len; i++) {
                    statementSetter.setParameters(namedSQL, stmt, N.asArray(parametersList.get(i)));
                    stmt.addBatch();
                    num++;

                    if ((num % batchSize) == 0) {
                        result += executeBatchUpdate(namedSQL, stmt);
                    }
                }

                if ((num % batchSize) > 0) {
                    result += executeBatchUpdate(namedSQL, stmt);
                }
            }

            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                localConn.commit();
            }

            if (N.firstNonNull(parametersList).orNull() instanceof DirtyMarker) {
                for (Object parameters : parametersList) {
                    ((DirtyMarker) parameters).markDirty(namedSQL.getNamedParameters(), false);
                }
            }

            return result;
        } catch (SQLException e) {
            if ((conn == null) && (len > batchSize) && autoCommit == true) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Trying to roll back ...");
                }

                try {
                    localConn.rollback();

                    if (logger.isWarnEnabled()) {
                        logger.warn("succeeded to roll back");
                    }
                } catch (SQLException e1) {
                    logger.error("Failed to roll back", e1);
                }
            }

            String msg = AbacusException.getErrorMsg(e) + ". [SQL] " + namedSQL.getNamedSQL();
            throw new UncheckedSQLException(msg, e);
        } finally {
            if ((conn == null) && (len > batchSize)) {
                try {
                    localConn.setAutoCommit(autoCommit);
                    localConn.setTransactionIsolation(originalIsolationLevel);
                } catch (SQLException e) {
                    logger.error("Failed to reset AutoCommit", e);
                }
            }

            close(stmt);
            close(localConn, conn, ds);
        }
    }

    protected int executeBatchUpdate(final NamedSQL namedSQL, final PreparedStatement stmt) throws SQLException {
        if (_isReadOnly) {
            throw new AbacusException("This SQL Executor is configured for read-only");
        }

        final int[] results = stmt.executeBatch();
        stmt.clearBatch();

        if ((results == null) || (results.length == 0)) {
            return 0;
        }

        int sum = 0;

        for (int i = 0; i < results.length; i++) {
            sum += results[i];
        }

        return sum;
    }

    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int update(final EntityId entityId, final Map<String, Object> props) {
    //        return update(null, entityId, props);
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int update(final Connection conn, final EntityId entityId, final Map<String, Object> props) {
    //        final Pair2 pair = generateUpdateSQL(entityId, props);
    //
    //        return update(conn, sp.sql, sp.parameters);
    //    }
    //
    //    private Pair2 generateUpdateSQL(final EntityId entityId, final Map<String, Object> props) {
    //        final Condition cond = EntityManagerUtil.entityId2Condition(entityId);
    //        final NamingPolicy namingPolicy = _jdbcSettings.getNamingPolicy();
    //
    //        if (namingPolicy == null) {
    //            return NE.update(entityId.entityName()).set(props).where(cond).pair();
    //        }
    //
    //        switch (namingPolicy) {
    //            case LOWER_CASE_WITH_UNDERSCORE: {
    //                return NE.update(entityId.entityName()).set(props).where(cond).pair();
    //            }
    //
    //            case UPPER_CASE_WITH_UNDERSCORE: {
    //                return NE2.update(entityId.entityName()).set(props).where(cond).pair();
    //            }
    //
    //            case CAMEL_CASE: {
    //                return NE3.update(entityId.entityName()).set(props).where(cond).pair();
    //            }
    //
    //            default:
    //                throw new IllegalArgumentException("Unsupported naming policy");
    //        }
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int delete(final EntityId entityId) {
    //        return delete(null, entityId);
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    int delete(final Connection conn, final EntityId entityId) {
    //        final Pair2 pair = generateDeleteSQL(entityId);
    //
    //        return update(conn, sp.sql, sp.parameters);
    //    }
    //
    //    private Pair2 generateDeleteSQL(final EntityId entityId) {
    //        final Condition cond = EntityManagerUtil.entityId2Condition(entityId);
    //        final NamingPolicy namingPolicy = _jdbcSettings.getNamingPolicy();
    //
    //        if (namingPolicy == null) {
    //            return NE.deleteFrom(entityId.entityName()).where(cond).pair();
    //        }
    //
    //        switch (namingPolicy) {
    //            case LOWER_CASE_WITH_UNDERSCORE: {
    //                return NE.deleteFrom(entityId.entityName()).where(cond).pair();
    //            }
    //
    //            case UPPER_CASE_WITH_UNDERSCORE: {
    //                return NE2.deleteFrom(entityId.entityName()).where(cond).pair();
    //            }
    //
    //            case CAMEL_CASE: {
    //                return NE3.deleteFrom(entityId.entityName()).where(cond).pair();
    //            }
    //
    //            default:
    //                throw new IllegalArgumentException("Unsupported naming policy");
    //        }
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    boolean exists(final EntityId entityId) {
    //        return exists(null, entityId);
    //    }
    //
    //    // mess up. To uncomment this method, also need to modify getNamingPolicy/setNamingPolicy in JdbcSettings.
    //    boolean exists(final Connection conn, final EntityId entityId) {
    //        final Pair2 pair = generateQuerySQL(entityId, NE._1_list);
    //
    //        return query(conn, sp.sql, StatementSetter.DEFAULT, EXISTS_RESULT_SET_EXTRACTOR, null, sp.parameters);
    //    }

    @SafeVarargs
    public final boolean exists(final String sql, final Object... parameters) {
        return exists(null, sql, parameters);
    }

    @SafeVarargs
    public final boolean exists(final Connection conn, final String sql, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, EXISTS_RESULT_SET_EXTRACTOR, null, parameters);
    }

    /**
     *
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @deprecated may be misused and it's inefficient.
     */
    @Deprecated
    @SafeVarargs
    public final int count(final String sql, final Object... parameters) {
        return count(null, sql, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @deprecated may be misused and it's inefficient.
     */
    @Deprecated
    @SafeVarargs
    public final int count(final Connection conn, final String sql, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, COUNT_RESULT_SET_EXTRACTOR, null, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, parameters));
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, statementSetter, parameters));
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, jdbcSettings, parameters));
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, sql, statementSetter, jdbcSettings, parameters));
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, parameters));
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, statementSetter, parameters));
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, jdbcSettings, parameters));
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(targetClass, conn, sql, statementSetter, jdbcSettings, parameters));
    }

    /**
     * v
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, rowMapper, parameters));
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> Optional<T> get(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, statementSetter, rowMapper, parameters));
    }

    /**
     *
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> Optional<T> get(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(sql, statementSetter, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     * @param conn
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return Optional.ofNullable(gett(conn, sql, rowMapper, parameters));
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final Object... parameters) {
        return Optional.ofNullable(gett(conn, sql, statementSetter, rowMapper, parameters));
    }

    /**
     *
     * @param conn
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(conn, sql, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> Optional<T> get(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return Optional.ofNullable(gett(conn, sql, statementSetter, rowMapper, jdbcSettings, parameters));
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters)
            throws DuplicatedResultException {
        return gett(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return gett(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) throws DuplicatedResultException {
        return gett(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return gett(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> T gett(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        N.checkArgNotNull(targetClass, "targetClass");

        final JdbcUtil.RowMapper<T> rowMapper = new JdbcUtil.RowMapper<T>() {
            private final BiRowMapper<T> biRowMapper = BiRowMapper.to(targetClass);

            @Override
            public T apply(ResultSet rs) throws SQLException {
                return biRowMapper.apply(rs, JdbcUtil.getColumnLabelList(rs));
            }
        };

        return gett(conn, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) throws DuplicatedResultException {
        return gett(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return gett(sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     *
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return gett(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return gett(null, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <T> T gett(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters)
            throws DuplicatedResultException {
        return gett(conn, sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> T gett(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final Object... parameters) throws DuplicatedResultException {
        return gett(conn, sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <T> T gett(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return gett(conn, sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *v
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> T gett(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        N.checkArgNotNull(rowMapper, "rowMapper");

        final ResultExtractor<T> resultExtractor = new ResultExtractor<T>() {
            @Override
            public T extractData(ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                int offset = jdbcSettings.getOffset();

                if (offset > 0) {
                    JdbcUtil.skip(rs, offset);
                }

                T result = null;

                if (rs.next()) {
                    result = Objects.requireNonNull(rowMapper.apply(rs));

                    if (rs.next()) {
                        throw new DuplicatedResultException("More than one records found by sql: " + sql);
                    }
                }

                return result;
            }
        };

        return query(conn, sql, statementSetter, resultExtractor, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final Object... parameters) {
        return findFirst(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return findFirst(targetClass, sql, statementSetter, null, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return findFirst(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters) {
        return findFirst(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) {
        return findFirst(targetClass, conn, sql, statementSetter, null, parameters);
    }

    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Just fetch the result in the 1st row. {@code null} is returned if no result is found. This method will try to
     * convert the column value to the type of mapping entity property if the mapping entity property is not assignable
     * from column value.
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(targetClass, "targetClass");

        final JdbcUtil.RowMapper<T> rowMapper = new JdbcUtil.RowMapper<T>() {
            private final BiRowMapper<T> biRowMapper = BiRowMapper.to(targetClass);

            @Override
            public T apply(ResultSet rs) throws SQLException {
                return biRowMapper.apply(rs, JdbcUtil.getColumnLabelList(rs));
            }
        };

        return findFirst(conn, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) {
        return findFirst(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final Object... parameters) {
        return findFirst(sql, statementSetter, rowMapper, null, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final String sql, final StatementSetter statementSetter, final JdbcUtil.RowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return findFirst(null, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) {
        return findFirst(conn, sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcUtil.RowMapper<T> rowMapper, final Object... parameters) {
        return findFirst(conn, sql, statementSetter, rowMapper, null, parameters);
    }

    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return findFirst(conn, sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> Optional<T> findFirst(final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcUtil.RowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(rowMapper, "rowMapper");

        final ResultExtractor<T> resultExtractor = new ResultExtractor<T>() {
            @Override
            public T extractData(ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                int offset = jdbcSettings.getOffset();

                if (offset > 0) {
                    JdbcUtil.skip(rs, offset);
                }

                return rs.next() ? Objects.requireNonNull(rowMapper.apply(rs)) : null;
            }
        };

        return Optional.ofNullable(query(conn, sql, statementSetter, resultExtractor, jdbcSettings, parameters));
    }

    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final Object... parameters) {
        return list(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return list(targetClass, sql, statementSetter, null, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return list(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final Object... parameters) {
        return list(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final Object... parameters) {
        return list(targetClass, conn, sql, statementSetter, null, parameters);
    }

    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return list(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> List<T> list(final Class<T> targetClass, final Connection conn, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(conn, sql, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final Object... parameters) {
        return list(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final Object... parameters) {
        return list(sql, statementSetter, rowMapper, null, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return list(null, sql, statementSetter, rowMapper, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> List<T> list(final Connection conn, final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final Object... parameters) {
        return list(conn, sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    public final <T> List<T> list(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final Object... parameters) {
        return list(conn, sql, statementSetter, rowMapper, null, parameters);
    }

    public final <T> List<T> list(final Connection conn, final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return list(conn, sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <T> List<T> list(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        N.checkArgNotNull(rowMapper);

        final ResultExtractor<List<T>> resultExtractor = new ResultExtractor<List<T>>() {
            @Override
            public List<T> extractData(ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                int offset = jdbcSettings.getOffset();
                int count = jdbcSettings.getCount();

                if (offset > 0) {
                    JdbcUtil.skip(rs, offset);
                }

                final List<T> result = new ArrayList<>(N.min(count, 16));
                final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);

                while (count-- > 0 && rs.next()) {
                    result.add(rowMapper.apply(rs, columnLabels));
                }

                return result;
            }
        };

        return query(conn, sql, statementSetter, resultExtractor, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return listAll(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return listAll(sql, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param targetClass
     * @param sqls
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final Class<T> targetClass, final List<String> sqls, final JdbcSettings jdbcSettings, final Object... parameters) {
        return listAll(targetClass, sqls, null, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param targetClass
     * @param sqls
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final Class<T> targetClass, final List<String> sqls, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return listAll(sqls, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        return listAll(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        checkJdbcSettingsForAllQuery(jdbcSettings);

        if (jdbcSettings == null || N.isNullOrEmpty(jdbcSettings.getQueryWithDataSources())) {
            return list(sql, statementSetter, rowMapper, jdbcSettings, parameters);
        }

        final Collection<String> dss = jdbcSettings.getQueryWithDataSources();
        List<List<T>> resultList = null;

        if (jdbcSettings.isQueryInParallel()) {
            resultList = Stream.of(dss).map(new Function<String, JdbcSettings>() {
                @Override
                public JdbcSettings apply(String ds) {
                    final JdbcSettings newJdbcSettings = jdbcSettings.copy();
                    newJdbcSettings.setQueryWithDataSources(null);
                    newJdbcSettings.setQueryWithDataSource(ds);
                    return newJdbcSettings;
                }
            }).parallel(dss.size()).map(new Function<JdbcSettings, List<T>>() {
                @Override
                public List<T> apply(JdbcSettings newJdbcSettings) {
                    return list(sql, statementSetter, rowMapper, newJdbcSettings, parameters);
                }
            }).toList();
        } else {
            final JdbcSettings newJdbcSettings = jdbcSettings.copy();
            newJdbcSettings.setQueryWithDataSources(null);
            resultList = new ArrayList<>(dss.size());

            for (String ds : dss) {
                newJdbcSettings.setQueryWithDataSource(ds);
                resultList.add(list(sql, statementSetter, rowMapper, newJdbcSettings, parameters));
            }
        }

        return N.concat(resultList);
    }

    private void checkJdbcSettingsForAllQuery(JdbcSettings jdbcSettings) {
        if (jdbcSettings != null && (jdbcSettings.getOffset() != 0 || jdbcSettings.getCount() != Integer.MAX_VALUE)) {
            throw new IllegalArgumentException("Can't set 'offset' or 'count' for findAll/queryAll/streamAll methods");
        }
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final List<String> sqls, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return listAll(sqls, null, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> List<T> listAll(final List<String> sqls, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        if (sqls.size() == 1) {
            return listAll(sqls.get(0), statementSetter, rowMapper, jdbcSettings, parameters);
        }

        List<List<T>> resultList = null;

        if (jdbcSettings != null && jdbcSettings.isQueryInParallel()) {
            resultList = Stream.of(sqls).parallel(sqls.size()).map(new Function<String, List<T>>() {
                @Override
                public List<T> apply(String sql) {
                    return listAll(sql, statementSetter, rowMapper, jdbcSettings, parameters);
                }
            }).toList();
        } else {
            resultList = new ArrayList<>(sqls.size());

            for (String sql : sqls) {
                resultList.add(listAll(sql, statementSetter, rowMapper, jdbcSettings, parameters));
            }
        }

        return N.concat(resultList);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalBoolean queryForBoolean(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_BOOLEAN_EXTRACTOR, null, parameters);
    }

    /**
     *
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalChar queryForChar(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_CHAR_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalByte queryForByte(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_BYTE_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalShort queryForShort(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_SHORT_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalInt queryForInt(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_INT_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalLong queryForLong(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_LONG_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalFloat queryForFloat(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_FLOAT_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final OptionalDouble queryForDouble(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_DOUBLE_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final Nullable<BigDecimal> queryForBigDecimal(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_BIG_DECIMAL_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, Connection, String, Object...).
     */
    @SafeVarargs
    public final Nullable<String> queryForString(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_STRING_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, String, Object...).
     */
    @SafeVarargs
    public final Nullable<Date> queryForDate(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_DATE_EXTRACTOR, null, parameters);
    }

    /**
     * @see SQLExecutor#queryForSingleResult(Class, String, Object...).
     */
    @SafeVarargs
    public final Nullable<Time> queryForTime(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_TIME_EXTRACTOR, null, parameters);
    }

    /**
    * @see SQLExecutor#queryForSingleResult(Class, String, Object...).
    */
    @SafeVarargs
    public final Nullable<Timestamp> queryForTimestamp(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, SINGLE_TIMESTAMP_EXTRACTOR, null, parameters);
    }

    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final Object... parameters) {
        return queryForSingleResult(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final Object... parameters) {
        return queryForSingleResult(targetClass, sql, statementSetter, null, parameters);
    }

    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return queryForSingleResult(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return queryForSingleResult(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql, final Object... parameters) {
        return queryForSingleResult(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final Object... parameters) {
        return queryForSingleResult(targetClass, conn, sql, statementSetter, null, parameters);
    }

    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return queryForSingleResult(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Returns a {@code Nullable} describing the value in the first row/column if it exists, otherwise return an empty {@code Nullable}.
     *

     *
     * Special note for type conversion for {@code boolean} or {@code Boolean} type: {@code true} is returned if the
     * {@code String} value of the target column is {@code "true"}, case insensitive. or it's an integer with value > 0.
     * Otherwise, {@code false} is returned.
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param targetClass
     *            set result type to avoid the NullPointerException if result is null and T is primitive type
     *            "int, long. short ... char, boolean..".
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <V> Nullable<V> queryForSingleResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(conn, sql, statementSetter, createSingleResultExtractor(targetClass), jdbcSettings, parameters);
    }

    private final ObjectPool<Class<?>, ResultExtractor<Nullable<?>>> singleResultExtractorPool = new ObjectPool<>(64);

    private <V> ResultExtractor<Nullable<V>> createSingleResultExtractor(final Class<V> targetClass) {
        @SuppressWarnings("rawtypes")
        ResultExtractor result = singleResultExtractorPool.get(targetClass);

        if (result == null) {
            result = new ResultExtractor<Nullable<V>>() {
                @Override
                public Nullable<V> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    JdbcUtil.skip(rs, jdbcSettings.getOffset());

                    if (rs.next()) {
                        return Nullable.of(N.convert(JdbcUtil.getColumnValue(rs, 1), targetClass));
                    }

                    return Nullable.empty();
                }
            };

            singleResultExtractorPool.put(targetClass, result);
        }

        return result;
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final Object... parameters)
            throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters)
            throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final String sql, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, null, sql, statementSetter, jdbcSettings, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql, final Object... parameters)
            throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, conn, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, conn, sql, statementSetter, null, parameters);
    }

    /**
     *
     * @param targetClass
     * @param conn
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     * @throws DuplicatedResultException if two or more records are found.
     */
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql, final JdbcSettings jdbcSettings,
            final Object... parameters) throws DuplicatedResultException {
        return queryForUniqueResult(targetClass, conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Returns a {@code Nullable} describing the value in the first row/column if it exists, otherwise return an empty {@code Nullable}.
     * And throws {@code DuplicatedResultException} if more than one record found.
     *

     *
     * Special note for type conversion for {@code boolean} or {@code Boolean} type: {@code true} is returned if the
     * {@code String} value of the target column is {@code "true"}, case insensitive. or it's an integer with value > 0.
     * Otherwise, {@code false} is returned.
     *
     * Remember to add {@code limit} condition if big result will be returned by the query.
     *
     * @param targetClass
     *            set result type to avoid the NullPointerException if result is null and T is primitive type
     *            "int, long. short ... char, boolean..".
     * @param conn
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @throws DuplicatedResultException if more than one record found.
     */
    @SuppressWarnings("unchecked")
    @SafeVarargs
    public final <V> Nullable<V> queryForUniqueResult(final Class<V> targetClass, final Connection conn, final String sql,
            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) throws DuplicatedResultException {
        return query(conn, sql, statementSetter, createUniqueResultExtractor(targetClass), jdbcSettings, parameters);
    }

    private final ObjectPool<Class<?>, ResultExtractor<Nullable<?>>> uniqueResultExtractorPool = new ObjectPool<>(64);

    private <V> ResultExtractor<Nullable<V>> createUniqueResultExtractor(final Class<V> targetClass) {
        @SuppressWarnings("rawtypes")
        ResultExtractor result = uniqueResultExtractorPool.get(targetClass);

        if (result == null) {
            result = new ResultExtractor<Nullable<V>>() {
                @Override
                public Nullable<V> extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    JdbcUtil.skip(rs, jdbcSettings.getOffset());

                    if (rs.next()) {
                        final Nullable<V> result = Nullable.of(N.convert(JdbcUtil.getColumnValue(rs, 1), targetClass));

                        if (result.isPresent() && rs.next()) {
                            throw new DuplicatedResultException("At least two results found: "
                                    + Strings.concat(result.get(), ", ", N.convert(JdbcUtil.getColumnValue(rs, 1), targetClass)));
                        }

                        return result;
                    }

                    return Nullable.empty();
                }
            };

            uniqueResultExtractorPool.put(targetClass, result);
        }

        return result;
    }

    @SafeVarargs
    public final DataSet query(final String sql, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final DataSet query(final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return query(sql, statementSetter, (JdbcSettings) null, parameters);
    }

    @SafeVarargs
    public final DataSet query(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final DataSet query(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(sql, statementSetter, ResultExtractor.DATA_SET, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> T query(final String sql, final ResultExtractor<T> resultExtractor, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, resultExtractor, parameters);
    }

    @SafeVarargs
    public final <T> T query(final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor, final Object... parameters) {
        return query(sql, statementSetter, resultExtractor, null, parameters);
    }

    @SafeVarargs
    public final <T> T query(final String sql, final ResultExtractor<T> resultExtractor, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(sql, StatementSetter.DEFAULT, resultExtractor, jdbcSettings, parameters);
    }

    /**
     * Remember to close the ResultSet, Statement and Connection if the return type T is ResultSet or RowIterator.
     *
     * If T is RowIterator, call rowIterator.close() to close ResultSet, Statement and Connection.
     *

     * If T is ResultSet, call below codes to close ResultSet, Statement and Connection.
     *
     *

     *
     * Connection conn = null;
     * Statement stmt = null;
     *
     * try {
     *     stmt = rs.getStatement();
     *     conn = stmt.getConnection();
     * } catch (SQLException e) {
     *     // TODO.
     * } finally {
     *     JdbcUtil.closeQuietly(rs, stmt, conn);
     * }
     *
     *

     *
     * @param sql
     * @param statementSetter
     * @param resultExtractor
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> T query(final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return query(null, sql, statementSetter, resultExtractor, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, parameters);
    }

    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return query(conn, sql, statementSetter, (JdbcSettings) null, parameters);
    }

    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final DataSet query(final Connection conn, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return query(conn, sql, statementSetter, ResultExtractor.DATA_SET, jdbcSettings, parameters);
    }

    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final ResultExtractor<T> resultExtractor, final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, resultExtractor, parameters);
    }

    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor,
            final Object... parameters) {
        return query(conn, sql, statementSetter, resultExtractor, null, parameters);
    }

    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final ResultExtractor<T> resultExtractor, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return query(conn, sql, StatementSetter.DEFAULT, resultExtractor, jdbcSettings, parameters);
    }

    /**
     * Remember to close the ResultSet, Statement and Connection if the return type T is ResultSet or RowIterator.
     *
     * If T is RowIterator, call rowIterator.close() to close ResultSet, Statement and Connection.
     *

     * If T is ResultSet, call below codes to close ResultSet, Statement and Connection.
     *
     *

     *
     * Connection conn = null;
     * Statement stmt = null;
     *
     * try {
     *     stmt = rs.getStatement();
     *     conn = stmt.getConnection();
     * } catch (SQLException e) {
     *     // TODO.
     * } finally {
     *     JdbcUtil.closeQuietly(rs, stmt, conn);
     * }
     *
     *

     *
     * @param conn
     * @param sql
     * @param statementSetter
     * @param resultExtractor
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> T query(final Connection conn, final String sql, final StatementSetter statementSetter, final ResultExtractor<T> resultExtractor,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return query(null, conn, sql, statementSetter, new ResultSetExtractor<T>() {
            @Override
            public T extractData(Class<?> targetClass, NamedSQL namedSQL, ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                return resultExtractor.extractData(rs, jdbcSettings);
            }

        }, jdbcSettings, parameters);
    }

    protected <T> T query(final Class<T> targetClass, final Connection conn, final String sql, StatementSetter statementSetter,
            ResultSetExtractor<T> resultExtractor, JdbcSettings jdbcSettings, final Object... parameters) {
        final NamedSQL namedSQL = getNamedSQL(sql);
        statementSetter = checkStatementSetter(namedSQL, statementSetter);
        resultExtractor = checkResultSetExtractor(namedSQL, resultExtractor);
        jdbcSettings = checkJdbcSettings(jdbcSettings, namedSQL, _sqlMapper.getAttrs(sql));

        T result = null;

        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            ds = getDataSource(namedSQL.getParameterizedSQL(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, SQLOperation.SELECT);

            stmt = prepareStatement(ds, localConn, namedSQL, statementSetter, jdbcSettings, false, false, parameters);

            if (jdbcSettings == null || jdbcSettings.getFetchDirection() == -1) {
                stmt.setFetchDirection(ResultSet.FETCH_FORWARD);
            }

            rs = stmt.executeQuery();

            result = resultExtractor.extractData(targetClass, namedSQL, rs, jdbcSettings);
        } catch (SQLException e) {
            String msg = AbacusException.getErrorMsg(e) + ". [SQL] " + namedSQL.getNamedSQL();
            throw new UncheckedSQLException(msg, e);
        } finally {
            if (result instanceof ResultSet) {
                if (conn == null) {
                    try {
                        close(rs, stmt);
                    } finally {
                        close(localConn, conn, ds);
                    }

                    throw new UnsupportedOperationException(
                            "The return type of 'ResultSetExtractor' can't be 'ResultSet' when Connection is not specified as input parameter.");
                }

                // delay.
            } else {
                try {
                    close(rs, stmt);
                } finally {
                    close(localConn, conn, ds);
                }
            }
        }

        return result;
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final DataSet queryAll(final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return queryAll(sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final DataSet queryAll(final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        checkJdbcSettingsForAllQuery(jdbcSettings);

        if (jdbcSettings == null || N.isNullOrEmpty(jdbcSettings.getQueryWithDataSources())) {
            return query(sql, statementSetter, jdbcSettings, parameters);
        }

        final Collection<String> dss = jdbcSettings.getQueryWithDataSources();

        if (jdbcSettings.isQueryInParallel()) {
            final List<DataSet> resultList = Stream.of(dss).map(new Function<String, JdbcSettings>() {
                @Override
                public JdbcSettings apply(String ds) {
                    final JdbcSettings newJdbcSettings = jdbcSettings.copy();
                    newJdbcSettings.setQueryWithDataSources(null);
                    newJdbcSettings.setQueryWithDataSource(ds);
                    return newJdbcSettings;
                }
            }).parallel(dss.size()).map(new Function<JdbcSettings, DataSet>() {
                @Override
                public DataSet apply(JdbcSettings newJdbcSettings) {
                    return query(sql, statementSetter, newJdbcSettings, parameters);
                }
            }).toList();

            return DataSetUtil.merge(resultList);
        } else {
            final JdbcSettings newJdbcSettings = jdbcSettings.copy();
            newJdbcSettings.setQueryWithDataSources(null);
            final List<DataSet> resultList = new ArrayList<>(dss.size());

            for (String ds : dss) {
                newJdbcSettings.setQueryWithDataSource(ds);

                resultList.add(query(sql, statementSetter, newJdbcSettings, parameters));
            }

            return DataSetUtil.merge(resultList);
        }
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final DataSet queryAll(final List<String> sqls, final JdbcSettings jdbcSettings, final Object... parameters) {
        return queryAll(sqls, null, jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final DataSet queryAll(final List<String> sqls, final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final Object... parameters) {
        if (sqls.size() == 1) {
            return queryAll(sqls.get(0), statementSetter, jdbcSettings, parameters);
        }

        if (jdbcSettings != null && jdbcSettings.isQueryInParallel()) {
            final List<DataSet> resultList = Stream.of(sqls).parallel(sqls.size()).map(new Function<String, DataSet>() {
                @Override
                public DataSet apply(String sql) {
                    return queryAll(sql, statementSetter, jdbcSettings, parameters);
                }
            }).toList();

            return DataSetUtil.merge(resultList);
        } else {
            final List<DataSet> resultList = new ArrayList<>(sqls.size());

            for (String sql : sqls) {
                resultList.add(queryAll(sql, statementSetter, jdbcSettings, parameters));
            }

            return DataSetUtil.merge(resultList);
        }
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param targetClass
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final Object... parameters) {
        return stream(targetClass, sql, StatementSetter.DEFAULT, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param targetClass
     * @param sql
     * @param statementSetter
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final Object... parameters) {
        return stream(targetClass, sql, statementSetter, null, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return stream(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return stream(sql, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param sql
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final Object... parameters) {
        return stream(sql, StatementSetter.DEFAULT, rowMapper, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final Object... parameters) {
        return stream(sql, statementSetter, rowMapper, null, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings, final Object... parameters) {
        return stream(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
     *
     * @param sql
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> stream(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {

        final ObjIteratorEx<T> lazyIter = ObjIteratorEx.of(new Supplier<ObjIteratorEx<T>>() {
            private ObjIteratorEx<T> internalIter = null;

            @Override
            public ObjIteratorEx<T> get() {
                if (internalIter == null) {
                    final Connection inputConn = null;

                    final JdbcSettings newJdbcSettings = jdbcSettings == null ? _jdbcSettings.copy() : jdbcSettings.copy();
                    final int offset = newJdbcSettings.getOffset();
                    final int count = newJdbcSettings.getCount();
                    newJdbcSettings.setOffset(0);
                    newJdbcSettings.setCount(Integer.MAX_VALUE);

                    final boolean streamTransactionIndependent = newJdbcSettings.streamTransactionIndependent();
                    final NamedSQL namedSQL = NamedSQL.parse(sql);
                    final DataSource ds = getDataSource(namedSQL.getParameterizedSQL(), parameters, newJdbcSettings);
                    final Connection localConn = streamTransactionIndependent ? getConnection(ds)
                            : getConnection(inputConn, ds, newJdbcSettings, SQLOperation.SELECT);

                    final ResultSet rs = SQLExecutor.this.query(localConn, sql, statementSetter, RESULT_SET_EXTRACTOR, newJdbcSettings, parameters);

                    internalIter = new ObjIteratorEx<T>() {
                        private boolean skipped = false;
                        private boolean hasNext = false;
                        private int cnt = 0;
                        private List<String> columnLabels = null;

                        @Override
                        public boolean hasNext() {
                            if (skipped == false) {
                                skip();
                            }

                            if (hasNext == false) {
                                try {
                                    if (cnt++ < count && rs.next()) {
                                        hasNext = true;
                                    }
                                } catch (SQLException e) {
                                    throw new UncheckedSQLException(e);
                                }
                            }

                            return hasNext;
                        }

                        @Override
                        public T next() {
                            if (hasNext() == false) {
                                throw new NoSuchElementException();
                            }

                            try {
                                final T result = rowMapper.apply(rs, columnLabels);
                                hasNext = false;
                                return result;
                            } catch (SQLException e) {
                                throw new UncheckedSQLException(e);
                            }
                        }

                        @Override
                        public void skip(long n) {
                            N.checkArgNotNegative(n, "n");

                            if (skipped == false) {
                                skip();
                            }

                            final long m = hasNext ? n - 1 : n;
                            hasNext = false;

                            try {
                                JdbcUtil.skip(rs, Math.min(m, count - cnt));
                            } catch (SQLException e) {
                                throw new UncheckedSQLException(e);
                            }
                        }

                        @Override
                        public long count() {
                            if (skipped == false) {
                                skip();
                            }

                            long result = hasNext ? 1 : 0;
                            hasNext = false;

                            try {
                                while (cnt++ < count && rs.next()) {
                                    result++;
                                }
                            } catch (SQLException e) {
                                throw new UncheckedSQLException(e);
                            }

                            return result;
                        }

                        @Override
                        public void close() {
                            try {
                                JdbcUtil.closeQuietly(rs, true, false);
                            } finally {
                                if (streamTransactionIndependent) {
                                    JdbcUtil.closeQuietly(localConn);
                                } else {
                                    SQLExecutor.this.close(localConn, inputConn, ds);
                                }
                            }
                        }

                        private void skip() {
                            if (skipped == false) {
                                skipped = true;

                                try {
                                    columnLabels = JdbcUtil.getColumnLabelList(rs);

                                    if (offset > 0) {
                                        JdbcUtil.skip(rs, offset);
                                    }
                                } catch (SQLException e) {
                                    throw new UncheckedSQLException(e);
                                }
                            }
                        }
                    };
                }

                return internalIter;
            }
        });

        return Stream.of(lazyIter).onClose(new Runnable() {
            @Override
            public void run() {
                lazyIter.close();
            }
        });
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param targetClass
     * @param sql
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final Class<T> targetClass, final String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        return streamAll(targetClass, sql, StatementSetter.DEFAULT, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final Class<T> targetClass, final String sql, final StatementSetter statementSetter, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return streamAll(sql, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final String sql, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return streamAll(sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sql
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final String sql, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        checkJdbcSettingsForAllQuery(jdbcSettings);

        if (jdbcSettings == null || N.isNullOrEmpty(jdbcSettings.getQueryWithDataSources())) {
            return stream(sql, statementSetter, rowMapper, jdbcSettings, parameters);
        }

        final Collection<String> dss = jdbcSettings.getQueryWithDataSources();

        return Stream.of(dss).map(new Function<String, JdbcSettings>() {
            @Override
            public JdbcSettings apply(String ds) {
                return jdbcSettings.copy().setQueryWithDataSources(null).setQueryWithDataSource(ds);
            }
        }).__(new Function<Stream<JdbcSettings>, Stream<JdbcSettings>>() {
            @Override
            public Stream<JdbcSettings> apply(Stream<JdbcSettings> s) {
                return jdbcSettings.isQueryInParallel() ? s.parallel(dss.size()) : s;
            }
        }).flatMap(new Function<JdbcSettings, Stream<T>>() {
            @Override
            public Stream<T> apply(JdbcSettings newJdbcSettings) {
                return stream(sql, statementSetter, rowMapper, newJdbcSettings, parameters);
            }
        });
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param targetClass
     * @param sqls
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final Class<T> targetClass, final List<String> sqls, final JdbcSettings jdbcSettings, final Object... parameters) {
        return streamAll(targetClass, sqls, null, jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param statementSetter
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final Class<T> targetClass, final List<String> sqls, final StatementSetter statementSetter,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        return streamAll(sqls, statementSetter, BiRowMapper.to(targetClass), jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final List<String> sqls, final JdbcUtil.BiRowMapper<T> rowMapper, final JdbcSettings jdbcSettings,
            final Object... parameters) {
        return streamAll(sqls, null, rowMapper, jdbcSettings, parameters);
    }

    /**
     * Execute one or more queries in one or more data sources specified by {@code jdbcSettings} and merge the results.
     * It's designed for partition.
     *
     * @param sqls
     * @param statementSetter
     * @param rowMapper
     * @param jdbcSettings
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     * @return
     */
    @SafeVarargs
    public final <T> Stream<T> streamAll(final List<String> sqls, final StatementSetter statementSetter, final JdbcUtil.BiRowMapper<T> rowMapper,
            final JdbcSettings jdbcSettings, final Object... parameters) {
        if (sqls.size() == 1) {
            return streamAll(sqls.get(0), statementSetter, rowMapper, jdbcSettings, parameters);
        }

        final boolean isQueryInParallel = jdbcSettings != null && jdbcSettings.isQueryInParallel();

        return Stream.of(sqls).__(new Function<Stream<String>, Stream<String>>() {
            @Override
            public Stream<String> apply(Stream<String> s) {
                return isQueryInParallel ? s.parallel(sqls.size()) : s;
            }
        }).flatMap(new Function<String, Stream<T>>() {
            @Override
            public Stream<T> apply(String sql) {
                return streamAll(sql, statementSetter, rowMapper, jdbcSettings, parameters);
            }
        });
    }

    public final void execute(final String sql, final Object... parameters) throws UncheckedSQLException {
        execute(null, sql, parameters);
    }

    /**
     * Execute the sql with the specified parameters.
     *
     * @param conn
     * @param sql
     * @param parameters it can be {@code Object[]/List} for (named) parameterized query, or {@code Map<String, Object>/Entity} for named parameterized query.
     * DO NOT use primitive array {@code boolean[]/char[]/byte[]/short[]/int[]/long[]/float[]/double[]} for passing multiple parameters.
     *
     * @see java.sql.PreparedStatement#execute()
     */
    @SafeVarargs
    public final void execute(final Connection conn, final String sql, final Object... parameters) throws UncheckedSQLException {
        final NamedSQL namedSQL = getNamedSQL(sql);
        final StatementSetter statementSetter = checkStatementSetter(namedSQL, null);
        final JdbcSettings jdbcSettings = checkJdbcSettings(null, namedSQL, _sqlMapper.getAttrs(sql));

        final SQLOperation sqlOperation = JdbcUtil.getSQLOperation(namedSQL.getParameterizedSQL());
        DataSource ds = null;
        Connection localConn = null;
        PreparedStatement stmt = null;

        try {
            ds = getDataSource(namedSQL.getParameterizedSQL(), parameters, jdbcSettings);

            localConn = getConnection(conn, ds, jdbcSettings, sqlOperation);

            stmt = prepareStatement(ds, localConn, namedSQL, statementSetter, jdbcSettings, false, false, parameters);

            stmt.execute();
        } catch (SQLException e) {
            String msg = AbacusException.getErrorMsg(e) + ". [SQL] " + namedSQL.getNamedSQL();
            throw new UncheckedSQLException(msg, e);
        } finally {
            close(stmt);
            close(localConn, conn, ds);
        }
    }

    /**
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction() {
        return beginTransaction(IsolationLevel.DEFAULT);
    }

    /**
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @param isolationLevel
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction(final IsolationLevel isolationLevel) {
        return beginTransaction(isolationLevel, false);
    }

    /**
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @param forUpdateOnly
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction(final boolean forUpdateOnly) {
        return beginTransaction(IsolationLevel.DEFAULT, forUpdateOnly);
    }

    /**
     * Refer to {@code beginTransaction(IsolationLevel, boolean, JdbcSettings)}.
     *
     * @param isolationLevel
     * @param forUpdateOnly
     * @return
     * @see #beginTransaction(IsolationLevel, boolean, JdbcSettings)
     */
    public SQLTransaction beginTransaction(IsolationLevel isolationLevel, boolean forUpdateOnly) {
        return beginTransaction(isolationLevel, forUpdateOnly, null);
    }

    /**
     * If this method is called where a transaction is started by {@code JdbcUtil.beginTransaction} or in {@code Spring} with the same {@code DataSource} in the same thread,
     * the {@code Connection} started the Transaction will be used here.
     * That's to say the transaction started by {@code JdbcUtil.beginTransaction} or in {@code Spring} will have the final control on commit/roll back over the {@code Connection}.
     *

     * Otherwise a {@code Connection} directly from the specified {@code DataSource}(Connection pool) will be borrowed and used.
     *

     * Transactions started by {@code SQLExecutor.beginTransaction} won't be shared by {@code JdbcUtil.beginTransaction} or Spring.
     *
     *

     *

     *
     * The connection opened in the transaction will be automatically closed after the transaction is committed or rolled back.
     * DON'T close it again by calling the close method.
     *

     *

<<<<<<< .working
     * The transaction will be shared cross the instances of {@code SQLExecutor} by the methods called with same {@code DataSource} in the same thread.
||||||| .merge-left.r3730
     * The transaction will be shared cross the instances of {@code SQLExecutor} by the methods called in the same thread with same {@code DataSource}.
=======
     * The transaction will be shared cross the instances of {@code SQLExecutor/JdbcUtil.Dao/CrudDao} by the methods called in the same thread with same {@code DataSource}.
>>>>>>> .merge-right.r3729
     *
     *

     *

     *
     * The general programming way with SQLExeucte is to execute sql scripts(generated by SQLBuilder) with array/list/map/entity by calling (batch)insert/update/delete/query/... methods.
     * if transaction is required, it can be started:
     *
     *

     *
     *   final SQLTransaction tran = sqlExecutor.beginTransaction(IsolationLevel.READ_COMMITTED);
     *   try {
     *       // sqlExecutor.insert(...);
     *       // sqlExecutor.update(...);
     *       // sqlExecutor.query(...);
     *
     *       tran.commit();
     *   } finally {
     *       // The connection will be automatically closed after the transaction is committed or rolled back.
     *       tran.rollbackIfNotCommitted();
     *   }
     *
     *

     *
     *
     * @param isolationLevel
     * @param forUpdateOnly
     * @param jdbcSettings
     * @return
     */
    public SQLTransaction beginTransaction(final IsolationLevel isolationLevel, final boolean forUpdateOnly, final JdbcSettings jdbcSettings) {
        N.checkArgNotNull(isolationLevel, "isolationLevel");

        final IsolationLevel isolation = isolationLevel == IsolationLevel.DEFAULT ? _defaultIsolationLevel : isolationLevel;
        final DataSource ds = jdbcSettings != null && jdbcSettings.getQueryWithDataSource() != null
                ? getDataSource(N.EMPTY_STRING, N.EMPTY_OBJECT_ARRAY, jdbcSettings)
                : _ds;

        SQLTransaction tran = SQLTransaction.getTransaction(ds, CreatedBy.SQL_EXECUTOR);

        if (tran == null) {
            tran = SQLTransaction.getTransaction(ds, CreatedBy.JDBC_UTIL);
        }

        if (tran == null) {
            Connection conn = null;
            boolean noException = false;

            try {
                conn = getConnection(ds);
                tran = new SQLTransaction(ds, conn, isolation, CreatedBy.SQL_EXECUTOR, true);
                tran.incrementAndGetRef(isolation, forUpdateOnly);

                noException = true;
            } catch (SQLException e) {
                throw new UncheckedSQLException(e);
            } finally {
                if (noException == false) {
                    close(conn, ds);
                }
            }

            logger.info("Create a new SQLTransaction(id={})", tran.id());
            SQLTransaction.putTransaction(tran);
        } else {
            logger.info("Reusing the existing SQLTransaction(id={})", tran.id());
            tran.incrementAndGetRef(isolation, forUpdateOnly);
        }

        return tran;
    }

    public DBSequence getDBSequence(final String tableName, final String seqName) {
        return new DBSequence(this, tableName, seqName, 0, 1000);
    }

    /**
     * Supports global sequence by db table.
     *
     * @param tableName
     * @param seqName
     * @param startVal
     * @param seqBufferSize the numbers to allocate/reserve from database table when cached numbers are used up.
     * @return
     */
    public DBSequence getDBSequence(final String tableName, final String seqName, final long startVal, final int seqBufferSize) {
        return new DBSequence(this, tableName, seqName, startVal, seqBufferSize);
    }

    /**
     * Supports global lock by db table
     *
     * @param tableName
     * @return
     */
    public DBLock getDBLock(final String tableName) {
        return new DBLock(this, tableName);
    }

    public boolean doesTableExist(final String tableName) {
        Connection conn = getConnection();

        try {
            return JdbcUtil.doesTableExist(conn, tableName);
        } finally {
            closeConnection(conn);
        }
    }

    /**
     * Returns {@code true} if succeed to create table, otherwise {@code false} is returned.
     *
     * @param tableName
     * @param schema
     * @return
     */
    public boolean createTableIfNotExists(final String tableName, final String schema) {
        Connection conn = getConnection();

        try {
            return JdbcUtil.createTableIfNotExists(conn, tableName, schema);
        } finally {
            closeConnection(conn);
        }
    }

    /**
     * Returns {@code true} if succeed to drop table, otherwise {@code false} is returned.
     *
     * @param tableName
     * @return
     */
    public boolean dropTableIfExists(final String tableName) {
        Connection conn = getConnection();

        try {
            return JdbcUtil.dropTableIfExists(conn, tableName);
        } finally {
            closeConnection(conn);
        }
    }

    /**
     *
     * @param tableName
     * @return
     */
    public List<String> getColumnNameList(final String tableName) {
        List<String> columnNameList = _tableColumnNamePool.get(tableName);

        if (columnNameList == null) {
            Connection conn = getConnection();

            try {
                columnNameList = ImmutableList.of(JdbcUtil.getColumnNameList(conn, tableName));
                _tableColumnNamePool.put(tableName, columnNameList);
            } catch (SQLException e) {
                throw new UncheckedSQLException(e);
            } finally {
                closeConnection(conn);
            }
        }

        return columnNameList;
    }

    public Connection getConnection() {
        return getConnection(_ds);
    }

    public void closeConnection(final Connection conn) {
        close(conn, _ds);
    }

    protected DataSource getDataSource(final String sql, final Object[] parameters, final JdbcSettings jdbcSettings) {
        if (_dsm == null || _dss == null) {
            if ((jdbcSettings != null) && (jdbcSettings.getQueryWithDataSource() != null || N.notNullOrEmpty(jdbcSettings.getQueryWithDataSources()))) {
                throw new IllegalArgumentException(
                        "No data source is available with name: " + (jdbcSettings.getQueryWithDataSource() != null ? jdbcSettings.getQueryWithDataSource()
                                : N.toString(jdbcSettings.getQueryWithDataSources())));
            }

            return _ds;
        } else {
            if ((jdbcSettings == null) || (jdbcSettings.getQueryWithDataSource() == null)) {
                return _dss.select(_dsm, null, sql, parameters, null);
            } else {
                return _dss.select(_dsm, null, sql, parameters, N.asProps(QUERY_WITH_DATA_SOURCE, jdbcSettings.getQueryWithDataSource()));
            }
        }
    }

    protected DataSource getDataSource(final String sql, final List<?> parametersList, final JdbcSettings jdbcSettings) {
        if (_dsm == null || _dss == null) {
            if ((jdbcSettings != null) && (jdbcSettings.getQueryWithDataSource() != null || N.notNullOrEmpty(jdbcSettings.getQueryWithDataSources()))) {
                throw new IllegalArgumentException(
                        "No data source is available with name: " + (jdbcSettings.getQueryWithDataSource() != null ? jdbcSettings.getQueryWithDataSource()
                                : N.toString(jdbcSettings.getQueryWithDataSources())));
            }

            return _ds;
        } else {
            if ((jdbcSettings == null) || (jdbcSettings.getQueryWithDataSource() == null)) {
                return _dss.select(_dsm, null, sql, parametersList, null);
            } else {
                return _dss.select(_dsm, null, sql, parametersList, N.asProps(QUERY_WITH_DATA_SOURCE, jdbcSettings.getQueryWithDataSource()));
            }
        }
    }

    protected Connection getConnection(final Connection inputConn, final DataSource ds, final JdbcSettings jdbcSettings, final SQLOperation op) {
        if (inputConn != null) {
            return inputConn;
        }

        SQLTransaction tran = SQLTransaction.getTransaction(ds, CreatedBy.SQL_EXECUTOR);

        if (tran == null) {
            tran = SQLTransaction.getTransaction(ds, CreatedBy.JDBC_UTIL);
        }

        if (tran != null && (tran.isForUpdateOnly() == false || op != SQLOperation.SELECT)) {
            return tran.connection();
        }

        return getConnection(ds);
    }

    protected Connection getConnection(final DataSource ds) {
        return JdbcUtil.getConnection(ds);
    }

    protected PreparedStatement prepareStatement(final DataSource ds, final Connection localConn, final NamedSQL namedSQL,
            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final boolean autoGeneratedKeys, final boolean isBatch,
            final Object... parameters) throws SQLException {
        String sql = namedSQL.getParameterizedSQL();

        if (ds instanceof com.landawn.abacus.DataSource) {
            final com.landawn.abacus.DataSource ds2 = (com.landawn.abacus.DataSource) ds;

            if (isBatch) {
                sql = ds2.getSliceSelector().select(null, sql, (List<?>) parameters[0], null);
            } else {
                sql = ds2.getSliceSelector().select(null, sql, parameters, null);
            }
        }

        logSQL(sql, jdbcSettings, parameters);

        final PreparedStatement stmt = prepareStatement(localConn, sql, autoGeneratedKeys, jdbcSettings);

        setParameters(namedSQL, stmt, statementSetter, isBatch, parameters);

        return stmt;
    }

    protected PreparedStatement prepareStatement(final Connection conn, String sql, final boolean autoGeneratedKeys, final JdbcSettings jdbcSettings)
            throws SQLException {
        PreparedStatement stmt = null;

        if (jdbcSettings == null) {
            stmt = conn.prepareStatement(sql, autoGeneratedKeys ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS);
        } else {
            if (N.notNullOrEmpty(jdbcSettings.getReturnedColumnIndexes())) {
                //    if (jdbcSettings.getReturnedColumnIndexes().length != 1) {
                //        throw new IllegalArgumentException("only 1 generated key is supported At present");
                //    }

                stmt = conn.prepareStatement(sql, jdbcSettings.getReturnedColumnIndexes());
            } else if (N.notNullOrEmpty(jdbcSettings.getReturnedColumnNames())) {
                //    if (jdbcSettings.getReturnedColumnNames().length != 1) {
                //        throw new IllegalArgumentException("only 1 generated key is supported At present");
                //    }

                stmt = conn.prepareStatement(sql, jdbcSettings.getReturnedColumnNames());
            } else if (jdbcSettings.isAutoGeneratedKeys() || autoGeneratedKeys) {
                stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            } else if ((jdbcSettings.getResultSetType() != -1) || (jdbcSettings.getResultSetConcurrency() != -1)
                    || (jdbcSettings.getResultSetHoldability() != -1)) {
                int resultSetType = (jdbcSettings.getResultSetType() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_TYPE : jdbcSettings.getResultSetType();

                int resultSetConcurrency = (jdbcSettings.getResultSetConcurrency() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_CONCURRENCY
                        : jdbcSettings.getResultSetConcurrency();

                if (jdbcSettings.getResultSetHoldability() != -1) {
                    stmt = conn.prepareStatement(sql, resultSetType, resultSetConcurrency, jdbcSettings.getResultSetHoldability());
                } else {
                    stmt = conn.prepareStatement(sql, resultSetType, resultSetConcurrency);
                }
            } else {
                stmt = conn.prepareStatement(sql);
            }

            if (jdbcSettings.getFetchSize() != -1) {
                stmt.setFetchSize(jdbcSettings.getFetchSize());
            }

            if (jdbcSettings.getMaxRows() != -1) {
                stmt.setMaxRows(jdbcSettings.getMaxRows());
            }

            if (jdbcSettings.getMaxFieldSize() != -1) {
                stmt.setMaxFieldSize(jdbcSettings.getMaxFieldSize());
            }

            if (jdbcSettings.getFetchDirection() != -1) {
                stmt.setFetchDirection(jdbcSettings.getFetchDirection());
            }

            if (jdbcSettings.getQueryTimeout() != -1) {
                stmt.setQueryTimeout(jdbcSettings.getQueryTimeout());
            }
        }

        return stmt;
    }

    //    protected CallableStatement prepareCallableStatement(final DataSource ds, final Connection localConn, final NamedSQL namedSQL,
    //            final StatementSetter statementSetter, final JdbcSettings jdbcSettings, final boolean autoGeneratedKeys, final boolean isBatch,
    //            final Object... parameters) throws SQLException {
    //        String sql = namedSQL.getPureSQL();
    //
    //        if (isBatch) {
    //            sql = ds.getSliceSelector().select(null, sql, (List<?>) parameters[0], null);
    //        } else {
    //            sql = ds.getSliceSelector().select(null, sql, parameters, null);
    //        }
    //
    //        logSQL(sql, jdbcSettings, parameters);
    //
    //        final CallableStatement stmt = prepareCallableStatement(localConn, sql, jdbcSettings);
    //
    //        setParameters(namedSQL, stmt, statementSetter, isBatch, parameters);
    //
    //        return stmt;
    //    }
    //
    //    protected CallableStatement prepareCallableStatement(final Connection conn, String sql, final JdbcSettings jdbcSettings) throws SQLException {
    //        CallableStatement stmt = null;
    //
    //        if (jdbcSettings == null) {
    //            stmt = conn.prepareCall(sql);
    //        } else {
    //            if ((jdbcSettings.getResultSetType() != -1) || (jdbcSettings.getResultSetConcurrency() != -1) || (jdbcSettings.getResultSetHoldability() != -1)) {
    //                int resultSetType = (jdbcSettings.getResultSetType() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_TYPE : jdbcSettings.getResultSetType();
    //
    //                int resultSetConcurrency = (jdbcSettings.getResultSetConcurrency() == -1) ? JdbcSettings.DEFAULT_RESULT_SET_CONCURRENCY
    //                        : jdbcSettings.getResultSetConcurrency();
    //
    //                if (jdbcSettings.getResultSetHoldability() != -1) {
    //                    stmt = conn.prepareCall(sql, resultSetType, resultSetConcurrency, jdbcSettings.getResultSetHoldability());
    //                } else {
    //                    stmt = conn.prepareCall(sql, resultSetType, resultSetConcurrency);
    //                }
    //            } else {
    //                stmt = conn.prepareCall(sql);
    //            }
    //
    //            if (jdbcSettings.getFetchSize() != -1) {
    //                stmt.setFetchSize(jdbcSettings.getFetchSize());
    //            }
    //
    //            if (jdbcSettings.getMaxRows() != -1) {
    //                stmt.setMaxRows(jdbcSettings.getMaxRows());
    //            }
    //
    //            if (jdbcSettings.getMaxFieldSize() != -1) {
    //                stmt.setMaxFieldSize(jdbcSettings.getMaxFieldSize());
    //            }
    //
    //            if (jdbcSettings.getFetchDirection() != -1) {
    //                stmt.setFetchDirection(jdbcSettings.getFetchDirection());
    //            }
    //
    //            if (jdbcSettings.getQueryTimeout() != -1) {
    //                stmt.setQueryTimeout(jdbcSettings.getQueryTimeout());
    //            }
    //        }
    //        return stmt;
    //    }

    protected void setParameters(final NamedSQL namedSQL, final PreparedStatement stmt, final StatementSetter statementSetter, final boolean isBatch,
            final Object... parameters) throws SQLException {
        if (isBatch || N.isNullOrEmpty(parameters)) {
            // ignore
        } else {
            statementSetter.setParameters(namedSQL, stmt, parameters);
        }
    }

    protected void logSQL(String sql, final JdbcSettings jdbcSettings, final Object... parameters) {
        if ((jdbcSettings != null) && (jdbcSettings.isLogSQL() || jdbcSettings.isLogSQLWithParameters()) && logger.isInfoEnabled()) {
            if (jdbcSettings.isLogSQLWithParameters()) {
                logger.info(sql + " {" + StringUtil.join(parameters, ", ") + "}");
            } else {
                logger.info(sql);
            }
        }
    }

    protected void close(final ResultSet rs) {
        JdbcUtil.closeQuietly(rs);
    }

    protected void close(final PreparedStatement stmt) {
        JdbcUtil.closeQuietly(stmt);
    }

    protected void close(final ResultSet rs, final PreparedStatement stmt) {
        JdbcUtil.closeQuietly(rs, stmt);
    }

    protected void close(final Connection localConn, final Connection inputConn, final DataSource ds) {
        if (inputConn == null) {
            SQLTransaction tran = SQLTransaction.getTransaction(ds, CreatedBy.SQL_EXECUTOR);

            if (tran == null) {
                tran = SQLTransaction.getTransaction(ds, CreatedBy.JDBC_UTIL);
            }

            if (tran != null && tran.connection() == localConn) {
                // ignore.
            } else {
                close(localConn, ds);
            }
        }
    }

    protected void close(final Connection conn, final DataSource ds) {
        JdbcUtil.releaseConnection(conn, ds);
    }

    /**
     * Close the underline data source
     */
    @Override
    public void close() throws IOException {
        try {
            if (_ds != null && _ds instanceof com.landawn.abacus.DataSource) {
                final com.landawn.abacus.DataSource ds = (com.landawn.abacus.DataSource) _ds;

                if (ds.isClosed() == false) {
                    ds.close();
                }
            }
        } finally {
            if (_dsm != null && _dsm.isClosed() == false) {
                _dsm.close();
            }
        }
    }

    protected int getBatchSize(final JdbcSettings jdbcSettings) {
        return ((jdbcSettings == null) || (jdbcSettings.getBatchSize() < 0)) ? JdbcSettings.DEFAULT_BATCH_SIZE : jdbcSettings.getBatchSize();
    }

    protected StatementSetter checkStatementSetter(final NamedSQL namedSQL, StatementSetter statementSetter) {
        if (statementSetter == null) {
            statementSetter = StatementSetter.DEFAULT;
        }

        return statementSetter;
    }

    @SuppressWarnings("unchecked")
    protected <T> ResultSetExtractor<T> checkResultSetExtractor(final NamedSQL namedSQL, ResultSetExtractor<T> resultExtractor) {
        if (resultExtractor == null) {
            resultExtractor = (ResultSetExtractor<T>) ResultExtractor.DATA_SET;
        }

        return resultExtractor;
    }

    protected JdbcSettings checkJdbcSettings(final JdbcSettings jdbcSettings, final NamedSQL namedSQL, final Map<String, String> attrs) {
        JdbcSettings newJdbcSettings = null;

        if (jdbcSettings == null) {
            newJdbcSettings = setJdbcSettingsForNamedSQL(_jdbcSettings, namedSQL, attrs);
        } else {
            newJdbcSettings = setJdbcSettingsForNamedSQL(jdbcSettings, namedSQL, attrs);
        }

        if ((newJdbcSettings.getOffset() < 0) || (newJdbcSettings.getCount() < 0)) {
            throw new IllegalArgumentException("offset or count can't be less than 0: " + newJdbcSettings.getOffset() + ", " + newJdbcSettings.getCount());
        }

        return newJdbcSettings;
    }

    protected JdbcSettings setJdbcSettingsForNamedSQL(JdbcSettings jdbcSettings, final NamedSQL namedSQL, final Map<String, String> attrs) {
        if ((namedSQL == null) || N.isNullOrEmpty(attrs)) {
            return jdbcSettings;
        } else {
            jdbcSettings = jdbcSettings.copy();

            String attr = attrs.get(SQLMapper.BATCH_SIZE);
            if (attr != null) {
                jdbcSettings.setBatchSize(N.parseInt(attr));
            }

            attr = attrs.get(SQLMapper.FETCH_SIZE);
            if (attr != null) {
                jdbcSettings.setFetchSize(N.parseInt(attr));
            }

            attr = attrs.get(SQLMapper.RESULT_SET_TYPE);
            if (attr != null) {
                Integer resultSetType = SQLMapper.RESULT_SET_TYPE_MAP.get(attr);

                if (resultSetType == null) {
                    throw new IllegalArgumentException("Result set type: '" + attr + "' is not supported");
                }

                jdbcSettings.setResultSetType(resultSetType);
            }

            attr = attrs.get(SQLMapper.TIMEOUT);
            if (attr != null) {
                jdbcSettings.setQueryTimeout(N.parseInt(attr));
            }

            return jdbcSettings;
        }
    }

    protected NamedSQL getNamedSQL(final String sql) {
        N.checkArgNotNull(sql, "sql");

        NamedSQL namedSQL = null;

        if (_sqlMapper != null) {
            namedSQL = _sqlMapper.get(sql);
        }

        if (namedSQL == null) {
            namedSQL = NamedSQL.parse(sql);
        }

        return namedSQL;
    }

    /**
     *
     * @param sql should be prepared sql because it will be cached.
     * @param rs
     * @return
     * @throws SQLException
     */
    protected static List<String> getColumnLabelList(final String sql, final ResultSet rs) throws SQLException {
        List<String> labelList = N.notNullOrEmpty(sql) ? _sqlColumnLabelPool.get(sql) : null;

        if (labelList == null) {
            labelList = ImmutableList.of(JdbcUtil.getColumnLabelList(rs));

            if (N.notNullOrEmpty(sql) && sql.length() <= CACHED_SQL_LENGTH) {
                if (_sqlColumnLabelPool.size() >= SQL_CACHE_SIZE) {
                    final List<String> tmp = new ArrayList<>(_sqlColumnLabelPool.keySet());
                    Maps.removeKeys(_sqlColumnLabelPool, tmp.subList(0, (int) (tmp.size() * 0.25)));
                }

                _sqlColumnLabelPool.put(sql, labelList);
            }
        }

        return labelList;
    }

    protected static boolean isEntityOrMapParameter(final NamedSQL namedSQL, final Object... parameters) {
        if (N.isNullOrEmpty(namedSQL.getNamedParameters())) {
            return false;
        }

        if (N.isNullOrEmpty(parameters) || (parameters.length != 1) || (parameters[0] == null)) {
            return false;
        }

        if (parameters[0] instanceof Map || (ClassUtil.isEntity(parameters[0].getClass()))) {
            return true;
        }

        return false;
    }

    /**
     *
     * @param <T>
     * @param <ID>
     *
     * @see {@link com.landawn.abacus.annotation.ReadOnly}
     * @see {@link com.landawn.abacus.annotation.ReadOnlyId}
     * @see {@link com.landawn.abacus.annotation.NonUpdatable}
     * @see {@link com.landawn.abacus.annotation.Transient}
     * @see {@link com.landawn.abacus.annotation.Table}
     * @see {@link com.landawn.abacus.annotation.Column}
     *
     * @see http://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html
     * @see http://docs.oracle.com/javase/8/docs/api/java/sql/Statement.html
     * @see http://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html
     * @see http://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html
     */
    public static final class Mapper<T, ID> {

        static final List<String> EXISTS_SELECT_PROP_NAMES = ImmutableList.of(NSC._1);
        static final List<String> COUNT_SELECT_PROP_NAMES = ImmutableList.of(NSC.COUNT_ALL);

        private final Class<T> targetClass;
        private final Type<T> targetType;
        private final List<String> propNameList;
        private final Set<String> propNameSet;
        private final List<String> defaultSelectPropNameList;
        private final String idPropName;
        private final List<String> idPropNameList;
        private final Set<String> idPropNameSet;
        private final SQLExecutor sqlExecutor;
        private final NamingPolicy namingPolicy;
        private final Condition idCond;
        private final String sql_exists_by_id;
        private final String sql_get_by_id;
        private final String sql_insert_with_id;
        private final String sql_insert_without_id;
        private final String sql_update_by_id;
        private final String sql_delete_by_id;
        private final AsyncMapper<T, ID> asyncMapper;

        // TODO cache more sqls to improve performance.

        Mapper(final Class<T> targetClass, final SQLExecutor sqlExecutor, final NamingPolicy namingPolicy) {
            this.sqlExecutor = sqlExecutor;
            this.namingPolicy = namingPolicy;

            @SuppressWarnings("deprecation")
            final List<String> idPropNames = ClassUtil.getIdFieldNames(targetClass, false);

            N.checkArgNotNullOrEmpty(idPropNames, "Target class: " + ClassUtil.getCanonicalClassName(targetClass)
                    + " must at least has one id property annotated by @Id or @ReadOnlyId on field or class");

            //    N.checkArgument(idPropNames.size() == 1, "Only one id is supported at present. But Entity class {} has {} ids: {}", targetClass, idPropNames.size(),
            //            idPropNames);

            this.targetClass = targetClass;
            this.targetType = N.typeOf(targetClass);
            this.propNameList = ImmutableList.copyOf(ClassUtil.getPropGetMethodList(targetClass).keySet());
            this.propNameSet = ImmutableSet.of(N.newLinkedHashSet(ClassUtil.getPropGetMethodList(targetClass).keySet()));

            this.idPropName = idPropNames.get(0);
            this.idPropNameList = ImmutableList.copyOf(idPropNames);
            this.idPropNameSet = ImmutableSet.copyOf(idPropNames);
            this.defaultSelectPropNameList = ImmutableList.copyOf(SQLBuilder.getSelectPropNamesByClass(targetClass, false, null));

            Condition cond = null;

            if (idPropNameList.size() == 1) {
                cond = CF.eq(idPropName);
            } else {
                final And and = CF.and();

                for (String idName : idPropNameList) {
                    and.add(CF.eq(idName));
                }

                cond = and;
            }

            this.idCond = cond;

            this.sql_exists_by_id = this.prepareQuery(SQLBuilder._1_list, idCond).sql;
            this.sql_get_by_id = this.prepareQuery(defaultSelectPropNameList, idCond).sql;
            this.sql_insert_with_id = this.prepareInsertSql(SQLBuilder.getInsertPropNamesByClass(targetClass, null));
            this.sql_insert_without_id = this.prepareInsertSql(SQLBuilder.getInsertPropNamesByClass(targetClass, idPropNameSet));
            this.sql_update_by_id = this.prepareUpdateSql(SQLBuilder.getUpdatePropNamesByClass(targetClass, idPropNameSet));
            this.sql_delete_by_id = this.prepareDelete(idCond).sql;

            this.asyncMapper = new AsyncMapper<T, ID>(this, sqlExecutor._asyncExecutor);
        }

        Class<T> targetClass() {
            return targetClass;
        }

        Type<T> targetType() {
            return targetType;
        }

        List<String> idPropNameList() {
            return idPropNameList;
        }

        Set<String> idPropNameSet() {
            return idPropNameSet;
        }

        List<String> propNameList() {
            return propNameList;
        }

        Set<String> propNameSet() {
            return propNameSet;
        }

        public AsyncMapper<T, ID> async() {
            return asyncMapper;
        }

        /**
         *
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @return
         */
        public boolean exists(final ID id) {
            checkId(id);

            return sqlExecutor.queryForInt(sql_exists_by_id, id).orElse(0) > 0;
        }

        public boolean exists(final Condition whereCause) {
            return exists(null, whereCause);
        }

        public boolean exists(final Connection conn, final ID id) {
            checkId(id);

            return sqlExecutor.queryForSingleResult(int.class, conn, sql_exists_by_id, id).orElse(0) > 0;
        }

        public boolean exists(final Connection conn, final Condition whereCause) {
            final SP sp = prepareQuery(EXISTS_SELECT_PROP_NAMES, whereCause, 1);

            return sqlExecutor.exists(conn, sp.sql, sp.parameters.toArray());
        }

        public int count(final Condition whereCause) {
            return count(null, whereCause);
        }

        public int count(final Connection conn, final Condition whereCause) {
            final SP sp = prepareQuery(COUNT_SELECT_PROP_NAMES, whereCause);

            return sqlExecutor.count(conn, sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public Optional<T> get(final ID id) throws DuplicatedResultException {
            return Optional.ofNullable(gett(id));
        }

        //    /**
        //     *
        //     * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
        //     * @param selectPropNames
        //     * @return
        //     * @deprecated replaced by {@code get(id, Arrays.asList(selectPropNames)}
        //     */
        //    @Deprecated
        //    @SafeVarargs
        //    public final Optional<T> get(final ID id, final String... selectPropNames) {
        //        return Optional.ofNullable(gett(id, selectPropNames));
        //    }

        /**
         *
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @param selectPropNames
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public Optional<T> get(final ID id, final Collection<String> selectPropNames) throws DuplicatedResultException {
            return Optional.ofNullable(gett(id, selectPropNames));
        }

        /**
         *
         * @param conn
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @param selectPropNames
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public Optional<T> get(final Connection conn, final ID id, final Collection<String> selectPropNames) throws DuplicatedResultException {
            return Optional.ofNullable(gett(conn, id, selectPropNames));
        }

        /**
         *
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public T gett(final ID id) throws DuplicatedResultException {
            return gett(id, (Collection<String>) null);
        }

        //    /**
        //     *
        //     * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
        //     * @param selectPropNames
        //     * @return
        //     * @deprecated replaced by {@code gett(id, Arrays.asList(selectPropNames)}
        //     */
        //    @Deprecated
        //    @SafeVarargs
        //    public final T gett(final ID id, final String... selectPropNames) {
        //        return gett(id, Arrays.asList(selectPropNames));
        //    }

        /**
         *
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @param selectPropNames
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public T gett(final ID id, final Collection<String> selectPropNames) throws DuplicatedResultException {
            return gett(null, id, selectPropNames);
        }

        /**
         *
         * @param conn
         * @param id which could be {@code Number}/{@code String}... or {@code Entity}/{@code Map} for composed id.
         * @param selectPropNames
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public T gett(final Connection conn, final ID id, final Collection<String> selectPropNames) throws DuplicatedResultException {
            checkId(id);

            if (N.isNullOrEmpty(selectPropNames)) {
                return sqlExecutor.gett(targetClass, conn, sql_get_by_id, id);
            } else {
                final SP sp = prepareQuery(selectPropNames, idCond);
                return sqlExecutor.gett(targetClass, conn, sp.sql, id);
            }
        }

        /**
         *
         * @param ids
         * @return
         */
        public List<T> batchGet(final List<ID> ids) {
            return batchGet(ids, (Collection<String>) null);
        }

        /**
         *
         * @param ids
         * @param selectPropNames
         * @return
         */
        public List<T> batchGet(final List<ID> ids, final Collection<String> selectPropNames) {
            return batchGet(ids, selectPropNames, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        /**
         *
         * @param ids
         * @param selectPropNames
         * @param batchSize
         * @return
         */
        public List<T> batchGet(final List<ID> ids, final Collection<String> selectPropNames, final int batchSize) {
            return batchGet(null, ids, selectPropNames, batchSize);
        }

        /**
         *
         * @param conn
         * @param ids
         * @param selectPropNames
         * @param batchSize
         * @return
         */
        public List<T> batchGet(final Connection conn, final List<ID> ids, final Collection<String> selectPropNames, final int batchSize) {
            N.checkArgPositive(batchSize, "batchSize");

            if (N.isNullOrEmpty(ids)) {
                return new ArrayList<>();
            }

            N.checkArgument(idPropNameList.size() > 1 || !(ids.get(0) instanceof Map || isEntity(ids.get(0))),
                    "Input 'ids' can not be Maps or entities for single id ");

            final List<T> entities = new ArrayList<>(ids.size());

            if (idPropNameList.size() == 1) {
                String sql = prepareQuery(selectPropNames, idCond).sql;
                sql = sql.substring(0, sql.lastIndexOf('=')) + "IN ";

                if (ids.size() >= batchSize) {
                    final Joiner joiner = Joiner.with(", ", "(", ")").reuseCachedBuffer(true);

                    for (int i = 0; i < batchSize; i++) {
                        joiner.append('?');
                    }

                    String inSQL = sql + joiner.toString();

                    for (int i = 0, to = ids.size() - batchSize; i <= to; i += batchSize) {
                        entities.addAll(sqlExecutor.list(targetClass, conn, inSQL, null, null, ids.subList(i, i + batchSize).toArray()));
                    }
                }

                if (ids.size() % batchSize != 0) {
                    final int remaining = ids.size() % batchSize;
                    final Joiner joiner = Joiner.with(", ", "(", ")").reuseCachedBuffer(true);

                    for (int i = 0; i < remaining; i++) {
                        joiner.append('?');
                    }

                    String inSQL = sql + joiner.toString();
                    entities.addAll(sqlExecutor.list(targetClass, conn, inSQL, null, null, ids.subList(ids.size() - remaining, ids.size()).toArray()));
                }

            } else {
                final boolean isMap = ids.get(0) instanceof Map;

                if (ids.size() >= batchSize) {
                    for (int i = 0, to = ids.size() - batchSize; i <= to; i += batchSize) {
                        if (isMap) {
                            entities.addAll(list(CF.eqAndOr((List<Map<String, ?>>) ids.subList(i, i + batchSize))));
                        } else {
                            entities.addAll(list(CF.eqAndOr(ids.subList(i, i + batchSize), idPropNameList)));
                        }
                    }
                }

                if (ids.size() % batchSize != 0) {
                    final int remaining = ids.size() % batchSize;

                    if (isMap) {
                        entities.addAll(list(CF.eqAndOr((List<Map<String, ?>>) ids.subList(ids.size() - remaining, ids.size()))));
                    } else {
                        entities.addAll(list(CF.eqAndOr(ids.subList(ids.size() - remaining, ids.size()), idPropNameList)));
                    }
                }
            }

            return entities;
        }

        public Optional<T> findFirst(final Condition whereCause) {
            return findFirst((Collection<String>) null, whereCause);
        }

        public Optional<T> findFirst(final Collection<String> selectPropNames, final Condition whereCause) {
            return findFirst(selectPropNames, whereCause, null);
        }

        public Optional<T> findFirst(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return findFirst(null, selectPropNames, whereCause, jdbcSettings);
        }

        public Optional<T> findFirst(final Connection conn, final Condition whereCause) {
            return findFirst(conn, null, whereCause);
        }

        public Optional<T> findFirst(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause) {
            return findFirst(conn, selectPropNames, whereCause, null);
        }

        public Optional<T> findFirst(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.findFirst(targetClass, conn, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        public <R> Optional<R> findFirst(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return findFirst(selectPropName, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return findFirst(null, selectPropName, rowMapper, whereCause, jdbcSettings);
        }

        public <R> Optional<R> findFirst(final Connection conn, String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return findFirst(conn, selectPropName, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(final Connection conn, String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return findFirst(conn, Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        public <R> Optional<R> findFirst(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return findFirst(selectPropName, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return findFirst(null, selectPropName, rowMapper, whereCause, jdbcSettings);
        }

        public <R> Optional<R> findFirst(final Connection conn, String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return findFirst(conn, selectPropName, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(final Connection conn, String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return findFirst(conn, Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        public <R> Optional<R> findFirst(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return findFirst(selectPropNames, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return findFirst(null, selectPropNames, rowMapper, whereCause, jdbcSettings);
        }

        public <R> Optional<R> findFirst(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return findFirst(conn, selectPropNames, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.findFirst(conn, sp.sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, sp.parameters.toArray());
        }

        public <R> Optional<R> findFirst(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return findFirst(selectPropNames, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return findFirst(null, selectPropNames, rowMapper, whereCause, jdbcSettings);
        }

        public <R> Optional<R> findFirst(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return findFirst(conn, selectPropNames, rowMapper, whereCause, null);
        }

        public <R> Optional<R> findFirst(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            final ResultExtractor<R> resultExtractor = new ResultExtractor<R>() {
                @Override
                public R extractData(ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    int offset = jdbcSettings.getOffset();

                    if (offset > 0) {
                        JdbcUtil.skip(rs, offset);
                    }

                    return rs.next() ? Objects.requireNonNull(rowMapper.apply(rs, JdbcUtil.getColumnLabelList(rs))) : null;
                }
            };

            return Optional.ofNullable(sqlExecutor.query(conn, sp.sql, StatementSetter.DEFAULT, resultExtractor, jdbcSettings, sp.parameters.toArray()));
        }

        public List<T> list(final Condition whereCause) {
            return list((Collection<String>) null, whereCause);
        }

        public List<T> list(final Collection<String> selectPropNames, final Condition whereCause) {
            return list(selectPropNames, whereCause, null);
        }

        public List<T> list(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return list(null, selectPropNames, whereCause, jdbcSettings);
        }

        public List<T> list(final Connection conn, final Condition whereCause) {
            return list(conn, null, whereCause);
        }

        public List<T> list(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause) {
            return list(conn, selectPropNames, whereCause, null);
        }

        public List<T> list(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.list(targetClass, conn, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        public <R> List<R> list(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return list(selectPropName, rowMapper, whereCause, null);
        }

        public <R> List<R> list(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return list(null, selectPropName, rowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(final Connection conn, String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return list(conn, selectPropName, rowMapper, whereCause, null);
        }

        public <R> List<R> list(final Connection conn, String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return list(conn, Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return list(selectPropName, rowMapper, whereCause, null);
        }

        public <R> List<R> list(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return list(null, selectPropName, rowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(final Connection conn, String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return list(conn, selectPropName, rowMapper, whereCause, null);
        }

        public <R> List<R> list(final Connection conn, String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return list(conn, Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return list(selectPropNames, rowMapper, whereCause, null);
        }

        public <R> List<R> list(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return list(null, selectPropNames, rowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return list(conn, selectPropNames, rowMapper, whereCause, null);
        }

        public <R> List<R> list(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            N.checkArgNotNull(rowMapper);

            final JdbcUtil.BiRowMapper<R> biRowMapper = new JdbcUtil.BiRowMapper<R>() {
                @Override
                public R apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
                    return rowMapper.apply(rs);
                }
            };

            return list(conn, selectPropNames, biRowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return list(selectPropNames, rowMapper, whereCause, null);
        }

        public <R> List<R> list(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return list(null, selectPropNames, rowMapper, whereCause, jdbcSettings);
        }

        public <R> List<R> list(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return list(conn, selectPropNames, rowMapper, whereCause, null);
        }

        public <R> List<R> list(final Connection conn, Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.list(conn, sp.sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see SQLExecutor#listAll(Class, String, StatementSetter, JdbcSettings, Object...)
         */
        public List<T> listAll(final Condition whereCause, final JdbcSettings jdbcSettings) {
            return listAll(null, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see SQLExecutor#listAll(Class, String, StatementSetter, JdbcSettings, Object...)
         */
        public List<T> listAll(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.listAll(targetClass, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> List<R> listAll(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return listAll(Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> List<R> listAll(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return listAll(Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> List<R> listAll(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            N.checkArgNotNull(rowMapper);

            final JdbcUtil.BiRowMapper<R> biRowMapper = new JdbcUtil.BiRowMapper<R>() {
                @Override
                public R apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
                    return rowMapper.apply(rs);
                }
            };

            return listAll(selectPropNames, biRowMapper, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> List<R> listAll(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.listAll(sp.sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param whereCause
         * @return
         */
        public Stream<T> stream(final Condition whereCause) {
            return stream((Collection<String>) null, whereCause);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropNames
         * @param whereCause
         * @return
         */
        public Stream<T> stream(final Collection<String> selectPropNames, final Condition whereCause) {
            return stream(selectPropNames, whereCause, null);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropNames
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public Stream<T> stream(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.stream(targetClass, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @return
         */
        public <R> Stream<R> stream(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return stream(selectPropName, rowMapper, whereCause, null);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> stream(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return stream(Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @return
         */
        public <R> Stream<R> stream(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return stream(selectPropName, rowMapper, whereCause, null);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> stream(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return stream(Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @return
         */
        public <R> Stream<R> stream(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return stream(selectPropNames, rowMapper, whereCause, null);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> stream(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            N.checkArgNotNull(rowMapper);

            final JdbcUtil.BiRowMapper<R> biRowMapper = new JdbcUtil.BiRowMapper<R>() {
                @Override
                public R apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
                    return rowMapper.apply(rs);
                }
            };

            return stream(selectPropNames, biRowMapper, whereCause, jdbcSettings);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @return
         */
        public <R> Stream<R> stream(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return stream(selectPropNames, rowMapper, whereCause, null);
        }

        /**
         * Lazy execution, lazy fetch. The query execution and record fetching only happen when a terminal operation of the stream is called.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> stream(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.stream(sp.sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see SQLExecutor#streamAll(Class, String, StatementSetter, JdbcSettings, Object...)
         */
        public Stream<T> streamAll(final Condition whereCause, final JdbcSettings jdbcSettings) {
            return streamAll(null, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see SQLExecutor#streamAll(Class, String, StatementSetter, JdbcSettings, Object...)
         */
        public Stream<T> streamAll(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.streamAll(targetClass, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> streamAll(String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return streamAll(Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropName
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> streamAll(String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return streamAll(Arrays.asList(selectPropName), rowMapper, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> streamAll(Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            N.checkArgNotNull(rowMapper);

            final JdbcUtil.BiRowMapper<R> biRowMapper = new JdbcUtil.BiRowMapper<R>() {
                @Override
                public R apply(final ResultSet rs, final List<String> columnLabels) throws SQLException {
                    return rowMapper.apply(rs);
                }
            };

            return streamAll(selectPropNames, biRowMapper, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param rowMapper
         * @param whereCause
         * @param jdbcSettings
         * @return
         */
        public <R> Stream<R> streamAll(Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.streamAll(sp.sql, StatementSetter.DEFAULT, rowMapper, jdbcSettings, sp.parameters.toArray());
        }

        public DataSet query(final Condition whereCause) {
            return query((Collection<String>) null, whereCause);
        }

        public DataSet query(final Collection<String> selectPropNames, final Condition whereCause) {
            return query(selectPropNames, whereCause, null);
        }

        public DataSet query(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return query(null, selectPropNames, whereCause, jdbcSettings);
        }

        public DataSet query(final Connection conn, final Condition whereCause) {
            return query(conn, null, whereCause);
        }

        public DataSet query(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause) {
            return query(conn, selectPropNames, whereCause, null);
        }

        public DataSet query(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.query(conn, sp.sql, StatementSetter.DEFAULT, null, jdbcSettings, sp.parameters.toArray());
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see SQLExecutor#queryAll(String, StatementSetter, JdbcSettings, Object...)
         */
        public DataSet queryAll(final Condition whereCause, final JdbcSettings jdbcSettings) {
            return queryAll(null, whereCause, jdbcSettings);
        }

        /**
         * Execute the query in one or more data sources specified by {@code jdbcSettings} and merge the results.
         * It's designed for partition.
         *
         * @param selectPropNames
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see SQLExecutor#queryAll(String, StatementSetter, JdbcSettings, Object...)
         */
        public DataSet queryAll(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(selectPropNames, whereCause);

            return sqlExecutor.queryAll(sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalBoolean queryForBoolean(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForBoolean(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalChar queryForChar(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForChar(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalByte queryForByte(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForByte(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalShort queryForShort(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForShort(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalInt queryForInt(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForInt(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalLong queryForLong(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForLong(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalFloat queryForFloat(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForFloat(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public OptionalDouble queryForDouble(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForDouble(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public Nullable<BigDecimal> queryForBigDecimal(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForBigDecimal(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public Nullable<String> queryForString(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForString(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public Nullable<java.sql.Date> queryForDate(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForDate(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public Nullable<java.sql.Time> queryForTime(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForTime(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public Nullable<java.sql.Timestamp> queryForTimestamp(final String selectPropName, final Condition whereCause) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForTimestamp(sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param targetValueClass
         * @param selectPropName
         * @param id
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public <V> Nullable<V> queryForSingleResult(final Class<V> targetValueClass, final String selectPropName, final ID id) {
            return queryForSingleResult(targetValueClass, selectPropName, id2Cond(id, false));
        }

        /**
         *
         * @param targetValueClass
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public <V> Nullable<V> queryForSingleResult(final Class<V> targetValueClass, final String selectPropName, final Condition whereCause) {
            return queryForSingleResult(targetValueClass, selectPropName, whereCause, null);
        }

        /**
         *
         * @param targetValueClass
         * @param selectPropName
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public <V> Nullable<V> queryForSingleResult(final Class<V> targetValueClass, final String selectPropName, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return queryForSingleResult(targetValueClass, null, selectPropName, whereCause, jdbcSettings);
        }

        /**
         *
         * @param targetValueClass
         * @param conn
         * @param selectPropName
         * @param id
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public <V> Nullable<V> queryForSingleResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName, final ID id) {
            return queryForSingleResult(targetValueClass, conn, selectPropName, id2Cond(id, false));
        }

        /**
         *
         * @param targetValueClass
         * @param conn
         * @param selectPropName
         * @param whereCause
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public <V> Nullable<V> queryForSingleResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause) {
            return queryForSingleResult(targetValueClass, conn, selectPropName, whereCause, null);
        }

        /**
         * Returns a {@code Nullable} describing the value in the first row/column if it exists, otherwise return an empty {@code Nullable}.
         *
         * @param targetValueClass
         * @param conn
         * @param selectPropName
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @see Mapper#queryForSingleResult(Class, Connection, String, Condition, JdbcSettings)
         */
        public <V> Nullable<V> queryForSingleResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForSingleResult(targetValueClass, conn, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        /**
         *
         * @param targetValueClass
         * @param selectPropName
         * @param id
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public <V> Nullable<V> queryForUniqueResult(final Class<V> targetValueClass, final String selectPropName, final ID id)
                throws DuplicatedResultException {
            return queryForUniqueResult(targetValueClass, selectPropName, id2Cond(id, false));
        }

        /**
         *
         * @param targetValueClass
         * @param selectPropName
         * @param whereCause
         * @return
         * @throws DuplicatedResultException
         */
        public <V> Nullable<V> queryForUniqueResult(final Class<V> targetValueClass, final String selectPropName, final Condition whereCause)
                throws DuplicatedResultException {
            return queryForUniqueResult(targetValueClass, selectPropName, whereCause, null);
        }

        /**
         *
         * @param targetValueClass
         * @param selectPropName
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public <V> Nullable<V> queryForUniqueResult(final Class<V> targetValueClass, final String selectPropName, final Condition whereCause,
                final JdbcSettings jdbcSettings) throws DuplicatedResultException {
            return queryForUniqueResult(targetValueClass, null, selectPropName, whereCause, jdbcSettings);
        }

        /**
         *
         * @param targetValueClass
         * @param conn
         * @param selectPropName
         * @param id
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public <V> Nullable<V> queryForUniqueResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName, final ID id)
                throws DuplicatedResultException {
            return queryForUniqueResult(targetValueClass, conn, selectPropName, id2Cond(id, false));
        }

        /**
         *
         * @param targetValueClass
         * @param conn
         * @param selectPropName
         * @param whereCause
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public <V> Nullable<V> queryForUniqueResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause) throws DuplicatedResultException {
            return queryForUniqueResult(targetValueClass, conn, selectPropName, whereCause, null);
        }

        /**
         *
         * @param targetValueClass
         * @param conn
         * @param selectPropName
         * @param whereCause
         * @param jdbcSettings
         * @return
         * @throws DuplicatedResultException if two or more records are found.
         */
        public <V> Nullable<V> queryForUniqueResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause, final JdbcSettings jdbcSettings) throws DuplicatedResultException {
            final SP sp = prepareQuery(Arrays.asList(selectPropName), whereCause, 1);

            return sqlExecutor.queryForUniqueResult(targetValueClass, conn, sp.sql, StatementSetter.DEFAULT, jdbcSettings, sp.parameters.toArray());
        }

        private SP prepareQuery(final Collection<String> selectPropNames, final Condition whereCause) {
            return prepareQuery(selectPropNames, whereCause, 0);
        }

        private SP prepareQuery(Collection<String> selectPropNames, final Condition whereCause, final int count) {
            if (N.isNullOrEmpty(selectPropNames)) {
                selectPropNames = defaultSelectPropNameList;
            }

            SQLBuilder sqlBuilder = null;

            switch (namingPolicy) {
                case LOWER_CASE_WITH_UNDERSCORE:
                    if (N.isNullOrEmpty(selectPropNames)) {
                        sqlBuilder = NSC.selectFrom(targetClass).where(whereCause);
                    } else {
                        sqlBuilder = NSC.select(selectPropNames).from(targetClass).where(whereCause);
                    }

                    break;

                case UPPER_CASE_WITH_UNDERSCORE:
                    if (N.isNullOrEmpty(selectPropNames)) {
                        sqlBuilder = NAC.selectFrom(targetClass).where(whereCause);
                    } else {
                        sqlBuilder = NAC.select(selectPropNames).from(targetClass).where(whereCause);
                    }

                    break;

                case LOWER_CAMEL_CASE:
                    if (N.isNullOrEmpty(selectPropNames)) {
                        sqlBuilder = NLC.selectFrom(targetClass).where(whereCause);
                    } else {
                        sqlBuilder = NLC.select(selectPropNames).from(targetClass).where(whereCause);
                    }

                    break;

                default:
                    throw new RuntimeException("Unsupported naming policy: " + namingPolicy);
            }

            if (count > 0 && count < Integer.MAX_VALUE) {
                switch (sqlExecutor.dbVersion()) {
                    case ORACLE:
                    case SQL_SERVER:
                        // Do nothing because limit is not supported.

                        break;

                    default:
                        sqlBuilder.limit(count);
                }
            }

            return sqlBuilder.pair();
        }

        /**
         * Insert the specified entity into data store, and set back the auto-generated id to the specified entity if there is the auto-generated id.
         *
         * @param entity
         * @return the auto-generated id or null if there is no auto-generated id.
         */
        public ID insert(final T entity) {
            return insert(null, entity);
        }

        public ID insert(final T entity, final Collection<String> propNamesToInsert) {
            return insert(null, entity, propNamesToInsert);
        }

        /**
         *
         * @param props
         * @return the auto-generated id or null if there is no auto-generated id.
         */
        public ID insert(final Map<String, Object> props) {
            return insert(null, props);
        }

        public ID insert(final Connection conn, final T entity) {
            return insert(conn, entity, null);
        }

        public ID insert(final Connection conn, final T entity, final Collection<String> propNamesToInsert) {
            N.checkArgNotNull(entity);

            final String sql = prepareInsertSql(entity, propNamesToInsert);

            return sqlExecutor.insert(conn, sql, entity);
        }

        /**
         * @param conn
         * @param props
         * @return the auto-generated id or null if there is no auto-generated id.
         */
        public ID insert(final Connection conn, final Map<String, Object> props) {
            N.checkArgNotNull(props);

            final String sql = prepareInsertSql(props);

            return sqlExecutor.insert(conn, sql, props);
        }

        public List<ID> batchInsert(final Collection<? extends T> entities) {
            return batchInsert(entities, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        public List<ID> batchInsert(final Collection<? extends T> entities, final int batchSize) {
            return batchInsert(entities, batchSize, IsolationLevel.DEFAULT);
        }

        public List<ID> batchInsert(final Collection<? extends T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            return batchInsert(entities, null, batchSize, isolationLevel);
        }

        public List<ID> batchInsert(final Collection<? extends T> entities, final Collection<String> propNamesToInsert, final int batchSize,
                final IsolationLevel isolationLevel) {
            return batchInsert(null, entities, propNamesToInsert, batchSize, isolationLevel);
        }

        public List<ID> batchInsert(final Connection conn, final Collection<? extends T> entities) {
            return batchInsert(conn, entities, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        public List<ID> batchInsert(final Connection conn, final Collection<? extends T> entities, final int batchSize) {
            return batchInsert(conn, entities, null, batchSize);
        }

        /**
         * Insert All the records by batch operation. And set back auto-generated ids to the specified entities if there are the auto-generated ids.
         *
         * @param conn
         * @param entities which must have the same properties set for insertion.
         * @param batchSize Default value is 200.
         * @param entities
         * @param propNamesToInsert
         * @param batchSize
         * @return the auto-generated id list or an empty list if there is no auto-generated id.
         */
        public List<ID> batchInsert(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToInsert,
                final int batchSize) {
            return batchInsert(conn, entities, propNamesToInsert, batchSize, IsolationLevel.DEFAULT);
        }

        @SuppressWarnings("deprecation")
        private List<ID> batchInsert(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToInsert,
                final int batchSize, final IsolationLevel isolationLevel) {
            N.checkArgPositive(batchSize, "batchSize");

            if (N.isNullOrEmpty(entities)) {
                return new ArrayList<>();
            }

            final T entity = N.firstOrNullIfEmpty(entities);
            Collection<String> insertingPropNames = null;

            if (N.isNullOrEmpty(propNamesToInsert) && N.isDirtyMarker(entity.getClass())) {
                insertingPropNames = new HashSet<>();

                for (T e : entities) {
                    insertingPropNames.addAll(((DirtyMarker) e).signedPropNames());
                }
            } else {
                final boolean isDefaultIdPropValue = JdbcUtil.isDefaultIdPropValue(ClassUtil.getPropValue(entity, idPropName));

                for (String idPropName : idPropNameList) {
                    for (T e : entities) {
                        if (isDefaultIdPropValue != JdbcUtil.isDefaultIdPropValue(ClassUtil.getPropValue(e, idPropName))) {
                            throw new IllegalArgumentException(
                                    "Inconsistent id properties initialiaztion. Id properties are set in some entities, but not in others");
                        }
                    }
                }

                insertingPropNames = propNamesToInsert;
            }

            final String sql = prepareInsertSql(entity, insertingPropNames);
            final JdbcSettings jdbcSettings = JdbcSettings.create().setBatchSize(batchSize).setIsolationLevel(isolationLevel);
            final List<?> parametersList = entities instanceof List ? (List<?>) entities : new ArrayList<>(entities);

            return sqlExecutor.batchInsert(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);
        }

        @SuppressWarnings("deprecation")
        private String prepareInsertSql(final T entity, final Collection<String> propNamesToInsert) {
            checkEntity(entity);

            final boolean isDirtyMarkerEntity = ClassUtil.isDirtyMarker(entity.getClass());
            Collection<String> insertingPropNames = propNamesToInsert;

            if (N.isNullOrEmpty(propNamesToInsert)) {
                if (isDirtyMarkerEntity) {
                    insertingPropNames = ((DirtyMarker) entity).signedPropNames();
                } else {
                    boolean isDefaultIdPropValue = true;

                    for (String idPropName : idPropNameList) {
                        if (!JdbcUtil.isDefaultIdPropValue(ClassUtil.getPropValue(entity, idPropName))) {
                            isDefaultIdPropValue = false;
                            break;
                        }
                    }

                    return isDefaultIdPropValue ? sql_insert_without_id : sql_insert_with_id;
                }
            }

            return prepareInsertSql(insertingPropNames);
        }

        private String prepareInsertSql(final Map<String, Object> props) {
            N.checkArgument(N.notNullOrEmpty(props), "props");

            return prepareInsertSql(props.keySet());
        }

        private String prepareInsertSql(final Collection<String> insertingPropNames) {
            N.checkArgument(N.notNullOrEmpty(insertingPropNames), "insertingPropNames");

            switch (namingPolicy) {
                case LOWER_CASE_WITH_UNDERSCORE:
                    return NSC.insert(insertingPropNames).into(targetClass).sql();

                case UPPER_CASE_WITH_UNDERSCORE:
                    return NAC.insert(insertingPropNames).into(targetClass).sql();

                case LOWER_CAMEL_CASE:
                    return NLC.insert(insertingPropNames).into(targetClass).sql();

                default:
                    throw new RuntimeException("Unsupported naming policy: " + namingPolicy);
            }
        }

        /**
         * Execute {@code add} and return the added entity if the record doesn't, otherwise, {@code update} is executed and updated db record is returned.
         *
         * @param entity
         * @return
         */
        public T upsert(final T entity) {
            final T dbEntity = idPropNameList.size() == 1 ? gett((ID) ClassUtil.getPropValue(entity, idPropName)) : gett((ID) entity);

            if (dbEntity == null) {
                insert(entity);
                return entity;
            } else {
                N.merge(entity, dbEntity, false, idPropNameSet);
                update(dbEntity);
                return dbEntity;
            }
        }

        /**
         * Execute {@code add} and return the added entity if the record doesn't, otherwise, {@code update} is executed and updated db record is returned.
         *
         * @param entity
         * @param whereCause to verify if the record exists or not.
         * @return
         */
        public T upsert(final T entity, final Condition whereCause) {
            N.checkArgNotNull(whereCause, "whereCause");

            final T dbEntity = findFirst(whereCause).orNull();

            if (dbEntity == null) {
                insert(entity);
                return entity;
            } else {
                N.merge(entity, dbEntity, false, idPropNameSet);
                update(dbEntity);
                return dbEntity;
            }
        }

        @SuppressWarnings("deprecation")
        public boolean refresh(final T entity) {
            final Collection<String> propNamesToRefresh = ClassUtil.isDirtyMarker(entity.getClass()) ? ((DirtyMarker) entity).signedPropNames()
                    : defaultSelectPropNameList;

            return refresh(entity, propNamesToRefresh);
        }

        /**
         *
         * @param entity
         * @param propNamesToRefresh
         * @return {@code false} if no record found by the ids in the specified {@code entity}.
         */
        @SuppressWarnings("deprecation")
        public boolean refresh(final T entity, Collection<String> propNamesToRefresh) {
            if (N.isNullOrEmpty(propNamesToRefresh)) {
                return idPropNameList.size() == 1 ? exists((ID) ClassUtil.getPropValue(entity, idPropName)) : exists((ID) entity);
            }

            final T dbEntity = idPropNameList.size() == 1 ? gett((ID) ClassUtil.getPropValue(entity, idPropName), propNamesToRefresh)
                    : gett((ID) entity, propNamesToRefresh);

            if (dbEntity == null) {
                return false;
            } else {
                N.merge(dbEntity, entity);

                if (ClassUtil.isDirtyMarker(entity.getClass())) {
                    ((DirtyMarker) entity).markDirty(propNamesToRefresh, false);
                }

                return true;
            }
        }

        public int update(final T entity) {
            return update(entity, (Collection<String>) null);
        }

        public int update(final T entity, final Collection<String> propNamesToUpdate) {
            return update((Connection) null, entity, propNamesToUpdate);
        }

        public int update(final Map<String, Object> props, final ID id) {
            return update((Connection) null, props, id);
        }

        public int update(final Map<String, Object> props, final Condition whereCause) {
            return update((Connection) null, props, whereCause);
        }

        public int update(final Connection conn, final T entity) {
            return update(conn, entity, (Collection<String>) null);
        }

        @SuppressWarnings("deprecation")
        public int update(final Connection conn, final T entity, final Collection<String> propNamesToUpdate) {
            N.checkArgNotNull(entity);

            if (propNamesToUpdate == null && ClassUtil.isDirtyMarker(entity.getClass()) && !((DirtyMarker) entity).isDirty()) {
                return 0;
            }

            final String sql = prepareUpdateSql(entity, propNamesToUpdate);

            final int updateCount = sqlExecutor.update(conn, sql, entity);

            // postUpdate(entity, propNamesToUpdate);

            return updateCount;
        }

        public int update(final Connection conn, final Map<String, Object> props, final ID id) {
            N.checkArgNotNull(id);

            return update(conn, props, id2Cond(id, false));
        }

        public int update(final Connection conn, final Map<String, Object> props, final Condition whereCause) {
            N.checkArgNotNull(props);
            N.checkArgNotNull(whereCause);

            if (N.isNullOrEmpty(props)) {
                return 0;
            }

            final SP sp = prepareUpdate(props, whereCause);

            return sqlExecutor.update(conn, sp.sql, sp.parameters.toArray());
        }

        /**
         * Update All the records by batch operation.
         *
         * @param entities which must have the same properties set for update.
         * @return
         */
        public int batchUpdate(final Collection<? extends T> entities) {
            return batchUpdate(entities, (Collection<String>) null);
        }

        /**
         *
         * @param entities
         * @param propNamesToUpdate
         * @return
         */
        public int batchUpdate(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate) {
            return batchUpdate(entities, propNamesToUpdate, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        public int batchUpdate(final Collection<? extends T> entities, final int batchSize) {
            return batchUpdate(entities, (Collection<String>) null, batchSize);
        }

        /**
         * Update All the records by batch operation.
         *
         * @param entities which must have the same properties set for update.
         * @param propNamesToUpdate
         * @param batchSize Default value is 200.
         * @return
         */
        public int batchUpdate(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate, final int batchSize) {
            return batchUpdate(entities, propNamesToUpdate, batchSize, IsolationLevel.DEFAULT);
        }

        public int batchUpdate(final Collection<? extends T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            return batchUpdate(entities, (Collection<String>) null, batchSize, isolationLevel);
        }

        public int batchUpdate(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate, final int batchSize,
                final IsolationLevel isolationLevel) {
            return batchUpdate((Connection) null, entities, propNamesToUpdate, batchSize, isolationLevel);
        }

        public int batchUpdate(final Connection conn, final Collection<? extends T> entities) {
            return batchUpdate(conn, entities, (Collection<String>) null);
        }

        public int batchUpdate(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToUpdate) {
            return batchUpdate(conn, entities, propNamesToUpdate, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        public int batchUpdate(final Connection conn, final Collection<? extends T> entities, final int batchSize) {
            return batchUpdate(conn, entities, (Collection<String>) null, batchSize);
        }

        public int batchUpdate(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToUpdate, final int batchSize) {
            return batchUpdate(conn, entities, propNamesToUpdate, batchSize, IsolationLevel.DEFAULT);
        }

        @SuppressWarnings("deprecation")
        private int batchUpdate(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToUpdate, final int batchSize,
                final IsolationLevel isolationLevel) {
            N.checkArgPositive(batchSize, "batchSize");

            if (N.isNullOrEmpty(entities)) {
                return 0;
            }

            final T entity = N.firstOrNullIfEmpty(entities);
            Collection<String> updatingPropNames = null;

            if (N.isNullOrEmpty(propNamesToUpdate) && N.isDirtyMarker(entity.getClass())) {
                updatingPropNames = new HashSet<>();

                for (T e : entities) {
                    updatingPropNames.addAll(((DirtyMarker) e).dirtyPropNames());
                }
            } else {
                updatingPropNames = propNamesToUpdate;
            }

            final String sql = prepareUpdateSql(entity, updatingPropNames);
            final JdbcSettings jdbcSettings = JdbcSettings.create().setBatchSize(batchSize).setIsolationLevel(isolationLevel);
            final List<?> parametersList = entities instanceof List ? (List<?>) entities : new ArrayList<>(entities);

            final int updateCount = sqlExecutor.batchUpdate(conn, sql, StatementSetter.DEFAULT, jdbcSettings, parametersList);

            //    if (N.firstNonNull(entities).orNull() instanceof DirtyMarker) {
            //        for (Object entity : entities) {
            //            postUpdate(entity, propNamesToUpdate);
            //        }
            //    }

            return updateCount;
        }

        @SuppressWarnings("deprecation")
        private String prepareUpdateSql(final T entity, final Collection<String> propNamesToUpdate) {
            checkEntity(entity);

            final boolean isDirtyMarkerEntity = ClassUtil.isDirtyMarker(entity.getClass());
            Collection<String> updatingpropNames = propNamesToUpdate;

            if (N.isNullOrEmpty(propNamesToUpdate)) {
                if (isDirtyMarkerEntity) {
                    updatingpropNames = ((DirtyMarker) entity).dirtyPropNames();
                } else {
                    return sql_update_by_id;
                }
            }

            return prepareUpdateSql(updatingpropNames);
        }

        private SP prepareUpdate(final Map<String, Object> props, final Condition whereCause) {
            N.checkArgument(N.notNullOrEmpty(props), "props");

            switch (namingPolicy) {
                case LOWER_CASE_WITH_UNDERSCORE:
                    return NSC.update(targetClass).set(props).where(whereCause).pair();

                case UPPER_CASE_WITH_UNDERSCORE:
                    return NAC.update(targetClass).set(props).where(whereCause).pair();

                case LOWER_CAMEL_CASE:
                    return NLC.update(targetClass).set(props).where(whereCause).pair();

                default:
                    throw new RuntimeException("Unsupported naming policy: " + namingPolicy);
            }
        }

        private String prepareUpdateSql(final Collection<String> propNamesToUpdate) {
            N.checkArgument(N.notNullOrEmpty(propNamesToUpdate), "propNamesToUpdate");

            Condition cond = null;

            if (idPropNameList.size() == 1) {
                cond = CF.eq(idPropName);
            } else {
                And and = new And();

                for (String idPropName : idPropNameList) {
                    and.add(CF.eq(idPropName));
                }

                cond = and;
            }

            switch (namingPolicy) {
                case LOWER_CASE_WITH_UNDERSCORE:
                    return NSC.update(targetClass).set(propNamesToUpdate).where(cond).sql();

                case UPPER_CASE_WITH_UNDERSCORE:
                    return NAC.update(targetClass).set(propNamesToUpdate).where(cond).sql();

                case LOWER_CAMEL_CASE:
                    return NLC.update(targetClass).set(propNamesToUpdate).where(cond).sql();

                default:
                    throw new RuntimeException("Unsupported naming policy: " + namingPolicy);
            }
        }

        //    @SuppressWarnings("deprecation")
        //    private void postUpdate(final Object entity, final Collection<String> propNamesToUpdate) {
        //        if (ClassUtil.isDirtyMarker(entity.getClass())) {
        //            if (propNamesToUpdate == null) {
        //                ((DirtyMarker) entity).markDirty(false);
        //            } else {
        //                ((DirtyMarker) entity).markDirty(propNamesToUpdate, false);
        //            }
        //        }
        //    }

        public int delete(final Condition whereCause) {
            return delete(null, whereCause);
        }

        public int delete(final Connection conn, final Condition whereCause) {
            N.checkArgNotNull(whereCause);

            if (idPropNameList.size() == 1 && whereCause instanceof Equal && ((Equal) whereCause).getPropName().equals(idPropName)) {
                final ID id = ((Equal) whereCause).getPropValue();
                return sqlExecutor.update(conn, sql_delete_by_id, id);
            }

            final SP sp = prepareDelete(whereCause);

            return sqlExecutor.update(conn, sp.sql, sp.parameters.toArray());
        }

        /**
         *
         * @param entity
         * @return
         */
        public int delete(final T entity) {
            return delete(null, entity);
        }

        /**
         *
         * @param conn
         * @param entity
         * @return
         */
        public int delete(final Connection conn, final T entity) {
            N.checkArgNotNull(entity);
            checkEntity(entity);

            return sqlExecutor.update(conn, sql_delete_by_id, entity);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param entities
         * @return
         */
        public int batchDelete(final Collection<T> entities) {
            return batchDelete(entities, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param entities
         * @param batchSize Default value is 200.
         * @return
         */
        public int batchDelete(final Collection<T> entities, final int batchSize) {
            return batchDelete(entities, batchSize, IsolationLevel.DEFAULT);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param entities
         * @param batchSize Default value is 200.
         * @param isolationLevel
         * @return
         */
        public int batchDelete(final Collection<T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            return batchDelete(null, entities, batchSize, isolationLevel);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param conn
         * @param entities
         * @return
         */
        public int batchDelete(final Connection conn, final Collection<T> entities) {
            return batchDelete(conn, entities, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param conn
         * @param entities
         * @param batchSize
         * @return
         */
        public int batchDelete(final Connection conn, final Collection<T> entities, final int batchSize) {
            return batchDelete(conn, entities, batchSize, IsolationLevel.DEFAULT);
        }

        private int batchDelete(final Connection conn, final Collection<T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            N.checkArgPositive(batchSize, "batchSize");

            if (N.isNullOrEmpty(entities)) {
                return 0;
            }

            final List<T> ids = entities instanceof List ? ((List<T>) entities) : N.newArrayList(entities);
            final JdbcSettings jdbcSettings = JdbcSettings.create().setBatchSize(batchSize).setIsolationLevel(isolationLevel);

            return sqlExecutor.batchUpdate(conn, sql_delete_by_id, jdbcSettings, ids);
        }

        private SP prepareDelete(final Condition whereCause) {
            SP sp = null;

            switch (namingPolicy) {
                case LOWER_CASE_WITH_UNDERSCORE:
                    sp = NSC.deleteFrom(targetClass).where(whereCause).pair();

                    break;

                case UPPER_CASE_WITH_UNDERSCORE:
                    sp = NAC.deleteFrom(targetClass).where(whereCause).pair();

                    break;

                case LOWER_CAMEL_CASE:
                    sp = NLC.deleteFrom(targetClass).where(whereCause).pair();

                    break;

                default:
                    throw new RuntimeException("Unsupported naming policy: " + namingPolicy);
            }

            return sp;
        }

        public int deleteById(final ID id) {
            return deleteById(null, id);
        }

        public int deleteById(final Connection conn, final ID id) {
            N.checkArgNotNull(id);
            checkId(id);

            return sqlExecutor.update(conn, sql_delete_by_id, id);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param ids
         * @return
         */
        public int batchDeleteByIds(final Collection<ID> ids) {
            return batchDeleteByIds(ids, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param ids
         * @param batchSize Default value is 200.
         * @return
         */
        public int batchDeleteByIds(final Collection<ID> ids, final int batchSize) {
            return batchDeleteByIds(ids, batchSize, IsolationLevel.DEFAULT);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param ids
         * @param batchSize Default value is 200.
         * @param isolationLevel
         * @return
         */
        public int batchDeleteByIds(final Collection<ID> ids, final int batchSize, final IsolationLevel isolationLevel) {
            return batchDeleteByIds(null, ids, batchSize, isolationLevel);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param conn
         * @param ids
         * @return
         */
        public int batchDeleteByIds(final Connection conn, final Collection<ID> ids) {
            return batchDeleteByIds(conn, ids, JdbcSettings.DEFAULT_BATCH_SIZE);
        }

        /**
         * Delete all the records by batch operation.
         *
         * @param conn
         * @param ids
         * @param batchSize
         * @return
         */
        public int batchDeleteByIds(final Connection conn, final Collection<ID> ids, final int batchSize) {
            return batchDeleteByIds(conn, ids, batchSize, IsolationLevel.DEFAULT);
        }

        private int batchDeleteByIds(final Connection conn, final Collection<ID> ids, final int batchSize, final IsolationLevel isolationLevel) {
            N.checkArgPositive(batchSize, "batchSize");

            if (N.isNullOrEmpty(ids)) {
                return 0;
            }

            final List<ID> listOfIds = ids instanceof List ? ((List<ID>) ids) : N.newArrayList(ids);

            checkId(listOfIds.get(0));

            final JdbcSettings jdbcSettings = JdbcSettings.create().setBatchSize(batchSize).setIsolationLevel(isolationLevel);

            return sqlExecutor.batchUpdate(conn, sql_delete_by_id, jdbcSettings, listOfIds);
        }

        private void checkId(final Object id) {
            N.checkArgument(idPropNameList.size() > 1 || !(id instanceof Map || isEntity(id)), "Input 'id' can not be Map or entity for single id ");
        }

        private boolean isEntity(Object obj) {
            return obj != null && ClassUtil.isEntity(obj.getClass());
        }

        private Condition id2Cond(final Object id, boolean isIdOrEntity) {
            if (isIdOrEntity == false) {
                checkId(id);
            }

            if (idPropNameList.size() == 1) {
                if (id instanceof Map) {
                    return CF.eq(idPropName, ((Map<String, Object>) id).get(idPropName));
                } else if (isEntity(id)) {
                    return CF.eq(idPropName, ClassUtil.getPropValue(id, idPropName));
                } else {
                    return CF.eq(idPropName, id);
                }
            }

            if (id instanceof Map) {
                return CF.eqAnd((Map<String, Object>) id);
            } else if (isEntity(id)) {
                return CF.eqAnd(id, idPropNameList);
            } else {
                throw new IllegalArgumentException("Not supported id type: " + (id == null ? "null" : ClassUtil.getClassName(id.getClass())));
            }
        }

        private void checkEntity(final Object entity) {
            final Class<?> cls = entity.getClass();

            if (ClassUtil.isEntity(cls)) {
                N.checkArgument(targetClass.isAssignableFrom(cls), "Delete wrong type: " + ClassUtil.getCanonicalClassName(cls) + " in " + toString());
            }
        }

        public String toStirng() {
            return "Mapper[" + ClassUtil.getCanonicalClassName(targetClass) + "]";
        }
    }

    public static final class AsyncMapper<T, ID> {
        private final Mapper<T, ID> mapper;
        private final AsyncExecutor asyncExecutor;

        AsyncMapper(Mapper<T, ID> mapper, AsyncExecutor asyncExecutor) {
            this.mapper = mapper;
            this.asyncExecutor = asyncExecutor;
        }

        public ContinuableFuture<Boolean> exists(final ID id) {
            return asyncExecutor.execute(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return mapper.exists(id);
                }
            });
        }

        public ContinuableFuture<Boolean> exists(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return mapper.exists(whereCause);
                }
            });
        }

        public ContinuableFuture<Boolean> exists(final Connection conn, final ID id) {
            return asyncExecutor.execute(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return mapper.exists(conn, id);
                }
            });
        }

        public ContinuableFuture<Boolean> exists(final Connection conn, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return mapper.exists(conn, whereCause);
                }
            });
        }

        public ContinuableFuture<Integer> count(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.count(whereCause);
                }
            });
        }

        public ContinuableFuture<Integer> count(final Connection conn, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.count(conn, whereCause);
                }
            });
        }

        public ContinuableFuture<Optional<T>> get(final ID id) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.get(id);
                }
            });
        }

        //    /**
        //     *
        //     * @param id
        //     * @param selectPropNames
        //     * @return
        //     * @deprecated replaced by {@code get(id, Arrays.asList(selectPropNames)}
        //     */
        //    @Deprecated
        //    @SafeVarargs
        //    public final ContinuableFuture<Optional<T>> get(final ID id, final String... selectPropNames) {
        //        return asyncExecutor.execute(new Callable<Optional<T>>() {
        //            @Override
        //            public Optional<T> call() throws Exception {
        //                return mapper.get(id, selectPropNames);
        //            }
        //        });
        //    }

        public ContinuableFuture<Optional<T>> get(final ID id, final Collection<String> selectPropNames) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.get(id, selectPropNames);
                }
            });
        }

        public ContinuableFuture<Optional<T>> get(final Connection conn, final ID id, final Collection<String> selectPropNames) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.get(conn, id, selectPropNames);
                }
            });
        }

        public ContinuableFuture<T> gett(final ID id) {
            return asyncExecutor.execute(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    return mapper.gett(id);
                }
            });
        }

        //    /**
        //     *
        //     * @param id
        //     * @param selectPropNames
        //     * @return
        //     * @deprecated replaced by {@code gett(id, Arrays.asList(selectPropNames)}
        //     */
        //    @Deprecated
        //    @SafeVarargs
        //    public final ContinuableFuture<T> gett(final ID id, final String... selectPropNames) {
        //        return asyncExecutor.execute(new Callable<T>() {
        //            @Override
        //            public T call() throws Exception {
        //                return mapper.gett(id, selectPropNames);
        //            }
        //        });
        //    }

        public ContinuableFuture<T> gett(final ID id, final Collection<String> selectPropNames) {
            return asyncExecutor.execute(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    return mapper.gett(id, selectPropNames);
                }
            });
        }

        public ContinuableFuture<T> gett(final Connection conn, final ID id, final Collection<String> selectPropNames) {
            return asyncExecutor.execute(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    return mapper.gett(conn, id, selectPropNames);
                }
            });
        }

        public ContinuableFuture<List<T>> batchGet(final List<ID> ids) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.batchGet(ids);
                }
            });
        }

        //    /**
        //     *
        //     * @param ids
        //     * @param selectPropNames
        //     * @return
        //     * @deprecated replaced by {@code batchGet(ids, Arrays.asList(selectPropNames)}
        //     */
        //    @Deprecated
        //    public ContinuableFuture<List<T>> batchGet(final List<?> ids, final String... selectPropNames) {
        //        return asyncExecutor.execute(new Callable<List<T>>() {
        //            @Override
        //            public List<T> call() throws Exception {
        //                return mapper.batchGet(ids, selectPropNames);
        //            }
        //        });
        //    }

        public ContinuableFuture<List<T>> batchGet(final List<ID> ids, final Collection<String> selectPropNames) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.batchGet(ids, selectPropNames);
                }
            });
        }

        public ContinuableFuture<List<T>> batchGet(final List<ID> ids, final Collection<String> selectPropNames, final int batchSize) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.batchGet(ids, selectPropNames, batchSize);
                }
            });
        }

        public ContinuableFuture<List<T>> batchGet(final Connection conn, final List<ID> ids, final Collection<String> selectPropNames, final int batchSize) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.batchGet(conn, ids, selectPropNames, batchSize);
                }
            });
        }

        public ContinuableFuture<Optional<T>> findFirst(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.findFirst(whereCause);
                }
            });
        }

        public ContinuableFuture<Optional<T>> findFirst(final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.findFirst(selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<Optional<T>> findFirst(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.findFirst(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<Optional<T>> findFirst(final Connection conn, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.findFirst(conn, whereCause);
                }
            });
        }

        public ContinuableFuture<Optional<T>> findFirst(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.findFirst(conn, selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<Optional<T>> findFirst(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<T>>() {
                @Override
                public Optional<T> call() throws Exception {
                    return mapper.findFirst(conn, selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final Collection<String> selectPropNames,
                final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final Collection<String> selectPropNames,
                final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final Collection<String> selectPropNames,
                final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Optional<R>> findFirst(final Connection conn, final Collection<String> selectPropNames,
                final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Optional<R>>() {
                @Override
                public Optional<R> call() throws Exception {
                    return mapper.findFirst(conn, selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<List<T>> list(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.list(whereCause);
                }
            });
        }

        public ContinuableFuture<List<T>> list(final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.list(selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<List<T>> list(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.list(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<List<T>> list(final Connection conn, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.list(conn, whereCause);
                }
            });
        }

        public ContinuableFuture<List<T>> list(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.list(conn, selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<List<T>> list(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.list(conn, selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> list(final Connection conn, final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.list(conn, selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<List<T>> listAll(final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.listAll(whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<List<T>> listAll(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<T>>() {
                @Override
                public List<T> call() throws Exception {
                    return mapper.listAll(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> listAll(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.listAll(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> listAll(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.listAll(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> listAll(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.listAll(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<List<R>> listAll(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<List<R>>() {
                @Override
                public List<R> call() throws Exception {
                    return mapper.listAll(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<Stream<T>> stream(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Stream<T>>() {
                @Override
                public Stream<T> call() throws Exception {
                    return mapper.stream(whereCause);
                }
            });
        }

        public ContinuableFuture<Stream<T>> stream(final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Stream<T>>() {
                @Override
                public Stream<T> call() throws Exception {
                    return mapper.stream(selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<Stream<T>> stream(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<T>>() {
                @Override
                public Stream<T> call() throws Exception {
                    return mapper.stream(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropName, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropNames, rowMapper, whereCause);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> stream(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.stream(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<Stream<T>> streamAll(final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<T>>() {
                @Override
                public Stream<T> call() throws Exception {
                    return mapper.streamAll(whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<Stream<T>> streamAll(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<T>>() {
                @Override
                public Stream<T> call() throws Exception {
                    return mapper.streamAll(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> streamAll(final String selectPropName, final JdbcUtil.RowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.streamAll(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> streamAll(final String selectPropName, final JdbcUtil.BiRowMapper<R> rowMapper, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.streamAll(selectPropName, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> streamAll(final Collection<String> selectPropNames, final JdbcUtil.RowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.streamAll(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public <R> ContinuableFuture<Stream<R>> streamAll(final Collection<String> selectPropNames, final JdbcUtil.BiRowMapper<R> rowMapper,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Stream<R>>() {
                @Override
                public Stream<R> call() throws Exception {
                    return mapper.streamAll(selectPropNames, rowMapper, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<DataSet> query(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.query(whereCause);
                }
            });
        }

        public ContinuableFuture<DataSet> query(final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.query(selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<DataSet> query(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.query(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<DataSet> query(final Connection conn, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.query(conn, whereCause);
                }
            });
        }

        public ContinuableFuture<DataSet> query(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.query(conn, selectPropNames, whereCause);
                }
            });
        }

        public ContinuableFuture<DataSet> query(final Connection conn, final Collection<String> selectPropNames, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.query(conn, selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<DataSet> queryAll(final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.queryAll(whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<DataSet> queryAll(final Collection<String> selectPropNames, final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<DataSet>() {
                @Override
                public DataSet call() throws Exception {
                    return mapper.queryAll(selectPropNames, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<OptionalBoolean> queryForBoolean(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalBoolean>() {
                @Override
                public OptionalBoolean call() throws Exception {
                    return mapper.queryForBoolean(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<OptionalByte> queryForByte(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalByte>() {
                @Override
                public OptionalByte call() throws Exception {
                    return mapper.queryForByte(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<OptionalShort> queryForShort(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalShort>() {
                @Override
                public OptionalShort call() throws Exception {
                    return mapper.queryForShort(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<OptionalInt> queryForInt(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalInt>() {
                @Override
                public OptionalInt call() throws Exception {
                    return mapper.queryForInt(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<OptionalLong> queryForLong(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalLong>() {
                @Override
                public OptionalLong call() throws Exception {
                    return mapper.queryForLong(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<OptionalFloat> queryForFloat(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalFloat>() {
                @Override
                public OptionalFloat call() throws Exception {
                    return mapper.queryForFloat(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<OptionalDouble> queryForDouble(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<OptionalDouble>() {
                @Override
                public OptionalDouble call() throws Exception {
                    return mapper.queryForDouble(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<Nullable<BigDecimal>> queryForBigDecimal(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<BigDecimal>>() {
                @Override
                public Nullable<BigDecimal> call() throws Exception {
                    return mapper.queryForBigDecimal(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<Nullable<String>> queryForString(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<String>>() {
                @Override
                public Nullable<String> call() throws Exception {
                    return mapper.queryForString(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<Nullable<java.sql.Date>> queryForDate(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<java.sql.Date>>() {
                @Override
                public Nullable<java.sql.Date> call() throws Exception {
                    return mapper.queryForDate(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<Nullable<java.sql.Time>> queryForTime(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<java.sql.Time>>() {
                @Override
                public Nullable<java.sql.Time> call() throws Exception {
                    return mapper.queryForTime(selectPropName, whereCause);
                }
            });
        }

        public ContinuableFuture<Nullable<java.sql.Timestamp>> queryForTimestamp(final String selectPropName, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<java.sql.Timestamp>>() {
                @Override
                public Nullable<java.sql.Timestamp> call() throws Exception {
                    return mapper.queryForTimestamp(selectPropName, whereCause);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForSingleResult(final Class<V> targetValueClass, final String selectPropName, final ID id) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForSingleResult(targetValueClass, selectPropName, id);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForSingleResult(final Class<V> targetValueClass, final String selectPropName,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForSingleResult(targetValueClass, selectPropName, whereCause);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForSingleResult(final Class<V> targetValueClass, final String selectPropName, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForSingleResult(targetValueClass, selectPropName, whereCause, jdbcSettings);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForSingleResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final ID id) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForSingleResult(targetValueClass, conn, selectPropName, id);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForSingleResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForSingleResult(targetValueClass, conn, selectPropName, whereCause);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForSingleResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForSingleResult(targetValueClass, conn, selectPropName, whereCause, jdbcSettings);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForUniqueResult(final Class<V> targetValueClass, final String selectPropName, final ID id) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForUniqueResult(targetValueClass, selectPropName, id);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForUniqueResult(final Class<V> targetValueClass, final String selectPropName,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForUniqueResult(targetValueClass, selectPropName, whereCause);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForUniqueResult(final Class<V> targetValueClass, final String selectPropName, final Condition whereCause,
                final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForUniqueResult(targetValueClass, selectPropName, whereCause, jdbcSettings);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForUniqueResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final ID id) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForUniqueResult(targetValueClass, conn, selectPropName, id);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForUniqueResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForUniqueResult(targetValueClass, conn, selectPropName, whereCause);
                }
            });
        }

        public <V> ContinuableFuture<Nullable<V>> queryForUniqueResult(final Class<V> targetValueClass, final Connection conn, final String selectPropName,
                final Condition whereCause, final JdbcSettings jdbcSettings) {
            return asyncExecutor.execute(new Callable<Nullable<V>>() {
                @Override
                public Nullable<V> call() throws Exception {
                    return mapper.queryForUniqueResult(targetValueClass, conn, selectPropName, whereCause, jdbcSettings);
                }
            });
        }

        public ContinuableFuture<ID> insert(final T entity) {
            return asyncExecutor.execute(new Callable<ID>() {
                @Override
                public ID call() throws Exception {
                    return mapper.insert(entity);
                }
            });
        }

        public ContinuableFuture<ID> insert(final T entity, final Collection<String> propNamesToInsert) {
            return asyncExecutor.execute(new Callable<ID>() {
                @Override
                public ID call() throws Exception {
                    return mapper.insert(entity, propNamesToInsert);
                }
            });
        }

        public ContinuableFuture<ID> insert(final Map<String, Object> props) {
            return asyncExecutor.execute(new Callable<ID>() {
                @Override
                public ID call() throws Exception {
                    return mapper.insert(props);
                }
            });
        }

        public ContinuableFuture<ID> insert(final Connection conn, final T entity) {
            return asyncExecutor.execute(new Callable<ID>() {
                @Override
                public ID call() throws Exception {
                    return mapper.insert(conn, entity);
                }
            });
        }

        public ContinuableFuture<ID> insert(final Connection conn, final T entity, final Collection<String> propNamesToInsert) {
            return asyncExecutor.execute(new Callable<ID>() {
                @Override
                public ID call() throws Exception {
                    return mapper.insert(conn, entity, propNamesToInsert);
                }
            });
        }

        public ContinuableFuture<ID> insert(final Connection conn, final Map<String, Object> props) {
            return asyncExecutor.execute(new Callable<ID>() {
                @Override
                public ID call() throws Exception {
                    return mapper.insert(conn, props);
                }
            });
        }

        //        @Deprecated
        //        public <ID> ContinuableFuture<List<ID>> insertAll(final Collection<? extends T> entities) {
        //            return asyncExecutor.execute(new Callable<List<ID>>() {
        //                @Override
        //                public List<ID> call() throws Exception {
        //                    return mapper.insertAll(entities);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public <ID> ContinuableFuture<List<ID>> insertAll(final Collection<? extends T> entities, final IsolationLevel isolationLevel) {
        //            return asyncExecutor.execute(new Callable<List<ID>>() {
        //                @Override
        //                public List<ID> call() throws Exception {
        //                    return mapper.insertAll(entities, isolationLevel);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public <ID> ContinuableFuture<List<ID>> insertAll(final Connection conn, final Collection<? extends T> entities) {
        //            return asyncExecutor.execute(new Callable<List<ID>>() {
        //                @Override
        //                public List<ID> call() throws Exception {
        //                    return mapper.insertAll(conn, entities);
        //                }
        //            });
        //        }

        public ContinuableFuture<List<ID>> batchInsert(final Collection<? extends T> entities) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(entities);
                }
            });
        }

        public ContinuableFuture<List<ID>> batchInsert(final Collection<? extends T> entities, final int batchSize) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(entities, batchSize);
                }
            });
        }

        public ContinuableFuture<List<ID>> batchInsert(final Collection<? extends T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(entities, batchSize, isolationLevel);
                }
            });
        }

        public ContinuableFuture<List<ID>> batchInsert(final Collection<? extends T> entities, final Collection<String> propNamesToInsert, final int batchSize,
                final IsolationLevel isolationLevel) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(entities, propNamesToInsert, batchSize, isolationLevel);
                }
            });
        }

        public ContinuableFuture<List<ID>> batchInsert(final Connection conn, final Collection<? extends T> entities) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(conn, entities);
                }
            });
        }

        public ContinuableFuture<List<ID>> batchInsert(final Connection conn, final Collection<? extends T> entities, final int batchSize) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(conn, entities, batchSize);
                }
            });
        }

        public ContinuableFuture<List<ID>> batchInsert(final Connection conn, final Collection<? extends T> entities,
                final Collection<String> propNamesToInsert, final int batchSize) {
            return asyncExecutor.execute(new Callable<List<ID>>() {
                @Override
                public List<ID> call() throws Exception {
                    return mapper.batchInsert(conn, entities, propNamesToInsert, batchSize);
                }
            });
        }

        public ContinuableFuture<T> upsert(final T entity) {
            return asyncExecutor.execute(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    return mapper.upsert(entity);
                }
            });
        }

        public ContinuableFuture<T> upsert(final T entity, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    return mapper.upsert(entity, whereCause);
                }
            });
        }

        public ContinuableFuture<Boolean> refresh(final T entity) {
            return asyncExecutor.execute(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return mapper.refresh(entity);
                }
            });
        }

        public ContinuableFuture<Boolean> refresh(final T entity, final Collection<String> propNamesToUpdate) {
            return asyncExecutor.execute(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return mapper.refresh(entity, propNamesToUpdate);
                }
            });
        }

        public ContinuableFuture<Integer> update(final T entity) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(entity);
                }
            });
        }

        public ContinuableFuture<Integer> update(final T entity, final Collection<String> propNamesToUpdate) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(entity, propNamesToUpdate);
                }
            });
        }

        public ContinuableFuture<Integer> update(final Map<String, Object> props, final ID id) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(props, id);
                }
            });
        }

        public ContinuableFuture<Integer> update(final Map<String, Object> props, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(props, whereCause);
                }
            });
        }

        public ContinuableFuture<Integer> update(final Connection conn, final T entity) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(conn, entity);
                }
            });
        }

        public ContinuableFuture<Integer> update(final Connection conn, final T entity, final Collection<String> propNamesToUpdate) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(conn, entity, propNamesToUpdate);
                }
            });
        }

        public ContinuableFuture<Integer> update(final Connection conn, final Map<String, Object> props, final ID id) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(conn, props, id);
                }
            });
        }

        public ContinuableFuture<Integer> update(final Connection conn, final Map<String, Object> props, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.update(conn, props, whereCause);
                }
            });
        }

        //        @Deprecated
        //        public ContinuableFuture<Integer> updateAll(final Collection<? extends T> entities) {
        //            return asyncExecutor.execute(new Callable<Integer>() {
        //                @Override
        //                public Integer call() throws Exception {
        //                    return mapper.updateAll(entities);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public ContinuableFuture<Integer> updateAll(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate) {
        //            return asyncExecutor.execute(new Callable<Integer>() {
        //                @Override
        //                public Integer call() throws Exception {
        //                    return mapper.updateAll(entities, propNamesToUpdate);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public ContinuableFuture<Integer> updateAll(final Collection<? extends T> entities, final IsolationLevel isolationLevel) {
        //            return asyncExecutor.execute(new Callable<Integer>() {
        //                @Override
        //                public Integer call() throws Exception {
        //                    return mapper.updateAll(entities, isolationLevel);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public ContinuableFuture<Integer> updateAll(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate,
        //                final IsolationLevel isolationLevel) {
        //            return asyncExecutor.execute(new Callable<Integer>() {
        //                @Override
        //                public Integer call() throws Exception {
        //                    return mapper.updateAll(entities, propNamesToUpdate, isolationLevel);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public ContinuableFuture<Integer> updateAll(final Connection conn, final Collection<? extends T> entities) {
        //            return asyncExecutor.execute(new Callable<Integer>() {
        //                @Override
        //                public Integer call() throws Exception {
        //                    return mapper.updateAll(conn, entities);
        //                }
        //            });
        //        }
        //
        //        @Deprecated
        //        public ContinuableFuture<Integer> updateAll(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToUpdate) {
        //            return asyncExecutor.execute(new Callable<Integer>() {
        //                @Override
        //                public Integer call() throws Exception {
        //                    return mapper.updateAll(conn, entities, propNamesToUpdate);
        //                }
        //            });
        //        }

        public ContinuableFuture<Integer> batchUpdate(final Collection<? extends T> entities) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(entities);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(entities, propNamesToUpdate);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Collection<? extends T> entities, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(entities, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(entities, propNamesToUpdate, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Collection<? extends T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(entities, batchSize, isolationLevel);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Collection<? extends T> entities, final Collection<String> propNamesToUpdate, final int batchSize,
                final IsolationLevel isolationLevel) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(entities, propNamesToUpdate, batchSize, isolationLevel);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Connection conn, final Collection<? extends T> entities) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(conn, entities);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Connection conn, final Collection<? extends T> entities,
                final Collection<String> propNamesToUpdate) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(conn, entities, propNamesToUpdate);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Connection conn, final Collection<? extends T> entities, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(conn, entities, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> batchUpdate(final Connection conn, final Collection<? extends T> entities, final Collection<String> propNamesToUpdate,
                final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchUpdate(conn, entities, propNamesToUpdate, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> delete(final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.delete(whereCause);
                }
            });
        }

        public ContinuableFuture<Integer> delete(final Connection conn, final Condition whereCause) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.delete(conn, whereCause);
                }
            });
        }

        public ContinuableFuture<Integer> delete(final T entity) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.delete(entity);
                }
            });
        }

        public ContinuableFuture<Integer> delete(final Connection conn, final T entity) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.delete(conn, entity);
                }
            });
        }

        public ContinuableFuture<Integer> batchDelete(final Collection<T> entities) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDelete(entities);
                }
            });
        }

        public ContinuableFuture<Integer> batchDelete(final Collection<T> entities, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDelete(entities, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> batchDelete(final Collection<T> entities, final int batchSize, final IsolationLevel isolationLevel) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDelete(entities, batchSize, isolationLevel);
                }
            });
        }

        public ContinuableFuture<Integer> batchDelete(final Connection conn, final Collection<T> entities) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDelete(conn, entities);
                }
            });
        }

        public ContinuableFuture<Integer> batchDelete(final Connection conn, final Collection<T> entities, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDelete(conn, entities, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> deleteById(final ID id) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.deleteById(id);
                }
            });
        }

        public ContinuableFuture<Integer> deleteById(final Connection conn, final ID id) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.deleteById(conn, id);
                }
            });
        }

        public ContinuableFuture<Integer> batchDeleteByIds(final Collection<ID> ids) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDeleteByIds(ids);
                }
            });
        }

        public ContinuableFuture<Integer> batchDeleteByIds(final Collection<ID> ids, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDeleteByIds(ids, batchSize);
                }
            });
        }

        public ContinuableFuture<Integer> batchDeleteByIds(final Collection<ID> ids, final int batchSize, final IsolationLevel isolationLevel) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDeleteByIds(ids, batchSize, isolationLevel);
                }
            });
        }

        public ContinuableFuture<Integer> batchDeleteByIds(final Connection conn, final Collection<ID> ids) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDeleteByIds(conn, ids);
                }
            });
        }

        public ContinuableFuture<Integer> batchDeleteByIds(final Connection conn, final Collection<ID> ids, final int batchSize) {
            return asyncExecutor.execute(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    return mapper.batchDeleteByIds(conn, ids, batchSize);
                }
            });
        }
    }

    /**
     * Refer to http://landawn.com/introduction-to-jdbc.html about how to set parameters in java.sql.PreparedStatement
     *
     * @author Haiyang Li
     *
     */
    public static interface StatementSetter {
        public static final StatementSetter DEFAULT = new AbstractStatementSetter() {
            @SuppressWarnings("rawtypes")
            @Override
            protected void setParameters(final PreparedStatement stmt, final int parameterCount, final Object[] parameters, final Type[] parameterTypes)
                    throws SQLException {
                if (N.notNullOrEmpty(parameterTypes) && parameterTypes.length >= parameterCount) {
                    for (int i = 0; i < parameterCount; i++) {
                        parameterTypes[i].set(stmt, i + 1, parameters[i]);
                    }
                } else if (N.notNullOrEmpty(parameters) && parameters.length >= parameterCount) {
                    for (int i = 0; i < parameterCount; i++) {
                        if (parameters[i] == null) {
                            stmt.setObject(i + 1, parameters[i]);
                        } else {
                            N.typeOf(parameters[i].getClass()).set(stmt, i + 1, parameters[i]);
                        }
                    }
                }
            }
        };

        public void setParameters(final NamedSQL namedSQL, final PreparedStatement stmt, final Object... parameters) throws SQLException;
    }

    /**
     * Refer to http://landawn.com/introduction-to-jdbc.html about how to read columns/rows from java.sql.ResultSet
     *
     * @author Haiyang Li
     *
     */
    public static interface ResultExtractor<T> {
        public static final ResultExtractor<DataSet> DATA_SET = new AbstractResultExtractor<DataSet>() {
            @Override
            protected DataSet convert(DataSet dataSet) {
                return dataSet;
            }
        };

        public T extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException;

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        public static <K, V> ResultExtractor<Map<K, V>> toMap(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor) {
            return toMap(keyExtractor, valueExtractor, Suppliers.<K, V> ofMap());
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param supplier
         * @return
         */
        public static <K, V, M extends Map<K, V>> ResultExtractor<M> toMap(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor,
                final Supplier<? extends M> supplier) {
            return toMap(keyExtractor, valueExtractor, FN.throwingMerger(), supplier);
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @return
         * @see {@link Fn.EE#throwingMerger()}
         * @see {@link Fn.EE#replacingMerger()}
         * @see {@link Fn.EE#ignoringMerger()}
         */
        public static <K, V> ResultExtractor<Map<K, V>> toMap(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor,
                final Try.BinaryOperator<V, SQLException> mergeFunction) {
            return toMap(keyExtractor, valueExtractor, mergeFunction, Suppliers.<K, V> ofMap());
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @param supplier
         * @return
         * @see {@link Fn.EE#throwingMerger()}
         * @see {@link Fn.EE#replacingMerger()}
         * @see {@link Fn.EE#ignoringMerger()}
         */
        public static <K, V, M extends Map<K, V>> ResultExtractor<M> toMap(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor,
                final Try.BinaryOperator<V, SQLException> mergeFunction, final Supplier<? extends M> supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(mergeFunction, "mergeFunction");
            N.checkArgNotNull(supplier, "supplier");

            return new ResultExtractor<M>() {
                @Override
                public M extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    final int offset = jdbcSettings.getOffset();
                    int count = jdbcSettings.getCount();

                    JdbcUtil.skip(rs, offset);

                    final M result = supplier.get();

                    while (count-- > 0 && rs.next()) {
                        Maps.merge(result, keyExtractor.apply(rs), valueExtractor.apply(rs), mergeFunction);
                    }

                    return result;
                }
            };
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @return
         */
        public static <K, V, A, D> ResultExtractor<Map<K, D>> toMap(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor,
                final Collector<? super V, A, D> downstream) {
            return toMap(keyExtractor, valueExtractor, downstream, Suppliers.<K, D> ofMap());
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @param supplier
         * @return
         */
        public static <K, V, A, D, M extends Map<K, D>> ResultExtractor<M> toMap(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor,
                final Collector<? super V, A, D> downstream, final Supplier<? extends M> supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(downstream, "downstream");
            N.checkArgNotNull(supplier, "supplier");

            return new ResultExtractor<M>() {
                @Override
                public M extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    final int offset = jdbcSettings.getOffset();
                    int count = jdbcSettings.getCount();

                    JdbcUtil.skip(rs, offset);

                    final Supplier downstreamSupplier = downstream.supplier();
                    final BiConsumer downstreamAccumulator = downstream.accumulator();
                    final Function downstreamFinisher = downstream.finisher();

                    final M result = supplier.get();
                    final Map<K, A> tmp = (Map<K, A>) result;
                    K key = null;
                    A container = null;

                    while (count-- > 0 && rs.next()) {
                        key = keyExtractor.apply(rs);
                        container = tmp.get(key);

                        if (container == null) {
                            container = downstreamSupplier.get();
                            tmp.put(key, container);
                        }

                        downstreamAccumulator.accept(container, valueExtractor.apply(rs));
                    }

                    for (Map.Entry<K, D> entry : result.entrySet()) {
                        entry.setValue(downstreamFinisher.apply((A) entry.getValue()));
                    }

                    return result;
                }
            };
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @return
         */
        public static <K, V> ResultExtractor<Map<K, V>> toMap(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor) {
            return toMap(keyExtractor, valueExtractor, Suppliers.<K, V> ofMap());
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param supplier
         * @return
         */
        public static <K, V, M extends Map<K, V>> ResultExtractor<M> toMap(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor,
                final Supplier<? extends M> supplier) {
            return toMap(keyExtractor, valueExtractor, FN.throwingMerger(), supplier);
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @return
         * @see {@link Fn.EE#throwingMerger()}
         * @see {@link Fn.EE#replacingMerger()}
         * @see {@link Fn.EE#ignoringMerger()}
         */
        public static <K, V> ResultExtractor<Map<K, V>> toMap(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor,
                final Try.BinaryOperator<V, SQLException> mergeFunction) {
            return toMap(keyExtractor, valueExtractor, mergeFunction, Suppliers.<K, V> ofMap());
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param mergeFunction
         * @param supplier
         * @return
         * @see {@link Fn.EE#throwingMerger()}
         * @see {@link Fn.EE#replacingMerger()}
         * @see {@link Fn.EE#ignoringMerger()}
         */
        public static <K, V, M extends Map<K, V>> ResultExtractor<M> toMap(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor,
                final Try.BinaryOperator<V, SQLException> mergeFunction, final Supplier<? extends M> supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(mergeFunction, "mergeFunction");
            N.checkArgNotNull(supplier, "supplier");

            return new ResultExtractor<M>() {
                @Override
                public M extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    final int offset = jdbcSettings.getOffset();
                    int count = jdbcSettings.getCount();

                    JdbcUtil.skip(rs, offset);

                    final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);
                    final M result = supplier.get();

                    while (count-- > 0 && rs.next()) {
                        Maps.merge(result, keyExtractor.apply(rs, columnLabels), valueExtractor.apply(rs, columnLabels), mergeFunction);
                    }

                    return result;
                }
            };
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @return
         */
        public static <K, V, A, D> ResultExtractor<Map<K, D>> toMap(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor,
                final Collector<? super V, A, D> downstream) {
            return toMap(keyExtractor, valueExtractor, downstream, Suppliers.<K, D> ofMap());
        }

        /**
         *
         * @param keyExtractor
         * @param valueExtractor
         * @param downstream
         * @param supplier
         * @return
         */
        public static <K, V, A, D, M extends Map<K, D>> ResultExtractor<M> toMap(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor,
                final Collector<? super V, A, D> downstream, final Supplier<? extends M> supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(downstream, "downstream");
            N.checkArgNotNull(supplier, "supplier");

            return new ResultExtractor<M>() {
                @Override
                public M extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    final int offset = jdbcSettings.getOffset();
                    int count = jdbcSettings.getCount();

                    JdbcUtil.skip(rs, offset);

                    final Supplier downstreamSupplier = downstream.supplier();
                    final BiConsumer downstreamAccumulator = downstream.accumulator();
                    final Function downstreamFinisher = downstream.finisher();

                    final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);
                    final M result = supplier.get();
                    final Map<K, A> tmp = (Map<K, A>) result;
                    K key = null;
                    A container = null;

                    while (count-- > 0 && rs.next()) {
                        key = keyExtractor.apply(rs, columnLabels);
                        container = tmp.get(key);

                        if (container == null) {
                            container = downstreamSupplier.get();
                            tmp.put(key, container);
                        }

                        downstreamAccumulator.accept(container, valueExtractor.apply(rs, columnLabels));
                    }

                    for (Map.Entry<K, D> entry : result.entrySet()) {
                        entry.setValue(downstreamFinisher.apply((A) entry.getValue()));
                    }

                    return result;
                }
            };
        }

        public static <K, V> ResultExtractor<Map<K, List<V>>> groupTo(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor) {
            return groupTo(keyExtractor, valueExtractor, Suppliers.<K, List<V>> ofMap());
        }

        public static <K, V, M extends Map<K, List<V>>> ResultExtractor<M> groupTo(final RowMapper<K> keyExtractor, final RowMapper<V> valueExtractor,
                final Supplier<? extends M> supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(supplier, "supplier");

            return new ResultExtractor<M>() {
                @Override
                public M extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    final int offset = jdbcSettings.getOffset();
                    int count = jdbcSettings.getCount();

                    JdbcUtil.skip(rs, offset);

                    final M result = supplier.get();
                    K key = null;
                    List<V> value = null;

                    while (count-- > 0 && rs.next()) {
                        key = keyExtractor.apply(rs);
                        value = result.get(key);

                        if (value == null) {
                            value = new ArrayList<>();
                            result.put(key, value);
                        }

                        value.add(valueExtractor.apply(rs));
                    }

                    return result;
                }
            };
        }

        public static <K, V> ResultExtractor<Map<K, List<V>>> groupTo(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor) {
            return groupTo(keyExtractor, valueExtractor, Suppliers.<K, List<V>> ofMap());
        }

        public static <K, V, M extends Map<K, List<V>>> ResultExtractor<M> groupTo(final BiRowMapper<K> keyExtractor, final BiRowMapper<V> valueExtractor,
                final Supplier<? extends M> supplier) {
            N.checkArgNotNull(keyExtractor, "keyExtractor");
            N.checkArgNotNull(valueExtractor, "valueExtractor");
            N.checkArgNotNull(supplier, "supplier");

            return new ResultExtractor<M>() {
                @Override
                public M extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
                    final int offset = jdbcSettings.getOffset();
                    int count = jdbcSettings.getCount();

                    JdbcUtil.skip(rs, offset);

                    final List<String> columnLabels = JdbcUtil.getColumnLabelList(rs);
                    final M result = supplier.get();
                    K key = null;
                    List<V> value = null;

                    while (count-- > 0 && rs.next()) {
                        key = keyExtractor.apply(rs, columnLabels);
                        value = result.get(key);

                        if (value == null) {
                            value = new ArrayList<>();
                            result.put(key, value);
                        }

                        value.add(valueExtractor.apply(rs, columnLabels));
                    }

                    return result;
                }
            };
        }
    }

    /**
     * Refer to http://landawn.com/introduction-to-jdbc.html about how to read columns/rows from java.sql.ResultSet
     *
     * @author Haiyang Li
     *
     */
    static interface ResultSetExtractor<T> {
        public T extractData(final Class<?> targetClass, final NamedSQL namedSQL, final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException;
    }

    public static abstract class AbstractStatementSetter implements StatementSetter {
        @SuppressWarnings("rawtypes")
        @Override
        public void setParameters(final NamedSQL namedSQL, final PreparedStatement stmt, final Object... parameters) throws SQLException {
            final int parameterCount = namedSQL.getParameterCount();

            if (parameterCount == 0) {
                return;
            } else if (N.isNullOrEmpty(parameters)) {
                throw new IllegalArgumentException(
                        "The count of parameter in sql is: " + namedSQL.getParameterCount() + ". But the specified parameters is null or empty");
            }

            Object[] parameterValues = null;
            Type[] parameterTypes = null;

            if (isEntityOrMapParameter(namedSQL, parameters)) {
                final List<String> namedParameters = namedSQL.getNamedParameters();
                Object parameter_0 = parameters[0];

                parameterValues = new Object[parameterCount];

                if (parameter_0 instanceof Map) {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> m = (Map<String, Object>) parameter_0;

                    for (int i = 0; i < parameterCount; i++) {
                        parameterValues[i] = m.get(namedParameters.get(i));

                        if ((parameterValues[i] == null) && !m.containsKey(namedParameters.get(i))) {
                            throw new IllegalArgumentException("Parameter for property '" + namedParameters.get(i) + "' is missed");
                        }
                    }
                } else {
                    final Object entity = parameter_0;
                    final Class<?> cls = entity.getClass();
                    final EntityInfo entityInfo = ParserUtil.getEntityInfo(cls);
                    parameterTypes = new Type[parameterCount];
                    PropInfo propInfo = null;

                    for (int i = 0; i < parameterCount; i++) {
                        propInfo = entityInfo.getPropInfo(namedParameters.get(i));

                        if (propInfo == null) {
                            throw new IllegalArgumentException("Parameter for property '" + namedParameters.get(i) + "' is missed");
                        }

                        parameterValues[i] = propInfo.getPropValue(entity);
                        parameterTypes[i] = propInfo.dbType;
                    }
                }
            } else {
                parameterValues = getParameterValues(namedSQL, parameters);
            }

            setParameters(stmt, parameterCount, parameterValues, parameterTypes);
        }

        @SuppressWarnings("rawtypes")
        protected abstract void setParameters(PreparedStatement stmt, int parameterCount, Object[] parameters, Type[] parameterTypes) throws SQLException;

        protected Object[] getParameterValues(final NamedSQL namedSQL, final Object... parameters) {
            if ((parameters.length == 1) && (parameters[0] != null)) {
                if (parameters[0] instanceof Object[] && ((((Object[]) parameters[0]).length) >= namedSQL.getParameterCount())) {
                    return (Object[]) parameters[0];
                } else if (parameters[0] instanceof List && (((List<?>) parameters[0]).size() >= namedSQL.getParameterCount())) {
                    final Collection<?> c = (Collection<?>) parameters[0];
                    return c.toArray(new Object[c.size()]);
                }
            }

            return parameters;
        }
    }

    public static abstract class AbstractResultExtractor<T> implements ResultExtractor<T> {
        @Override
        public T extractData(final ResultSet rs, final JdbcSettings jdbcSettings) throws SQLException {
            return convert(JdbcUtil.extractData(rs, jdbcSettings.getOffset(), jdbcSettings.getCount(), false));
        }

        protected abstract T convert(final DataSet dataSet);
    }

    public static class JdbcSettings {
        public static final String DEFAULT_GENERATED_ID_PROP_NAME = "id";
        public static final int DEFAULT_BATCH_SIZE = 200;
        public static final int DEFAULT_NO_GENERATED_KEYS = Statement.NO_GENERATED_KEYS;
        public static final int DEFAULT_FETCH_DIRECTION = ResultSet.FETCH_FORWARD;
        public static final int DEFAULT_RESULT_SET_TYPE = ResultSet.TYPE_FORWARD_ONLY;
        public static final int DEFAULT_RESULT_SET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;
        public static final int DEFAULT_RESULT_SET_HOLDABILITY = ResultSet.HOLD_CURSORS_OVER_COMMIT;
        private boolean logSQL = false;
        private boolean logSQLWithParameters = false;
        private int batchSize = -1;
        private int queryTimeout = -1;
        private boolean autoGeneratedKeys = false;
        private int[] returnedColumnIndexes = null;
        private String[] returnedColumnNames = null;
        private int maxRows = -1;
        private int maxFieldSize = -1;
        private int fetchSize = -1;
        private int fetchDirection = -1;
        private int resultSetType = -1;
        private int resultSetConcurrency = -1;
        private int resultSetHoldability = -1;
        private int offset = 0;
        private int count = Integer.MAX_VALUE;
        private String queryWithDataSource;
        private Collection<String> queryWithDataSources;
        private boolean queryInParallel = false;
        private IsolationLevel isolationLevel = null;

        private boolean streamTransactionIndependent = false;

        private boolean fozen = false;

        protected JdbcSettings() {
        }

        public static JdbcSettings create() {
            return new JdbcSettings();
        }

        public JdbcSettings copy() {
            JdbcSettings copy = new JdbcSettings();
            copy.logSQL = this.logSQL;
            copy.batchSize = this.batchSize;
            copy.queryTimeout = this.queryTimeout;
            copy.autoGeneratedKeys = this.autoGeneratedKeys;
            copy.returnedColumnIndexes = (this.returnedColumnIndexes == null) ? null : N.copyOf(this.returnedColumnIndexes, this.returnedColumnIndexes.length);
            copy.returnedColumnNames = (this.returnedColumnNames == null) ? null : N.copyOf(this.returnedColumnNames, this.returnedColumnNames.length);
            copy.maxRows = this.maxRows;
            copy.maxFieldSize = this.maxFieldSize;
            copy.fetchSize = this.fetchSize;
            copy.fetchDirection = this.fetchDirection;
            copy.resultSetType = this.resultSetType;
            copy.resultSetConcurrency = this.resultSetConcurrency;
            copy.resultSetHoldability = this.resultSetHoldability;
            copy.offset = this.offset;
            copy.count = this.count;
            copy.queryWithDataSource = this.queryWithDataSource;
            copy.queryWithDataSources = this.queryWithDataSources == null ? null : new ArrayList<>(this.queryWithDataSources);
            copy.queryInParallel = this.queryInParallel;
            copy.isolationLevel = this.isolationLevel;
            copy.streamTransactionIndependent = this.streamTransactionIndependent;

            return copy;
        }

        public boolean isLogSQL() {
            return logSQL;
        }

        public JdbcSettings setLogSQL(final boolean logSQL) {
            assertNotFrozen();

            this.logSQL = logSQL;

            return this;
        }

        public boolean isLogSQLWithParameters() {
            return logSQLWithParameters;
        }

        public JdbcSettings setLogSQLWithParameters(final boolean logSQLWithParameters) {
            assertNotFrozen();

            this.logSQLWithParameters = logSQLWithParameters;

            return this;
        }

        public int getBatchSize() {
            return batchSize;
        }

        public JdbcSettings setBatchSize(final int batchSize) {
            assertNotFrozen();

            this.batchSize = batchSize;

            return this;
        }

        public int getQueryTimeout() {
            return queryTimeout;
        }

        public JdbcSettings setQueryTimeout(final int queryTimeout) {
            assertNotFrozen();

            this.queryTimeout = queryTimeout;

            return this;
        }

        public boolean isAutoGeneratedKeys() {
            return autoGeneratedKeys;
        }

        public JdbcSettings setAutoGeneratedKeys(final boolean autoGeneratedKeys) {
            assertNotFrozen();

            this.autoGeneratedKeys = autoGeneratedKeys;

            return this;
        }

        /**
         *
         * @return
         * @see {@link Connection#prepareStatement(String, int[])}
         */
        public int[] getReturnedColumnIndexes() {
            return returnedColumnIndexes;
        }

        /**
         *
         * @param columnIndexes
         * @return
         * @see {@link Connection#prepareStatement(String, int[])}
         */
        public JdbcSettings setReturnedColumnIndexes(final int[] columnIndexes) {
            assertNotFrozen();

            this.returnedColumnIndexes = columnIndexes;

            return this;
        }

        /**
         *
         * @return
         * @see {@link Connection#prepareStatement(String, String[])}
         */
        public String[] getReturnedColumnNames() {
            return returnedColumnNames;
        }

        /**
         *
         * @param columnNames
         * @return
         * @see {@link Connection#prepareStatement(String, String[])}
         */
        public JdbcSettings setReturnedColumnNames(final String[] columnNames) {
            assertNotFrozen();

            this.returnedColumnNames = columnNames;

            return this;
        }

        public int getMaxRows() {
            return maxRows;
        }

        public JdbcSettings setMaxRows(final int maxRows) {
            assertNotFrozen();

            this.maxRows = maxRows;

            return this;
        }

        public int getMaxFieldSize() {
            return maxFieldSize;
        }

        public JdbcSettings setMaxFieldSize(final int maxFieldSize) {
            assertNotFrozen();

            this.maxFieldSize = maxFieldSize;

            return this;
        }

        public int getFetchSize() {
            return fetchSize;
        }

        public JdbcSettings setFetchSize(final int fetchSize) {
            assertNotFrozen();

            this.fetchSize = fetchSize;

            return this;
        }

        public int getFetchDirection() {
            return fetchDirection;
        }

        public JdbcSettings setFetchDirection(final int fetchDirection) {
            assertNotFrozen();

            this.fetchDirection = fetchDirection;

            return this;
        }

        public int getResultSetType() {
            return resultSetType;
        }

        public JdbcSettings setResultSetType(final int resultSetType) {
            assertNotFrozen();

            this.resultSetType = resultSetType;

            return this;
        }

        public int getResultSetConcurrency() {
            return resultSetConcurrency;
        }

        public JdbcSettings setResultSetConcurrency(final int resultSetConcurrency) {
            assertNotFrozen();

            this.resultSetConcurrency = resultSetConcurrency;

            return this;
        }

        public int getResultSetHoldability() {
            return resultSetHoldability;
        }

        public JdbcSettings setResultSetHoldability(final int resultSetHoldability) {
            assertNotFrozen();

            this.resultSetHoldability = resultSetHoldability;

            return this;
        }

        public int getOffset() {
            return offset;
        }

        public JdbcSettings setOffset(final int offset) {
            assertNotFrozen();

            this.offset = offset;

            return this;
        }

        public int getCount() {
            return count;
        }

        public JdbcSettings setCount(final int count) {
            assertNotFrozen();

            this.count = count;

            return this;
        }

        public String getQueryWithDataSource() {
            return queryWithDataSource;
        }

        public JdbcSettings setQueryWithDataSource(final String queryWithDataSource) {
            assertNotFrozen();

            this.queryWithDataSource = queryWithDataSource;

            return this;
        }

        public Collection<String> getQueryWithDataSources() {
            return queryWithDataSources;
        }

        public JdbcSettings setQueryWithDataSources(final Collection<String> queryWithDataSources) {
            assertNotFrozen();

            this.queryWithDataSources = queryWithDataSources;

            return this;
        }

        public boolean isQueryInParallel() {
            return queryInParallel;
        }

        public JdbcSettings setQueryInParallel(final boolean queryInParallel) {
            assertNotFrozen();

            this.queryInParallel = queryInParallel;

            return this;
        }

        public IsolationLevel getIsolationLevel() {
            return isolationLevel;
        }

        public JdbcSettings setIsolationLevel(IsolationLevel isolationLevel) {
            assertNotFrozen();

            this.isolationLevel = isolationLevel;

            return this;
        }

        /**
         *
         * @return
         */
        boolean streamTransactionIndependent() {
            return streamTransactionIndependent;
        }

        /**
         * {@code streamTransactionIndependent = true} means the query executed by {@code stream/streamAll(...)} methods won't be in any transaction(using connection started by transaction), even the {@code stream/streamAll(...)} methods are invoked inside of a transaction block.
         *
         * @param streamTransactionIndependent
         * @return
         */
        JdbcSettings setStreamTransactionIndependent(final boolean streamTransactionIndependent) {
            assertNotFrozen();

            this.streamTransactionIndependent = streamTransactionIndependent;

            return this;
        }

        void freeze() {
            fozen = true;
        }

        void assertNotFrozen() {
            if (fozen) {
                throw new AbacusException("It's finalized. No change is allowed");
            }
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = (prime * result) + (logSQL ? 1231 : 1237);
            result = (prime * result) + (logSQLWithParameters ? 1231 : 1237);
            result = (prime * result) + batchSize;
            result = (prime * result) + queryTimeout;
            result = (prime * result) + (autoGeneratedKeys ? 1231 : 1237);
            result = (prime * result) + Arrays.hashCode(returnedColumnIndexes);
            result = (prime * result) + Arrays.hashCode(returnedColumnNames);
            result = (prime * result) + maxRows;
            result = (prime * result) + maxFieldSize;
            result = (prime * result) + fetchSize;
            result = (prime * result) + fetchDirection;
            result = (prime * result) + resultSetType;
            result = (prime * result) + resultSetConcurrency;
            result = (prime * result) + resultSetHoldability;
            result = (prime * result) + offset;
            result = (prime * result) + count;
            result = (prime * result) + ((queryWithDataSource == null) ? 0 : queryWithDataSource.hashCode());
            result = (prime * result) + ((queryWithDataSources == null) ? 0 : queryWithDataSources.hashCode());
            result = (prime * result) + (queryInParallel ? 1231 : 1237);
            result = (prime * result) + ((isolationLevel == null) ? 0 : isolationLevel.hashCode());
            result = (prime * result) + (streamTransactionIndependent ? 1231 : 1237);

            return result;
        }

        @Override
        public boolean equals(final Object obj) {
            if (this == obj) {
                return true;
            }

            if (obj instanceof JdbcSettings) {
                JdbcSettings other = (JdbcSettings) obj;

                return N.equals(logSQL, other.logSQL) && N.equals(logSQLWithParameters, other.logSQLWithParameters) && N.equals(batchSize, other.batchSize)
                        && N.equals(queryTimeout, other.queryTimeout) && N.equals(autoGeneratedKeys, other.autoGeneratedKeys)
                        && N.equals(returnedColumnIndexes, other.returnedColumnIndexes) && N.equals(returnedColumnNames, other.returnedColumnNames)
                        && N.equals(maxRows, other.maxRows) && N.equals(maxFieldSize, other.maxFieldSize) && N.equals(fetchSize, other.fetchSize)
                        && N.equals(fetchDirection, other.fetchDirection) && N.equals(resultSetType, other.resultSetType)
                        && N.equals(resultSetConcurrency, other.resultSetConcurrency) && N.equals(resultSetHoldability, other.resultSetHoldability)
                        && N.equals(offset, other.offset) && N.equals(count, other.count) && N.equals(queryWithDataSource, other.queryWithDataSource)
                        && N.equals(queryWithDataSources, other.queryWithDataSources) && N.equals(queryInParallel, other.queryInParallel)
                        && N.equals(isolationLevel, other.isolationLevel) && N.equals(streamTransactionIndependent, other.streamTransactionIndependent);
            }

            return false;
        }

        @Override
        public String toString() {
            return "{logSQL=" + logSQL + ", logSQLWithParameters=" + logSQLWithParameters + ", batchSize=" + batchSize + ", queryTimeout=" + queryTimeout
                    + ", autoGeneratedKeys=" + autoGeneratedKeys + ", returnedColumnIndexes=" + N.toString(returnedColumnIndexes) + ", returnedColumnNames="
                    + N.toString(returnedColumnNames) + ", maxRows=" + maxRows + ", maxFieldSize=" + maxFieldSize + ", fetchSize=" + fetchSize
                    + ", fetchDirection=" + fetchDirection + ", resultSetType=" + resultSetType + ", resultSetConcurrency=" + resultSetConcurrency
                    + ", resultSetHoldability=" + resultSetHoldability + ", offset=" + offset + ", count=" + count + ", queryWithDataSource="
                    + queryWithDataSource + ", queryWithDataSources=" + queryWithDataSources + ", queryInParallel=" + queryInParallel + ", isolationLevel="
                    + isolationLevel + ", streamTransactionIndependent=" + streamTransactionIndependent + "}";
        }
    }
}