All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.core.DBAccessImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015, Haiyang Li.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.landawn.abacus.core;

import static com.landawn.abacus.core.EntityManagerUtil.checkConflictOptions;
import static com.landawn.abacus.core.EntityManagerUtil.checkEntityId;
import static com.landawn.abacus.core.EntityManagerUtil.checkPropsList;
import static com.landawn.abacus.core.EntityManagerUtil.checkResultHandle;
import static com.landawn.abacus.core.EntityManagerUtil.checkSelectPropNamesForGet;
import static com.landawn.abacus.core.EntityManagerUtil.entityId2Condition;
import static com.landawn.abacus.core.EntityManagerUtil.getBatchSize;
import static com.landawn.abacus.core.EntityManagerUtil.getCacheCondition;
import static com.landawn.abacus.core.EntityManagerUtil.getCacheRange;
import static com.landawn.abacus.core.EntityManagerUtil.getCount;
import static com.landawn.abacus.core.EntityManagerUtil.getHandleLiveTime;
import static com.landawn.abacus.core.EntityManagerUtil.getHandleMaxIdleTime;
import static com.landawn.abacus.core.EntityManagerUtil.getOffset;
import static com.landawn.abacus.core.EntityManagerUtil.getQueryCacheLiveTime;
import static com.landawn.abacus.core.EntityManagerUtil.getQueryCacheMaxIdleTime;
import static com.landawn.abacus.core.EntityManagerUtil.getUncachePropNames;
import static com.landawn.abacus.core.EntityManagerUtil.hasOffsetCount;
import static com.landawn.abacus.core.EntityManagerUtil.isCacheResult;
import static com.landawn.abacus.core.EntityManagerUtil.isGetByResultHandle;
import static com.landawn.abacus.core.EntityManagerUtil.isGetFromCache;
import static com.landawn.abacus.core.EntityManagerUtil.isInTransaction;
import static com.landawn.abacus.core.EntityManagerUtil.isNullOrEmptyIdValue;
import static com.landawn.abacus.core.EntityManagerUtil.isRefreshCache;
import static com.landawn.abacus.core.EntityManagerUtil.notInTransaction;
import static com.landawn.abacus.core.EntityManagerUtil.parseInsertPropsList;
import static com.landawn.abacus.core.EntityManagerUtil.parseUpdateProps;
import static com.landawn.abacus.core.EntityManagerUtil.requiresAutoGeneratedKeys;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.landawn.abacus.DataSet;
import com.landawn.abacus.EntityId;
import com.landawn.abacus.IsolationLevel;
import com.landawn.abacus.Transaction.Action;
import com.landawn.abacus.cache.Cache;
import com.landawn.abacus.cache.CacheFactory;
import com.landawn.abacus.cache.DataGrid;
import com.landawn.abacus.cache.QueryCache;
import com.landawn.abacus.condition.Condition;
import com.landawn.abacus.core.AbacusConfiguration.EntityManagerConfiguration;
import com.landawn.abacus.core.AbacusConfiguration.EntityManagerConfiguration.QueryCacheConfiguration;
import com.landawn.abacus.core.command.Command;
import com.landawn.abacus.exception.DuplicatedResultException;
import com.landawn.abacus.exception.InvalidResultHandleException;
import com.landawn.abacus.idGenerator.IdGenerator;
import com.landawn.abacus.lock.RefReentrantReadWriteLock;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.metadata.EntityDefinition;
import com.landawn.abacus.metadata.EntityDefinitionFactory;
import com.landawn.abacus.metadata.Property;
import com.landawn.abacus.pool.KeyedObjectPool;
import com.landawn.abacus.pool.PoolFactory;
import com.landawn.abacus.util.ExceptionUtil;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.Options;
import com.landawn.abacus.util.Options.Query;
import com.landawn.abacus.util.Properties;
import com.landawn.abacus.util.SQLParser;
import com.landawn.abacus.util.WD;
import com.landawn.abacus.util.u.Holder;
import com.landawn.abacus.util.u.Optional;

// TODO: Auto-generated Javadoc
/**
 *
 * @author Haiyang Li
 * @since 0.8
 */
class DBAccessImpl implements com.landawn.abacus.DBAccess {

    private static final Logger logger = LoggerFactory.getLogger(DBAccessImpl.class);

    private final KeyedObjectPool handleResultPool = PoolFactory.createKeyedObjectPool(3000, 1000);

    private final RefReentrantReadWriteLock queryCacheRefLock = new RefReentrantReadWriteLock();

    private final EntityManagerConfiguration entityManagerConfig;

    private final EntityDefinitionFactory entityDefFactory;

    private final Executant executant;

    private final Cache> dataCridCache;

    private final QueryCachePool queryCachePool;

    private final boolean isAutoRefreshQueryCache;

    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected DBAccessImpl(EntityManagerConfiguration entityManagerConfig, EntityDefinitionFactory entityDefFactory, Executant executant) {
        this.entityManagerConfig = entityManagerConfig;
        this.entityDefFactory = entityDefFactory;
        this.executant = executant;

        for (EntityDefinition entityDef : entityDefFactory.getDefinitionList()) {
            for (IdGenerator idGenerator : entityDef.getIdGeneratorList()) {
                idGenerator.initialize(executant);
            }
        }

        final QueryCacheConfiguration qcc = entityManagerConfig.getQueryCacheConfiguration();

        dataCridCache = ((qcc == null) || (qcc.getProvider() == null)) ? null : (Cache) CacheFactory.createCache(qcc.getProvider());

        queryCachePool = createQueryCachePool(qcc);

        isAutoRefreshQueryCache = (qcc == null) ? QueryCacheConfiguration.DEFAULT_AUTO_REFRESH : qcc.isAutoRefresh();
    }

    /**
     * Creates the query cache pool.
     *
     * @param qcc
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private QueryCachePool createQueryCachePool(QueryCacheConfiguration qcc) {
        if (qcc == null) {
            return new QueryCachePool(QueryCacheConfiguration.DEFAULT_CAPACITY, QueryCacheConfiguration.DEFAULT_EVICT_DELAY, null);
        } else {
            return new QueryCachePool(qcc.getCapacity(), qcc.getEvictDelay(), qcc);
        }
    }

    /**
     *
     * @param 
     * @param entityId
     * @return
     */
    @Override
    public  Optional get(EntityId entityId) {
        return Optional.ofNullable((T) gett(entityId));
    }

    /**
     *
     * @param 
     * @param entityId
     * @param selectPropNames
     * @return
     */
    @Override
    public  Optional get(EntityId entityId, Collection selectPropNames) {
        return Optional.ofNullable((T) gett(entityId, selectPropNames));
    }

    /**
     *
     * @param 
     * @param entityId
     * @param selectPropNames
     * @param options
     * @return
     */
    @Override
    public  Optional get(EntityId entityId, Collection selectPropNames, Map options) {
        return Optional.ofNullable((T) gett(entityId, selectPropNames, options));
    }

    /**
     * Gets the t.
     *
     * @param 
     * @param entityId
     * @return
     */
    @Override
    public  T gett(EntityId entityId) {
        return gett(entityId, null, null);
    }

    /**
     * Gets the t.
     *
     * @param 
     * @param entityId
     * @param selectPropNames
     * @return
     */
    @Override
    public  T gett(EntityId entityId, Collection selectPropNames) {
        return gett(entityId, selectPropNames, null);
    }

    /**
     * Gets the t.
     *
     * @param 
     * @param entityId
     * @param selectPropNames
     * @param options
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  T gett(EntityId entityId, Collection selectPropNames, Map options) {
        checkEntityId(getEntityDefinitionFactory(), entityId);

        checkOffsetCountForGet(options);

        final String entityName = entityId.entityName();
        List entities = (List) list(entityName, selectPropNames, entityId2Condition(entityId), options);

        if (entities.size() > 1) {
            throw new DuplicatedResultException(
                    "More than one records are found by one entity Id. [entityId]: " + entityId + ". [entities]: " + N.toString(entities));
        }

        return (entities.size() > 0) ? entities.get(0) : null;
    }

    /**
     * Check offset count for get.
     *
     * @param options
     */
    private void checkOffsetCountForGet(Map options) {
        if (hasOffsetCount(options)) {
            throw new IllegalArgumentException(
                    "Can't set offset[" + options.get(Query.OFFSET) + "] or count[" + options.get(Query.COUNT) + "] options when get entity by EntityId(s). ");
        }
    }

    /**
     *
     * @param 
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @return
     */
    @Override
    public  List list(String entityName, Collection selectPropNames, Condition condition) {
        return list(entityName, selectPropNames, condition, null);
    }

    /**
     *
     * @param 
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @param options
     * @return
     */
    @SuppressWarnings("unchecked")
    @Override
    public  List list(String entityName, Collection selectPropNames, Condition condition, Map options) {
        final EntityDefinition entityDef = checkEntityName(entityName);

        checkSelectPropNamesForGet(entityDef, selectPropNames);

        DataSet dataSet = query(entityName, selectPropNames, condition, null, true, options);

        return (List) dataSet2Entities(entityDef, dataSet);
    }

    /**
     *
     * @param entityName
     * @param props
     * @param options
     * @return
     */
    @Override
    public EntityId add(String entityName, Map props, Map options) {
        return addAll(entityName, ParametersUtil.asList(props), options).get(0);
    }

    /**
     * Adds the all.
     *
     * @param entityName
     * @param propsList
     * @param options
     * @return
     */
    @Override
    public List addAll(String entityName, List> propsList, Map options) {
        return addAll(entityName, propsList, false, options);
    }

    /**
     *
     * @param entityName
     * @param propsList
     * @param isPropChecked
     * @param options
     * @return
     */
    List addAll(String entityName, List> propsList, boolean isPropChecked, Map options) {
        final EntityDefinition entityDef = checkEntityName(entityName);

        checkPropsList(propsList);

        int batchSize = getBatchSize(options);
        int size = propsList.size();

        if (size <= batchSize) {
            return addEntities(entityDef, propsList, isPropChecked, options);
        } else {
            List entityIds = new ArrayList<>(size);
            String transactionId = null;

            if (notInTransaction(options)) {
                transactionId = startDefaultTransactionForUpdate(options);
                options = ParametersUtil.copy(options);
                options.put(Options.TRANSACTION_ID, transactionId);
            }

            boolean noException = false;
            incrementRefCount();

            try {
                for (int from = 0, to = Math.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
                    entityIds.addAll(addEntities(entityDef, propsList.subList(from, to), isPropChecked, options));
                }

                noException = true;
            } finally {
                decrementRefCount();

                if (transactionId != null) {
                    if (noException) {
                        endTransaction(transactionId, Action.COMMIT, null);
                    } else {
                        try {
                            endTransaction(transactionId, Action.ROLLBACK, null);
                        } catch (Exception e) {
                            // ignore
                            logger.error("Failed to roll back with transaction id: " + transactionId + ". " + ExceptionUtil.getMessage(e), e);
                        }
                    }
                }
            }

            return entityIds;
        }
    }

    /**
     * Check entity name.
     *
     * @param entityName
     * @return
     */
    private EntityDefinition checkEntityName(String entityName) {
        return EntityManagerUtil.checkEntityName(entityDefFactory, entityName);
    }

    /**
     * Adds the entities.
     *
     * @param entityDef
     * @param propsList
     * @param isPropChecked
     * @param options
     * @return
     */
    private List addEntities(EntityDefinition entityDef, List> propsList, boolean isPropChecked, Map options) {
        propsList = isPropChecked ? propsList : checkInsertPropsList(entityDef, propsList);

        boolean isAutoGeneratedKeys = requiresAutoGeneratedKeys(entityDef, propsList);

        List entityIds = null;
        SQLResult result = null;

        incrementRefCount();

        try {
            Command command = executant.getInterpreter().interpretAdd(entityDef, propsList, options);

            result = executeUpdate(command, isAutoGeneratedKeys, options);

            List autoGeneratedkeys = null;

            if (isAutoGeneratedKeys) {
                autoGeneratedkeys = result.getGeneratedKeys();

                // SQL Server/DB2 don't return the auto-generated ids for batch
                // insertion.

                // if (autoGeneratedkeys.size() != propsList.size()) {
                // throw new UncheckedSQLException(
                // "Succeed to add entity. but the size of auto-generated ids isn't equal to the size of properties list. The auto-generated ids are: "
                // + N.toString(autoGeneratedkeys));
                // }
            }

            entityIds = createEntityId(entityDef, propsList, autoGeneratedkeys);
        } finally {
            decrementRefCount();

            // [TODO] no lock/synchronize? query may be executed before the
            // update and put to cache pool later.
            if (N.notNullOrEmpty(entityIds) && isAutoRefreshQueryCache && (queryCachePool.size() > 0)) {
                Command updateCmd = executant.getInterpreter().interpretUpdate(entityDef, propsList.get(0), entityId2Condition(entityIds), options);

                queryCachePool.updateCache(updateCmd, options);
            }
        }

        return entityIds;
    }

    /**
     * Creates the entity id.
     *
     * @param entityDef
     * @param propsList
     * @param autoGeneratedkeys
     * @return
     */
    @SuppressWarnings("deprecation")
    private List createEntityId(EntityDefinition entityDef, List> propsList, List autoGeneratedkeys) {
        int size = propsList.size();
        final List entityIds = new ArrayList<>(size);
        final List idPropList = entityDef.getIdPropertyList();
        Seid entityId = null;
        Object idPropValue = null;

        for (int index = 0, i = 0; i < size; i++) {
            entityId = Seid.of(entityDef.getName());

            for (Property idProp : idPropList) {
                idPropValue = propsList.get(i).get(idProp.getName());

                if (idPropValue == null) {
                    idPropValue = propsList.get(i).get(idProp.getName());
                }

                if (isNullOrEmptyIdValue(idPropValue) && N.notNullOrEmpty(autoGeneratedkeys)) {
                    idPropValue = autoGeneratedkeys.get(index++);
                    propsList.get(i).put(idProp.getName(), idPropValue);
                }

                entityId.set(idProp.getName(), N.convert(idPropValue, idProp.getType().clazz()));
            }

            entityIds.add(entityId);
        }

        return entityIds;
    }

    //    @Override
    //    public List add(String entityName, List propNamesToInsert, Condition condition, Map options)  {
    //        EntityDefinition entityDef = checkEntityName(entityName);
    //
    //        if ((condition == null) || !(condition instanceof SubQuery)) {
    //            throw new IllegalArgumentException("The specified condition must be a SubQuery condition. but it's: " + N.toString(condition));
    //        }
    //
    //        DataSet resultList = null;
    //        Command command = (Command) executor.getInterpreter(null).interpretCondition(entityDef, condition);
    //        SQLResult queryResult = executeQuery(entityName, command, options);
    //
    //        try {
    //            resultList = getResultListBySQLResult(queryResult, null, options);
    //        } finally {
    //            queryResult.close();
    //        }
    //
    //        if (propNamesToInsert != null) {
    //            if (resultList.getColumnNameList().size() != propNamesToInsert.size()) {
    //                throw new IllegalArgumentException("The DataSet generated by Sub Query doesn't include all the necessary propNamesToInsert: "
    //                        + propNamesToInsert.toString());
    //            }
    //
    //            for (int i = 0; i < propNamesToInsert.size(); i++) {
    //                resultList.changeColumnName(i, propNamesToInsert.get(i));
    //            }
    //        }
    //
    //        List> propsList = resultList2PropsList(resultList);
    //
    //        return add(entityName, propsList, options);
    //    }

    /**
     * Update.
     * @param props
     * @param entityId
     * @return
     */
    @Override
    public int update(Map props, EntityId entityId) {
        return update(props, entityId, null);
    }

    /**
     * Update.
     * @param props
     * @param entityId
     * @param options
     * @return
     */
    @Override
    public int update(Map props, EntityId entityId, Map options) {
        checkEntityId(getEntityDefinitionFactory(), entityId);

        final String entityName = entityId.entityName();

        return update(entityName, props, entityId2Condition(entityId), options);
    }

    /**
     * Update all.
     * @param props
     * @param entityIds
     * @return
     */
    @Override
    public int updateAll(Map props, List entityIds) {
        return updateAll(props, entityIds, null);
    }

    /**
     * Update all.
     * @param props
     * @param entityIds
     * @param options
     * @return
     */
    @Override
    public int updateAll(Map props, List entityIds, Map options) {
        checkEntityId(getEntityDefinitionFactory(), entityIds);

        final String entityName = entityIds.get(0).entityName();

        return update(entityName, props, entityId2Condition(entityIds), options);
    }

    /**
     *
     * @param entityName
     * @param props
     * @param condition
     * @param options
     * @return
     */
    @Override
    public int update(String entityName, Map props, Condition condition, Map options) {
        return update(entityName, props, condition, false, options);
    }

    /**
     *
     * @param entityName
     * @param props
     * @param condition
     * @param isPropChecked
     * @param options
     * @return
     */
    int update(String entityName, Map props, Condition condition, boolean isPropChecked, Map options) {
        final EntityDefinition entityDef = checkEntityName(entityName);

        if (N.isNullOrEmpty(props)) {
            throw new IllegalArgumentException("The parameter props can't be null or empty");
        }

        props = isPropChecked ? props : checkUpateProps(entityDef, props);

        final Command command = executant.getInterpreter().interpretUpdate(entityDef, props, condition, options);

        int result = 0;
        incrementRefCount();

        try {
            if (isAutoRefreshQueryCache && (queryCachePool.size() > 0)) {
                queryCachePool.updateCache(command, options);
            }

            result = executeUpdate(command, false, options).getUpateCount();
        } finally {
            decrementRefCount();

            if ((result > 0) && isAutoRefreshQueryCache && (queryCachePool.size() > 0)) {
                queryCachePool.updateCache(command, options);
            }
        }

        return result;
    }

    /**
     *
     * @param entityId
     * @return
     */
    @Override
    public int delete(EntityId entityId) {
        return delete(entityId, null);
    }

    /**
     *
     * @param entityId
     * @param options
     * @return
     */
    @Override
    public int delete(EntityId entityId, Map options) {
        checkEntityId(getEntityDefinitionFactory(), entityId);

        final String entityName = entityId.entityName();

        return delete(entityName, entityId2Condition(entityId), options);
    }

    /**
     *
     * @param entityIds
     * @return
     */
    @Override
    public int deleteAll(List entityIds) {
        return deleteAll(entityIds, null);
    }

    /**
     *
     * @param entityIds
     * @param options
     * @return
     */
    @Override
    public int deleteAll(List entityIds, Map options) {
        checkEntityId(getEntityDefinitionFactory(), entityIds);

        final String entityName = entityIds.get(0).entityName();

        return delete(entityName, entityId2Condition(entityIds), options);
    }

    /**
     *
     * @param entityName
     * @param condition
     * @param options
     * @return
     */
    @Override
    public int delete(String entityName, Condition condition, Map options) {
        final EntityDefinition entityDef = checkEntityName(entityName);
        final Command command = executant.getInterpreter().interpretDelete(entityDef, condition, options);

        int result = 0;
        incrementRefCount();

        try {
            if (isAutoRefreshQueryCache && (queryCachePool.size() > 0)) {
                queryCachePool.updateCache(command, options);
            }

            result = executeUpdate(command, false, options).getUpateCount();
        } finally {
            decrementRefCount();
        }

        return result;
    }

    /**
     *
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @return
     */
    @Override
    public DataSet query(String entityName, Collection selectPropNames, Condition condition) {
        return query(entityName, selectPropNames, condition, null, null);
    }

    /**
     *
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @param resultHandle
     * @param options
     * @return
     */
    @Override
    public DataSet query(String entityName, Collection selectPropNames, Condition condition, Holder resultHandle, Map options) {
        return query(entityName, selectPropNames, condition, resultHandle, false, options);
    }

    /**
     *
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @param resultHandle
     * @param isGet
     * @param options
     * @return
     */
    DataSet query(String entityName, Collection selectPropNames, Condition condition, Holder resultHandle, boolean isGet,
            Map options) {
        EntityDefinition entityDef = checkEntityName(entityName);
        selectPropNames = checkSelectPropNames(entityDef, selectPropNames, resultHandle, isGet);

        checkConflictOptions(options);

        boolean isRefreshCache = isRefreshCache(options);
        boolean isCacheResult = isCacheResult(options);
        boolean isGetFromCache = isGetFromCache(options);
        boolean isInTransaction = isInTransaction(options);

        // May cache dirty data if the update in transaction which is rolled backed.
        if ((isCacheResult || isGetFromCache) && isInTransaction) {
            throw new IllegalArgumentException("Can't cache or get result from cache in transaction. ");
        }

        final String cacheKey = ((isCacheResult || isGetFromCache || isRefreshCache) && (resultHandle == null)) ? createQueryCacheKey(entityDef, condition)
                : null;

        boolean needCloseResult = true;
        SQLResult queryResult = null;
        QueryCache queryCache = null;
        DataSet cachedResult = null;

        if (resultHandle == null) {
            if (isRefreshCache) {
                queryCachePool.remove(cacheKey);
            } else if (isGetFromCache) {
                queryCache = queryCachePool.get(cacheKey);
                cachedResult = (queryCache == null) ? null : getResultFromCache(queryCache, selectPropNames, options);
            }

            if (isCacheResult && (queryCache == null)) {
                queryCache = createQueryCache(cacheKey, options);
            }
        } else {
            if (isGetByResultHandle(resultHandle)) {
                HandleResult handleResult = getHandleResult(resultHandle.value());
                queryResult = handleResult.getSQLResult();

                if (isRefreshCache) {
                    handleResult.refreshQueryCache();
                } else if (isGetFromCache) {
                    queryCache = handleResult.getQueryCache();
                    cachedResult = (queryCache == null) ? null : getResultFromCache(queryCache, selectPropNames, options);
                }

                if (isCacheResult && (queryCache == null)) {
                    queryCache = createQueryCache(options);
                }
            } else {
                queryResult = getSQLResultWithHandleBySearch(entityDef, selectPropNames, condition, options);
            }

            needCloseResult = false;
        }

        DataSet dataSet = null;
        boolean isAllResultCached = false;

        try {
            // create returned result.
            if (cachedResult == null) {
                if (queryResult == null) {
                    queryResult = getSQLResultBySearch(entityDef, selectPropNames, condition, options);
                }

                dataSet = getResultListBySQLResult(queryResult, selectPropNames, options);
            } else {
                if (cachedResult.columnNameList().containsAll(selectPropNames)) {
                    dataSet = cachedResult;
                    isAllResultCached = true;
                } else {
                    List uncachePropNames = new ArrayList<>(selectPropNames);
                    uncachePropNames.removeAll(cachedResult.columnNameList());

                    if (queryResult == null) {
                        queryResult = getSQLResultBySearch(entityDef, uncachePropNames, condition, options);
                    }

                    DataSet uncachedResult = getResultListBySQLResult(queryResult, uncachePropNames, options);

                    dataSet = combineResultList(uncachedResult, cachedResult);
                }
            }

            if (isCacheResult && (queryCache != null) && (!isAllResultCached || options.containsKey(Options.Cache.CACHE_RESULT_RANGE))) {
                if (queryResult == null) {
                    queryResult = getSQLResultBySearch(entityDef, selectPropNames, condition, options);
                }

                needCloseResult = cacheResult(queryCache, cacheKey, selectPropNames, queryResult, needCloseResult, options);
            }
        } finally {
            if (needCloseResult && (queryResult != null)) {
                queryResult.close();
            }
        }

        // Keep query result.
        saveHandleResult(resultHandle, entityDef, selectPropNames, queryResult, queryCache, options);

        return dataSet;
    }

    /**
     *
     * @param queryCache
     * @param cacheKey
     * @param selectPropNames
     * @param queryResult
     * @param closeResult
     * @param options
     * @return true, if successful
     */
    @SuppressWarnings("unchecked")
    private boolean cacheResult(QueryCache queryCache, String cacheKey, final Collection selectPropNames, final SQLResult queryResult,
            boolean closeResult, final Map options) {
        // cache key is null when resultHandle is set.
        if ((cacheKey == null) || (queryCache == queryCachePool.get(cacheKey))) {
            Collection cachePropNames = selectPropNames;
            Object uncachedPropNames = getUncachePropNames(options);

            if (uncachedPropNames != null) {
                cachePropNames = new ArrayList<>(selectPropNames);

                if (uncachedPropNames instanceof Collection) {
                    cachePropNames.removeAll((Collection) uncachedPropNames);
                } else {
                    cachePropNames.remove(uncachedPropNames);
                }
            }

            Options.Cache.Condition cacheCond = getCacheCondition(entityManagerConfig.getQueryCacheConfiguration(), options);
            Options.Cache.Range range = getCacheRange(options);

            if (Query.CACHE_RESULT_SYNC.equals(options.get(Query.CACHE_RESULT))) {
                queryCache.cacheResult(queryResult, cachePropNames, cacheCond, range);
            } else if (Query.CACHE_RESULT_ASYNC.equals(options.get(Query.CACHE_RESULT))) {
                queryCache.asyncCacheResult(queryResult, cachePropNames, cacheCond, range, closeResult);
                closeResult = false;
            } else {
                throw new IllegalArgumentException(
                        "Invalid 'CACHE_RESULT' option '" + options.get(Query.CACHE_RESULT) + "'. It must be 'CACHE_RESULT_SYN' or 'CACHE_RESULT_ASY'. ");
            }
        }

        return closeResult;
    }

    //    @Override
    //    public ResultSet queryBy(String entityName, String sql, Object parameters, Handle resultHandle, Map options)  {
    //        EntityDefinition entityDef = checkEntityName(entityName);
    //
    //        if (isCacheResult(options) || isGetFromCache(options) || isRefreshCache(options)) {
    //            throw new IllegalArgumentException("'queryBy' doesn't support 'CACHE_RESULT', 'FROM_CACHE' and 'REFRESH_CACHE' option. ");
    //        }
    //
    //        checkConflictOptions(options);
    //
    //        NamedSQL namedSQL = getNamedQuery(sql);
    //        Map attrs = namedSQL.getAttribes();
    //
    //        if (!N.isNullOrEmpty(attrs)) {
    //            if ((attrs.get(SQLMapper.BATCH_SIZE) != null) && ((options == null) || (options.get(Options.BATCH_SIZE) == null))) {
    //                options = InternalParametersUtil.copy(options);
    //                options.put(Options.BATCH_SIZE, Numbers.toInt(attrs.get(SQLMapper.BATCH_SIZE)));
    //            }
    //
    //            if ((attrs.get(SQLMapper.FETCH_SIZE) != null) && ((options == null) || (options.get(Options.Jdbc.FETCH_SIZE) == null))) {
    //                options = InternalParametersUtil.copy(options);
    //                options.put(Options.Jdbc.FETCH_SIZE, Numbers.toInt(attrs.get(SQLMapper.FETCH_SIZE)));
    //            }
    //
    //            if ((attrs.get(SQLMapper.RESULT_SET_TYPE) != null) && ((options == null) || (options.get(Options.Jdbc.RESULT_SET_TYPE) == null))) {
    //                options = InternalParametersUtil.copy(options);
    //                options.put(Options.Jdbc.RESULT_SET_TYPE, SQLMapper.RESULT_SET_TYPE_MAP.get(attrs.get(SQLMapper.RESULT_SET_TYPE)));
    //            }
    //        }
    //
    //        sql = namedSQL.getPureSQL();
    //
    //        List parameterList = getNamedParameters(namedSQL, parameters);
    //        boolean needCloseResult = true;
    //        ResultSet resultList = null;
    //        SQLResult queryResult = null;
    //
    //        try {
    //            if (resultHandle == null) {
    //                Command command = executor.getInterpreter(null).interpretQueryBy(entityDef, sql, parameterList, options);
    //                queryResult = executeQuery(entityName, command, options);
    //            } else {
    //                // get result from handle or create result handle.
    //                if (isGetByResultHandle(resultHandle, options)) {
    //                    queryResult = getHandleResult(resultHandle).getSQLResult();
    //                } else {
    //                    Command command = executor.getInterpreter(null).interpretQueryBy(entityDef, sql, parameterList, options);
    //                    queryResult = getExecutant().executeQueryWithHandle(command, options);
    //                }
    //
    //                needCloseResult = false;
    //            }
    //
    //            resultList = getResultListBySQLResult(queryResult, null, options);
    //        } finally {
    //            if (needCloseResult && (queryResult != null)) {
    //                queryResult.close();
    //            }
    //        }
    //
    //        // Keep query result.
    //        saveHandleResult(resultHandle, entityDef, null, queryResult, null, options);
    //
    //        return resultList;
    //    }

    /**
     * Gets the result by handle.
     *
     * @param resultHandle
     * @param selectPropNames
     * @param options
     * @return
     */
    @Override
    public DataSet getResultByHandle(String resultHandle, Collection selectPropNames, Map options) {
        final HandleResult handleResult = getHandleResult(resultHandle);
        final EntityDefinition entityDef = handleResult.getEntityDef();

        return query(entityDef.getName(), selectPropNames, null, Holder.of(resultHandle), options);
    }

    /**
     * Release result handle.
     *
     * @param resultHandle
     */
    @Override
    public void releaseResultHandle(String resultHandle) {
        checkResultHandle(resultHandle);

        handleResultPool.remove(resultHandle);
    }

    /**
     *
     * @param isolationLevel
     * @param options
     * @return
     */
    @Override
    public String beginTransaction(IsolationLevel isolationLevel, Map options) {
        if (isolationLevel == null) {
            throw new IllegalArgumentException("The parameter isolationLevel can't be null");
        }

        final String result = executant.beginTransaction(isolationLevel, options);

        incrementRefCount();

        return result;
    }

    /**
     * Start default transaction for update.
     *
     * @param options
     * @return
     */
    String startDefaultTransactionForUpdate(Map options) {
        return beginTransaction(IsolationLevel.DEFAULT, options);
    }

    /**
     *
     * @param transactionId
     * @param transactionAction
     * @param options
     */
    @Override
    public void endTransaction(String transactionId, Action transactionAction, Map options) {
        N.checkArgNotNullOrEmpty(transactionId, "TransactionId");

        if (transactionAction == null) {
            throw new IllegalArgumentException("The parameter transactionAction can't be null");
        }

        try {
            executant.endTransaction(transactionId, transactionAction, options);
        } finally {
            decrementRefCount();
        }
    }

    /**
     * Gets the entity definition factory.
     *
     * @return
     */
    @Override
    public EntityDefinitionFactory getEntityDefinitionFactory() {
        return entityDefFactory;
    }

    /**
     * Gets the executant.
     *
     * @return
     */
    Executant getExecutant() {
        return executant;
    }

    /**
     *
     * @param command
     * @param isAutoGeneratedKeys
     * @param options
     * @return
     */
    private SQLResult executeUpdate(Command command, boolean isAutoGeneratedKeys, Map options) {
        return executant.executeUpdate(command, isAutoGeneratedKeys, options);
    }

    /**
     *
     * @param command
     * @param options
     * @return
     */
    SQLResult executeQuery(Command command, Map options) {
        return executant.executeQuery(command, options);
    }

    /**
     * Gets the result list by SQL result.
     *
     * @param queryResult
     * @param selectPropNames
     * @param options
     * @return
     */
    DataSet getResultListBySQLResult(SQLResult queryResult, Collection selectPropNames, Map options) {
        synchronized (queryResult) {
            return queryResult.getResultSet(selectPropNames, options);
        }
    }

    /**
     * Gets the handle result.
     *
     * @param resultHandle
     * @return
     */
    HandleResult getHandleResult(String resultHandle) {
        checkResultHandle(resultHandle);

        final HandleResult queryResult = handleResultPool.get(resultHandle);

        if (queryResult == null) {
            throw new

            InvalidResultHandleException("Invalid result handle: " + resultHandle);
        }

        return queryResult;

    }

    /**
     * Increment ref count.
     */
    private void incrementRefCount() {
        if (isAutoRefreshQueryCache) {
            queryCacheRefLock.writeLock().lock();

            try {
                queryCacheRefLock.incrementRefCount();
            } finally {
                queryCacheRefLock.writeLock().unlock();
            }
        }
    }

    /**
     * Decrement ref count.
     */
    private void decrementRefCount() {
        if (isAutoRefreshQueryCache) {
            queryCacheRefLock.decrementRefCount();
        }
    }

    /**
     * Gets the SQL result by search.
     *
     * @param entityDef
     * @param selectPropNames
     * @param condition
     * @param options
     * @return
     */
    private SQLResult getSQLResultBySearch(EntityDefinition entityDef, Collection selectPropNames, Condition condition, Map options) {
        final Command command = executant.getInterpreter().interpretQuery(entityDef, selectPropNames, condition, options);

        return executeQuery(command, options);
    }

    /**
     * Gets the SQL result with handle by search.
     *
     * @param entityDef
     * @param selectPropNames
     * @param condition
     * @param options
     * @return
     */
    private SQLResult getSQLResultWithHandleBySearch(EntityDefinition entityDef, Collection selectPropNames, Condition condition,
            Map options) {
        final Command command = executant.getInterpreter().interpretQuery(entityDef, selectPropNames, condition, options);

        return executant.executeQueryWithHandle(command, options);
    }

    /**
     * Gets the cached prop names.
     *
     * @param queryCache
     * @param selectPropNames
     * @param options
     * @return
     */
    private List getCachedPropNames(QueryCache queryCache, Collection selectPropNames, Map options) {
        if (queryCache.size() < 0) {
            return null;
        }

        int offset = getOffset(options);
        int count = getCount(options);

        int fromIndex = (offset > queryCache.size()) ? queryCache.size() : offset;
        int endIndex = (count > (queryCache.size() - offset)) ? queryCache.size() : (count + offset);
        List cachedPropNames = new ArrayList<>();

        Options.Cache.Range range = Options.Cache.range(fromIndex, endIndex);

        for (String propName : selectPropNames) {
            if (queryCache.isCachedResult(propName, range)) {
                cachedPropNames.add(propName);
            }
        }

        return cachedPropNames;
    }

    /**
     * Gets the result from cache.
     *
     * @param queryCache
     * @param selectPropNames
     * @param options
     * @return
     */
    private DataSet getResultFromCache(QueryCache queryCache, Collection selectPropNames, Map options) {
        if (queryCache != null) {
            queryCache.lock();

            try {
                List cachedPropNames = getCachedPropNames(queryCache, selectPropNames, options);

                if (N.notNullOrEmpty(cachedPropNames)) {
                    int offset = getOffset(options);
                    int count = getCount(options);

                    int fromIndex = (offset > queryCache.size()) ? queryCache.size() : offset;
                    int endIndex = (count > (queryCache.size() - offset)) ? queryCache.size() : (count + offset);

                    Object[][] columnArray = queryCache.getResult(cachedPropNames, fromIndex, endIndex);

                    // The cache may be cleared because of SoftReference after
                    // getCachedPropNames.
                    // Need to check it again
                    if (columnArray.length > 0) {
                        List> columnList = new ArrayList<>(columnArray.length);

                        for (int i = 0; i < columnArray.length; i++) {
                            columnList.add(N.asList(columnArray[i]));
                        }

                        final Properties properties = new Properties<>();
                        properties.put(RowDataSet.CACHED_PROP_NAMES, cachedPropNames);

                        return new RowDataSet(new ArrayList<>(cachedPropNames), columnList, properties);
                    }
                }
            } finally {
                queryCache.unlock();
            }
        }

        return null;
    }

    /**
     * Combine result list.
     *
     * @param sourceResult
     * @param targetResult
     * @return
     */
    private DataSet combineResultList(DataSet sourceResult, DataSet targetResult) {
        // the cache result has 'cache properties' cmd,
        // the search result doesn't has this cmd.
        if (targetResult == null) {
            return sourceResult;
        } else {
            int propNum = sourceResult.columnNameList().size();

            for (int i = 0; i < propNum; i++) {
                targetResult.addColumn(sourceResult.getColumnName(i), sourceResult.getColumn(i));
            }

            sourceResult.clear();
        }

        return targetResult;
    }

    /**
     * Data set 2 entities.
     *
     * @param 
     * @param entityDef
     * @param result
     * @return
     */
    private  List dataSet2Entities(EntityDefinition entityDef, DataSet result) {
        return (List) DataSetUtil.row2Entity(entityDef.getTypeClass(), (RowDataSet) result, entityDef, 0, result.size());
    }

    /**
     * Check select prop names.
     *
     * @param entityDef
     * @param selectPropNames
     * @param resultHandle
     * @param isGet
     * @return
     */
    private Collection checkSelectPropNames(EntityDefinition entityDef, Collection selectPropNames, Holder resultHandle,
            boolean isGet) {
        if (N.isNullOrEmpty(selectPropNames)) {
            if ((resultHandle != null) && N.notNullOrEmpty(resultHandle.value())) {
                return getHandleResult(resultHandle.value()).getSelectPropNames();
            } else {
                return entityDef.getDefaultLoadPropertyNameList();
            }
        }

        final List selectPropNameList = new ArrayList<>(selectPropNames.size());

        for (String propName : selectPropNames) {
            Property prop = entityDef.getProperty(propName);
            if (prop != null && prop.getEntityDefinition() == entityDef) {
                selectPropNameList.add(prop.getName());
            } else {
                selectPropNameList.add(propName);
            }
        }

        if (isGet && N.notNullOrEmpty(entityDef.getIdPropertyList())) {
            for (Property idProp : entityDef.getIdPropertyList()) {
                if (!selectPropNameList.contains(idProp.getName())) {
                    selectPropNameList.add(idProp.getName());
                }
            }
        }

        return selectPropNameList;
    }

    /**
     * Check insert props list.
     *
     * @param entityDef
     * @param propsList
     * @return
     */
    private List> checkInsertPropsList(EntityDefinition entityDef, List> propsList) {
        final InsertPropsListView insertPropsListView = parseInsertPropsList(entityDef, propsList, false);

        return insertPropsListView.writePropsList;
    }

    /**
     * Check upate props.
     *
     * @param entityDef
     * @param props
     * @return
     */
    private Map checkUpateProps(EntityDefinition entityDef, Map props) {
        final UpdatePropsView updatePropsView = parseUpdateProps(entityDef, props, false);

        return updatePropsView.updateProps;
    }

    /**
     * Save handle result.
     *
     * @param resultHandle
     * @param entityDef
     * @param selectPropNames
     * @param queryResult
     * @param queryCache
     * @param options
     */
    private void saveHandleResult(Holder resultHandle, EntityDefinition entityDef, Collection selectPropNames, SQLResult queryResult,
            QueryCache queryCache, Map options) {
        if ((resultHandle != null)) {
            if (resultHandle.value() == null) {
                resultHandle.setValue(N.uuid());
                long liveTime = getHandleLiveTime(options);
                long maxIdleTime = getHandleMaxIdleTime(options);
                HandleResult handleResult = new HandleResult(entityDef, new ArrayList<>(selectPropNames), queryResult, queryCache, liveTime, maxIdleTime);
                if (!handleResultPool.put(resultHandle.value(), handleResult)) {
                    handleResult.close();

                    throw new RuntimeException(
                            "Failed to save the handled result because the pool is full. The max pool capacity is: " + handleResultPool.getCapacity());
                }
            } else {
                if ((queryCache != null) && (handleResultPool.get(resultHandle.value()).getQueryCache() == null)) {
                    handleResultPool.get(resultHandle.value()).setQueryCache(queryCache);
                }
            }
        }
    }

    /**
     * Creates the query cache key.
     *
     * @param entityDef
     * @param condition
     * @return
     */
    private String createQueryCacheKey(EntityDefinition entityDef, Condition condition) {
        final String cacheKey = condition == null ? entityDef.getName() : condition.toString();

        if (N.notNullOrEmpty(cacheKey)) {
            if ((SQLParser.indexWord(cacheKey, WD.LIMIT, 0, false) >= 0) || (SQLParser.indexWord(cacheKey, WD.OFFSET, 0, false) >= 0)) {
                throw new IllegalArgumentException("'LIMIT' or 'OFFSET' are supported for cached related operation");
            }
        }

        return cacheKey;
    }

    /**
     * Creates the query cache.
     *
     * @param cacheKey
     * @param options
     * @return
     */
    private QueryCache createQueryCache(String cacheKey, final Map options) {
        queryCacheRefLock.readLock().lock();

        try {
            QueryCache queryCache = queryCachePool.get(cacheKey);

            if ((queryCache == null) && (queryCacheRefLock.getRefCount() == 0)) {
                queryCache = createQueryCache(options);
                queryCachePool.put(cacheKey, queryCache);
            }

            return queryCache;
        } finally {
            queryCacheRefLock.readLock().unlock();
        }
    }

    /**
     * Creates the query cache.
     *
     * @param options
     * @return
     */
    private QueryCache createQueryCache(Map options) {
        final long liveTime = getQueryCacheLiveTime(entityManagerConfig.getQueryCacheConfiguration(), options);
        final long maxIdleTime = getQueryCacheMaxIdleTime(entityManagerConfig.getQueryCacheConfiguration(), options);

        return new SQLQueryCache(liveTime, maxIdleTime, dataCridCache);
    }
}