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

com.landawn.abacus.core.EntityManagerImpl 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.checkPropsList;
import static com.landawn.abacus.core.EntityManagerUtil.checkResultHandle;
import static com.landawn.abacus.core.EntityManagerUtil.checkSelectPropNamesForGet;
import static com.landawn.abacus.core.EntityManagerUtil.cleanUpDirtyPropNames;
import static com.landawn.abacus.core.EntityManagerUtil.entity2Map;
import static com.landawn.abacus.core.EntityManagerUtil.entityId2Condition;
import static com.landawn.abacus.core.EntityManagerUtil.getBatchSize;
import static com.landawn.abacus.core.EntityManagerUtil.getEntityIdByEntity;
import static com.landawn.abacus.core.EntityManagerUtil.getPropValue;
import static com.landawn.abacus.core.EntityManagerUtil.getUpdatedProps;
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.isResultCombined;
import static com.landawn.abacus.core.EntityManagerUtil.parseInsertPropsList;
import static com.landawn.abacus.core.EntityManagerUtil.parseSelectPropNamesInGet;
import static com.landawn.abacus.core.EntityManagerUtil.parseSelectPropNamesInQuery;
import static com.landawn.abacus.core.EntityManagerUtil.parseUpdateProps;
import static com.landawn.abacus.core.EntityManagerUtil.removeOffsetCount;
import static com.landawn.abacus.core.EntityManagerUtil.resultSet2EntityId;
import static com.landawn.abacus.core.EntityManagerUtil.setIdValue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.landawn.abacus.DataSet;
import com.landawn.abacus.EntityId;
import com.landawn.abacus.IsolationLevel;
import com.landawn.abacus.LockMode;
import com.landawn.abacus.Transaction.Action;
import com.landawn.abacus.condition.Condition;
import com.landawn.abacus.condition.ConditionFactory.CF;
import com.landawn.abacus.condition.Criteria;
import com.landawn.abacus.condition.CriteriaUtil;
import com.landawn.abacus.condition.Expression;
import com.landawn.abacus.condition.Or;
import com.landawn.abacus.core.AbacusConfiguration.EntityManagerConfiguration;
import com.landawn.abacus.core.command.Command;
import com.landawn.abacus.core.command.SQLCommand;
import com.landawn.abacus.exception.DuplicatedResultException;
import com.landawn.abacus.logging.Logger;
import com.landawn.abacus.logging.LoggerFactory;
import com.landawn.abacus.metadata.Association;
import com.landawn.abacus.metadata.Association.JoinType;
import com.landawn.abacus.metadata.EntityDefinition;
import com.landawn.abacus.metadata.EntityDefinitionFactory;
import com.landawn.abacus.metadata.OnDeleteAction;
import com.landawn.abacus.metadata.Property;
import com.landawn.abacus.util.ExceptionUtil;
import com.landawn.abacus.util.N;
import com.landawn.abacus.util.OperationType;
import com.landawn.abacus.util.Options;
import com.landawn.abacus.util.Options.Query;
import com.landawn.abacus.util.u.Holder;

// TODO: Auto-generated Javadoc
/**
 *
 * @author Haiyang Li
 * @param 
 * @since 0.8
 */
class EntityManagerImpl extends AbstractEntityManager {

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

    private static final Condition NON_EXISTED_CONDITION = CF.eq("1", "2");

    private final Map, SelectPropNameView>> getSelectPropNameViewPool = new ConcurrentHashMap<>();

    private final Map, SelectPropNameView>> querySelectPropNameViewPool = new ConcurrentHashMap<>();

    private final Map, CachedQueryCmd>>> queryCmdPool = new HashMap<>();

    protected final DBAccessImpl dbAccess;

    protected EntityManagerImpl(EntityManagerConfiguration entityManagerConfig, DBAccessImpl dbAccess) {
        super(dbAccess.getEntityDefinitionFactory().domainName(), entityManagerConfig);
        this.dbAccess = dbAccess;
    }

    /**
     * Internal get entity definition factory.
     *
     * @return
     */
    @Override
    protected EntityDefinitionFactory internalGetEntityDefinitionFactory() {
        return dbAccess.getEntityDefinitionFactory();
    }

    /**
     *
     * @param 
     * @param targetClass
     * @param entityId
     * @param selectPropNames
     * @param options
     * @return
     */
    @Override
    protected  T internalGet(Class targetClass, EntityId entityId, Collection selectPropNames, Map options) {
        List entities = internalGet(targetClass, N.asList(entityId), selectPropNames, options);

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

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

    /**
     *
     * @param 
     * @param targetClass
     * @param entityIds
     * @param selectPropNames
     * @param options
     * @return
     */
    @Override
    protected  List internalGet(Class targetClass, List entityIds, Collection selectPropNames,
            Map options) {
        final EntityDefinition entityDef = checkEntityId(entityIds.get(0));

        if (targetClass == null) {
            targetClass = entityDef.getTypeClass();
        }

        if (hasOffsetCount(options)) {
            throw new IllegalArgumentException("Can not set offset[" + options.get(Query.OFFSET) + "] or count[" + options.get(Query.COUNT)
                    + "] options when get entity by EntityId(s). ");
        }

        List entities = getEntities(targetClass, entityDef, entityIds, selectPropNames, options);

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

        return entities;
    }

    /**
     * Gets the entities.
     *
     * @param 
     * @param targetClass
     * @param entityDef
     * @param entityIds
     * @param selectPropNames
     * @param options
     * @return
     */
    @SuppressWarnings("unchecked")
    protected  List getEntities(Class targetClass, final EntityDefinition entityDef, List entityIds,
            Collection selectPropNames, Map options) {
        if (targetClass == null) {
            targetClass = entityDef.getTypeClass();
        }

        if (entityIds.size() == 0) {
            return new ArrayList<>();
        }

        final int batchSize = getBatchSize(options);

        if (entityIds.size() <= batchSize) {
            return getEntities(targetClass, entityDef, selectPropNames, entityId2Condition(entityIds), options);
        } else {
            List entityList = new ArrayList<>(entityIds.size());

            for (int size = entityIds.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = N.min(from + batchSize, size)) {
                entityList.addAll(getEntities(targetClass, entityDef, selectPropNames, entityId2Condition(entityIds, from, to), options));
            }

            return entityList;
        }
    }

    /**
     * Gets the entities.
     *
     * @param 
     * @param targetClass
     * @param entityDef
     * @param selectPropNames
     * @param condition
     * @param options
     * @return
     */
    protected  List getEntities(Class targetClass, final EntityDefinition entityDef, Collection selectPropNames, Condition condition,
            Map options) {
        SelectPropNameView xa = checkSelectPropNamesInGet(entityDef, selectPropNames);

        DataSet rs = dbAccess.query(entityDef.getName(), xa.simplePropNames, condition, null, options);

        if (rs.size() > 0) {
            if (xa.entityPropNames.size() > 0) {
                options = removeOffsetCount(options);

                for (String propName : xa.entityPropNames) {
                    Property entityProp = entityDef.getProperty(propName);
                    Map propValueEntityMap = getPropEntity(entityProp, rs, options);

                    final List column = new ArrayList<>(rs.size());
                    final Property srcProp = entityProp.getAssociation().getSrcProperty();
                    final int srcPropIndex = rs.getColumnIndex(srcProp.getName());

                    for (int i = 0; i < rs.size(); i++) {
                        rs.absolute(i);
                        column.add(propValueEntityMap.get(rs.get(srcPropIndex)));
                    }

                    rs.addColumn(propName, column);
                }
            }
        }

        return resultList2Entities(targetClass, entityDef, rs);
    }

    /**
     * Check select prop names in get.
     *
     * @param entityDef
     * @param selectPropNames
     * @return
     */
    private SelectPropNameView checkSelectPropNamesInGet(final EntityDefinition entityDef, Collection selectPropNames) {
        SelectPropNameView xa = null;
        Map, SelectPropNameView> entitySelectPropNamesView = getSelectPropNameViewPool.get(entityDef.getName());

        if (entitySelectPropNamesView == null) {
            entitySelectPropNamesView = new HashMap<>();
            getSelectPropNameViewPool.put(entityDef.getName(), entitySelectPropNamesView);
        } else {
            xa = entitySelectPropNamesView.get(selectPropNames);
        }

        if (xa == null) {
            checkSelectPropNamesForGet(entityDef, selectPropNames);

            xa = parseSelectPropNamesInGet(entityDef, selectPropNames);

            synchronized (getSelectPropNameViewPool) {
                entitySelectPropNamesView.put(selectPropNames, xa);
            }
        }

        return xa;
    }

    /**
     *
     * @param cls
     * @param prop
     * @param dataSet
     * @param isResultCombined
     */
    protected void composeProperty(Class cls, Property prop, DataSet dataSet, boolean isResultCombined) {
        EntityDefinition columnEntityDef = prop.getColumnEntityDef();

        Class propClass = null;

        if ((cls == null) || MapEntity.class.isAssignableFrom(cls)) {
            propClass = columnEntityDef.getTypeClass();
        } else {
            propClass = prop.getGetMethod(cls).getReturnType();
        }

        final List entities = DataSetUtil.row2Entity(propClass, (RowDataSet) dataSet, columnEntityDef, 0, dataSet.size());

        dataSet.addColumn(prop.getName(), entities);

        if (isResultCombined && prop.isCollection()) {
            final EntityDefinition entityDef = prop.getEntityDefinition();
            final String[] idPropNames = new String[entityDef.getIdPropertyList().size()];

            for (int i = 0, size = entityDef.getIdPropertyList().size(); i < size; i++) {
                idPropNames[i] = entityDef.getIdPropertyList().get(i).getName();
            }

            DataSetUtil.combine((RowDataSet) dataSet, prop, idPropNames);
        }
    }

    /**
     * Gets the prop entity.
     *
     * @param entityProp
     * @param rs
     * @param options
     * @return
     */
    @SuppressWarnings({ "unchecked" })
    protected Map getPropEntity(Property entityProp, DataSet rs, Map options) {
        if (N.isNullOrEmpty(rs)) {
            return new HashMap<>();
        }

        final EntityDefinition columnEntityDef = entityProp.getColumnEntityDef();
        final String propEntityName = columnEntityDef.getName();
        final Association association = entityProp.getAssociation();
        final Property srcProp = association.getSrcProperty();
        final int srcPropIndex = rs.getColumnIndex(srcProp.getName());

        final Property targetProp = association.getTargetProperty();
        final String targetPropName = targetProp.getName();

        final EntityDefinition biEntityDef = association.getBiEntityDef();

        boolean isIdReference = targetProp.isId();

        final int batchSize = getBatchSize(options);
        final Class propEntityClass = columnEntityDef.getTypeClass();
        final Collection propEntitySelectPropNames = entityProp.getSubPropertyNameList();

        final Map propValueEntityMap = new HashMap<>();
        final List propEntityList = new ArrayList<>();

        if (biEntityDef == null) {
            if (isIdReference) {
                final List entityIds = new ArrayList<>(rs.size());

                for (int i = 0; i < rs.size(); i++) {
                    rs.absolute(i);
                    entityIds.add(EntityId.of(propEntityName, targetPropName, rs.get(srcPropIndex)));
                }

                if (N.notNullOrEmpty(entityIds)) {
                    List entities = internalGet(propEntityClass, entityIds, propEntitySelectPropNames, options);

                    if (N.notNullOrEmpty(entities)) {
                        propEntityList.addAll(entities);
                    }
                }
            } else {
                for (int size = rs.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
                    Or propEntityCond = CF.or();

                    for (int i = from; i < to; i++) {
                        rs.absolute(i);

                        propEntityCond.add(CF.eq(targetPropName, rs.get(srcPropIndex)));
                    }

                    Condition cond = N.isNullOrEmpty(entityProp.getOrderBy()) ? propEntityCond
                            : CF.criteria().where(propEntityCond).orderBy(CF.expr(entityProp.getOrderBy()));

                    List entities = internalList(propEntityClass, propEntityName, propEntitySelectPropNames, cond, options);

                    if (N.notNullOrEmpty(entities)) {
                        for (Object entity : entities) {
                            propEntityList.add(entity);
                        }
                    }
                }
            }

            Object foreignPropValue = null;
            for (Object entity : propEntityList) {
                foreignPropValue = getPropValue(entity, targetProp);

                if (entityProp.isCollection()) {
                    Collection c = (Collection) propValueEntityMap.get(foreignPropValue);

                    if (c == null) {
                        c = entityProp.asCollection();
                        propValueEntityMap.put(foreignPropValue, c);
                    }

                    c.add(entity);
                } else {
                    propValueEntityMap.put(foreignPropValue, entity);
                }
            }

        } else {
            final Property leftProp = association.getBiEntityProperties()[0];
            final Property rightProp = association.getBiEntityProperties()[1];

            final List biSelectPropNames = N.asList(leftProp.getName(), rightProp.getName());
            final Map> rightLeftPropValueMap = new HashMap<>();

            for (int size = rs.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
                Or or = CF.or();

                for (int i = from; i < to; i++) {
                    rs.absolute(i);

                    or.add(CF.eq(leftProp.getName(), rs.get(srcPropIndex)));
                }

                DataSet biResultSet = dbAccess.query(biEntityDef.getName(), biSelectPropNames, or, null, options);

                if (N.isNullOrEmpty(biResultSet)) {
                    continue;
                }

                if (isIdReference) {
                    final List entityIds = new ArrayList<>(biResultSet.size());

                    for (int i = 0; i < biResultSet.size(); i++) {
                        biResultSet.absolute(i);

                        entityIds.add(EntityId.of(propEntityName, targetPropName, biResultSet.get(1)));
                    }

                    List entities = internalGet(propEntityClass, entityIds, propEntitySelectPropNames, options);

                    if (N.notNullOrEmpty(entities)) {
                        propEntityList.addAll(entities);
                    }
                } else {
                    Or propEntityCond = CF.or();

                    for (int i = 0; i < biResultSet.size(); i++) {
                        biResultSet.absolute(i);

                        propEntityCond.add(CF.eq(targetPropName, biResultSet.get(1)));
                    }

                    Condition cond = N.isNullOrEmpty(entityProp.getOrderBy()) ? propEntityCond
                            : CF.criteria().where(propEntityCond).orderBy(CF.expr(entityProp.getOrderBy()));

                    List entities = internalList(propEntityClass, propEntityName, propEntitySelectPropNames, cond, options);

                    if (N.notNullOrEmpty(entities)) {
                        for (Object entity : entities) {
                            propEntityList.add(entity);
                        }
                    }
                }

                Object rightPropValue = null;
                for (int i = 0; i < biResultSet.size(); i++) {
                    rightPropValue = biResultSet.absolute(i).get(1);

                    Collection c = rightLeftPropValueMap.get(rightPropValue);
                    if (c == null) {
                        c = N.newHashSet();

                        rightLeftPropValueMap.put(rightPropValue, c);
                    }

                    c.add(biResultSet.get(0));
                }
            }

            // ============
            if (N.notNullOrEmpty(propEntityList)) {
                Object foreignPropValue = null;

                for (Object entity : propEntityList) {
                    foreignPropValue = getPropValue(entity, targetProp);

                    for (Object leftPropValue : rightLeftPropValueMap.get(foreignPropValue)) {
                        if (entityProp.isCollection()) {
                            Collection c = (Collection) propValueEntityMap.get(leftPropValue);

                            if (c == null) {
                                c = entityProp.asCollection();
                                propValueEntityMap.put(leftPropValue, c);
                            }

                            c.add(entity);
                        } else {
                            propValueEntityMap.put(leftPropValue, entity);
                        }
                    }
                }
            }
            // ============
        }

        return propValueEntityMap;
    }

    //    protected Class getPropEntityClass(Class clazz, Property entityProp, EntityDefinition propEntityDef) {
    //        Class propClass = null;
    //
    //        if ((clazz == null) || MapEntity.class.isAssignableFrom(clazz)) {
    //            propClass = propEntityDef.getTypeClass();
    //        } else {
    //            if (entityProp.isCollection()) {
    //                // try to find the same package POJO class.
    //                try {
    //                    propClass = N.forClass(clazz.getPackage().getName() + D.PERIOD + propEntityDef.getTypeClass().getSimpleName());
    //                } catch (ClassNotFoundException e) {
    //                    propClass = propEntityDef.getTypeClass();
    //                }
    //            } else {
    //                propClass = entityProp.getGetMethod(clazz).getReturnType();
    //            }
    //        }
    //
    //        return propClass;
    //
    //        return propEntityDef.getTypeClass();
    //    }

    /**
     *
     * @param 
     * @param targetClass
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @param options
     * @return
     */
    @Override
    protected  List internalList(Class targetClass, String entityName, Collection selectPropNames, Condition condition,
            Map options) {
        return getEntities(targetClass, checkEntityName(entityName), selectPropNames, condition, options);
    }

    /**
     *
     * @param entity
     * @param options
     * @return
     */
    @Override
    protected EntityId internalAdd(E entity, Map options) {
        return internalAdd(N.asList(entity), options).get(0);
    }

    /**
     *
     * @param entities
     * @param options
     * @return
     */
    @Override
    protected List internalAdd(Collection entities, Map options) {
        return addEntities(checkEntity(entities.iterator().next()), entities, options);
    }

    /**
     * Adds the entities.
     *
     * @param entityDef
     * @param entities
     * @param options
     * @return
     */
    protected List addEntities(final EntityDefinition entityDef, Collection entities, Map options) {
        List entityIds = addEntities(entityDef, entity2Map(entityDef, entities), options);

        int idx = 0;

        for (Object entity : entities) {
            setIdValue(entityDef, entity, entityIds.get(idx++));
        }

        cleanUpDirtyPropNames(entities);

        return entityIds;
    }

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

    /**
     *
     * @param entityName
     * @param propsList
     * @param options
     * @return
     */
    @Override
    protected List internalAdd(String entityName, List> propsList, Map options) {
        checkPropsList(propsList);

        return addEntities(checkEntityName(entityName), propsList, options);
    }

    /**
     * Adds the entities.
     *
     * @param entityDef
     * @param propsList
     * @param options
     * @return
     */
    @SuppressWarnings("unchecked")
    protected List addEntities(final EntityDefinition entityDef, List> propsList, Map options) {
        final String entityName = entityDef.getName();
        List entityIds = null;
        Map> entityProps = EntityManagerUtil.getPropEntity(entityDef, propsList);

        if (entityProps.size() == 0) {
            entityIds = dbAccess.addAll(entityName, propsList, options);
        } else {
            String transactionId = null;

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

            boolean noException = false;

            try {
                for (Property prop : entityProps.keySet()) {
                    if (prop.getAssociation().getJoinType() == JoinType.OUTER) {
                        List entityList = entityProps.get(prop);

                        if (N.notNullOrEmpty(entityList)) {
                            internalAdd((List) entityList, options);
                        }
                    }
                }

                InsertPropsListView insertPropsListView = parseInsertPropsList(entityDef, propsList, true);
                propsList = insertPropsListView.writePropsList;

                entityIds = dbAccess.addAll(entityName, propsList, true, options);

                Map> propEntitiesList = insertPropsListView.propEntityPropsList;
                Map> propBiEntityPropsList = insertPropsListView.propBiEntityPropsList;

                List propEntities = null;
                List biEntiyPropsList = null;

                for (Property prop : propEntitiesList.keySet()) {
                    if (prop.getAssociation().getJoinType() == JoinType.INNER) {
                        propEntities = propEntitiesList.get(prop);

                        if (propEntities.size() > 0) {
                            final List entities = new ArrayList<>(propEntities.size());

                            for (PropEntityProps propEntity : propEntities) {
                                entities.add(propEntity.getPropEntity());
                            }

                            internalAdd((List) entities, options);
                        }

                        if (N.notNullOrEmpty(propBiEntityPropsList)) {
                            biEntiyPropsList = propBiEntityPropsList.get(prop);

                            if (N.notNullOrEmpty(biEntiyPropsList)) {
                                List> biPropsList = new ArrayList<>(biEntiyPropsList.size());

                                for (BiEntityProps biEntityProps : biEntiyPropsList) {
                                    biPropsList.add(biEntityProps.createProps());
                                }

                                internalAdd(prop.getAssociation().getBiEntityDef().getName(), biPropsList, options);
                            }
                        }
                    }
                }

                noException = true;
            } finally {
                if (noException) {
                    commitTransaction(transactionId, options);
                } else {
                    rollbackTransaction(transactionId, options);
                }
            }
        }

        return entityIds;
    }

    /**
     *
     * @param entity
     * @param options
     * @return
     */
    @Override
    protected int internalUpdate(E entity, Map options) {
        final EntityDefinition entityDef = checkEntity(entity);
        final EntityId entityId = getEntityIdByEntity(entityDef, entity);
        final Map props = getUpdatedProps(entityDef, entity);

        int result = internalUpdate(entityId, props, options);

        cleanUpDirtyPropNames(entity);

        if (!isInTransaction(options)) {
            // can not reback for cache and version.
            // RecycableEntityId.reback(entityId);
        }

        return result;
    }

    /**
     *
     * @param entityId
     * @param props
     * @param options
     * @return
     */
    @Override
    protected int internalUpdate(EntityId entityId, Map props, Map options) {
        return internalUpdate(N.asList(entityId), props, options);
    }

    /**
     *
     * @param entities
     * @param options
     * @return
     */
    @Override
    protected int internalUpdate(Collection entities, Map options) {
        if (entities.size() == 1) {
            return internalUpdate(entities.iterator().next(), options);
        } else {
            String transactionId = null;

            if (!isInTransaction(options) && (entities.size() > 1)) {
                transactionId = dbAccess.startDefaultTransactionForUpdate(options);
                options = ParametersUtil.copy(options);
                options.put(Options.TRANSACTION_ID, transactionId);
            }

            int result = 0;
            boolean noException = false;

            try {
                for (E entity : entities) {
                    result += internalUpdate(entity, options);
                }

                noException = true;
            } finally {
                if (noException) {
                    commitTransaction(transactionId, options);
                } else {
                    rollbackTransaction(transactionId, options);
                }
            }

            return result;
        }
    }

    /**
     *
     * @param entityIds
     * @param props
     * @param options
     * @return
     */
    @Override
    protected int internalUpdate(List entityIds, Map props, Map options) {
        return updateEntities(checkEntityId(entityIds.get(0)), entityIds, props, options);
    }

    /**
     *
     * @param entityDef
     * @param entityIds
     * @param props
     * @param options
     * @return
     */
    protected int updateEntities(final EntityDefinition entityDef, List entityIds, Map props, Map options) {
        int batchSize = getBatchSize(options);

        if (entityIds.size() <= batchSize) {
            return updateEntities(entityDef, props, entityId2Condition(entityIds), options);
        } else {
            String transactionId = null;

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

            int result = 0;
            boolean noException = false;

            try {
                for (int size = entityIds.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = Math.min(from + batchSize, size)) {
                    result += updateEntities(entityDef, props, entityId2Condition(entityIds, from, to), options);
                }

                noException = true;
            } finally {
                if (noException) {
                    commitTransaction(transactionId, options);
                } else {
                    rollbackTransaction(transactionId, options);
                }
            }

            return result;
        }
    }

    /**
     *
     * @param entityName
     * @param props
     * @param condition
     * @param options
     * @return
     */
    @Override
    protected int internalUpdate(String entityName, Map props, Condition condition, Map options) {
        return updateEntities(checkEntityName(entityName), props, condition, options);
    }

    /**
     *
     * @param entityDef
     * @param props
     * @param condition
     * @param options
     * @return
     */
    @SuppressWarnings("unchecked")
    protected int updateEntities(final EntityDefinition entityDef, Map props, Condition condition, Map options) {
        if (N.isNullOrEmpty(props)) {
            throw new IllegalArgumentException("The parameter props can not be null or empty");
        }

        final String entityName = entityDef.getName();
        UpdatePropsView updatePropsView = parseUpdateProps(entityDef, props, true);
        props = updatePropsView.updateProps;

        Map> propEntiyList = updatePropsView.propEntityList;

        int result = 0;

        if (N.isNullOrEmpty(propEntiyList)) {
            result = dbAccess.update(entityName, props, condition, true, options);
        } else {
            String transactionId = null;

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

            boolean noException = false;

            try {
                result = dbAccess.update(entityName, props, condition, true, options);

                for (Property prop : propEntiyList.keySet()) {
                    List propEntities = propEntiyList.get(prop);

                    for (Object propEntity : propEntities) {
                        internalUpdate((E) propEntity, options);
                    }
                }

                noException = true;
            } finally {
                if (noException) {
                    commitTransaction(transactionId, options);
                } else {
                    rollbackTransaction(transactionId, options);
                }
            }
        }

        return result;
    }

    /**
     *
     * @param entityId
     * @param options
     * @return
     */
    @Override
    protected int internalDelete(EntityId entityId, Map options) {
        return internalDelete(N.asList(entityId), options);
    }

    /**
     *
     * @param entity
     * @param options
     * @return
     */
    @Override
    protected int internalDelete(E entity, Map options) {
        return internalDelete(N.asList(entity), options);
    }

    /**
     *
     * @param entityIds
     * @param options
     * @return
     */
    @Override
    protected int internalDelete(List entityIds, Map options) {
        return deleteEntities(checkEntityId(entityIds.get(0)), entityIds, options);
    }

    /**
     *
     * @param entityDef
     * @param entityIds
     * @param options
     * @return
     */
    protected int deleteEntities(final EntityDefinition entityDef, List entityIds, Map options) {
        int result = 0;

        final int batchSize = getBatchSize(options);
        final boolean isInTransaction = isInTransaction(options);
        String transactionId = null;

        if ((entityIds.size() > batchSize) && !isInTransaction) {
            transactionId = dbAccess.startDefaultTransactionForUpdate(options);
            options = ParametersUtil.copy(options);
            options.put(Options.TRANSACTION_ID, transactionId);
        }

        boolean noException = false;

        try {
            for (int size = entityIds.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = N.min(from + batchSize, size)) {
                List batchEntityIds = entityIds.subList(from, to);
                Condition condition = entityId2Condition(batchEntityIds);

                CascadeDeleteResult cascadeDeleteResult = deleteCascade(entityDef, batchEntityIds, condition, options);

                if (cascadeDeleteResult != null) {
                    if ((cascadeDeleteResult.transactionId != null) && (transactionId == null)) {
                        transactionId = cascadeDeleteResult.transactionId;
                        options = cascadeDeleteResult.options;
                    }

                    condition = cascadeDeleteResult.cond;
                }

                if (condition != NON_EXISTED_CONDITION) {
                    result += dbAccess.delete(entityDef.getName(), condition, options);
                }
            }

            noException = true;
        } finally {
            if (noException) {
                commitTransaction(transactionId, options);
            } else {
                // it's OK to roll back again if it has been rolled back.
                rollbackTransaction(transactionId, options);
            }
        }

        return result;
    }

    /**
     *
     * @param entities
     * @param options
     * @return
     */
    @Override
    protected int internalDelete(Collection entities, Map options) {
        final EntityDefinition entityDef = checkEntity(entities.iterator().next());
        final List entityIds = getEntityIdByEntity(entityDef, entities);

        final int result = internalDelete(entityIds, options);

        cleanUpDirtyPropNames(entities);

        return result;
    }

    /**
     *
     * @param entityName
     * @param cond
     * @param options
     * @return
     */
    @Override
    protected int internalDelete(String entityName, Condition cond, Map options) {
        final EntityDefinition entityDef = checkEntityName(entityName);

        int result = 0;
        String transactionId = null;
        boolean noException = false;

        try {
            CascadeDeleteResult cascadeDeleteResult = deleteCascade(entityDef, null, cond, options);

            if (cascadeDeleteResult != null) {
                if (cascadeDeleteResult.transactionId != null) {
                    transactionId = cascadeDeleteResult.transactionId;
                    options = cascadeDeleteResult.options;
                }

                cond = cascadeDeleteResult.cond;
            }

            if (cond != NON_EXISTED_CONDITION) {
                result = dbAccess.delete(entityName, cond, options);
            }

            noException = true;
        } finally {
            if (noException) {
                commitTransaction(transactionId, options);
            } else {
                rollbackTransaction(transactionId, options);
            }
        }

        return result;
    }

    /**
     *
     * @param entityDef
     * @param entityIds
     * @param cond
     * @param options
     * @return
     */
    protected CascadeDeleteResult deleteCascade(final EntityDefinition entityDef, List entityIds, Condition cond,
            Map options) {
        Set casadePropNames = null;

        for (Property prop : entityDef.getEntiyPropertyList()) {
            OnDeleteAction ct = prop.getOnDeleteAction();

            if (ct != OnDeleteAction.NO_ACTION) {
                if (casadePropNames == null) {
                    casadePropNames = ParametersUtil.asSet();
                }

                casadePropNames.add(prop.getAssociation().getSrcProperty().getName());
            }
        }

        if (N.isNullOrEmpty(casadePropNames)) {
            return null;
        }

        final int batchSize = getBatchSize(options);

        DataSet rs = null;

        if ((entityIds != null) && entityDef.getIdPropertyNameList().containsAll(casadePropNames)) {
            List columnNameList = new ArrayList<>(entityDef.getIdPropertyNameList());
            List> columnList = new ArrayList<>();

            for (String propName : columnNameList) {
                List column = new ArrayList<>(entityIds.size());

                for (EntityId entityId : entityIds) {
                    column.add(entityId.get(propName));
                }

                columnList.add(column);
            }

            rs = new RowDataSet(columnNameList, columnList);
        } else {
            if (entityIds == null) {
                casadePropNames.addAll(entityDef.getIdPropertyNameList());
            }

            rs = dbAccess.query(entityDef.getName(), casadePropNames, cond, null, options);

            // can not convert the original condition to the condition composed by entity ids if size is too big.
            if ((rs.size() > 0 && rs.size() <= batchSize) && (entityDef.getIdPropertyList().size() > 0)
                    && ((entityIds == null) || (entityIds.size() > rs.size()))) {
                cond = entityId2Condition(resultSet2EntityId(entityDef, rs));
            }
        }

        if (rs.size() == 0) {
            return new CascadeDeleteResult(null, NON_EXISTED_CONDITION, options);
        }

        String transactionId = null;

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

        CascadeDeleteResult cascadeDeleteResult = new CascadeDeleteResult(transactionId, cond, options);

        for (Property prop : entityDef.getEntiyPropertyList()) {
            OnDeleteAction ct = prop.getOnDeleteAction();

            if ((ct == null) || (ct == OnDeleteAction.NO_ACTION)) {
                continue;
            }

            EntityDefinition columnEntityDef = prop.getColumnEntityDef();
            String propEntityName = columnEntityDef.getName();

            Association association = prop.getAssociation();
            Property srcProp = association.getSrcProperty();
            int srcPropIndex = rs.getColumnIndex(srcProp.getName());

            Property targetProp = association.getTargetProperty();
            String targetPropName = targetProp.getName();
            boolean isIdTargetProp = targetProp.isId();

            EntityDefinition biEntityDef = association.getBiEntityDef();

            if (biEntityDef == null) {
                if (isIdTargetProp) {
                    final List propEntityEntityIds = new ArrayList<>(rs.size());

                    for (int i = 0; i < rs.size(); i++) {
                        rs.absolute(i);
                        propEntityEntityIds.add(EntityId.of(propEntityName, targetPropName, rs.get(srcPropIndex)));
                    }

                    boolean noException = false;

                    try {
                        if (ct == OnDeleteAction.CASCADE) {
                            internalDelete(propEntityEntityIds, options);
                        } else if (ct == OnDeleteAction.SET_NULL) {
                            Map props = new HashMap<>();
                            props.put(targetPropName, N.defaultValueOf(targetProp.getType().clazz()));
                            internalUpdate(propEntityEntityIds, props, options);
                        }

                        noException = true;
                    } finally {
                        if (noException) {
                            // commitTransaction(transactionId, options);
                        } else {
                            rollbackTransaction(transactionId, options);
                        }
                    }
                } else {
                    boolean noException = false;

                    try {
                        for (int size = rs.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = N.min(from + batchSize, size)) {
                            final Or targetPropCond = CF.or();

                            for (int i = from; i < to; i++) {
                                rs.absolute(i);
                                targetPropCond.add(CF.eq(targetPropName, rs.get(srcPropIndex)));
                            }

                            if (ct == OnDeleteAction.CASCADE) {
                                internalDelete(propEntityName, targetPropCond, options);
                            } else if (ct == OnDeleteAction.SET_NULL) {
                                Map props = new HashMap<>();
                                props.put(targetPropName, N.defaultValueOf(targetProp.getType().clazz()));
                                internalUpdate(propEntityName, props, targetPropCond, options);
                            }
                        }

                        noException = true;
                    } finally {
                        if (noException) {
                            // commitTransaction(transactionId, options);
                        } else {
                            rollbackTransaction(transactionId, options);
                        }
                    }
                }
            } else {
                final Property leftProp = association.getBiEntityProperties()[0];
                final Property rightProp = association.getBiEntityProperties()[1];

                final Set selectPropNames = N.newHashSet(biEntityDef.getIdPropertyNameList());
                selectPropNames.add(leftProp.getName());
                selectPropNames.add(rightProp.getName());

                boolean noException = false;

                try {
                    for (int size = rs.size(), from = 0, to = N.min(from + batchSize, size); from < size; from = to, to = N.min(from + batchSize, size)) {
                        Or leftPropCond = CF.or();

                        for (int i = from; i < to; i++) {
                            rs.absolute(i);
                            leftPropCond.add(CF.eq(leftProp.getName(), rs.get(srcPropIndex)));
                        }

                        final DataSet biEntityResultSet = dbAccess.query(biEntityDef.getName(), selectPropNames, leftPropCond, null, options);

                        if (biEntityResultSet.size() > 0) {
                            if ((ct == OnDeleteAction.SET_NULL) || (ct == OnDeleteAction.CASCADE)) {
                                if (biEntityDef.getIdPropertyList().size() > 0) {
                                    List biEntityEntityIds = resultSet2EntityId(biEntityDef, biEntityResultSet);
                                    internalDelete(biEntityEntityIds, options);
                                } else {
                                    leftPropCond = CF.or();
                                    int leftPropIndex = biEntityResultSet.getColumnIndex(leftProp.getName());

                                    for (int i = 0; i < biEntityResultSet.size(); i++) {
                                        biEntityResultSet.absolute(i);
                                        leftPropCond.add(CF.eq(leftProp.getName(), biEntityResultSet.get(leftPropIndex)));
                                    }

                                    internalDelete(biEntityDef.getName(), leftPropCond, options);
                                }
                            }

                            int rightPropIndex = biEntityResultSet.getColumnIndex(rightProp.getName());

                            if (ct == OnDeleteAction.CASCADE) {
                                if (isIdTargetProp) {
                                    final List propEntityEntityIds = new ArrayList<>(biEntityResultSet.size());

                                    for (int i = 0; i < biEntityResultSet.size(); i++) {
                                        biEntityResultSet.absolute(i);

                                        propEntityEntityIds.add(EntityId.of(propEntityName, targetPropName, biEntityResultSet.get(rightPropIndex)));
                                    }

                                    internalDelete(propEntityEntityIds, options);
                                } else {
                                    Or targetPropCond = CF.or();

                                    for (int i = 0; i < biEntityResultSet.size(); i++) {
                                        biEntityResultSet.absolute(i);
                                        targetPropCond.add(CF.eq(targetPropName, biEntityResultSet.get(rightPropIndex)));
                                    }

                                    internalDelete(propEntityName, targetPropCond, options);
                                }
                            }
                        }
                    }

                    noException = true;
                } finally {
                    if (noException) {
                        // commitTransaction(transactionId, options);
                    } else {
                        rollbackTransaction(transactionId, options);
                    }
                }
            }
        }

        return cascadeDeleteResult;
    }

    /**
     *
     * @param entityName
     * @param selectPropNames
     * @param condition
     * @param resultHandle
     * @param options
     * @return
     */
    @Override
    protected DataSet internalQuery(String entityName, Collection selectPropNames, Condition condition, Holder resultHandle,
            Map options) {
        final EntityDefinition entityDef = checkEntityName(entityName);
        SelectPropNameView xa = checkSelectPropNamesInQuery(entityDef, selectPropNames, resultHandle, options);

        DataSet dataSet = null;
        boolean isNullSelectPropNames = (selectPropNames == null) ? true : false;
        boolean isCachableCondition = false;
        String conditionKey = null;

        if (!(isGetFromCache(options) || isCacheResult(options) || (resultHandle != null))) {
            isCachableCondition = isCachable(condition);

            if (isCachableCondition) {
                conditionKey = createConditionCacheKey(entityDef, condition);

                CachedQueryCmd cachedQueryCmd = getCachedQueryCmd(conditionKey, entityDef, selectPropNames);

                if (cachedQueryCmd != null) {
                    xa = cachedQueryCmd.xa;

                    Command queryCmd = cachedQueryCmd.getCommand(condition);

                    SQLResult queryResult = dbAccess.executeQuery(queryCmd, options);

                    try {
                        dataSet = dbAccess.getResultListBySQLResult(queryResult, xa.simplePropNames, options);
                    } finally {
                        queryResult.close();
                    }
                }
            }
        }

        if (dataSet == null) {
            Criteria criteria = condition2Criteria(condition);

            if (xa.entityPropNames.size() > 0) {
                if (criteria == condition) {
                    criteria = criteria.copy();
                }

                Property prop = null;

                for (String propName : xa.entityPropNames) {
                    prop = entityDef.getProperty(propName);

                    criteria.join(prop.getAssociation().getJoinCondition());
                }
            }

            dataSet = dbAccess.query(entityName, xa.simplePropNames, criteria, resultHandle, options);

            // replace the selection name with the original input selection names.
            if ((resultHandle != null) && N.notNullOrEmpty(resultHandle.value())) {
                dbAccess.getHandleResult(resultHandle.value()).setSelectPropNames(xa.selectPropNames);
            }

            if (isCachableCondition && (resultHandle == null)) {
                cacheQueryCmd(conditionKey, entityDef, isNullSelectPropNames ? null : selectPropNames, xa, criteria, options);
            }
        }

        composeResultSet(entityDef, xa.selectPropNames, xa.entityPropNames, dataSet, options);

        return dataSet;
    }

    /**
     * Check select prop names in query.
     *
     * @param entityDef
     * @param selectPropNames
     * @param resultHandle
     * @param options
     * @return
     */
    SelectPropNameView checkSelectPropNamesInQuery(final EntityDefinition entityDef, Collection selectPropNames, Holder resultHandle,
            Map options) {
        if ((selectPropNames == null) && isGetByResultHandle(resultHandle)) {
            selectPropNames = dbAccess.getHandleResult(resultHandle.value()).getSelectPropNames();
        }

        SelectPropNameView xa = null;
        Map, SelectPropNameView> entitySelectPropNamesView = querySelectPropNameViewPool.get(entityDef.getName());

        if (entitySelectPropNamesView == null) {
            entitySelectPropNamesView = new HashMap<>();
            querySelectPropNameViewPool.put(entityDef.getName(), entitySelectPropNamesView);
        } else {
            xa = entitySelectPropNamesView.get(selectPropNames);
        }

        if (xa == null) {
            Collection newSelectPropNames = selectPropNames;

            if (newSelectPropNames == null) {
                if (isGetByResultHandle(resultHandle)) {
                    newSelectPropNames = dbAccess.getHandleResult(resultHandle.value()).getSelectPropNames();
                } else {
                    newSelectPropNames = entityDef.getDefaultLoadPropertyNameList();
                }
            }

            xa = parseSelectPropNamesInQuery(entityDef, newSelectPropNames, options);

            synchronized (querySelectPropNameViewPool) {
                entitySelectPropNamesView.put(selectPropNames, xa);
            }
        }

        return xa;
    }

    /**
     * Compose result set.
     *
     * @param entityDef
     * @param selectPropNames
     * @param entityPropNames
     * @param result
     * @param options
     */
    protected void composeResultSet(final EntityDefinition entityDef, Collection selectPropNames, Collection entityPropNames, DataSet result,
            Map options) {
        if ((result.size() > 0) && (entityPropNames.size() > 0)) {
            boolean isResultCombined = isResultCombined(options);

            for (String compositePropName : entityPropNames) {
                composeProperty(null, entityDef.getProperty(compositePropName), result, isResultCombined);
            }

            final int[] indecs = new int[result.columnNameList().size()];

            for (String propName : selectPropNames) {
                indecs[result.getColumnIndex(propName)] = 1;
            }

            final List columnNameList = new ArrayList<>(result.columnNameList());

            for (int i = 0, len = indecs.length; i < len; i++) {
                if (indecs[i] == 0) {
                    result.removeColumn(columnNameList.get(i));
                }
            }
        }
    }

    /**
     * Internal get result by handle.
     *
     * @param resultHandle
     * @param selectPropNames
     * @param options
     * @return
     */
    @Override
    protected DataSet internalGetResultByHandle(String resultHandle, Collection selectPropNames, Map options) {
        checkResultHandle(resultHandle);

        HandleResult handleResult = dbAccess.getHandleResult(resultHandle);

        final EntityDefinition entityDef = handleResult.getEntityDef();

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

    /**
     * Internal release result handle.
     *
     * @param resultHandle
     */
    @Override
    protected void internalReleaseResultHandle(String resultHandle) {
        dbAccess.releaseResultHandle(resultHandle);
    }

    /**
     * Internal begin transaction.
     *
     * @param isolationLevel
     * @param options
     * @return
     */
    @Override
    protected String internalBeginTransaction(IsolationLevel isolationLevel, Map options) {
        return dbAccess.beginTransaction(isolationLevel, options);
    }

    /**
     * Internal end transaction.
     *
     * @param transactionId
     * @param transactionAction
     * @param options
     */
    @Override
    protected void internalEndTransaction(String transactionId, Action transactionAction, Map options) {
        dbAccess.endTransaction(transactionId, transactionAction, options);
    }

    /**
     * Internal get record version.
     *
     * @param entityId
     * @param options
     * @return
     */
    @Override
    protected long internalGetRecordVersion(EntityId entityId, Map options) {
        throw new UnsupportedOperationException();
    }

    /**
     * Internal lock record.
     *
     * @param entityId
     * @param lockMode
     * @param options
     * @return
     */
    @Override
    protected String internalLockRecord(EntityId entityId, LockMode lockMode, Map options) {
        throw new UnsupportedOperationException();
    }

    /**
     * Internal unlock record.
     *
     * @param entityId
     * @param lockCode
     * @param options
     * @return true, if successful
     */
    @Override
    protected boolean internalUnlockRecord(EntityId entityId, String lockCode, Map options) {
        throw new UnsupportedOperationException();
    }

    /**
     * Condition 2 criteria.
     *
     * @param condition
     * @return
     */
    private Criteria condition2Criteria(Condition condition) {
        if (condition == null) {
            return CF.criteria();
        } else if (condition instanceof Criteria) {
            return (Criteria) condition;
        } else {
            if (!CriteriaUtil.isClause(condition)) {
                condition = CF.where(condition);
            }

            Criteria criteria = CF.criteria();
            CriteriaUtil.add(criteria, condition);

            return criteria;
        }
    }

    /**
     * Checks if is cachable.
     *
     * @param condition
     * @return true, if is cachable
     */
    private boolean isCachable(Condition condition) {
        if (condition != null) {
            for (Object propValue : condition.getParameters()) {
                if ((propValue != null) && (propValue instanceof Condition)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Gets the cached query cmd.
     *
     * @param conditionKey
     * @param entityDef
     * @param selectPropNames
     * @return
     */
    private CachedQueryCmd getCachedQueryCmd(String conditionKey, final EntityDefinition entityDef, Collection selectPropNames) {
        CachedQueryCmd cachedQueryCmd = null;

        synchronized (queryCmdPool) {
            Map, CachedQueryCmd>> entityQueryCmdPool = queryCmdPool.get(entityDef.getName());

            if (N.notNullOrEmpty(entityQueryCmdPool)) {
                Map, CachedQueryCmd> condCmdMap = entityQueryCmdPool.get(conditionKey);

                if (N.notNullOrEmpty(condCmdMap)) {
                    cachedQueryCmd = condCmdMap.get(selectPropNames);
                }
            }
        }

        return cachedQueryCmd;
    }

    /**
     * Creates the condition cache key.
     *
     * @param entityDef
     * @param condition
     * @return
     */
    private String createConditionCacheKey(final EntityDefinition entityDef, Condition condition) {
        if (condition == null) {
            return entityDef.getName();
        } else {
            // if (condition instanceof EntityId) {
            // EntityId entityId = (EntityId) condition;
            //
            // return (entityId.getPropNames().size() == 1) ?
            // entityId.getPropNames().iterator().next()
            // : entityId.getPropNames().toString();
            // } else if (condition instanceof Expression) {
            if (condition instanceof Expression) {
                return ((Expression) condition).getLiteral();
            } else {
                SQLCommand cmd = (SQLCommand) dbAccess.getExecutant().getInterpreter().interpretCondition(entityDef, condition);

                return cmd.getSql();
            }
        }
    }

    /**
     * Cache query cmd.
     *
     * @param conditionKey
     * @param entityDef
     * @param selectPropNames
     * @param xa
     * @param condition
     * @param options
     */
    private void cacheQueryCmd(String conditionKey, final EntityDefinition entityDef, Collection selectPropNames, SelectPropNameView xa,
            Condition condition, Map options) {
        Command queryCmd = dbAccess.getExecutant().getInterpreter().interpretQuery(entityDef, xa.simplePropNames, condition, options);
        queryCmd.clearParameters();

        CachedQueryCmd cachedQueryCmd = new CachedQueryCmd(queryCmd, xa);

        synchronized (queryCmdPool) {
            Map, CachedQueryCmd>> entityQueryCmdPool = queryCmdPool.get(entityDef.getName());

            if (entityQueryCmdPool == null) {
                entityQueryCmdPool = new HashMap<>();
                queryCmdPool.put(entityDef.getName(), entityQueryCmdPool);
            }

            Map, CachedQueryCmd> condCmdMap = entityQueryCmdPool.get(conditionKey);

            if (condCmdMap == null) {
                condCmdMap = new HashMap<>();
                entityQueryCmdPool.put(conditionKey, condCmdMap);
            }

            condCmdMap.put(selectPropNames, cachedQueryCmd);
        }
    }

    /**
     *
     * @param transactionId
     * @param options
     */
    void commitTransaction(String transactionId, Map options) {
        if (transactionId != null) {
            try {
                endTransaction(transactionId, Action.COMMIT, null);
            } finally {
                options.remove(Options.TRANSACTION_ID);
            }
        }
    }

    /**
     *
     * @param transactionId
     * @param options
     */
    void rollbackTransaction(String transactionId, Map options) {
        if (transactionId != null) {
            try {
                endTransaction(transactionId, Action.ROLLBACK, null);
            } catch (Exception e) {
                // ignore
                logger.error("Failed to roll back with transaction id: " + transactionId + ". " + ExceptionUtil.getMessage(e), e);
            } finally {
                options.remove(Options.TRANSACTION_ID);
            }
        }
    }

    /**
     * Result list 2 entities.
     *
     * @param 
     * @param targetClass
     * @param entityDef
     * @param result
     * @return
     */
    protected  List resultList2Entities(Class targetClass, final EntityDefinition entityDef, DataSet result) {
        if (targetClass == null) {
            targetClass = entityDef.getTypeClass();
        }

        return DataSetUtil.row2Entity(targetClass, (RowDataSet) result, entityDef, 0, result.size());
    }

    /**
     * The Class CachedQueryCmd.
     */
    static class CachedQueryCmd {

        /** The query cmd. */
        private final Command queryCmd;

        /** The xa. */
        private final SelectPropNameView xa;

        /**
         * Instantiates a new cached query cmd.
         *
         * @param queryCmd
         * @param xa
         */
        CachedQueryCmd(Command queryCmd, SelectPropNameView xa) {
            this.queryCmd = queryCmd;
            this.xa = xa;
        }

        /**
         * Gets the command.
         *
         * @param condition
         * @return
         */
        Command getCommand(Condition condition) {
            if (queryCmd != null) {
                Command copy = queryCmd.copy();

                if (condition != null) {
                    List parameters = condition.getParameters();

                    for (int index = 0; index < parameters.size(); index++) {
                        copy.setParameter(index, parameters.get(index), queryCmd.getParameterType(index));
                    }
                }

                return copy;
            } else {
                return null;
            }
        }
    }

    /**
     * The Class CascadeDeleteResult.
     */
    static class CascadeDeleteResult {

        /** The transaction id. */
        String transactionId = null;

        /** The options. */
        Map options = null;

        /** The cond. */
        Condition cond = null;

        /**
         * Instantiates a new cascade delete result.
         *
         * @param transactionId
         * @param cond
         * @param options
         */
        CascadeDeleteResult(String transactionId, Condition cond, Map options) {
            this.transactionId = transactionId;
            this.cond = cond;
            this.options = options;
        }
    }

    /**
     * The Class EntityIdMemo.
     */
    static final class EntityIdMemo {

        /** The entity ids. */
        final List entityIds;

        /** The operation type. */
        final OperationType operationType;

        /**
         * Instantiates a new entity id memo.
         *
         * @param entityIds
         * @param op
         */
        EntityIdMemo(List entityIds, OperationType op) {
            this.entityIds = entityIds;
            operationType = op;
        }
    }
}