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

shz.core.orm.AbstractOrmService Maven / Gradle / Ivy

There is a newer version: 2024.0.2
Show newest version
package shz.core.orm;

import shz.core.*;
import shz.core.constant.ArrayConstant;
import shz.core.function.ActionRunner;
import shz.core.model.PageInfo;
import shz.core.model.TreeNode;
import shz.core.msg.ClientFailureMsg;
import shz.core.orm.entity.TreeEntity;
import shz.core.orm.param.OrmConsumer;
import shz.core.orm.param.OrmFilter;
import shz.core.orm.param.OrmMapConsumer;
import shz.core.orm.param.OrmMapFilter;
import shz.core.orm.param.OrmMapping;
import shz.core.orm.service.*;
import shz.core.reference.IReference;
import shz.core.reference.LReference;
import shz.core.reference.ZReference;
import shz.core.serializable.SerializableGetter;
import shz.core.serializable.Serializer;
import shz.core.structure.limiter.Limiter;
import shz.core.tag.ixx.ILTag;
import shz.core.orm.enums.Condition;
import shz.core.orm.enums.DataType;
import shz.core.orm.sql.Sql;
import shz.core.orm.sql.ValueType;
import shz.core.orm.sql.WhereSql;
import shz.core.orm.sql.builder.SqlBuilder;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.*;
import java.util.stream.Collectors;

@SuppressWarnings("unchecked")
abstract class AbstractOrmService extends OrmServiceHelper
        implements OrmInsertService, OrmUpdateService, OrmInsertOrUpdateService, OrmDeleteService,
        OrmCursorService, OrmCountService, OrmSelectListService, OrmPageService, OrmSelectOneService, OrmTreeService {
    public final ClassInfo classInfo(Class entityClass) {
        ClassInfo classInfo = ClassInfo.CLASS_INFO_CACHE.get(entityClass);
        if (classInfo == null) classInfo = ClassInfo.load(entityClass, this);
        return classInfo;
    }

    public final ClassInfo nonNullClassInfo(Class entityClass) {
        return Objects.requireNonNull(classInfo(entityClass));
    }

    public final WhereSql whereSql(ClassInfo classInfo, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return WhereInfo.where(classInfo, fieldName, fieldValue, condition, this::builder, logic);
    }

    public final WhereSql whereSql(ClassInfo classInfo, List fieldNames, List fieldValues, List conditions, Boolean logic) {
        return WhereInfo.where(classInfo, fieldNames, fieldValues, conditions, this::builder, logic);
    }

    public final WhereSql whereSql(ClassInfo classInfo, List fieldNames, List fieldValues, Condition condition, Boolean logic) {
        return WhereInfo.where(classInfo, fieldNames, fieldValues, condition, this::builder, logic);
    }

    public final WhereSql whereSql(ClassInfo classInfo, Object obj, List conditions, Boolean logic, boolean orderBy, String... fieldNames) {
        return WhereInfo.where(classInfo, obj, conditions, this::builder, logic, orderBy, fieldNames);
    }

    public final WhereSql whereSql(ClassInfo classInfo, Object obj, Condition condition, Boolean logic, boolean orderBy, String... fieldNames) {
        return WhereInfo.where(classInfo, obj, condition, this::builder, logic, orderBy, fieldNames);
    }

    public final WhereSql whereSql(ClassInfo classInfo, Object obj, Boolean logic, boolean orderBy) {
        return WhereInfo.where(classInfo, obj, this::builder, logic, orderBy);
    }

    public final WhereSql whereSql(Class entityClass, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return WhereInfo.where(nonNullClassInfo(entityClass), fieldName, fieldValue, condition, this::builder, logic);
    }

    public final WhereSql whereSql(Class entityClass, List fieldNames, List fieldValues, List conditions, Boolean logic) {
        return WhereInfo.where(nonNullClassInfo(entityClass), fieldNames, fieldValues, conditions, this::builder, logic);
    }

    public final WhereSql whereSql(Class entityClass, List fieldNames, List fieldValues, Condition condition, Boolean logic) {
        return WhereInfo.where(nonNullClassInfo(entityClass), fieldNames, fieldValues, condition, this::builder, logic);
    }

    public final WhereSql whereSql(Class entityClass, Object obj, List conditions, Boolean logic, boolean orderBy, String... fieldNames) {
        return WhereInfo.where(nonNullClassInfo(entityClass), obj, conditions, this::builder, logic, orderBy, fieldNames);
    }

    public final WhereSql whereSql(Class entityClass, Object obj, Condition condition, Boolean logic, boolean orderBy, String... fieldNames) {
        return WhereInfo.where(nonNullClassInfo(entityClass), obj, condition, this::builder, logic, orderBy, fieldNames);
    }

    public final WhereSql whereSql(Class entityClass, Object obj, Boolean logic, boolean orderBy) {
        return WhereInfo.where(nonNullClassInfo(entityClass), obj, this::builder, logic, orderBy);
    }

    public abstract int[] executeBatch(int batchSize, String sql, List values);

    public abstract int[] executeBatch(int batchSize, String... sqls);

    ///////////////////////////////////////////////OrmInsertService
    @Override
    public final int insert(Tnp tnp, Object entity, List fieldNames) {
        return entity == null ? 0 : insert(tnp, MetaObject.of(entity, nonNullClassInfo(entity.getClass()), fieldNames));
    }

    private int insert(Tnp tnp, MetaObject metaObject) {
        insertFill(metaObject);
        backId(metaObject);
        SqlBuilder builder = builder();
        List columns = metaObject.columnsForInsert();
        String sql = builder.insert(tnp == null ? metaObject.classInfo.tnp : tnp, columns, columns, ValueType.PLACEHOLDER, false);
        return metaObject.classInfo.auto ? insert(metaObject::setId, sql, metaObject.values(columns).toArray()) : update(sql, metaObject.values(columns).toArray());
    }

    protected abstract int insert(Consumer idSetter, String sql, Object... params);

    @Override
    public final int[] batchInsert(Tnp tnp, List entities, List fieldNames, int batchSize) {
        if (NullHelp.isAnyNull(entities)) return ArrayConstant.EMPTY_INT_ARRAY;
        return batchInsert(tnp, MetaObject.of(entities, nonNullClassInfo(entities.get(0).getClass()), fieldNames), batchSize, NullHelp.nonEmpty(fieldNames));
    }

    int[] batchInsert(Tnp tnp, List metaObjects, int batchSize, boolean force) {
        if (metaObjects.isEmpty()) return ArrayConstant.EMPTY_INT_ARRAY;
        metaObjects.forEach(meta -> {
            insertFill(meta);
            backId(meta);
        });
        if (force) {
            SqlBuilder builder = builder();
            MetaObject metaObject = metaObjects.get(0);
            List columns = metaObject.columnsForInsert();
            String sql = builder.insert(tnp == null ? metaObject.classInfo.tnp : tnp, columns, columns, ValueType.PLACEHOLDER, true);
            if (metaObject.classInfo.auto) return batchInsert((idx, id) -> {
                MetaObject meta = metaObjects.get(idx);
                if (meta != null) meta.setId(id);
            }, batchSize, sql, ToList.explicitCollect(metaObjects.stream().map(meta -> meta.values(columns).toArray()), metaObjects.size()));
            return executeBatch(batchSize, sql, ToList.explicitCollect(metaObjects.stream().map(meta -> meta.values(columns).toArray()), metaObjects.size()));
        }
        Tnp tnp1 = tnp == null ? metaObjects.get(0).classInfo.tnp : tnp;
        return executeBatch(batchSize, metaObjects.stream().map(meta -> {
            SqlBuilder builder = builder();
            List columns = meta.columnsForInsert();
            return builder.insert(tnp1, columns, meta.values(columns), ValueType.ESCAPE, true);
        }).toArray(String[]::new));
    }

    protected abstract int[] batchInsert(BiConsumer idSetter, int batchSize, String sql, List values);
    ///////////////////////////////////////////////OrmInsertService

    ///////////////////////////////////////////////OrmUpdateService
    @Override
    public final int update(Tnp tnp, Object entity, List fieldNames, WhereSql whereSql) {
        return entity == null ? 0 : update(tnp, MetaObject.of(entity, nonNullClassInfo(entity.getClass()), fieldNames), whereSql);
    }

    private int update(Tnp tnp, MetaObject metaObject, WhereSql whereSql) {
        if (whereSql == WhereSql.EMPTY) return 0;
        checkVersion(metaObject);
        updateFill(metaObject);
        List columns = metaObject.columnsForUpdate();
        SqlBuilder builder = builder().update(tnp == null ? metaObject.classInfo.tnp : tnp, columns, columns, ValueType.PLACEHOLDER, false);
        if (metaObject.classInfo.versionField != null)
            builder.comma().wrap(metaObject.classInfo.versionName).eq(metaObject.classInfo.versionName, ValueType.WRAP).plus().append('1');
        List params = null;
        if (whereSql != null) {
            String whereSql0 = whereSql.value();
            if (metaObject.classInfo.versionField != null) {
                if (NullHelp.isBlank(whereSql0)) builder.where();
                else builder.space().append(whereSql0).and();
                builder.wrap(metaObject.classInfo.versionName).eq();
                List params0 = whereSql.params();
                if (NullHelp.isEmpty(params0)) params = ToList.get(1).add(metaObject.getVersion()).build();
                else params = ToList.get(params0.size() + 1).add(params0).add(metaObject.getVersion()).build();
            } else {
                builder.space().append(whereSql0);
                params = whereSql.params();
            }
        } else if (metaObject.classInfo.versionField != null) {
            builder.where().wrap(metaObject.classInfo.versionName).eq();
            params = ToList.get(1).add(metaObject.getVersion()).build();
        }
        return update(builder.build(), metaObject.values(columns, params).toArray());
    }

    private void checkVersion(MetaObject metaObject) {
        ClientFailureMsg.requireNon(metaObject.classInfo.versionField != null && NullHelp.isBlank(metaObject.getVersion()), "数据缺失版本号");
    }

    @Override
    public final int updateByColumn(Tnp tnp, Object entity, List fieldNames, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return entity == null ? 0 : update(tnp, entity, fieldNames, whereSql(nonNullClassInfo(entity.getClass()), fieldName, fieldValue, condition, logic));
    }

    @Override
    public final int updateById(Tnp tnp, Object entity, List fieldNames) {
        return entity == null ? 0 : updateById(tnp, MetaObject.of(entity, nonNullClassInfo(entity.getClass()), fieldNames));
    }

    private int updateById(Tnp tnp, MetaObject metaObject) {
        if (NullHelp.isBlank(metaObject.getId())) return 0;
        checkVersion(metaObject);
        updateFill(metaObject);
        List columns = metaObject.columnsForUpdate();
        String sql = updateByIdSql(tnp, metaObject, columns, columns, ValueType.PLACEHOLDER, false);
        Object[] params = updateByIdParams(metaObject, metaObject.values(columns));
        return update(sql, params);
    }

    private String updateByIdSql(Tnp tnp, MetaObject metaObject, List columns, List values, ValueType type, boolean ignore) {
        SqlBuilder builder = builder().update(tnp == null ? metaObject.classInfo.tnp : tnp, columns, values, type, ignore);
        if (metaObject.classInfo.versionField != null)
            builder.comma().wrap(metaObject.classInfo.versionName).eq(metaObject.classInfo.versionName, ValueType.WRAP).plus().append('1');
        builder.where();
        if (metaObject.classInfo.versionField != null)
            builder.wrap(metaObject.classInfo.versionName).eq(metaObject.getVersion(), type).and();
        return builder.wrap(metaObject.classInfo.idName).eq(metaObject.getId(), type).build();
    }

    private Object[] updateByIdParams(MetaObject metaObject, List values) {
        List result = ToList.get(values.size() + 2).add(values).build();
        if (metaObject.classInfo.versionField != null) result.add(metaObject.getVersion());
        result.add(metaObject.getId());
        return result.toArray();
    }

    @Override
    public final int[] batchUpdateById(Tnp tnp, List entities, List fieldNames, int batchSize) {
        if (NullHelp.isAnyNull(entities)) return ArrayConstant.EMPTY_INT_ARRAY;
        return batchUpdateById(tnp, MetaObject.of(entities, nonNullClassInfo(entities.get(0).getClass()), fieldNames), batchSize, NullHelp.nonEmpty(fieldNames));
    }

    int[] batchUpdateById(Tnp tnp, List metaObjects, int batchSize, boolean force) {
        if (metaObjects.isEmpty()) return ArrayConstant.EMPTY_INT_ARRAY;
        metaObjects.forEach(meta -> {
            checkVersion(meta);
            updateFill(meta);
        });
        if (force) {
            MetaObject metaObject = metaObjects.get(0);
            List columns = metaObject.columnsForUpdate();
            String sql = updateByIdSql(tnp, metaObject, columns, columns, ValueType.PLACEHOLDER, true);
            return executeBatch(batchSize, sql, ToList.explicitCollect(metaObjects.stream().map(meta -> updateByIdParams(meta, meta.values(columns))), metaObjects.size()));
        }
        return executeBatch(batchSize, metaObjects.stream().map(meta -> {
            List columns = meta.columnsForUpdate();
            return updateByIdSql(tnp, meta, columns, meta.values(columns), ValueType.ESCAPE, true);
        }).toArray(String[]::new));
    }
    ///////////////////////////////////////////////OrmUpdateService

    ///////////////////////////////////////////////OrmInsertOrUpdateService
    @Override
    public final int insertOrUpdate(Tnp tnp, Object entity, List fieldNames, Boolean logic, String... uniqueFields) {
        if (entity == null) return 0;
        ClassInfo classInfo = nonNullClassInfo(entity.getClass());
        return insertOrUpdate(tnp, MetaObject.of(setId(tnp, classInfo, entity, logic, uniqueFields), classInfo, fieldNames));
    }

    private int insertOrUpdate(Tnp tnp, MetaObject metaObject) {
        return metaObject.existId ? updateById(tnp, metaObject) : insert(tnp, metaObject);
    }

    private Object setId(Tnp tnp, ClassInfo classInfo, Object entity, Boolean logic, String... uniqueFields) {
        if (NullHelp.isEmpty(uniqueFields)) return entity;
        Object id = classInfo.getId(entity);
        if (NullHelp.nonBlank(id)) return entity;
        WhereSql whereSql = null;
        if (uniqueFields.length == 1) {
            if (!classInfo.idField.getName().equals(uniqueFields[0]))
                whereSql = whereSql(classInfo, uniqueFields[0], classInfo.getByName(uniqueFields[0], entity), Condition.EQ, logic);
        } else {
            List fieldNames = new ArrayList<>(uniqueFields.length);
            List fieldValues = new ArrayList<>(uniqueFields.length);
            for (String uniqueField : uniqueFields) {
                if (classInfo.idField.getName().equals(uniqueField)) continue;
                fieldNames.add(uniqueField);
                fieldValues.add(classInfo.getByName(uniqueField, entity));
            }
            if (!fieldNames.isEmpty()) whereSql = whereSql(classInfo, fieldNames, fieldValues, Condition.EQ, logic);
        }
        if (whereSql != null) {
            Object oldEntity = selectOne(tnp, classInfo.cls, true, Collections.singletonList(classInfo.idField.getName()), whereSql);
            if (oldEntity != null) classInfo.setId(entity, classInfo.getId(oldEntity));
        }
        return entity;
    }

    @Override
    public final  int insertOrUpdateMultiUnique(Tnp tnp, T entity, List> fieldNames, Boolean logic, List[]> uniqueFields) {
        if (entity == null) return 0;
        ClassInfo classInfo = nonNullClassInfo(entity.getClass());
        if (NullHelp.nonEmpty(uniqueFields)) for (SerializableGetter[] ufs : uniqueFields)
            setId(tnp, classInfo, entity, logic, Serializer.getFieldNameArray(ufs));
        return insertOrUpdate(tnp, MetaObject.of(entity, classInfo, Serializer.getFieldNames(fieldNames)));
    }

    @Override
    public final int[] batchInsertOrUpdate(Tnp tnp, List entities, List fieldNames, Boolean logic, int batchSize, String... uniqueFields) {
        ClientFailureMsg.requireNonAnyNull(entities, "批量插入集合不允许存在空元素");
        ClassInfo classInfo = nonNullClassInfo(entities.get(0).getClass());
        return batchInsertOrUpdate(tnp, MetaObject.of(setIds(tnp, classInfo, entities, logic, uniqueFields), classInfo, fieldNames), batchSize, NullHelp.nonEmpty(fieldNames));
    }

    private int[] batchInsertOrUpdate(Tnp tnp, List metaObjects, int batchSize, boolean force) {
        int len = metaObjects.size();
        if (len == 0) return ArrayConstant.EMPTY_INT_ARRAY;
        int[] result = new int[len];
        List inserts = new LinkedList<>(), updates = new LinkedList<>();
        for (int i = 0; i < len; ++i) {
            MetaObject meta = metaObjects.get(i);
            if (meta.existId) {
                updates.add(meta);
                result[i] = -1;
            } else inserts.add(meta);
        }
        int[] insertResult = batchInsert(tnp, inserts, batchSize, force);
        int[] updateResult = batchUpdateById(tnp, updates, batchSize, force);
        return merge(result, insertResult, updateResult);
    }

    private int[] merge(int[] result, int[] insertResult, int[] updateResult) {
        int ulen = updateResult.length, ilen = insertResult.length;
        if (ulen == 0) return insertResult;
        else if (ilen == 0) return updateResult;
        int uidx = 0, iidx = 0;
        int len = result.length, i = 0;
        for (; i < len; ++i) {
            if (result[i] == -1) {
                result[i] = updateResult[uidx++];
                if (uidx == ulen) break;
            } else {
                result[i] = insertResult[iidx++];
                if (iidx == ilen) break;
            }
        }
        if (uidx == ulen) System.arraycopy(insertResult, iidx, result, i + 1, ilen - iidx);
        else System.arraycopy(updateResult, uidx, result, i + 1, ulen - uidx);
        return result;
    }

    private List setIds(Tnp tnp, ClassInfo classInfo, List entities, Boolean logic, String... uniqueFields) {
        if (NullHelp.isEmpty(uniqueFields)) return entities;
        List nonIdEntities = ToList.explicitCollect(entities.stream().filter(entity -> classInfo.getId(entity) == null), entities.size());
        int nonIdEntitiesSize = nonIdEntities.size();
        if (nonIdEntitiesSize == 0) return entities;
        if (uniqueFields.length == 1) {
            if (!classInfo.idField.getName().equals(uniqueFields[0])) {
                Map> uniqueValEntityMap = ToMap.get(nonIdEntitiesSize, 1).build();
                Field uniqueField = classInfo.getFieldByName(uniqueFields[0]);
                for (Object entity : nonIdEntities)
                    uniqueValEntityMap.computeIfAbsent(AccessibleHelp.getField(uniqueField, entity), k -> ToSet.get(nonIdEntitiesSize, 1).build()).add(entity);
                if (!uniqueValEntityMap.isEmpty()) {
                    WhereSql whereSql = whereSql(classInfo, uniqueFields[0], uniqueValEntityMap.keySet(), Condition.IN, logic);
                    if (whereSql != null) query(tnp, classInfo.cls, null, null, null, entity -> {
                        Set newEntities = uniqueValEntityMap.get(AccessibleHelp.getField(uniqueField, entity));
                        if (newEntities != null) {
                            Object id = classInfo.getId(entity);
                            newEntities.forEach(newEntity -> classInfo.setId(newEntity, id));
                        }
                    }, Integer.MAX_VALUE, Arrays.asList(classInfo.idField.getName(), uniqueFields[0]), whereSql);
                }
            }
        } else {
            List uniqueFieldNames = new ArrayList<>(uniqueFields.length);
            for (String uniqueField : uniqueFields)
                if (!classInfo.idField.getName().equals(uniqueField)) uniqueFieldNames.add(uniqueField);
            int uniqueFieldSize = uniqueFieldNames.size();
            if (uniqueFieldSize > 0) {
                Map> uniqueFieldFieldValesMap = ToMap.get(uniqueFieldSize).build();
                Map uniqueValEntityMap = ToMap.get(nonIdEntitiesSize, uniqueFieldSize).build();
                for (Object entity : nonIdEntities) {
                    Map uniqueValEntityMapRef = uniqueValEntityMap;
                    for (int i = 0; i < uniqueFieldSize - 1; ++i) {
                        String uniqueField = uniqueFieldNames.get(i);
                        Object uniqueVal = classInfo.getByName(uniqueField, entity);
                        uniqueFieldFieldValesMap.computeIfAbsent(uniqueField, k -> ToSet.get(nonIdEntitiesSize, uniqueFieldSize).build()).add(uniqueVal);
                        uniqueValEntityMapRef = (Map) uniqueValEntityMapRef.computeIfAbsent(uniqueVal, k -> ToMap.get(nonIdEntitiesSize, uniqueFieldSize).build());
                    }
                    String uniqueField = uniqueFieldNames.get(uniqueFieldSize - 1);
                    Object uniqueVal = classInfo.getByName(uniqueField, entity);
                    uniqueFieldFieldValesMap.computeIfAbsent(uniqueField, k -> ToSet.get(nonIdEntitiesSize, uniqueFieldSize).build()).add(uniqueVal);
                    uniqueValEntityMapRef.put(uniqueVal, entity);
                }
                List uniqueFieldValues = ToList.explicitCollect(uniqueFieldNames.stream().map(uniqueFieldFieldValesMap::get), uniqueFieldSize);
                WhereSql whereSql = whereSql(classInfo, uniqueFieldNames, uniqueFieldValues, Condition.IN, logic);
                if (whereSql != null) {
                    List fieldNames = new ArrayList<>(uniqueFieldSize + 1);
                    fieldNames.add(classInfo.idField.getName());
                    fieldNames.addAll(ToSet.asSet(uniqueFieldNames));
                    query(tnp, classInfo.cls, null, null, null, entity -> {
                        Map uniqueValEntityMapRef = uniqueValEntityMap;
                        for (int i = 0; i < uniqueFieldSize - 1; ++i) {
                            if (uniqueValEntityMapRef == null) return;
                            uniqueValEntityMapRef = (Map) uniqueValEntityMapRef.get(classInfo.getByName(uniqueFieldNames.get(i), entity));
                        }
                        if (uniqueValEntityMapRef != null) {
                            Object newEntity = uniqueValEntityMapRef.get(classInfo.getByName(uniqueFieldNames.get(uniqueFieldSize - 1), entity));
                            if (newEntity != null) classInfo.setId(newEntity, classInfo.getId(entity));
                        }
                    }, Integer.MAX_VALUE, fieldNames, whereSql);
                }
            }
        }
        return entities;
    }

    @Override
    public final  int[] batchInsertOrUpdateMultiUnique(Tnp tnp, List entities, List> fieldNames, Boolean logic, int batchSize, List[]> uniqueFields) {
        ClientFailureMsg.requireNonAnyNull(entities, "批量插入集合不允许存在空元素");
        ClassInfo classInfo = nonNullClassInfo(entities.get(0).getClass());
        if (NullHelp.nonEmpty(uniqueFields)) for (SerializableGetter[] ufs : uniqueFields)
            setIds(tnp, classInfo, entities, logic, Serializer.getFieldNameArray(ufs));
        return batchInsertOrUpdate(tnp, MetaObject.of(entities, classInfo, Serializer.getFieldNames(fieldNames)), batchSize, NullHelp.nonEmpty(fieldNames));
    }

    ///////////////////////////////////////////////OrmInsertOrUpdateService

    ///////////////////////////////////////////////OrmDeleteService
    @Override
    public final int delete(Tnp tnp, Class entityClass, WhereSql whereSql, Boolean logic) {
        return delete(tnp, nonNullClassInfo(entityClass), whereSql, logic);
    }

    private int delete(Tnp tnp, ClassInfo classInfo, WhereSql whereSql, Boolean logic) {
        if (whereSql == WhereSql.EMPTY) return 0;
        tnp = tnp == null ? classInfo.tnp : tnp;
        SqlBuilder builder = builder();
        if (logic == null ? classInfo.logicField != null : logic) {
            if (classInfo.logicDelVal == null)
                builder.update(tnp, Collections.singletonList(classInfo.logicName), Collections.singletonList(classInfo.idName), ValueType.WRAP, false);
            else
                builder.update(tnp, Collections.singletonList(classInfo.logicName), Collections.singletonList(classInfo.logicDelVal), ValueType.ESCAPE, false);
        } else builder.delete(tnp);
        Object[] params = ArrayConstant.EMPTY_OBJECT_ARRAY;
        if (whereSql != null) {
            builder.space().append(whereSql.value());
            List whereParams = whereSql.params();
            if (NullHelp.nonEmpty(whereParams)) params = whereParams.toArray();
        }
        return update(builder.build(), params);
    }

    @Override
    public final int deleteByColumn(Tnp tnp, Class entityClass, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        ClassInfo classInfo = nonNullClassInfo(entityClass);
        return delete(tnp, classInfo, whereSql(classInfo, fieldName, fieldValue, condition, Boolean.FALSE), logic);
    }

    @Override
    public final int deleteByIds(Tnp tnp, Class entityClass, Collection ids, Boolean logic) {
        ClassInfo classInfo = nonNullClassInfo(entityClass);
        return delete(tnp, classInfo, whereSql(classInfo, classInfo.idField.getName(), ids, Condition.IN, Boolean.FALSE), logic);
    }

    @Override
    public final int deleteByIds(Tnp tnp, Collection entities, Boolean logic) {
        if (NullHelp.isAnyNull(entities)) return 0;
        ClassInfo classInfo = nonNullClassInfo(entities.iterator().next().getClass());
        return delete(tnp, classInfo, whereSql(classInfo, classInfo.idField.getName(), ToSet.explicitCollect(entities.stream().map(classInfo::getId), entities.size()), Condition.IN, Boolean.FALSE), logic);
    }

    @Override
    public final int deleteById(Tnp tnp, Class entityClass, Object id, Boolean logic) {
        return deleteById(tnp, nonNullClassInfo(entityClass), id, logic);
    }

    private int deleteById(Tnp tnp, ClassInfo classInfo, Object id, Boolean logic) {
        return update(deleteByIdSql(tnp, classInfo, logic, false), id);
    }

    private String deleteByIdSql(Tnp tnp, ClassInfo classInfo, Boolean logic, boolean ignore) {
        tnp = tnp == null ? classInfo.tnp : tnp;
        SqlBuilder builder = builder();
        if (logic == null ? classInfo.logicField != null : logic) {
            if (classInfo.logicDelVal == null)
                builder.update(tnp, Collections.singletonList(classInfo.logicName), Collections.singletonList(classInfo.idName), ValueType.WRAP, ignore);
            else
                builder.update(tnp, Collections.singletonList(classInfo.logicName), Collections.singletonList(classInfo.logicDelVal), ValueType.ESCAPE, ignore);
        } else builder.delete(tnp);
        return builder.where().wrap(classInfo.idName).eq().build();
    }

    @Override
    public final int deleteById(Tnp tnp, Object entity, Boolean logic) {
        if (entity == null) return 0;
        ClassInfo classInfo = nonNullClassInfo(entity.getClass());
        return deleteById(tnp, classInfo, classInfo.getId(entity), logic);
    }

    @Override
    public final int[] batchDeleteById(Tnp tnp, Class entityClass, List ids, Boolean logic, int batchSize) {
        return batchDeleteById(tnp, nonNullClassInfo(entityClass), ids, logic, batchSize);
    }

    private int[] batchDeleteById(Tnp tnp, ClassInfo classInfo, List ids, Boolean logic, int batchSize) {
        if (NullHelp.isEmpty(ids)) return ArrayConstant.EMPTY_INT_ARRAY;
        return executeBatch(batchSize, deleteByIdSql(tnp, classInfo, logic, true), ToList.explicitCollect(ids.stream().map(id -> new Object[]{id}), ids.size()));
    }

    @Override
    public final int[] batchDeleteById(Tnp tnp, List entities, Boolean logic, int batchSize) {
        if (NullHelp.isAnyNull(entities)) return ArrayConstant.EMPTY_INT_ARRAY;
        ClassInfo classInfo = nonNullClassInfo(entities.get(0).getClass());
        return batchDeleteById(tnp, classInfo, ToList.explicitCollect(entities.stream().map(classInfo::getId), entities.size()), logic, batchSize);
    }
    ///////////////////////////////////////////////OrmDeleteService

    ///////////////////////////////////////////////OrmCursorService
    @Override
    public final  void query(OrmMapping mapping, Type type, Limiter limiter, OrmMapFilter mapFilter, OrmFilter filter, OrmConsumer consumer, int fetchSize, String sql, Object... params) {
        NullHelp.requireNon(mapFilter == null && filter == null && consumer == null);
        OrmMapConsumer mapConsumer;
        if (filter == null && consumer == null) mapConsumer = null;
        else {
            DataType dataType = dataType(type);
            mapConsumer = map -> {
                if (limiter != null) {
                    if (limiter.isLimit()) return;
                    if (filter == null) {
                        if (!limiter.isLimitBefore()) consumer.accept(dataType.parse(type, map));
                    } else {
                        T t = dataType.parse(type, map);
                        if (filter.test(t) && !limiter.isLimitBefore() && consumer != null) consumer.accept(t);
                    }
                } else {
                    T t = dataType.parse(type, map);
                    if ((filter == null || filter.test(t)) && consumer != null) consumer.accept(t);
                }
            };
        }
        query(mapping, type, limiter, mapFilter, mapConsumer, fetchSize, sql, params);
    }

    @Override
    public final void query(Tnp tnp, Class entityClass, Limiter limiter, OrmMapFilter mapFilter, OrmMapConsumer mapConsumer, int fetchSize, List fieldNames, WhereSql whereSql) {
        Sql sql = querySql(tnp, nonNullClassInfo(entityClass), fieldNames, whereSql);
        if (sql != null)
            query(null, entityClass, limiter, mapFilter, mapConsumer, fetchSize, sql.value(), sql.toArray());
    }

    final Sql querySql(Tnp tnp, ClassInfo classInfo, List fieldNames, WhereSql whereSql) {
        return whereSql == WhereSql.EMPTY ? null : sql(builder().select(tnp == null ? classInfo.tnp : tnp, columns(classInfo, fieldNames)), whereSql);
    }

    private List columns(ClassInfo classInfo, List fieldNames) {
        if (NullHelp.isEmpty(fieldNames)) return classInfo.getColumns();
        return ToList.explicitCollect(fieldNames.stream().map(classInfo::toColumnName), fieldNames.size());
    }

    private Sql sql(SqlBuilder builder, WhereSql whereSql) {
        if (whereSql == null) return builder::build;
        builder.space().append(whereSql.value());
        return Sql.of(builder.build(), whereSql.params());
    }

    @Override
    public final  void query(Tnp tnp, Class entityClass, Limiter limiter, OrmMapFilter mapFilter, OrmFilter filter, OrmConsumer consumer, int fetchSize, List fieldNames, WhereSql whereSql) {
        Sql sql = querySql(tnp, nonNullClassInfo(entityClass), fieldNames, whereSql);
        if (sql != null)
            query(null, entityClass, limiter, mapFilter, filter, consumer, fetchSize, sql.value(), sql.toArray());
    }

    @Override
    public final void query(Set tnpSet, Class entityClass, Limiter limiter, OrmMapFilter mapFilter, OrmMapConsumer mapConsumer, int fetchSize, Executor executor, List fieldNames, WhereSql whereSql) {
        if (NullHelp.isEmpty(tnpSet)) return;
        ClassInfo classInfo = nonNullClassInfo(entityClass);
        Consumer action = tnp -> {
            Sql sql = querySql(tnp, classInfo, fieldNames, whereSql);
            if (sql != null)
                query(null, entityClass, limiter, mapFilter, mapConsumer, fetchSize, sql.value(), sql.toArray());
        };
        if (tnpSet.size() == 1) {
            action.accept(tnpSet.iterator().next());
            return;
        }
        Runner.run(ToList.explicitCollect(tnpSet.stream().map(tnp -> () -> action.accept(tnp)), tnpSet.size()), executor);
    }

    @Override
    public final  void query(Set tnpSet, Class entityClass, Limiter limiter, OrmMapFilter mapFilter, OrmFilter filter, OrmConsumer consumer, int fetchSize, Executor executor, List fieldNames, WhereSql whereSql) {
        if (NullHelp.isEmpty(tnpSet)) return;
        DataType dataType = dataType(entityClass);
        query(tnpSet, entityClass, limiter, mapFilter, map -> {
            if (limiter != null) {
                if (limiter.isLimit()) return;
                if (filter == null) {
                    if (!limiter.isLimitBefore()) consumer.accept(dataType.parse(entityClass, map));
                } else {
                    T t = dataType.parse(entityClass, map);
                    if (filter.test(t) && !limiter.isLimitBefore()) consumer.accept(t);
                }
            } else {
                T t = dataType.parse(entityClass, map);
                if (filter == null || filter.test(t)) consumer.accept(t);
            }
        }, fetchSize, executor, fieldNames, whereSql);
    }

    @Override
    public final  void update(ActionRunner runner, List updateFieldNames, Executor executor) {
        LReference reference = new LReference<>();
        boolean force = NullHelp.nonEmpty(updateFieldNames);
        Runner.run(runner, entities -> {
            if (reference.get() == null) reference.set(nonNullClassInfo(entities.get(0).getClass()));
            batchUpdateById(null, MetaObject.of(entities, reference.get(), updateFieldNames), -1, force);
        }, null, 0, executor);
    }

    @Override
    public final  void delete(ActionRunner runner, Boolean logic, Executor executor) {
        LReference reference = new LReference<>();
        Runner.run(runner, entities -> {
            if (reference.get() == null) reference.set(nonNullClassInfo(entities.get(0).getClass()));
            executeBatch(-1, deleteByIdSql(null, reference.get(), logic, true), ToList.explicitCollect(entities.stream().map(entity -> new Object[]{reference.get().getId(entity)}), entities.size()));
        }, null, -1, executor);
    }

    @Override
    public final  List collect(ActionRunner runner) {
        return ToList.collect(runner);
    }

    @Override
    public final  int count(ActionRunner runner) {
        AtomicInteger count = new AtomicInteger();
        runner.accept(t -> count.incrementAndGet());
        return count.get();
    }

    @Override
    public final  PageInfo page(PageInfo pageInfo, ActionRunner runner, OrmFilter filter, BiPredicate equals, Comparator comparator) {
        BiPredicate eq;
        if (equals == null) {
            LReference reference = new LReference<>();
            eq = (a, b) -> {
                if (reference.get() == null) reference.set(nonNullClassInfo(a.getClass()));
                return Objects.equals(reference.get().getId(a), reference.get().getId(b));
            };
        } else eq = equals;
        pageInfo.page(runner, filter, predicate -> {
            AtomicInteger count = new AtomicInteger();
            runner.accept(t -> {
                if (predicate == null || predicate.test(t)) count.incrementAndGet();
            });
            return count.get();
        }, eq, comparator, false);
        return pageInfo;
    }

    @Override
    public final  List selectTopN(ActionRunner runner, int n, Comparator comparator) {
        return ToList.topN(runner, n, null, comparator);
    }

    @Override
    public final  Map> selectTopN(ActionRunner runner, Function> group, Comparator comparator) {
        return ToMap.topN(runner, group, null, comparator);
    }

    @Override
    public final  Map selectTopOne(ActionRunner runner, Function group, Comparator comparator) {
        return ToMap.topOne(runner, group, null, comparator);
    }

    @Override
    public final  Map selectMerge(ActionRunner runner, Function group, BinaryOperator merger, Function, ? extends R> mapper) {
        return ToMap.merge(runner, group, null, merger, mapper);
    }
    ///////////////////////////////////////////////OrmCursorService

    ///////////////////////////////////////////////OrmCountService
    @Override
    public final int count(Tnp tnp, Class entityClass, WhereSql whereSql) {
        if (whereSql == WhereSql.EMPTY) return 0;
        Sql sql = sql(builder().count(tnp == null ? nonNullClassInfo(entityClass).tnp : tnp), whereSql);
        return count(sql.value(), sql.toArray());
    }

    @Override
    public final int countByColumn(Tnp tnp, Class entityClass, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return count(tnp, entityClass, whereSql(nonNullClassInfo(entityClass), fieldName, fieldValue, condition, logic));
    }

    @Override
    public final Double maxColumn(Tnp tnp, Class entityClass, String fieldName, WhereSql whereSql) {
        if (whereSql == WhereSql.EMPTY) return null;
        Sql sql = sql(builder().max(tnp == null ? nonNullClassInfo(entityClass).tnp : tnp, humpToUnderline(fieldName)), whereSql);
        return selectOne(Double.class, true, sql.value(), sql.toArray());
    }

    @Override
    public final Double minColumn(Tnp tnp, Class entityClass, String fieldName, WhereSql whereSql) {
        if (whereSql == WhereSql.EMPTY) return null;
        Sql sql = sql(builder().min(tnp == null ? nonNullClassInfo(entityClass).tnp : tnp, humpToUnderline(fieldName)), whereSql);
        return selectOne(Double.class, true, sql.value(), sql.toArray());
    }

    @Override
    public final Double avgColumn(Tnp tnp, Class entityClass, String fieldName, WhereSql whereSql) {
        if (whereSql == WhereSql.EMPTY) return null;
        Sql sql = sql(builder().avg(tnp == null ? nonNullClassInfo(entityClass).tnp : tnp, humpToUnderline(fieldName)), whereSql);
        return selectOne(Double.class, true, sql.value(), sql.toArray());
    }

    @Override
    public final Double randColumn(Tnp tnp, Class entityClass, String fieldName, WhereSql whereSql) {
        if (whereSql == WhereSql.EMPTY) return null;
        String sql = builder().rand(tnp == null ? nonNullClassInfo(entityClass).tnp : tnp, humpToUnderline(fieldName), whereSql);
        return selectOne(Double.class, true, sql, whereSql == null ? ArrayConstant.EMPTY_OBJECT_ARRAY : whereSql.params().toArray());
    }
    ///////////////////////////////////////////////OrmCountService

    ///////////////////////////////////////////////OrmSelectListService
    @Override
    public final  List selectList(Type type, String sql, Object... params) {
        List> maps = new LinkedList<>();
        query(null, type, null, null, maps::add, Integer.MAX_VALUE, sql, params);
        return dataType(type).parse(type, maps);
    }

    @Override
    public final  List selectList(Tnp tnp, Class entityClass, List fieldNames, WhereSql whereSql) {
        Sql sql = querySql(tnp, nonNullClassInfo(entityClass), fieldNames, whereSql);
        return sql == null ? Collections.emptyList() : selectList(entityClass, sql.value(), sql.toArray());
    }

    @Override
    public final  List selectListByColumn(Tnp tnp, Class entityClass, List fieldNames, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return selectList(tnp, entityClass, fieldNames, whereSql(nonNullClassInfo(entityClass), fieldName, fieldValue, condition, logic));
    }

    @Override
    public final  List selectByIds(Tnp tnp, Class entityClass, List fieldNames, Collection ids, Boolean logic) {
        if (NullHelp.isAnyNull(ids)) return Collections.emptyList();
        ClassInfo classInfo = nonNullClassInfo(entityClass);
        return selectList(tnp, entityClass, fieldNames, whereSql(classInfo, classInfo.idField.getName(), ids, Condition.IN, logic));
    }

    @Override
    public final  List selectByIds(Tnp tnp, Collection entities, List fieldNames, Boolean logic) {
        if (NullHelp.isAnyNull(entities)) return Collections.emptyList();
        Class cls = (Class) entities.iterator().next().getClass();
        ClassInfo classInfo = nonNullClassInfo(cls);
        return selectByIds(tnp, cls, fieldNames, ToSet.explicitCollect(entities.stream().map(classInfo::getId), entities.size()), logic);
    }

    @Override
    public final  Map mapByIds(Class entityClass, Collection ids) {
        if (NullHelp.isEmpty(ids)) return Collections.emptyMap();
        List entities = selectByIds(entityClass, ids);
        if (NullHelp.isEmpty(entities)) return Collections.emptyMap();
        ClassInfo classInfo = nonNullClassInfo(entityClass);
        return ToMap.explicitCollect(entities.stream(), classInfo::getId, Function.identity(), entities.size());
    }

    @Override
    public final  Map mapByIds(Class entityClass, Collection data, Function idGetter) {
        if (NullHelp.isEmpty(data)) return Collections.emptyMap();
        return mapByIds(entityClass, ToSet.explicitCollect(data.stream().map(idGetter), data.size()));
    }

    ///////////////////////////////////////////////OrmSelectListService

    ///////////////////////////////////////////////OrmPageService
    @Override
    public final  PageInfo page(PageInfo pageInfo, Type type, String sql, Object... params) {
        if (pageInfo.getSize() == Integer.MAX_VALUE) {
            pageInfo.setData(selectList(type, sql, params));
            pageInfo.setPage(1);
            pageInfo.setTotal(pageInfo.getData().size());
            pageInfo.reset();
        } else {
            pageInfo.setTotal(count(builder().append(sql).count(), params));
            pageInfo.reset();
            if (pageInfo.getTotal() <= 0 || pageInfo.getPage() > pageInfo.getPages()) return pageInfo.map();
            pageInfo.setData(selectList(type, builder().append(sql).page(pageInfo), params));
        }
        return pageInfo;
    }

    @Override
    public final  PageInfo page(PageInfo pageInfo, Tnp tnp, Class entityClass, List fieldNames, WhereSql whereSql) {
        Sql sql = querySql(tnp, nonNullClassInfo(entityClass), fieldNames, whereSql);
        return sql == null ? pageInfo.map() : page(pageInfo, entityClass, sql.value(), sql.toArray());
    }

    @Override
    public final  PageInfo pageByColumn(PageInfo pageInfo, Tnp tnp, Class entityClass, List fieldNames, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return page(pageInfo, tnp, entityClass, fieldNames, whereSql(nonNullClassInfo(entityClass), fieldName, fieldValue, condition, logic));
    }
    ///////////////////////////////////////////////OrmPageService

    ///////////////////////////////////////////////OrmSelectOneService
    @Override
    public final  T selectOne(Type type, boolean unique, String sql, Object... params) {
        List ts;
        if (unique) ts = selectList(type, sql, params);
        else {
            PageInfo pageInfo = PageInfo.of(1, 1);
            pageInfo.setTotal(1);
            pageInfo.reset();
            ts = selectList(type, builder().append(sql).page(pageInfo), params);
        }
        if (NullHelp.isEmpty(ts)) return null;
        if (unique && ts.size() > 1)
            throw new RuntimeException(ToString.format("TooManyResults[size:%d,sql:%s]", ts.size(), sql));
        return ts.get(0);
    }

    @Override
    public final  T selectOne(Tnp tnp, Class entityClass, boolean unique, List fieldNames, WhereSql whereSql) {
        Sql sql = querySql(tnp, nonNullClassInfo(entityClass), fieldNames, whereSql);
        return sql == null ? null : selectOne(entityClass, unique, sql.value(), sql.toArray());
    }

    @Override
    public final  T selectOneByColumn(Tnp tnp, Class entityClass, boolean unique, List fieldNames, String fieldName, Object fieldValue, Condition condition, Boolean logic) {
        return selectOne(tnp, entityClass, unique, fieldNames, whereSql(nonNullClassInfo(entityClass), fieldName, fieldValue, condition, logic));
    }

    @Override
    public final  T selectById(Tnp tnp, Class entityClass, List fieldNames, Object id, Boolean logic) {
        if (id == null) return null;
        ClassInfo classInfo = nonNullClassInfo(entityClass);
        return selectOne(tnp, entityClass, true, fieldNames, whereSql(classInfo, classInfo.idField.getName(), id, Condition.EQ, logic));
    }

    @Override
    public final  T selectById(Tnp tnp, T entity, List fieldNames, Boolean logic) {
        if (entity == null) return null;
        Class cls = (Class) entity.getClass();
        ClassInfo classInfo = nonNullClassInfo(cls);
        return selectById(tnp, cls, fieldNames, classInfo.getId(entity), logic);
    }
    ///////////////////////////////////////////////OrmSelectOneService

    ///////////////////////////////////////////////OrmTreeService
    @Override
    public final > int insertTree(T tree, TreeNode.SetterFromParent setter, Consumer checker, String name) {
        if (tree == null) return 0;
        Class cls = (Class) tree.getClass();
        ClassInfo classInfo = nonNullClassInfo(cls);

        Long pId = tree.getParentId();
        boolean hasPid = pId != null && pId > 0L;
        T pTree;
        if (hasPid) ClientFailureMsg.requireNonNull(pTree = selectById(cls, pId), "%s父级ID:%s不存在", name, pId);
        else pTree = null;
        return apply(() -> {
            T updatePTree = null;
            if (hasPid) {
                tree.setLevel(pTree.getLevel() + 1);
                tree.setRootId(pTree.getRootId() == 0L ? pId : pTree.getRootId());
                ActionRunner runner = runner(null, cls, null, null, null, 0, null, whereSql(classInfo, "parentId", pId, Condition.EQ, Boolean.TRUE));
                List lbt = selectTopN(runner, 1, (t, u) -> TreeEntity.lbtCmp(pTree.getTag().length() + 1, t.getTag(), u.getTag()));
                T pre = lbt.isEmpty() ? null : lbt.get(0);
                tree.setTag(TreeEntity.tag(pTree.getTag(), pre == null ? null : pre.getTag()));
                if (tree.getSort() == null) tree.setSort(pre == null ? 0 : pre.getSort() + 1);
                if (setter != null) setter.accept(pTree, tree);
                if (Help.isTrue(pTree.getLeaf())) {
                    updatePTree = AccessibleHelp.newInstance(cls);
                    updatePTree.setId(pTree.getId());
                    updatePTree.setLeaf(Boolean.FALSE);
                }
            } else {
                tree.setParentId(0L);
                tree.setLevel(1);
                tree.setRootId(0L);
                ActionRunner runner = runner(null, cls, null, null, null, 0, null, whereSql(classInfo, "level", 1, Condition.EQ, Boolean.TRUE));
                List lbt = selectTopN(runner, 1, (t, u) -> TreeEntity.lbtCmp(0, t.getTag(), u.getTag()));
                T pre = lbt.isEmpty() ? null : lbt.get(0);
                tree.setTag(TreeEntity.tag(null, pre == null ? null : pre.getTag()));
                if (tree.getSort() == null) tree.setSort(pre == null ? 0 : pre.getSort() + 1);
            }
            if (checker != null) checker.accept(tree);
            int row = insert(tree);
            NullHelp.requireNon(fail(row) || updatePTree != null && fail(updateById(updatePTree)));
            return row;
        });
    }

    @Override
    public final > int[] batchInsertTree(List trees, TreeNode.SetterFromParent setter, Consumer> checker, String name) {
        if (NullHelp.isEmpty(trees)) return ArrayConstant.EMPTY_INT_ARRAY;
        Class cls = (Class) trees.iterator().next().getClass();
        ClassInfo classInfo = nonNullClassInfo(cls);

        Map> map = trees.stream().collect(Collectors.partitioningBy(tree -> tree.getParentId() == null || tree.getParentId() <= 0L));
        List nonParentIdList = map.get(Boolean.TRUE);
        List hasParentIdList = map.get(Boolean.FALSE);
        Set pIds;
        List pTrees;
        if (!hasParentIdList.isEmpty()) {
            pIds = ToSet.explicitCollect(hasParentIdList.stream().map(TreeEntity::getParentId), hasParentIdList.size());
            pTrees = selectByIds(cls, pIds);
            if (pTrees.size() != pIds.size()) {
                Set ids = ToSet.explicitCollect(pTrees.stream().map(TreeEntity::getId), pTrees.size());
                pIds.removeAll(ids);
                throw PRException.client("%s父级ID:%s不存在", name, ToString.joining(pIds, ","));
            }
        } else {
            pIds = Collections.emptySet();
            pTrees = Collections.emptyList();
        }
        return apply(() -> {
            if (!nonParentIdList.isEmpty()) {
                ActionRunner runner = runner(null, cls, null, null, null, 0, null, whereSql(classInfo, "level", 1, Condition.EQ, Boolean.TRUE));
                List lbt = selectTopN(runner, 1, (t, u) -> TreeEntity.lbtCmp(0, t.getTag(), u.getTag()));
                LReference tag = new LReference<>(lbt.isEmpty() ? null : lbt.get(0).getTag());
                IReference sort = new IReference(lbt.isEmpty() ? 0 : lbt.get(0).getSort() + 1);
                nonParentIdList.forEach(tree -> {
                    tree.setParentId(0L);
                    tree.setLevel(1);
                    tree.setRootId(0L);
                    tree.setTag(TreeEntity.tag(null, tag.get()));
                    tag.set(tree.getTag());
                    if (tree.getSort() == null) tree.setSort(sort.getAndIncrement());
                });
            }
            List updatePTrees = new ArrayList<>(pTrees.size());
            if (!hasParentIdList.isEmpty()) {
                Map pTreeMap = ToMap.explicitCollect(pTrees.stream(), TreeEntity::getId, Function.identity(), pTrees.size());
                ActionRunner runner = runner(null, cls, null, null, null, 0, null, whereSql(classInfo, "parentId", pIds, Condition.IN, Boolean.TRUE));
                Map pIdMap = selectTopOne(runner, TreeEntity::getParentId, (t, u) -> TreeEntity.lbtCmp(pTreeMap.get(t.getParentId()).getTag().length() + 1, t.getTag(), u.getTag()));
                Map tags = ToMap.explicitCollect(pIds.stream(), Function.identity(), pId -> {
                    T lbt = pIdMap.get(pId);
                    return lbt == null ? null : lbt.getTag();
                }, pIdMap.size());
                Map sorts = ToMap.explicitCollect(pIds.stream(), Function.identity(), pId -> {
                    T lbt = pIdMap.get(pId);
                    return new IReference(lbt == null ? 0 : lbt.getSort() + 1);
                }, pIdMap.size());
                hasParentIdList.forEach(tree -> {
                    Long pId = tree.getParentId();
                    T pTree = pTreeMap.get(pId);
                    tree.setLevel(pTree.getLevel() + 1);
                    tree.setRootId(pTree.getRootId() == 0L ? pId : pTree.getRootId());
                    tree.setTag(TreeEntity.tag(pTree.getTag(), tags.get(pId)));
                    tags.put(pId, tree.getTag());
                    if (tree.getSort() == null) tree.setSort(sorts.get(pId).getAndIncrement());
                    if (setter != null) setter.accept(pTree, tree);
                    if (Help.isTrue(pTree.getLeaf())) {
                        T updatePTree = AccessibleHelp.newInstance(cls);
                        updatePTree.setId(pTree.getId());
                        updatePTree.setLeaf(Boolean.FALSE);
                        updatePTrees.add(updatePTree);
                    }
                });
            }
            if (checker != null) checker.accept(trees);
            int[] rows = batchInsert(trees);
            NullHelp.requireNon(batchFail(rows) || !updatePTrees.isEmpty() && batchFail(batchUpdateById(updatePTrees)));
            return rows;
        });
    }

    @Override
    public final > int updateTree(T tree, T oldTree, TreeNode.SetterFromParent setter, Consumer checker, String name) {
        if (tree == null) return 0;
        Class cls = (Class) tree.getClass();
        ClassInfo classInfo = nonNullClassInfo(cls);

        if (oldTree == null) oldTree = selectById(cls, tree.getId());
        ClientFailureMsg.requireNonNull(oldTree, "%sID:%s不存在", name, tree.getId());
        Long oldPid = oldTree.getParentId();
        String oldTag = oldTree.getTag();
        int oldLevel = oldTree.getLevel();

        T oldPTree;
        if (oldPid > 0L)
            ClientFailureMsg.requireNonNull(oldPTree = selectById(cls, oldPid), "%s父级ID:%s不存在", name, oldPid);
        else oldPTree = null;

        Long newPid = tree.getParentId();
        boolean hasPid = newPid != null && newPid > 0L;
        T newPTree;
        if (hasPid) {
            if (Objects.equals(oldPid, newPid)) newPTree = oldPTree;
            else ClientFailureMsg.requireNonNull(newPTree = selectById(cls, newPid), "%s父级ID:%s不存在", name, newPid);
        } else newPTree = null;
        return apply(() -> {
            ZReference change = new ZReference();
            if (hasPid && !Objects.equals(oldPid, newPid)) {
                //根菜单变子菜单或子菜单变更父级
                change.set(true);
                tree.setLevel(newPTree.getLevel() + 1);
                tree.setRootId(newPTree.getRootId() == 0L ? newPid : newPTree.getRootId());
                ActionRunner runner = runner(null, cls, null, null, null, 0, null, whereSql(classInfo, "parentId", newPid, Condition.EQ, Boolean.TRUE));
                List lbt = selectTopN(runner, 1, (t, u) -> TreeEntity.lbtCmp(newPTree.getTag().length() + 1, t.getTag(), u.getTag()));
                T pre = lbt.isEmpty() ? null : lbt.get(0);
                tree.setTag(TreeEntity.tag(newPTree.getTag(), pre == null ? null : pre.getTag()));
                if (setter != null) setter.accept(newPTree, tree);
            } else if (!hasPid && oldPid > 0L) {
                //子菜单变根菜单
                change.set(true);
                tree.setParentId(0L);
                tree.setLevel(1);
                tree.setRootId(0L);
                ActionRunner runner = runner(null, cls, null, null, null, 0, null, whereSql(classInfo, "parentId", 0L, Condition.EQ, Boolean.TRUE));
                List lbt = selectTopN(runner, 1, (t, u) -> TreeEntity.lbtCmp(0, t.getTag(), u.getTag()));
                T pre = lbt.isEmpty() ? null : lbt.get(0);
                tree.setTag(TreeEntity.tag(null, pre == null ? null : pre.getTag()));
            }
            if (!change.get()) tree.setParentId(oldPid);
            if (checker != null) checker.accept(tree);
            int row = updateById(tree);
            NullHelp.requireNon(fail(row));
            if (change.get()) {
                //获取所有子节点变更子节点tag
                List children = children(cls, oldTag);
                if (!children.isEmpty()) {
                    int delta = tree.getLevel() - oldLevel;
                    Long newRootId = tree.getRootId() == 0L ? tree.getId() : tree.getRootId();
                    String newTag = tree.getTag();
                    int idx = oldTag.length();
                    children.forEach(e -> {
                        e.setLevel(e.getLevel() + delta);
                        e.setRootId(newRootId);
                        e.setTag(newTag + e.getTag().substring(idx));
                    });
                    if (setter != null) TreeNode.setFromParent(TreeEntity.group(children), tree, setter);
                    NullHelp.requireNon(batchFail(batchUpdateById(children)));
                }
                if (newPTree != null && Help.isTrue(newPTree.getLeaf())) {
                    T updatePTree = AccessibleHelp.newInstance(cls);
                    updatePTree.setId(newPTree.getId());
                    updatePTree.setLeaf(Boolean.FALSE);
                    NullHelp.requireNon(fail(updateById(updatePTree)));
                }
                if (oldPTree != null) {
                    List ts = nextChildren(cls, oldPTree.getTag());
                    if (ts.isEmpty() || ts.size() == 1 && Objects.equals(ts.get(0).getId(), tree.getId())) {
                        T updatePTree = AccessibleHelp.newInstance(cls);
                        updatePTree.setId(oldPTree.getId());
                        updatePTree.setLeaf(Boolean.TRUE);
                        NullHelp.requireNon(fail(updateById(updatePTree)));
                    }
                }
            }
            return row;
        });
    }

    @Override
    public final > int insertOrUpdateTree(T tree, TreeNode.SetterFromParent setter, List> uniqueFields) {
        if (tree == null) return 0;
        Class cls = (Class) tree.getClass();
        ClassInfo classInfo = nonNullClassInfo(cls);
        WhereSql whereSql = WhereInfo.uniqueWhere(classInfo, tree, this::builder, Serializer.getFieldNameArray(uniqueFields));
        T oldTree = selectOne(cls, true, null, whereSql);
        if (oldTree == null) return insertTree(tree, setter, null, null);
        tree.setId(oldTree.getId());
        return updateTree(tree, oldTree, setter, null, null);
    }

    @Override
    public final > int deleteTree(Collection entities) {
        if (NullHelp.isEmpty(entities)) return 0;
        Class cls = (Class) entities.iterator().next().getClass();

        Map> map = entities.stream().filter(t -> t.getParentId() > 0L).collect(Collectors.groupingBy(TreeEntity::getParentId, Collectors.mapping(TreeEntity::getId, Collectors.toSet())));
        if (map.isEmpty()) return deleteByIds(entities);
        return apply(() -> {
            int row = deleteByIds(entities);
            NullHelp.requireNon(row != entities.size());
            Set pIds = map.keySet();
            List pTrees = selectByIds(cls, pIds);
            if (pTrees.size() != pIds.size()) {
                Set ids = ToSet.explicitCollect(pTrees.stream().map(TreeEntity::getId), pTrees.size());
                pIds.removeAll(ids);
                throw PRException.client("父级ID:%s不存在", ToString.joining(pIds, ","));
            }
            for (T pTree : pTrees) {
                List ts = nextChildren(cls, pTree.getTag());
                if (ts.isEmpty() || ts.size() == 1 && map.get(pTree.getId()).contains(ts.get(0).getId())) {
                    T updatePTree = AccessibleHelp.newInstance(cls);
                    updatePTree.setId(pTree.getId());
                    updatePTree.setLeaf(Boolean.TRUE);
                    NullHelp.requireNon(fail(updateById(updatePTree)));
                }
            }
            return row;
        });
    }

    @Override
    public final > int deleteTree(Class cls, Collection ids) {
        return deleteTree(selectByIds(cls, ids));
    }

    @Override
    public final > List parents(Class cls, String tag, Integer levelDelta) {
        return selectByTags(cls, TreeEntity.dpTags(tag, levelDelta));
    }

    private > List selectByTags(Class cls, Collection tags) {
        if (tags.isEmpty()) return Collections.emptyList();
        return selectListByColumn(cls, null, "tag", tags, Condition.IN, Boolean.TRUE);
    }

    @Override
    public final > List children(Class cls, String tag, Integer levelDelta) {
        if (NullHelp.isBlank(tag)) return Collections.emptyList();
        if (levelDelta == null || levelDelta <= 0)
            return selectListByColumn(cls, null, "tag", tag + ".", Condition.LIKE_RIGHT, Boolean.TRUE);
        return selectList(cls, whereSql(cls,
                Arrays.asList("tag", "level"),
                Arrays.asList(tag + ".", tag.split("\\.").length + levelDelta),
                Arrays.asList(Condition.LIKE_RIGHT, Condition.LE),
                Boolean.TRUE
        ));
    }

    @Override
    public final > List completion(Class cls, Collection tags, Integer levelDelta) {
        if (NullHelp.isEmpty(tags)) return Collections.emptyList();
        return selectByTags(cls, ToSet.collect(tags.stream().flatMap(tag -> TreeEntity.dpTags(tag, true, levelDelta).stream())));
    }
    ///////////////////////////////////////////////OrmTreeService
}