/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.redkale.source;

import java.io.Serializable;
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import java.util.logging.Level;
import java.util.stream.Stream;
import org.redkale.annotation.*;
import org.redkale.annotation.AutoLoad;
import org.redkale.annotation.ResourceType;
import org.redkale.inject.ResourceEvent;
import org.redkale.service.Local;
import static org.redkale.source.DataSources.*;
import org.redkale.util.*;
import org.redkale.annotation.ResourceChanged;

/**
 * DataSource的JDBC实现类
 *
 * <p>
 * 详情见: https://redkale.org
 *
 * @author zhangjx
 */
@Local
@AutoLoad(false)
@SuppressWarnings("unchecked")
@ResourceType(DataSource.class)
public class DataJdbcSource extends AbstractDataSqlSource {

    protected JdbcConnectionPool readPool;

    protected JdbcConnectionPool writePool;

    public DataJdbcSource() {
        super();
    }

    @Override
    public void init(AnyValue conf) {
        super.init(conf);
        this.readPool = new JdbcConnectionPool(true, readConfProps);
        if (readConfProps == writeConfProps) {
            this.writePool = readPool;
        } else {
            this.writePool = new JdbcConnectionPool(false, writeConfProps);
        }
    }

    @Override
    protected void updateOneResourceChange(Properties newProps, ResourceEvent[] events) {
        this.readPool.onResourceChange(events);
    }

    @Override
    protected void updateReadResourceChange(Properties newReadProps, ResourceEvent[] events) {
        this.readPool.onResourceChange(events);
    }

    @Override
    protected void updateWriteResourceChange(Properties newWriteProps, ResourceEvent[] events) {
        this.writePool.onResourceChange(events);
    }

    @Override
    protected int readMaxConns() {
        return this.readPool.maxConns;
    }

    @Override
    protected int writeMaxConns() {
        return this.writePool.maxConns;
    }

    @Override
    public void destroy(AnyValue config) {
        if (readPool != null) {
            readPool.close();
        }
        if (writePool != null && writePool != readPool) {
            writePool.close();
        }
    }

    @Override
    public void close() throws Exception {
        super.close();
        if (readPool != null) {
            readPool.close();
        }
        if (writePool != null && writePool != readPool) {
            writePool.close();
        }
    }

    public static boolean acceptsConf(AnyValue conf) {
        try {
            AnyValue read = conf.getAnyValue("read");
            AnyValue node = read == null ? conf : read;
            final Class driverClass = DriverManager.getDriver(node.getValue(DATA_SOURCE_URL)).getClass();
            RedkaleClassLoader.putReflectionDeclaredConstructors(driverClass, driverClass.getName());
            RedkaleClassLoader.putServiceLoader(java.sql.Driver.class);
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    protected JdbcConnectionPool readPool() {
        return readPool;
    }

    protected JdbcConnectionPool writePool() {
        return writePool;
    }

    @Local
    public DataJdbcConnection getReadJdbcConnection() {
        return readPool().pollConnection();
    }

    @Local
    public DataJdbcConnection getWriteJdbcConnection() {
        return writePool().pollConnection();
    }

    @Local
    public void offerJdbcConnection(DataJdbcConnection conn) {
        JdbcConnectionPool pool = conn.readFlag ? readPool() : writePool();
        pool.offerConnection(conn);
    }

    @Override
    protected final String prepareParamSign(int index) {
        return "?";
    }

    @Override
    protected final boolean isAsync() {
        return false;
    }

    protected <T> List<PreparedStatement> prepareInsertEntityStatements(JdbcConnection conn, EntityInfo<T> info, Map<String, PrepareInfo<T>> prepareInfos, T... entitys) throws SQLException {
        Attribute<T, Serializable>[] attrs = info.insertAttributes;
        final List<PreparedStatement> prestmts = new ArrayList<>();
        for (Map.Entry<String, PrepareInfo<T>> en : prepareInfos.entrySet()) {
            PrepareInfo<T> prepareInfo = en.getValue();
            PreparedStatement prestmt = info.isAutoGenerated()
                ? conn.prepareUpdateStatement(prepareInfo.prepareSql, Statement.RETURN_GENERATED_KEYS)
                : conn.prepareUpdateStatement(prepareInfo.prepareSql);
            for (final T value : prepareInfo.entitys) {
                bindStatementParameters(conn, prestmt, info, attrs, value);
                prestmt.addBatch();
            }
            prestmts.add(prestmt);
        }
        return prestmts;
    }

    protected <T> PreparedStatement prepareInsertEntityStatement(JdbcConnection conn, String sql, EntityInfo<T> info, T... entitys) throws SQLException {
        Attribute<T, Serializable>[] attrs = info.insertAttributes;
        final PreparedStatement prestmt = info.isAutoGenerated()
            ? conn.prepareUpdateStatement(sql, Statement.RETURN_GENERATED_KEYS)
            : conn.prepareUpdateStatement(sql);
        for (final T value : entitys) {
            bindStatementParameters(conn, prestmt, info, attrs, value);
            prestmt.addBatch();
        }
        return prestmt;
    }

    protected <T> List<PreparedStatement> prepareUpdateEntityStatements(JdbcConnection conn, EntityInfo<T> info, Map<String, PrepareInfo<T>> prepareInfos, T... entitys) throws SQLException {
        Attribute<T, Serializable> primary = info.primary;
        Attribute<T, Serializable>[] attrs = info.updateAttributes;
        final List<PreparedStatement> prestmts = new ArrayList<>();
        for (Map.Entry<String, PrepareInfo<T>> en : prepareInfos.entrySet()) {
            PrepareInfo<T> prepareInfo = en.getValue();
            PreparedStatement prestmt = conn.prepareUpdateStatement(prepareInfo.prepareSql);
            for (final T value : prepareInfo.entitys) {
                int k = bindStatementParameters(conn, prestmt, info, attrs, value);
                prestmt.setObject(++k, primary.get(value));
                prestmt.addBatch();
            }
            prestmts.add(prestmt);
        }
        return prestmts;
    }

    protected <T> PreparedStatement prepareUpdateEntityStatement(JdbcConnection conn, String prepareSQL, EntityInfo<T> info, T... entitys) throws SQLException {
        Attribute<T, Serializable> primary = info.primary;
        Attribute<T, Serializable>[] attrs = info.updateAttributes;
        final PreparedStatement prestmt = conn.prepareUpdateStatement(prepareSQL);
        for (final T value : entitys) {
            int k = bindStatementParameters(conn, prestmt, info, attrs, value);
            prestmt.setObject(++k, primary.get(value));
            prestmt.addBatch();
        }
        return prestmt;
    }

    protected <T> int bindStatementParameters(JdbcConnection conn, PreparedStatement prestmt, EntityInfo<T> info, Attribute<T, Serializable>[] attrs, T entity) throws SQLException {
        int i = 0;
        for (Attribute<T, Serializable> attr : attrs) {
            Object val = getEntityAttrValue(info, attr, entity);
            if (val instanceof byte[]) {
                Blob blob = conn.createBlob();
                blob.setBytes(1, (byte[]) val);
                prestmt.setObject(++i, blob);
            } else if (val instanceof Boolean) {
                prestmt.setObject(++i, ((Boolean) val) ? (byte) 1 : (byte) 0);
            } else if (val instanceof AtomicInteger) {
                prestmt.setObject(++i, ((AtomicInteger) val).get());
            } else if (val instanceof AtomicLong) {
                prestmt.setObject(++i, ((AtomicLong) val).get());
            } else {
                prestmt.setObject(++i, val);
            }
        }
        return i;
    }

    @Override
    public int batch(final DataBatch batch) {
        Objects.requireNonNull(batch);
        final DefaultDataBatch dataBatch = (DefaultDataBatch) batch;
        if (dataBatch.actions.isEmpty()) {
            return 0;
        }
        int c = 0;
        JdbcConnection conn = null;
        List<Statement> stmtsRef = new ArrayList<>();
        try {
            conn = writePool.pollTransConnection();
            conn.setAutoCommit(false);
            for (BatchAction action : dataBatch.actions) {
                if (action instanceof RunnableBatchAction) {
                    RunnableBatchAction act = (RunnableBatchAction) action;
                    act.task.run();

                } else if (action instanceof InsertBatchAction1) {
                    InsertBatchAction1 act = (InsertBatchAction1) action;
                    EntityInfo info = apply(act.entity.getClass());
                    c += insertDBStatement(stmtsRef, conn, info, act.entity);

                } else if (action instanceof DeleteBatchAction1) {
                    DeleteBatchAction1 act = (DeleteBatchAction1) action;
                    EntityInfo info = apply(act.entity.getClass());
                    Serializable pk = info.getPrimaryValue(act.entity);
                    Map<String, List<Serializable>> pkmap = info.getTableMap(pk);
                    String[] tables = pkmap.keySet().toArray(new String[pkmap.size()]);
                    String[] sqls = deleteSql(info, pkmap);
                    c += deleteDBStatement(stmtsRef, conn, info, tables, null, null, pkmap, sqls);

                } else if (action instanceof DeleteBatchAction2) {
                    DeleteBatchAction2 act = (DeleteBatchAction2) action;
                    EntityInfo info = apply(act.clazz);
                    Map<String, List<Serializable>> pkmap = info.getTableMap(act.pk);
                    String[] tables = pkmap.keySet().toArray(new String[pkmap.size()]);
                    String[] sqls = deleteSql(info, pkmap);
                    c += deleteDBStatement(stmtsRef, conn, info, tables, null, null, pkmap, sqls);

                } else if (action instanceof DeleteBatchAction3) {
                    DeleteBatchAction3 act = (DeleteBatchAction3) action;
                    EntityInfo info = apply(act.clazz);
                    String[] tables = info.getTables(act.node);
                    String[] sqls = deleteSql(info, tables, act.flipper, act.node);
                    c += deleteDBStatement(stmtsRef, conn, info, tables, act.flipper, act.node, null, sqls);

                } else if (action instanceof UpdateBatchAction1) {
                    UpdateBatchAction1 act = (UpdateBatchAction1) action;
                    EntityInfo info = apply(act.entity.getClass());
                    c += updateEntityDBStatement(stmtsRef, conn, info, act.entity);

                } else if (action instanceof UpdateBatchAction2) {
                    UpdateBatchAction2 act = (UpdateBatchAction2) action;
                    EntityInfo info = apply(act.clazz);
                    UpdateSqlInfo sql = updateColumnSql(info, act.pk, act.values);
                    c += updateColumnDBStatement(stmtsRef, conn, info, null, sql);

                } else if (action instanceof UpdateBatchAction3) {
                    UpdateBatchAction3 act = (UpdateBatchAction3) action;
                    EntityInfo info = apply(act.clazz);
                    UpdateSqlInfo sql = updateColumnSql(info, act.node, act.flipper, act.values);
                    c += updateColumnDBStatement(stmtsRef, conn, info, act.flipper, sql);

                } else if (action instanceof UpdateBatchAction4) {
                    UpdateBatchAction4 act = (UpdateBatchAction4) action;
                    EntityInfo info = apply(act.entity.getClass());
                    UpdateSqlInfo sql = updateColumnSql(info, false, act.entity, act.node, act.selects);
                    c += updateColumnDBStatement(stmtsRef, conn, info, null, sql);
                }
            }
            conn.commit();
            return c;
        } catch (SourceException se) {
            if (conn != null) {
                conn.rollback(stmtsRef);
            }
            throw se;
        } catch (SQLException e) {
            conn.rollback(stmtsRef);
            throw new SourceException(e);
        } finally {
            writePool.offerTransConnection(conn);
        }
    }

    @Override
    public CompletableFuture<Integer> batchAsync(final DataBatch batch) {
        return supplyAsync(() -> batch(batch));
    }

    @Override
    protected <T> CompletableFuture<Integer> insertDBAsync(EntityInfo<T> info, T... entitys) {
        return supplyAsync(() -> insertDB(info, entitys));
    }

    @Override
    protected <T> int insertDB(EntityInfo<T> info, T... entitys) {
        JdbcConnection conn = null;
        List<Statement> stmtsRef = new ArrayList<>();
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c = insertDBStatement(stmtsRef, conn, info, entitys);
            conn.offerUpdateStatement(stmtsRef);
            return c;
        } catch (SQLException e) {
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    private <T> int insertDBStatement(List<Statement> stmtsRef, final JdbcConnection conn, final EntityInfo<T> info, T... entitys) throws SQLException {
        final long s = System.currentTimeMillis();
        int c = 0;
        String presql = null;
        PreparedStatement prestmt = null;
        List<PreparedStatement> prestmts = null;
        Map<String, PrepareInfo<T>> prepareInfos = null;
        final Attribute primary = info.getPrimary();
        final Class primaryType = primary.type();
        Attribute<T, Serializable>[] attrs = info.insertAttributes;
        if (info.getTableStrategy() == null) { //单库单表
            presql = info.getInsertQuestionPrepareSQL(entitys[0]);
            prestmt = prepareInsertEntityStatement(conn, presql, info, entitys);
        } else {  //分库分表
            prepareInfos = getInsertQuestionPrepareInfo(info, entitys);
            prestmts = prepareInsertEntityStatements(conn, info, prepareInfos, entitys);
        }
        try {
            if (info.getTableStrategy() == null) { //单库单表                
                c = Utility.sum(prestmt.executeBatch());
                if (info.isAutoGenerated()) { //由数据库自动生成主键值
                    ResultSet set = prestmt.getGeneratedKeys();
                    int i = -1;
                    while (set.next()) {
                        T entity = entitys[++i];
                        if (primaryType == int.class || primaryType == Integer.class) {
                            primary.set(entity, set.getInt(1));
                        } else if (primaryType == long.class || primaryType == Long.class) {
                            primary.set(entity, set.getLong(1));
                        } else if (primaryType == String.class) {
                            primary.set(entity, set.getString(1));
                        } else {
                            primary.set(entity, set.getObject(1));
                        }
                    }
                }
            } else {  //分库分表
                int c1 = 0;
                for (PreparedStatement stmt : prestmts) {
                    c1 += Utility.sum(stmt.executeBatch());
                }
                c = c1;
                if (info.isAutoGenerated()) { //由数据库自动生成主键值
                    int j = -1;
                    for (Map.Entry<String, PrepareInfo<T>> en : prepareInfos.entrySet()) {
                        PrepareInfo<T> prepareInfo = en.getValue();
                        PreparedStatement stmt = prestmts.get(++j);
                        ResultSet set = stmt.getGeneratedKeys();
                        int i = -1;
                        while (set.next()) {
                            T entity = prepareInfo.entitys.get(++i);
                            if (primaryType == int.class || primaryType == Integer.class) {
                                primary.set(entity, set.getInt(1));
                            } else if (primaryType == long.class || primaryType == Long.class) {
                                primary.set(entity, set.getLong(1));
                            } else if (primaryType == String.class) {
                                primary.set(entity, set.getString(1));
                            } else {
                                primary.set(entity, set.getObject(1));
                            }
                        }
                    }
                }
            }
            if (prestmt != null) {
                stmtsRef.add(prestmt);
            } else {
                stmtsRef.addAll(prestmts);
            }
        } catch (SQLException se) {
            conn.rollback(prestmt, prestmts);
            if (!isTableNotExist(info, se.getSQLState())) {
                throw se;
            }
            if (info.getTableStrategy() == null) { //单库单表
                String[] tableSqls = createTableSqls(info);
                if (tableSqls == null) {
                    throw se;
                }
                //创建单表结构
                Statement stmt = conn.createUpdateStatement();
                if (tableSqls.length == 1) {
                    stmt.execute(tableSqls[0]);
                } else {
                    for (String tableSql : tableSqls) {
                        stmt.addBatch(tableSql);
                    }
                    stmt.executeBatch();
                }
                conn.offerUpdateStatement(stmt);
            } else { //分库分表
                info.disTableLock().lock();
                try {
                    final Set<String> newCatalogs = new LinkedHashSet<>();
                    final List<String> tableCopys = new ArrayList<>();
                    prepareInfos.forEach((t, p) -> {
                        int pos = t.indexOf('.');
                        if (pos > 0) {
                            newCatalogs.add(t.substring(0, pos));
                        }
                        tableCopys.add(getTableCopySQL(info, t));
                    });
                    try {
                        //执行一遍创建分表操作
                        Statement stmt = conn.createUpdateStatement();
                        for (String copySql : tableCopys) {
                            stmt.addBatch(copySql);
                        }
                        stmt.executeBatch();
                        conn.offerUpdateStatement(stmt);
                    } catch (SQLException sqle) { //多进程并发时可能会出现重复建表
                        if (isTableNotExist(info, sqle.getSQLState())) {
                            if (newCatalogs.isEmpty()) { //分表的原始表不存在
                                String[] tableSqls = createTableSqls(info);
                                if (tableSqls != null) {
                                    //创建原始表
                                    Statement stmt = conn.createUpdateStatement();
                                    if (tableSqls.length == 1) {
                                        stmt.execute(tableSqls[0]);
                                    } else {
                                        for (String tableSql : tableSqls) {
                                            stmt.addBatch(tableSql);
                                        }
                                        stmt.executeBatch();
                                    }
                                    conn.offerUpdateStatement(stmt);
                                    //再执行一遍创建分表操作
                                    stmt = conn.createUpdateStatement();
                                    for (String copySql : tableCopys) {
                                        stmt.addBatch(copySql);
                                    }
                                    stmt.executeBatch();
                                    conn.offerUpdateStatement(stmt);
                                }
                            } else { //需要先建库
                                Statement stmt;
                                try {
                                    stmt = conn.createUpdateStatement();
                                    for (String newCatalog : newCatalogs) {
                                        stmt.addBatch(("postgresql".equals(dbtype()) ? "CREATE SCHEMA IF NOT EXISTS " : "CREATE DATABASE IF NOT EXISTS ") + newCatalog);
                                    }
                                    stmt.executeBatch();
                                    conn.offerUpdateStatement(stmt);
                                } catch (SQLException sqle1) {
                                    logger.log(Level.SEVERE, "create database " + tableCopys + " error", sqle1);
                                }
                                try {
                                    //再执行一遍创建分表操作
                                    stmt = conn.createUpdateStatement();
                                    for (String copySql : tableCopys) {
                                        stmt.addBatch(copySql);
                                    }
                                    stmt.executeBatch();
                                    conn.offerUpdateStatement(stmt);
                                } catch (SQLException sqle2) {
                                    if (isTableNotExist(info, sqle2.getSQLState())) {
                                        String[] tableSqls = createTableSqls(info);
                                        if (tableSqls != null) { //创建原始表
                                            stmt = conn.createUpdateStatement();
                                            if (tableSqls.length == 1) {
                                                stmt.execute(tableSqls[0]);
                                            } else {
                                                for (String tableSql : tableSqls) {
                                                    stmt.addBatch(tableSql);
                                                }
                                                stmt.executeBatch();
                                            }
                                            conn.offerUpdateStatement(stmt);
                                            //再执行一遍创建分表操作
                                            stmt = conn.createUpdateStatement();
                                            for (String copySql : tableCopys) {
                                                stmt.addBatch(copySql);
                                            }
                                            stmt.executeBatch();
                                            conn.offerUpdateStatement(stmt);
                                        }
                                    } else {
                                        logger.log(Level.SEVERE, "create table2 " + tableCopys + " error", sqle2);
                                    }
                                }
                            }
                        }
                    }
                } finally {
                    info.disTableLock().unlock();
                }
            }
            if (info.getTableStrategy() == null) { //单库单表
                conn.offerUpdateStatement(prestmt);
                prestmt = prepareInsertEntityStatement(conn, presql, info, entitys);
                c = Utility.sum(prestmt.executeBatch());
                if (info.isAutoGenerated()) { //由数据库自动生成主键值
                    ResultSet set = prestmt.getGeneratedKeys();
                    int i = -1;
                    while (set.next()) {
                        T entity = entitys[++i];
                        if (primaryType == int.class || primaryType == Integer.class) {
                            primary.set(entity, set.getInt(1));
                        } else if (primaryType == long.class || primaryType == Long.class) {
                            primary.set(entity, set.getLong(1));
                        } else if (primaryType == String.class) {
                            primary.set(entity, set.getString(1));
                        } else {
                            primary.set(entity, set.getObject(1));
                        }
                    }
                }
            } else { //分库分表
                conn.offerUpdateStatement(prestmts);
                prestmts = prepareInsertEntityStatements(conn, info, prepareInfos, entitys);
                int c1 = 0;
                for (PreparedStatement stmt : prestmts) {
                    c1 += Utility.sum(stmt.executeBatch());
                }
                c = c1;
                if (info.isAutoGenerated()) { //由数据库自动生成主键值
                    int j = -1;
                    for (Map.Entry<String, PrepareInfo<T>> en : prepareInfos.entrySet()) {
                        PrepareInfo<T> prepareInfo = en.getValue();
                        PreparedStatement stmt = prestmts.get(++j);
                        ResultSet set = stmt.getGeneratedKeys();
                        int i = -1;
                        while (set.next()) {
                            T entity = prepareInfo.entitys.get(++i);
                            if (primaryType == int.class || primaryType == Integer.class) {
                                primary.set(entity, set.getInt(1));
                            } else if (primaryType == long.class || primaryType == Long.class) {
                                primary.set(entity, set.getLong(1));
                            } else if (primaryType == String.class) {
                                primary.set(entity, set.getString(1));
                            } else {
                                primary.set(entity, set.getObject(1));
                            }
                        }
                    }
                }
            }
            if (prestmt != null) {
                stmtsRef.add(prestmt);
            } else {
                stmtsRef.addAll(prestmts);
            }
        }
        //------------------------------------------------------------
        if (info.isLoggable(logger, Level.FINEST)) {  //打印调试信息
            if (info.getTableStrategy() == null) {
                char[] sqlchars = presql.toCharArray();
                for (final T value : entitys) {
                    //-----------------------------
                    StringBuilder sb = new StringBuilder(128);
                    int i = 0;
                    for (char ch : sqlchars) {
                        if (ch == '?') {
                            Object obj = info.getSQLValue(attrs[i++], value);
                            if (obj != null && obj.getClass().isArray()) {
                                sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'");
                            } else {
                                sb.append(info.formatSQLValue(obj, sqlFormatter));
                            }
                        } else {
                            sb.append(ch);
                        }
                    }
                    String debugsql = sb.toString();
                    if (info.isLoggable(logger, Level.FINEST, debugsql)) {
                        logger.finest(info.getType().getSimpleName() + " insert sql=" + debugsql.replaceAll("(\r|\n)", "\\n"));
                    }
                }
            } else {
                prepareInfos.forEach((t, p) -> {
                    char[] sqlchars = p.prepareSql.toCharArray();
                    for (final T value : p.entitys) {
                        //-----------------------------
                        StringBuilder sb = new StringBuilder(128);
                        int i = 0;
                        for (char ch : sqlchars) {
                            if (ch == '?') {
                                Object obj = info.getSQLValue(attrs[i++], value);
                                if (obj != null && obj.getClass().isArray()) {
                                    sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'");
                                } else {
                                    sb.append(info.formatSQLValue(obj, sqlFormatter));
                                }
                            } else {
                                sb.append(ch);
                            }
                        }
                        String debugsql = sb.toString();
                        if (info.isLoggable(logger, Level.FINEST, debugsql)) {
                            logger.finest(info.getType().getSimpleName() + " insert sql=" + debugsql.replaceAll("(\r|\n)", "\\n"));
                        }
                    }
                });
            }
        } //打印结束         
        if (info.getTableStrategy() == null) {
            slowLog(s, presql);
        } else {
            List<String> presqls = new ArrayList<>();
            prepareInfos.forEach((t, p) -> {
                presqls.add(p.prepareSql);
            });
            slowLog(s, presqls.toArray(new String[presqls.size()]));
        }
        return c;
    }

    @Override
    protected <T> CompletableFuture<Integer> deleteDBAsync(final EntityInfo<T> info, String[] tables, Flipper flipper, FilterNode node, Map<String, List<Serializable>> pkmap, final String... sqls) {
        return supplyAsync(() -> deleteDB(info, tables, flipper, node, pkmap, sqls));
    }

    @Override
    protected <T> int deleteDB(EntityInfo<T> info, String[] tables, Flipper flipper, FilterNode node, Map<String, List<Serializable>> pkmap, String... sqls) {
        JdbcConnection conn = null;
        List<Statement> stmtsRef = new ArrayList<>();
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c = deleteDBStatement(stmtsRef, conn, info, tables, flipper, node, pkmap, sqls);
            conn.offerUpdateStatement(stmtsRef);
            return c;
        } catch (SQLException e) {
            conn.rollback(stmtsRef);
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    private <T> int deleteDBStatement(List<Statement> stmtsRef, final JdbcConnection conn, final EntityInfo<T> info, String[] tables, Flipper flipper, FilterNode node, Map<String, List<Serializable>> pkmap, String... sqls) throws SQLException {
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        try {
            int c;
            if (sqls.length == 1) {
                stmt = conn.createUpdateStatement();
                c = stmt.executeUpdate(sqls[0]);
                conn.offerUpdateStatement(stmt);
            } else {
                stmt = conn.createUpdateStatement();
                for (String sql : sqls) {
                    stmt.addBatch(sql);
                }
                c = Utility.sum(stmt.executeBatch());
            }
            stmtsRef.add(stmt);
            slowLog(s, sqls);
            return c;
        } catch (SQLException e) {
            conn.rollback(stmt);
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    String[] tableSqls = createTableSqls(info);
                    if (tableSqls != null) {
                        Statement stmt2 = conn.createUpdateStatement();
                        if (tableSqls.length == 1) {
                            stmt2.execute(tableSqls[0]);
                        } else {
                            for (String tableSql : tableSqls) {
                                stmt2.addBatch(tableSql);
                            }
                            stmt2.executeBatch();
                        }
                        conn.offerUpdateStatement(stmt2);
                        return 0;
                    }
                    //单表结构不存在
                    return 0;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return 0;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw e;
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw e;
                    }
                    for (String t : notExistTables) {
                        if (pkmap != null) {
                            pkmap.remove(t);
                        } else {
                            tables = Utility.remove(tables, t);
                        }
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "delete, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + (pkmap != null ? pkmap.keySet() : Arrays.toString(tables)));
                    }
                    if ((pkmap != null ? pkmap.size() : tables.length) == 0) { //分表全部不存在
                        return 0;
                    }
                    sqls = pkmap != null ? deleteSql(info, pkmap) : deleteSql(info, tables, flipper, node);
                    if (info.isLoggable(logger, Level.FINEST, sqls[0])) {
                        logger.finest(info.getType().getSimpleName() + " delete sql=" + Arrays.toString(sqls));
                    }
                    Statement stmt2 = null;
                    try {
                        stmt2 = conn.createUpdateStatement();
                        for (String sql : sqls) {
                            stmt2.addBatch(sql);
                        }
                        int c = Utility.sum(stmt2.executeBatch());
                        slowLog(s, sqls);
                        return c;
                    } catch (SQLException se) {
                        throw se;
                    } finally {
                        conn.offerUpdateStatement(stmt2);
                    }
                } else {
                    throw e;
                }
            }
            throw e;
        }
    }

    @Override
    protected <T> CompletableFuture<Integer> clearTableDBAsync(EntityInfo<T> info, final String[] tables, FilterNode node, String... sqls) {
        return supplyAsync(() -> clearTableDB(info, tables, node, sqls));
    }

    @Override
    protected <T> int clearTableDB(EntityInfo<T> info, String[] tables, FilterNode node, String... sqls) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c;
            if (sqls.length == 1) {
                stmt = conn.createUpdateStatement();
                c = stmt.executeUpdate(sqls[0]);
            } else {
                stmt = conn.createUpdateStatement();
                for (String sql : sqls) {
                    stmt.addBatch(sql);
                }
                c = Utility.sum(stmt.executeBatch());
            }
            conn.offerUpdateStatement(stmt);
            slowLog(s, sqls);
            return c;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //单表结构不存在
                    return 0;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return 0;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "clearTable, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return 0;
                    }
                    sqls = clearTableSql(info, tables, node);
                    if (info.isLoggable(logger, Level.FINEST, sqls[0])) {
                        logger.finest(info.getType().getSimpleName() + " clearTable sql=" + Arrays.toString(sqls));
                    }
                    Statement stmt2 = null;
                    try {
                        stmt2 = conn.createUpdateStatement();
                        for (String sql : sqls) {
                            stmt2.addBatch(sql);
                        }
                        int c = Utility.sum(stmt2.executeBatch());
                        conn.offerUpdateStatement(stmt2);
                        slowLog(s, sqls);
                        return c;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    } finally {
                        conn.rollback(stmt2);
                    }
                } else {
                    throw new SourceException(e);
                }
            }
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    @Override
    protected <T> CompletableFuture<Integer> createTableDBAsync(EntityInfo<T> info, String copyTableSql, final Serializable pk, String... sqls) {
        return supplyAsync(() -> createTableDB(info, copyTableSql, pk, sqls));
    }

    @Override
    protected <T> CompletableFuture<Integer> dropTableDBAsync(EntityInfo<T> info, final String[] tables, FilterNode node, String... sqls) {
        return supplyAsync(() -> dropTableDB(info, tables, node, sqls));
    }

    @Override
    protected <T> int createTableDB(EntityInfo<T> info, String copyTableSql, Serializable pk, String... sqls) {
        JdbcConnection conn = null;
        Statement stmt = null;
        final long s = System.currentTimeMillis();
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c;
            if (copyTableSql == null) {
                if (sqls.length == 1) {
                    stmt = conn.createUpdateStatement();
                    c = stmt.executeUpdate(sqls[0]);
                } else {
                    stmt = conn.createUpdateStatement();
                    for (String sql : sqls) {
                        stmt.addBatch(sql);
                    }
                    c = Utility.sum(stmt.executeBatch());
                }
            } else { //建分表
                try {
                    stmt = conn.createUpdateStatement();
                    c = stmt.executeUpdate(copyTableSql);
                } catch (SQLException se) {
                    if (isTableNotExist(info, se.getSQLState())) { //分表的原始表不存在
                        conn.offerUpdateStatement(stmt);
                        final String newTable = info.getTable(pk);
                        if (newTable.indexOf('.') <= 0) { //分表的原始表不存在
                            if (info.isLoggable(logger, Level.FINEST, sqls[0])) {
                                logger.finest(info.getType().getSimpleName() + " createTable sql=" + Arrays.toString(sqls));
                            }
                            //创建原始表
                            stmt = conn.createUpdateStatement();
                            if (sqls.length == 1) {
                                stmt.execute(sqls[0]);
                            } else {
                                for (String tableSql : sqls) {
                                    stmt.addBatch(tableSql);
                                }
                                stmt.executeBatch();
                            }
                            conn.offerUpdateStatement(stmt);
                            //再执行一遍创建分表操作
                            if (info.isLoggable(logger, Level.FINEST, copyTableSql)) {
                                logger.finest(info.getType().getSimpleName() + " createTable sql=" + copyTableSql);
                            }
                            stmt = conn.createUpdateStatement();
                            c = stmt.executeUpdate(copyTableSql);
                        } else { //需要先建库
                            String newCatalog = newTable.substring(0, newTable.indexOf('.'));
                            String catalogSql = ("postgresql".equals(dbtype()) ? "CREATE SCHEMA IF NOT EXISTS " : "CREATE DATABASE IF NOT EXISTS ") + newCatalog;
                            try {
                                if (info.isLoggable(logger, Level.FINEST, catalogSql)) {
                                    logger.finest(info.getType().getSimpleName() + " createCatalog sql=" + catalogSql);
                                }
                                stmt = conn.createUpdateStatement();
                                stmt.executeUpdate(catalogSql);
                                conn.offerUpdateStatement(stmt);
                            } catch (SQLException sqle1) {
                                logger.log(Level.SEVERE, "create database " + copyTableSql + " error", sqle1);
                            }
                            try {
                                //再执行一遍创建分表操作
                                if (info.isLoggable(logger, Level.FINEST, copyTableSql)) {
                                    logger.finest(info.getType().getSimpleName() + " createTable sql=" + copyTableSql);
                                }
                                stmt = conn.createUpdateStatement();
                                c = stmt.executeUpdate(copyTableSql);
                            } catch (SQLException sqle2) {
                                conn.offerUpdateStatement(stmt);
                                if (isTableNotExist(info, sqle2.getSQLState())) {
                                    if (info.isLoggable(logger, Level.FINEST, sqls[0])) {
                                        logger.finest(info.getType().getSimpleName() + " createTable sql=" + Arrays.toString(sqls));
                                    }
                                    //创建原始表
                                    stmt = conn.createUpdateStatement();
                                    if (sqls.length == 1) {
                                        stmt.execute(sqls[0]);
                                    } else {
                                        for (String tableSql : sqls) {
                                            stmt.addBatch(tableSql);
                                        }
                                        stmt.executeBatch();
                                    }
                                    conn.offerUpdateStatement(stmt);
                                    //再执行一遍创建分表操作
                                    if (info.isLoggable(logger, Level.FINEST, copyTableSql)) {
                                        logger.finest(info.getType().getSimpleName() + " createTable sql=" + copyTableSql);
                                    }
                                    stmt = conn.createUpdateStatement();
                                    c = stmt.executeUpdate(copyTableSql);
                                } else {
                                    throw new SourceException(sqle2);
                                }
                            }
                        }
                    }
                    throw se;
                }
            }
            conn.offerUpdateStatement(stmt);
            slowLog(s, sqls);
            return c;
        } catch (SQLException e) {
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    @Override
    protected <T> int dropTableDB(EntityInfo<T> info, String[] tables, FilterNode node, String... sqls) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c;
            if (sqls.length == 1) {
                stmt = conn.createUpdateStatement();
                c = stmt.executeUpdate(sqls[0]);
            } else {
                stmt = conn.createUpdateStatement();
                for (String sql : sqls) {
                    stmt.addBatch(sql);
                }
                c = Utility.sum(stmt.executeBatch());
            }
            conn.offerUpdateStatement(stmt);
            slowLog(s, sqls);
            return c;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //单表结构不存在
                    return 0;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return 0;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "dropTable, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return 0;
                    }
                    sqls = dropTableSql(info, tables, node);
                    if (info.isLoggable(logger, Level.FINEST, sqls[0])) {
                        logger.finest(info.getType().getSimpleName() + " dropTable sql=" + Arrays.toString(sqls));
                    }
                    try {
                        final Statement stmt2 = conn.createUpdateStatement();
                        for (String sql : sqls) {
                            stmt2.addBatch(sql);
                        }
                        int c = Utility.sum(stmt2.executeBatch());
                        conn.offerUpdateStatement(stmt2);
                        slowLog(s, sqls);
                        return c;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                } else {
                    throw new SourceException(e);
                }
            }
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    @Override
    protected <T> CompletableFuture<Integer> updateEntityDBAsync(EntityInfo<T> info, T... entitys) {
        return supplyAsync(() -> updateEntityDB(info, entitys));
    }

    @Override
    protected <T> int updateEntityDB(EntityInfo<T> info, T... entitys) {
        JdbcConnection conn = null;
        List<Statement> stmtsRef = new ArrayList<>();
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c = updateEntityDBStatement(stmtsRef, conn, info, entitys);
            conn.offerUpdateStatement(stmtsRef);
            return c;
        } catch (SQLException e) {
            conn.rollback(stmtsRef);
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    private <T> int updateEntityDBStatement(List<Statement> stmtsRef, final JdbcConnection conn, final EntityInfo<T> info, T... entitys) throws SQLException {
        final long s = System.currentTimeMillis();
        String presql = null;
        PreparedStatement prestmt = null;
        List<PreparedStatement> prestmts = null;
        Map<String, PrepareInfo<T>> prepareInfos = null;
        int c = -1;
        final Attribute<T, Serializable>[] attrs = info.updateAttributes;
        try {
            if (info.getTableStrategy() == null) {
                presql = info.getUpdateQuestionPrepareSQL(entitys[0]);
                prestmt = prepareUpdateEntityStatement(conn, presql, info, entitys);
                int c1 = 0;
                int[] pc = prestmt.executeBatch();
                for (int p : pc) {
                    if (p >= 0) {
                        c1 += p;
                    }
                }
                c = c1;
            } else {
                prepareInfos = getUpdateQuestionPrepareInfo(info, entitys);
                prestmts = prepareUpdateEntityStatements(conn, info, prepareInfos, entitys);
                int c1 = 0;
                for (PreparedStatement stmt : prestmts) {
                    int[] cs = stmt.executeBatch();
                    for (int cc : cs) {
                        c1 += cc;
                    }
                }
                c = c1;
            }
            if (prestmt != null) {
                stmtsRef.add(prestmt);
            } else {
                stmtsRef.addAll(prestmts);
            }
        } catch (SQLException se) {
            conn.rollback(prestmt, prestmts);
            if (isTableNotExist(info, se.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    String[] tableSqls = createTableSqls(info);
                    if (tableSqls != null) {
                        try {
                            Statement stmt = conn.createUpdateStatement();
                            if (tableSqls.length == 1) {
                                stmt.execute(tableSqls[0]);
                            } else {
                                for (String tableSql : tableSqls) {
                                    stmt.addBatch(tableSql);
                                }
                                stmt.executeBatch();
                            }
                            conn.offerUpdateStatement(stmt);
                        } catch (SQLException e2) {
                            //do nothing
                        }
                    }
                    //表不存在，更新条数为0
                    return 0;
                } else {
                    //String tableName = parseNotExistTableName(se);
                    if (prepareInfos == null) {
                        throw se;
                    }
                    String[] oldTables = prepareInfos.keySet().toArray(new String[prepareInfos.size()]);
                    List<String> notExistTables = checkNotExistTables(conn, oldTables);
                    if (notExistTables.isEmpty()) {
                        throw se;
                    }
                    for (String t : notExistTables) {
                        prepareInfos.remove(t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "update entitys, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + prepareInfos.keySet());
                    }
                    if (prepareInfos.isEmpty()) { //分表全部不存在
                        return 0;
                    }
                    prestmts = prepareUpdateEntityStatements(conn, info, prepareInfos, entitys);
                    int c1 = 0;
                    for (PreparedStatement stmt : prestmts) {
                        c1 += Utility.sum(stmt.executeBatch());
                    }
                    c = c1;
                    stmtsRef.addAll(prestmts);
                }
            } else {
                throw se;
            }
        }

        if (info.isLoggable(logger, Level.FINEST)) {  //打印调试信息
            Attribute<T, Serializable> primary = info.getPrimary();
            if (info.getTableStrategy() == null) {
                char[] sqlchars = presql.toCharArray();
                for (final T value : entitys) {
                    //-----------------------------
                    StringBuilder sb = new StringBuilder(128);
                    int i = 0;
                    for (char ch : sqlchars) {
                        if (ch == '?') {
                            Object obj = i == attrs.length ? info.getSQLValue(primary, value) : info.getSQLValue(attrs[i++], value);
                            if (obj != null && obj.getClass().isArray()) {
                                sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'");
                            } else {
                                sb.append(info.formatSQLValue(obj, sqlFormatter));
                            }
                        } else {
                            sb.append(ch);
                        }
                    }
                    String debugsql = sb.toString();
                    if (info.isLoggable(logger, Level.FINEST, debugsql)) {
                        logger.finest(info.getType().getSimpleName() + " update sql=" + debugsql.replaceAll("(\r|\n)", "\\n"));
                    }
                }
            } else {
                prepareInfos.forEach((t, p) -> {
                    char[] sqlchars = p.prepareSql.toCharArray();
                    for (final T value : p.entitys) {
                        //-----------------------------
                        StringBuilder sb = new StringBuilder(128);
                        int i = 0;
                        for (char ch : sqlchars) {
                            if (ch == '?') {
                                Object obj = i == attrs.length ? info.getSQLValue(primary, value) : info.getSQLValue(attrs[i++], value);
                                if (obj != null && obj.getClass().isArray()) {
                                    sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'");
                                } else {
                                    sb.append(info.formatSQLValue(obj, sqlFormatter));
                                }
                            } else {
                                sb.append(ch);
                            }
                        }
                        String debugsql = sb.toString();
                        if (info.isLoggable(logger, Level.FINEST, debugsql)) {
                            logger.finest(info.getType().getSimpleName() + " update sql=" + debugsql.replaceAll("(\r|\n)", "\\n"));
                        }
                    }
                });
            }
        } //打印结束         
        if (info.getTableStrategy() == null) {
            slowLog(s, presql);
        } else {
            List<String> presqls = new ArrayList<>();
            prepareInfos.forEach((t, p) -> {
                presqls.add(p.prepareSql);
            });
            slowLog(s, presqls.toArray(new String[presqls.size()]));
        }
        return c;
    }

    @Override
    protected <T> CompletableFuture<Integer> updateColumnDBAsync(EntityInfo<T> info, Flipper flipper, UpdateSqlInfo sql) {
        return supplyAsync(() -> updateColumnDB(info, flipper, sql));
    }

    @Override
    protected <T> int updateColumnDB(EntityInfo<T> info, Flipper flipper, UpdateSqlInfo sql) {
        JdbcConnection conn = null;
        List<Statement> stmtsRef = new ArrayList<>();
        try {
            conn = writePool.pollConnection();
            conn.setAutoCommit(true);
            int c = updateColumnDBStatement(stmtsRef, conn, info, flipper, sql);
            conn.offerUpdateStatement(stmtsRef);
            return c;
        } catch (SQLException e) {
            conn.rollback(stmtsRef);
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    private <T> int updateColumnDBStatement(List<Statement> stmtsRef, final JdbcConnection conn, final EntityInfo<T> info, Flipper flipper, UpdateSqlInfo sql) throws SQLException { //String sql, boolean prepared, Object... blobs) {
        final long s = System.currentTimeMillis();
        int c = -1;
        String firstTable = null;
        Statement onestmt = null;
        List<PreparedStatement> prestmts = null;
        try {
            if (sql.blobs != null || sql.tables != null) {
                if (sql.tables == null) {
                    PreparedStatement prestmt = conn.prepareUpdateStatement(sql.sql);
                    onestmt = prestmt;
                    int index = 0;
                    for (byte[] param : sql.blobs) {
                        Blob blob = conn.createBlob();
                        blob.setBytes(1, param);
                        prestmt.setBlob(++index, blob);
                    }
                    if (info.isLoggable(logger, Level.FINEST, sql.sql)) {
                        logger.finest(info.getType().getSimpleName() + " updateColumn sql=" + sql.sql);
                    }
                    c = prestmt.executeUpdate();
                    if (onestmt != null) {
                        stmtsRef.add(onestmt);
                    }
                    slowLog(s, sql.sql);
                    return c;
                } else {
                    firstTable = sql.tables[0];
                    prestmts = new ArrayList<>();
                    String[] sqls = new String[sql.tables.length];
                    for (int i = 0; i < sql.tables.length; i++) {
                        sqls[i] = i == 0 ? sql.sql : sql.sql.replaceFirst(firstTable, sql.tables[i]);
                        PreparedStatement stmt = conn.prepareUpdateStatement(sqls[i]);
                        int index = 0;
                        if (sql.blobs != null) {
                            for (byte[] param : sql.blobs) {
                                Blob blob = conn.createBlob();
                                blob.setBytes(1, param);
                                stmt.setBlob(++index, blob);
                            }
                        }
                        stmt.addBatch();
                        prestmts.add(stmt);
                    }
                    if (info.isLoggable(logger, Level.FINEST, sql.sql)) {
                        logger.finest(info.getType().getSimpleName() + " updateColumn sql=" + Arrays.toString(sqls));
                    }
                    int c1 = 0;
                    for (PreparedStatement stmt : prestmts) {
                        c1 += Utility.sum(stmt.executeBatch());
                    }
                    c = c1;
                    if (prestmts != null) {
                        stmtsRef.addAll(prestmts);
                    }
                    slowLog(s, sqls);
                }
                return c;
            } else {
                if (info.isLoggable(logger, Level.FINEST, sql.sql)) {
                    logger.finest(info.getType().getSimpleName() + " updateColumn sql=" + sql.sql);
                }
                onestmt = conn.createUpdateStatement();
                c = onestmt.executeUpdate(sql.sql);
                if (onestmt != null) {
                    stmtsRef.add(onestmt);
                }
                slowLog(s, sql.sql);
                return c;
            }
        } catch (SQLException se) {
            conn.rollback(onestmt, prestmts);
            if (isTableNotExist(info, se.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    String[] tableSqls = createTableSqls(info);
                    if (tableSqls != null) {
                        try {
                            onestmt = conn.createUpdateStatement();
                            if (tableSqls.length == 1) {
                                onestmt.execute(tableSqls[0]);
                            } else {
                                for (String tableSql : tableSqls) {
                                    onestmt.addBatch(tableSql);
                                }
                                onestmt.executeBatch();
                            }
                            conn.offerUpdateStatement(onestmt);
                        } catch (SQLException e2) {
                            conn.rollback(onestmt);
                        }
                    }
                    //表不存在，更新条数为0
                    return 0;
                } else if (sql.tables == null) {
                    //单一分表不存在
                    return 0;
                } else {
//                        String tableName = parseNotExistTableName(se);
//                        if (tableName == null) {
//                            throw se;
//                        }
                    String[] oldTables = sql.tables;
                    List<String> notExistTables = checkNotExistTables(conn, oldTables);
                    if (notExistTables.isEmpty()) {
                        throw se;
                    }
                    for (String t : notExistTables) {
                        sql.tables = Utility.remove(sql.tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "updateColumn, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(sql.tables));
                    }
                    if (sql.tables.length == 0) { //分表全部不存在
                        return 0;
                    }
                    prestmts = new ArrayList<>();
                    String[] sqls = new String[sql.tables.length];
                    for (int i = 0; i < sql.tables.length; i++) {
                        sqls[i] = sql.sql.replaceFirst(firstTable, sql.tables[i]);
                        PreparedStatement prestmt = conn.prepareUpdateStatement(sqls[i]);
                        int index = 0;
                        if (sql.blobs != null) {
                            for (byte[] param : sql.blobs) {
                                Blob blob = conn.createBlob();
                                blob.setBytes(1, param);
                                prestmt.setBlob(++index, blob);
                            }
                        }
                        prestmt.addBatch();
                        prestmts.add(prestmt);
                    }
                    if (info.isLoggable(logger, Level.FINEST, sql.sql)) {
                        logger.finest(info.getType().getSimpleName() + " updateColumn sql=" + Arrays.toString(sqls));
                    }
                    int c1 = 0;
                    for (PreparedStatement stmt : prestmts) {
                        c1 += Utility.sum(stmt.executeBatch());
                    }
                    c = c1;
                    if (prestmts != null) {
                        stmtsRef.addAll(prestmts);
                    }
                    slowLog(s, sqls);
                    return c;
                }
            } else {
                throw se;
            }
        }
    }

    @Override
    protected <T, N extends Number> CompletableFuture<Map<String, N>> getNumberMapDBAsync(EntityInfo<T> info, String[] tables, String sql, FilterNode node, FilterFuncColumn... columns) {
        return supplyAsync(() -> getNumberMapDB(info, tables, sql, node, columns));
    }

    @Override
    protected <T, N extends Number> Map<String, N> getNumberMapDB(EntityInfo<T> info, String[] tables, String sql, FilterNode node, FilterFuncColumn... columns) {
        JdbcConnection conn = null;
        final Map map = new HashMap<>();
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            stmt = conn.createQueryStatement();
            ResultSet set = stmt.executeQuery(sql);
            if (set.next()) {
                int index = 0;
                for (FilterFuncColumn ffc : columns) {
                    for (String col : ffc.cols()) {
                        Object o = set.getObject(++index);
                        Number rs = ffc.getDefvalue();
                        if (o != null) {
                            rs = (Number) o;
                        }
                        map.put(ffc.col(col), rs);
                    }
                }
            }
            set.close();
            conn.offerQueryStatement(stmt);
            slowLog(s, sql);
            return map;
        } catch (SQLException e) {
            map.clear();
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return map;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return map;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "getNumberMap, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return map;
                    }

                    //重新查询一次
                    try {
                        sql = getNumberMapSql(info, tables, node, columns);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " getNumberMap sql=" + sql);
                        }
                        if (stmt != null) {
                            conn.offerQueryStatement(stmt);
                        }
                        stmt = conn.createQueryStatement();
                        ResultSet set = stmt.executeQuery(sql);
                        if (set.next()) {
                            int index = 0;
                            for (FilterFuncColumn ffc : columns) {
                                for (String col : ffc.cols()) {
                                    Object o = set.getObject(++index);
                                    Number rs = ffc.getDefvalue();
                                    if (o != null) {
                                        rs = (Number) o;
                                    }
                                    map.put(ffc.col(col), rs);
                                }
                            }
                        }
                        set.close();
                        conn.offerQueryStatement(stmt);
                        slowLog(s, sql);
                        return map;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T> CompletableFuture<Number> getNumberResultDBAsync(EntityInfo<T> info, String[] tables, String sql, FilterFunc func, Number defVal, String column, FilterNode node) {
        return supplyAsync(() -> getNumberResultDB(info, tables, sql, func, defVal, column, node));
    }

    @Override
    protected <T> Number getNumberResultDB(EntityInfo<T> info, String[] tables, String sql, FilterFunc func, Number defVal, String column, FilterNode node) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            stmt = conn.createQueryStatement();
            Number rs = defVal;
            ResultSet set = stmt.executeQuery(sql);
            if (set.next()) {
                Object o = set.getObject(1);
                if (o != null) {
                    rs = (Number) o;
                }
            }
            set.close();
            conn.offerQueryStatement(stmt);
            slowLog(s, sql);
            return rs;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return defVal;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return defVal;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "getNumberResult, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return defVal;
                    }

                    //重新查询一次
                    try {
                        sql = getNumberResultSql(info, info.getType(), tables, func, defVal, column, node);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " getNumberResult sql=" + sql);
                        }
                        if (stmt != null) {
                            conn.offerQueryStatement(stmt);
                        }
                        stmt = conn.createQueryStatement();
                        Number rs = defVal;
                        ResultSet set = stmt.executeQuery(sql);
                        if (set.next()) {
                            Object o = set.getObject(1);
                            if (o != null) {
                                rs = (Number) o;
                            }
                        }
                        set.close();
                        conn.offerQueryStatement(stmt);
                        slowLog(s, sql);
                        return rs;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T, K extends Serializable, N extends Number> CompletableFuture<Map<K, N>> queryColumnMapDBAsync(EntityInfo<T> info, String[] tables, String sql, String keyColumn, FilterFunc func, String funcColumn, FilterNode node) {
        return supplyAsync(() -> queryColumnMapDB(info, tables, sql, keyColumn, func, funcColumn, node));
    }

    @Override
    protected <T, K extends Serializable, N extends Number> Map<K, N> queryColumnMapDB(EntityInfo<T> info, String[] tables, String sql, String keyColumn, FilterFunc func, String funcColumn, FilterNode node) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        Map<K, N> rs = new LinkedHashMap<>();
        Statement stmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            stmt = conn.createQueryStatement();
            ResultSet set = stmt.executeQuery(sql);
            ResultSetMetaData rsd = set.getMetaData();
            boolean smallint = rsd != null && rsd.getColumnType(1) == Types.SMALLINT;
            while (set.next()) {
                rs.put((K) (smallint ? set.getShort(1) : set.getObject(1)), (N) set.getObject(2));
            }
            set.close();
            conn.offerQueryStatement(stmt);
            slowLog(s, sql);
            return rs;
        } catch (SQLException e) {
            rs.clear();
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return rs;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return rs;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "queryColumnMap, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return rs;
                    }

                    //重新查询一次
                    try {
                        sql = queryColumnMapSql(info, tables, keyColumn, func, funcColumn, node);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " queryColumnMap sql=" + sql);
                        }
                        if (stmt != null) {
                            conn.offerQueryStatement(stmt);
                        }
                        stmt = conn.createQueryStatement();
                        ResultSet set = stmt.executeQuery(sql);
                        ResultSetMetaData rsd = set.getMetaData();
                        boolean smallint = rsd != null && rsd.getColumnType(1) == Types.SMALLINT;
                        while (set.next()) {
                            rs.put((K) (smallint ? set.getShort(1) : set.getObject(1)), (N) set.getObject(2));
                        }
                        set.close();
                        conn.offerQueryStatement(stmt);
                        slowLog(s, sql);
                        return rs;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T, K extends Serializable, N extends Number> CompletableFuture<Map<K[], N[]>> queryColumnMapDBAsync(EntityInfo<T> info, String[] tables, String sql, final ColumnNode[] funcNodes, final String[] groupByColumns, final FilterNode node) {
        return supplyAsync(() -> queryColumnMapDB(info, tables, sql, funcNodes, groupByColumns, node));
    }

    @Override
    protected <T, K extends Serializable, N extends Number> Map<K[], N[]> queryColumnMapDB(EntityInfo<T> info, String[] tables, String sql, final ColumnNode[] funcNodes, final String[] groupByColumns, final FilterNode node) {
        JdbcConnection conn = null;
        Map rs = new LinkedHashMap<>();
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            stmt = conn.createQueryStatement();
            ResultSet set = stmt.executeQuery(sql);
            ResultSetMetaData rsd = set.getMetaData();
            boolean[] smallints = null;
            while (set.next()) {
                int index = 0;
                Serializable[] keys = new Serializable[groupByColumns.length];
                if (smallints == null) {
                    smallints = new boolean[keys.length];
                    for (int i = 0; i < keys.length; i++) {
                        smallints[i] = rsd != null && rsd.getColumnType(i + 1) == Types.SMALLINT;
                    }
                }
                for (int i = 0; i < keys.length; i++) {
                    keys[i] = (Serializable) ((smallints[i] && index == 0) ? set.getShort(++index) : set.getObject(++index));
                }
                Number[] vals = new Number[funcNodes.length];
                for (int i = 0; i < vals.length; i++) {
                    vals[i] = (Number) set.getObject(++index);
                }
                rs.put(keys, vals);
            }
            set.close();
            conn.offerQueryStatement(stmt);
            slowLog(s, sql);
            return rs;
        } catch (SQLException e) {
            rs.clear();
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return rs;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return rs;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "queryColumnMap, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return rs;
                    }

                    //重新查询一次
                    try {
                        sql = queryColumnMapSql(info, tables, funcNodes, groupByColumns, node);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " queryColumnMap sql=" + sql);
                        }
                        if (stmt != null) {
                            conn.offerQueryStatement(stmt);
                        }
                        stmt = conn.createQueryStatement();
                        ResultSet set = stmt.executeQuery(sql);
                        ResultSetMetaData rsd = set.getMetaData();
                        boolean smallint = rsd != null && rsd.getColumnType(1) == Types.SMALLINT;
                        while (set.next()) {
                            rs.put((K) (smallint ? set.getShort(1) : set.getObject(1)), (N) set.getObject(2));
                        }
                        set.close();
                        conn.offerQueryStatement(stmt);
                        slowLog(s, sql);
                        return rs;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T> T findUnCache(final EntityInfo<T> info, final SelectColumn selects, final Serializable pk) {
        if (selects == null && info.getTableStrategy() == null) {
            return findDB(info, pk);
        } else {
            return super.findUnCache(info, selects, pk);
        }
    }

    @Override
    protected <T> CompletableFuture<T> findUnCacheAsync(final EntityInfo<T> info, final SelectColumn selects, final Serializable pk) {
        if (selects == null && info.getTableStrategy() == null) {
            return supplyAsync(() -> findDB(info, pk));
        } else {
            return super.findUnCacheAsync(info, selects, pk);
        }
    }

    protected <T> T findDB(EntityInfo<T> info, Serializable pk) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        PreparedStatement prestmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            String prepareSQL = info.getFindQuestionPrepareSQL(pk);
            prestmt = conn.prepareQueryStatement(prepareSQL);
            prestmt.setObject(1, pk);
            ResultSet rr = prestmt.executeQuery();
            T rs = rr.next() ? info.getBuilder().getFullEntityValue(createDataResultSet(info, rr)) : null;
            rr.close();
            conn.offerQueryStatement(prestmt);
            slowLog(s, prepareSQL);
            return rs;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                return null;
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T> CompletableFuture<T> findDBAsync(EntityInfo<T> info, String[] tables, String sql, boolean onlypk, SelectColumn selects, Serializable pk, FilterNode node) {
        return supplyAsync(() -> findDB(info, tables, sql, onlypk, selects, pk, node));
    }

    @Override
    protected <T> T findDB(EntityInfo<T> info, String[] tables, String sql, boolean onlypk, SelectColumn selects, Serializable pk, FilterNode node) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        PreparedStatement prestmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            prestmt = conn.prepareQueryStatement(sql);
            prestmt.setFetchSize(1);
            ResultSet rr = prestmt.executeQuery();
            T rs = rr.next() ? info.getBuilder().getEntityValue(selects, createDataResultSet(info, rr)) : null;
            rr.close();
            conn.offerQueryStatement(prestmt);
            slowLog(s, sql);
            return rs;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return null;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return null;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "find, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return null;
                    }

                    //重新查询一次
                    try {
                        sql = findSql(info, tables, selects, node);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " find sql=" + sql);
                        }
                        if (prestmt != null) {
                            conn.offerQueryStatement(prestmt);
                        }
                        prestmt = conn.prepareQueryStatement(sql);
                        prestmt.setFetchSize(1);
                        ResultSet rr = prestmt.executeQuery();
                        T rs = rr.next() ? info.getBuilder().getEntityValue(selects, createDataResultSet(info, rr)) : null;
                        rr.close();
                        conn.offerQueryStatement(prestmt);
                        slowLog(s, sql);
                        return rs;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T> CompletableFuture<Serializable> findColumnDBAsync(EntityInfo<T> info, final String[] tables, String sql, boolean onlypk, String column, Serializable defValue, Serializable pk, FilterNode node) {
        return supplyAsync(() -> findColumnDB(info, tables, sql, onlypk, column, defValue, pk, node));
    }

    @Override
    protected <T> Serializable findColumnDB(EntityInfo<T> info, String[] tables, String sql, boolean onlypk, String column, Serializable defValue, Serializable pk, FilterNode node) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        PreparedStatement prestmt = null;
        final Attribute<T, Serializable> attr = info.getAttribute(column);
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            prestmt = conn.prepareQueryStatement(sql);
            prestmt.setFetchSize(1);
            final DataResultSet set = createDataResultSet(info, prestmt.executeQuery());
            Serializable val = defValue;
            if (set.next()) {
                val = info.getBuilder().getFieldValue(attr, set, 1);
            }
            set.close();
            conn.offerQueryStatement(prestmt);
            slowLog(s, sql);
            return val == null ? defValue : val;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return defValue;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return defValue;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "findColumn, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return defValue;
                    }

                    //重新查询一次
                    try {
                        sql = findColumnSql(info, tables, column, defValue, node);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " findColumn sql=" + sql);
                        }
                        if (prestmt != null) {
                            conn.offerQueryStatement(prestmt);
                        }
                        prestmt = conn.prepareQueryStatement(sql);
                        prestmt.setFetchSize(1);
                        final DataResultSet set = createDataResultSet(info, prestmt.executeQuery());
                        Serializable val = defValue;
                        if (set.next()) {
                            val = info.getBuilder().getFieldValue(attr, set, 1);
                        }
                        set.close();
                        conn.offerQueryStatement(prestmt);
                        slowLog(s, sql);
                        return val == null ? defValue : val;
                    } catch (Exception se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } catch (Exception ex) {
            throw new SourceException(ex);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T> CompletableFuture<Boolean> existsDBAsync(EntityInfo<T> info, final String[] tables, String sql, boolean onlypk, Serializable pk, FilterNode node) {
        return supplyAsync(() -> existsDB(info, tables, sql, onlypk, pk, node));
    }

    @Override
    protected <T> boolean existsDB(EntityInfo<T> info, String[] tables, String sql, boolean onlypk, Serializable pk, FilterNode node) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        PreparedStatement prestmt = null;
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            prestmt = conn.prepareQueryStatement(sql);
            final ResultSet set = prestmt.executeQuery();
            boolean rs = set.next() ? (set.getInt(1) > 0) : false;
            set.close();
            conn.offerQueryStatement(prestmt);
            if (info.isLoggable(logger, Level.FINEST, sql)) {
                logger.finest(info.getType().getSimpleName() + " exists (" + rs + ") sql=" + sql);
            }
            slowLog(s, sql);
            return rs;
        } catch (SQLException e) {
            if (isTableNotExist(info, e.getSQLState())) {
                if (info.getTableStrategy() == null) {
                    //读操作不自动创建表，可能存在读写分离
                    return false;
                } else if (tables != null && tables.length == 1) {
                    //只查一个不存在的分表
                    return false;
                } else if (tables != null && tables.length > 1) {
                    //多分表查询中一个或多个分表不存在
//                    String tableName = parseNotExistTableName(e);
//                    if (tableName == null) {
//                        throw new SourceException(e);
//                    }
                    String[] oldTables = tables;
                    List<String> notExistTables = checkNotExistTablesNoThrows(conn, tables);
                    if (notExistTables.isEmpty()) {
                        throw new SourceException(e);
                    }
                    for (String t : notExistTables) {
                        tables = Utility.remove(tables, t);
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "exists, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                    }
                    if (tables.length == 0) { //分表全部不存在
                        return false;
                    }

                    //重新查询一次
                    try {
                        sql = existsSql(info, tables, node);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " exists sql=" + sql);
                        }
                        if (prestmt != null) {
                            conn.offerQueryStatement(prestmt);
                        }
                        prestmt = conn.prepareQueryStatement(sql);
                        final ResultSet set = prestmt.executeQuery();
                        boolean rs = set.next() && (set.getInt(1) > 0);
                        set.close();
                        conn.offerQueryStatement(prestmt);
                        if (info.isLoggable(logger, Level.FINEST, sql)) {
                            logger.finest(info.getType().getSimpleName() + " exists (" + rs + ") sql=" + sql);
                        }
                        slowLog(s, sql);
                        return rs;
                    } catch (SQLException se) {
                        throw new SourceException(se);
                    }
                }
            }
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    public <D extends Serializable, T> List<T> findsList(Class<T> clazz, Stream<D> pks) {
        final EntityInfo<T> info = loadEntityInfo(clazz);
        Serializable[] ids = pks.toArray(serialArrayFunc);
        if (info.getTableStrategy() == null) {
            JdbcConnection conn = null;
            final long s = System.currentTimeMillis();
            final List<T> list = new ArrayList();
            try {
                conn = readPool.pollConnection();
                conn.setAutoCommit(true);
                try {
                    String prepareSQL = info.getFindQuestionPrepareSQL(ids[0]);
                    PreparedStatement prestmt = conn.prepareQueryStatement(prepareSQL);
                    DataJdbcResultSet rr = new DataJdbcResultSet(info);
                    for (Serializable pk : ids) {
                        prestmt.setObject(1, pk);
                        ResultSet set = prestmt.executeQuery();
                        rr.resultSet(set);
                        if (set.next()) {
                            list.add(getEntityValue(info, null, rr));
                        } else {
                            list.add(null);
                        }
                        set.close();
                    }
                    conn.offerQueryStatement(prestmt);
                    slowLog(s, prepareSQL);
                    return list;
                } catch (SQLException se) {
                    if (isTableNotExist(info, se.getSQLState())) {
                        return list;
                    }
                    throw new SourceException(se);
                }
            } catch (SourceException se) {
                throw se;
            } catch (Exception e) {
                throw new SourceException(e);
            } finally {
                readPool.offerConnection(conn);
            }
        } else {
            return queryList(info.getType(), null, null, FilterNodes.in(info.getPrimarySQLColumn(), ids));
        }
    }

    @Override
    public <D extends Serializable, T> CompletableFuture<List<T>> findsListAsync(final Class<T> clazz, final Stream<D> pks) {
        return supplyAsync(() -> findsList(clazz, pks));
    }

    @Override
    protected <T> CompletableFuture<Sheet<T>> querySheetDBAsync(EntityInfo<T> info, final boolean readCache, boolean needTotal, final boolean distinct, SelectColumn selects, Flipper flipper, FilterNode node) {
        return supplyAsync(() -> querySheetDB(info, readCache, needTotal, distinct, selects, flipper, node));
    }

    protected <T> Sheet<T> querySheetFullListDB(EntityInfo<T> info) {
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            final List<T> list = new ArrayList();
            try {
                String prepareSQL = info.getAllQueryPrepareSQL();
                PreparedStatement prestmt = conn.prepareQueryStatement(prepareSQL);
                ResultSet set = prestmt.executeQuery();
                final DataResultSet rr = createDataResultSet(info, set);
                while (set.next()) {
                    list.add(getEntityValue(info, null, rr));
                }
                set.close();
                conn.offerQueryStatement(prestmt);
                slowLog(s, prepareSQL);
                return Sheet.asSheet(list);
            } catch (SQLException se) {
                if (isTableNotExist(info, se.getSQLState())) {
                    return Sheet.asSheet(list);
                }
                throw new SourceException(se);
            }
        } catch (SourceException se) {
            throw se;
        } catch (Exception e) {
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    protected <T> Sheet<T> querySheetDB(EntityInfo<T> info, final boolean readCache, boolean needTotal, final boolean distinct, SelectColumn selects, Flipper flipper, FilterNode node) {
        if (!needTotal && !distinct && selects == null && flipper == null && node == null && info.getTableStrategy() == null) {
            return querySheetFullListDB(info);
        }
        JdbcConnection conn = null;
        final long s = System.currentTimeMillis();
        final SelectColumn sels = selects;
        final Map<Class, String> joinTabalis = node == null ? null : node.getJoinTabalis();
        final CharSequence join = node == null ? null : node.createSQLJoin(this, false, joinTabalis, new HashSet<>(), info);
        final CharSequence where = node == null ? null : node.createSQLExpress(this, info, joinTabalis);
        String[] tables = info.getTables(node);
        final String joinAndWhere = (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where));
        final boolean mysqlOrPgsql = "mysql".equals(dbtype()) || "postgresql".equals(dbtype());
        try {
            conn = readPool.pollConnection();
            conn.setAutoCommit(true);
            String[] sqls = createSheetListAndCountSql(info, readCache, needTotal, distinct, selects, flipper, mysqlOrPgsql, tables, joinAndWhere);
            String listSql = sqls[0];
            String countSql = sqls[1];
            try {
                return executeQuerySheet(info, needTotal, flipper, sels, s, conn, mysqlOrPgsql, listSql, countSql);
            } catch (SQLException se) {
                if (isTableNotExist(info, se.getSQLState())) {
                    if (info.getTableStrategy() == null) {
                        //读操作不自动创建表，可能存在读写分离
                        return new Sheet<>(0, new ArrayList());
                    } else if (tables != null && tables.length == 1) {
                        //只查一个不存在的分表
                        return new Sheet<>(0, new ArrayList());
                    } else if (tables != null && tables.length > 1) {
                        //多分表查询中一个或多个分表不存在
//                        String tableName = parseNotExistTableName(se);
//                        if (tableName == null) {
//                            throw new SourceException(se);
//                        }
                        String[] oldTables = tables;
                        List<String> notExistTables = checkNotExistTables(conn, tables);
                        if (notExistTables.isEmpty()) {
                            throw new SourceException(se);
                        }
                        for (String t : notExistTables) {
                            tables = Utility.remove(tables, t);
                        }
                        if (logger.isLoggable(Level.FINE)) {
                            logger.log(Level.FINE, "querySheet, old-tables: " + Arrays.toString(oldTables) + ", new-tables: " + Arrays.toString(tables));
                        }
                        if (tables.length == 0) { //分表全部不存在
                            return new Sheet<>(0, new ArrayList());
                        }
                        if (tables.length == oldTables.length) { //没有变化, 不异常会陷入死循环
                            throw new SourceException(se);
                        }

                        //重新查询一次
                        sqls = createSheetListAndCountSql(info, readCache, needTotal, distinct, selects, flipper, mysqlOrPgsql, tables, joinAndWhere);
                        listSql = sqls[0];
                        countSql = sqls[1];
                        return executeQuerySheet(info, needTotal, flipper, sels, s, conn, mysqlOrPgsql, listSql, countSql);
                    } else {
                        throw new SourceException(se);
                    }
                }
                throw new SourceException(se);
            }
        } catch (SourceException se) {
            throw se;
        } catch (Exception e) {
            throw new SourceException(e);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    private <T> Sheet<T> executeQuerySheet(EntityInfo<T> info, boolean needTotal, Flipper flipper, SelectColumn sels,
        long s, JdbcConnection conn, boolean mysqlOrPgsql, String listSql, String countSql) throws SQLException {
        final List<T> list = new ArrayList();
        if (mysqlOrPgsql) {  //sql可以带limit、offset   
            ResultSet set;
            PreparedStatement prestmt;
            long total = -1;
            if (needTotal) {
                prestmt = conn.prepareQueryStatement(countSql);
                set = prestmt.executeQuery();
                if (set.next()) {
                    total = set.getLong(1);
                }
                set.close();
                conn.offerQueryStatement(prestmt);
            }
            if (total != 0) {
                prestmt = conn.prepareQueryStatement(listSql);
                set = prestmt.executeQuery();
                final DataResultSet rr = createDataResultSet(info, set);
                while (set.next()) {
                    list.add(getEntityValue(info, sels, rr));
                }
                set.close();
                conn.offerQueryStatement(prestmt);
                if (!needTotal) {
                    total = list.size();
                }
            }
            slowLog(s, listSql);
            return new Sheet<>(total, list);
        } else {
            PreparedStatement prestmt = conn.prepareQueryStatement(listSql);
            if (flipper != null && flipper.getLimit() > 0) {
                prestmt.setFetchSize(flipper.getLimit());
            }
            ResultSet set = prestmt.executeQuery();
            if (flipper != null && flipper.getOffset() > 0) {
                set.absolute(flipper.getOffset());
            }
            final int limit = flipper == null || flipper.getLimit() < 1 ? Integer.MAX_VALUE : flipper.getLimit();
            int i = 0;
            final DataResultSet rr = createDataResultSet(info, set);
            EntityBuilder<T> builder = info.getBuilder();
            if (sels == null) {
                while (set.next()) {
                    i++;
                    list.add(builder.getFullEntityValue(rr));
                    if (limit <= i) {
                        break;
                    }
                }
            } else {
                while (set.next()) {
                    i++;
                    list.add(builder.getEntityValue(sels, rr));
                    if (limit <= i) {
                        break;
                    }
                }
            }
            long total = list.size();
            if (needTotal && flipper != null) {
                set.last();
                total = set.getRow();
            }
            set.close();
            conn.offerQueryStatement(prestmt);
            slowLog(s, listSql);
            return new Sheet<>(total, list);
        }
    }

    private <T> String[] createSheetListAndCountSql(EntityInfo<T> info, final boolean readCache, boolean needTotal,
        final boolean distinct, SelectColumn selects, Flipper flipper, boolean mysqlOrPgsql, String[] tables, String joinAndWhere) {
        String listSql = null;
        String countSql = null;
        {  //组装listSql、countSql
            String listSubSql;
            StringBuilder union = new StringBuilder();
            if (tables.length == 1) {
                listSubSql = "SELECT " + (distinct ? "DISTINCT " : "") + info.getQueryColumns("a", selects) + " FROM " + tables[0] + " a" + joinAndWhere;
            } else {
                int b = 0;
                for (String table : tables) {
                    if (union.length() > 0) {
                        union.append(" UNION ALL ");
                    }
                    union.append("SELECT ").append(info.getQueryColumns("a", selects)).append(" FROM ").append(table).append(" a").append(joinAndWhere);
                }
                listSubSql = "SELECT " + (distinct ? "DISTINCT " : "") + info.getQueryColumns("a", selects) + " FROM (" + (union) + ") a";
            }
            listSql = listSubSql + createSQLOrderby(info, flipper);
            if (mysqlOrPgsql) {
                listSql += (flipper == null || flipper.getLimit() < 1 ? "" : (" LIMIT " + flipper.getLimit() + " OFFSET " + flipper.getOffset()));
                if (readCache && info.isLoggable(logger, Level.FINEST, listSql)) {
                    logger.finest(info.getType().getSimpleName() + " query sql=" + listSql);
                }
            } else {
                if (readCache && info.isLoggable(logger, Level.FINEST, listSql)) {
                    logger.finest(info.getType().getSimpleName() + " query sql=" + listSql + (flipper == null || flipper.getLimit() < 1 ? "" : (" LIMIT " + flipper.getLimit() + " OFFSET " + flipper.getOffset())));
                }
            }
            if (mysqlOrPgsql && needTotal) {
                String countSubSql;
                if (tables.length == 1) {
                    countSubSql = "SELECT " + (distinct ? "DISTINCT COUNT(" + info.getQueryColumns("a", selects) + ")" : "COUNT(*)") + " FROM " + tables[0] + " a" + joinAndWhere;
                } else {
                    countSubSql = "SELECT " + (distinct ? "DISTINCT COUNT(" + info.getQueryColumns("a", selects) + ")" : "COUNT(*)") + " FROM (" + (union) + ") a";
                }
                countSql = countSubSql;
                if (readCache && info.isLoggable(logger, Level.FINEST, countSql)) {
                    logger.finest(info.getType().getSimpleName() + " querySheet countsql=" + countSql);
                }
            }
        }
        return new String[]{listSql, countSql};
    }

    protected List<String> checkNotExistTablesNoThrows(JdbcConnection conn, String[] tables) {
        try {
            return checkNotExistTables(conn, tables); //, firstNotExistTable
        } catch (SQLException e) {
            throw new SourceException(e);
        }
    }

    protected List<String> checkNotExistTables(JdbcConnection conn, String[] tables) throws SQLException { //, String firstNotExistTable
//        数据库不一定要按批量提交的SQL顺序执行， 所以第一个不存在的表不一定在tables的第一位, 
//        比如 DELETE FROM  table1; DELETE FROM  table2;  如果table1、table2都不存在，SQL可能会抛出table2不存在的异常
//        List<String> maybeNoTables = new ArrayList<>();
//        String minTableName = (firstNotExistTable.indexOf('.') > 0) ? firstNotExistTable.substring(firstNotExistTable.indexOf('.') + 1) : null;
//        for (String t : tables) {
//            if (!maybeNoTables.isEmpty()) {
//                maybeNoTables.add(t);
//            }
//            if (t.equals(firstNotExistTable) || (minTableName != null && t.equals(minTableName))) {
//                maybeNoTables.add(t);
//            }
//        }
//        if (maybeNoTables.isEmpty()) {
//            return maybeNoTables;
//        }

        String[] tableTypes = new String[]{"TABLE"};
        DatabaseMetaData dmd = conn.getMetaData();
        List<String> rs = new ArrayList<>();
        for (String t : tables) { //maybeNoTables
            String catalog = null;
            String table = t;
            int pos = t.indexOf('.');
            if (pos > 0) {
                catalog = t.substring(0, pos);
                table = t.substring(pos + 1);
            }
            ResultSet dmdrs = dmd.getTables(catalog, null, table, tableTypes);
            if (!dmdrs.next()) { //不存在表             
                rs.add(t);
            }
            dmdrs.close();
        }
        return rs;
    }

    /**
     * 直接本地执行SQL语句进行增删改操作，远程模式不可用   <br>
     * 通常用于复杂的更新操作   <br>
     *
     * @param sqls SQL语句
     *
     * @return 结果数组
     */
    @Local
    @Override
    public int[] nativeUpdates(String... sqls) {
        if (sqls.length == 0) {
            return new int[0];
        }
        final long s = System.currentTimeMillis();
        Statement stmt = null;
        JdbcConnection conn = writePool.pollConnection();
        try {
            conn.setAutoCommit(false);
            stmt = conn.createUpdateStatement();
            final int[] rs = new int[sqls.length];
            int i = -1;
            for (String sql : sqls) {
                rs[++i] = stmt.executeUpdate(sql);
            }
            conn.commit();
            conn.offerUpdateStatement(stmt);
            slowLog(s, sqls);
            return rs;
        } catch (SQLException e) {
            conn.rollback(stmt);
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    /**
     * 直接本地执行SQL语句进行增删改操作，远程模式不可用   <br>
     * 通常用于复杂的更新操作   <br>
     *
     * @param sql SQL语句
     *
     * @return 结果数组
     */
    @Local
    @Override
    public int nativeUpdate(String sql) {
        return nativeUpdates(sql)[0];
    }

    @Local
    @Override
    public int nativeUpdate(String sql, Map<String, Object> params) {
        DataNativeSqlStatement sinfo = super.nativeParse(sql, params);
        final long s = System.currentTimeMillis();
        JdbcConnection conn = writePool.pollConnection();
        Statement stmt = null;
        try {
            conn.setAutoCommit(false);
            int rs;
            if (sinfo.isEmptyNamed()) {
                stmt = conn.createUpdateStatement();
                rs = stmt.executeUpdate(sinfo.getNativeSql());
                conn.offerUpdateStatement(stmt);
            } else {
                final PreparedStatement prestmt = conn.prepareQueryStatement(sinfo.getNativeSql());
                Map<String, Object> paramValues = sinfo.getParamValues();
                int index = 0;
                for (String n : sinfo.getParamNames()) {
                    prestmt.setObject(++index, paramValues.get(n));
                }
                rs = prestmt.executeUpdate();
                stmt = prestmt;
            }
            conn.commit();
            conn.offerUpdateStatement(stmt);
            slowLog(s, sinfo.nativeSql);
            return rs;
        } catch (SQLException e) {
            conn.rollback(stmt);
            throw new SourceException(e);
        } finally {
            writePool.offerConnection(conn);
        }
    }

    /**
     * 直接本地执行SQL语句进行查询，远程模式不可用   <br>
     * 通常用于复杂的关联查询   <br>
     *
     * @param <V>     泛型
     * @param sql     SQL语句
     * @param handler 回调函数
     *
     * @return 结果
     */
    @Local
    @Override
    public <V> V nativeQuery(String sql, BiConsumer<Object, Object> consumer, Function<DataResultSet, V> handler) {
        final long s = System.currentTimeMillis();
        final JdbcConnection conn = readPool.pollConnection();
        try {
            conn.setAutoCommit(true);
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("executeQuery sql=" + sql);
            }
            final Statement stmt = conn.createQueryStatement();
            if (consumer != null) {
                consumer.accept(conn, stmt);
            }
            final ResultSet set = stmt.executeQuery(sql);
            V rs = handler.apply(createDataResultSet(null, set));
            set.close();
            conn.offerQueryStatement(stmt);
            slowLog(s, sql);
            return rs;
        } catch (Exception ex) {
            throw new SourceException(ex);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Local
    @Override
    public <V> V nativeQuery(String sql, BiConsumer<Object, Object> consumer, Function<DataResultSet, V> handler, Map<String, Object> params) {
        DataNativeSqlStatement sinfo = super.nativeParse(sql, params);
        final long s = System.currentTimeMillis();
        final JdbcConnection conn = readPool.pollConnection();
        try {
            conn.setAutoCommit(true);
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("executeQuery sql=" + sinfo.getNativeSql());
            }
            V rs;
            if (sinfo.isEmptyNamed()) {
                final Statement stmt = conn.createQueryStatement();
                if (consumer != null) {
                    consumer.accept(conn, stmt);
                }
                ResultSet set = stmt.executeQuery(sinfo.getNativeSql());
                rs = handler.apply(createDataResultSet(null, set));
                set.close();
                conn.offerQueryStatement(stmt);
            } else {
                final PreparedStatement prestmt = conn.prepareQueryStatement(sinfo.getNativeSql());
                Map<String, Object> paramValues = sinfo.getParamValues();
                int index = 0;
                for (String n : sinfo.getParamNames()) {
                    prestmt.setObject(++index, paramValues.get(n));
                }
                if (consumer != null) {
                    consumer.accept(conn, prestmt);
                }
                ResultSet set = prestmt.executeQuery();
                rs = handler.apply(createDataResultSet(null, set));
                set.close();
                conn.offerQueryStatement(prestmt);
            }
            slowLog(s, sinfo.getNativeSql());
            return rs;
        } catch (Exception ex) {
            throw new SourceException(ex);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    public <V> Sheet<V> nativeQuerySheet(Class<V> type, String sql, Flipper flipper, Map<String, Object> params) {
        final boolean mysqlOrPgsql = "mysql".equals(dbtype()) || "postgresql".equals(dbtype());
        DataNativeSqlStatement sinfo = super.nativeParse(sql, params);
        final long s = System.currentTimeMillis();
        final JdbcConnection conn = readPool.pollConnection();
        try {
            conn.setAutoCommit(true);
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("executeQuery sql=" + sinfo.nativeSql);
            }
            long total = -1;
            List<V> list;
            if (sinfo.isEmptyNamed()) {
                Statement stmt = conn.createQueryStatement();
                ResultSet set = stmt.executeQuery(sinfo.getNativeCountSql());
                if (set.next()) {
                    total = set.getLong(1);
                }
                set.close();
                conn.offerQueryStatement(stmt);
            } else {
                final PreparedStatement prestmt = conn.prepareQueryStatement(sinfo.getNativeCountSql());
                Map<String, Object> paramValues = sinfo.getParamValues();
                int index = 0;
                for (String n : sinfo.getParamNames()) {
                    prestmt.setObject(++index, paramValues.get(n));
                }
                ResultSet set = prestmt.executeQuery();
                if (set.next()) {
                    total = set.getLong(1);
                }
                set.close();
                conn.offerQueryStatement(prestmt);
            }
            slowLog(s, sinfo.getNativeCountSql());
            if (total > 0) {
                String listSql = sinfo.getNativeSql();
                if (mysqlOrPgsql) {
                    listSql += (flipper == null || flipper.getLimit() < 1 ? "" : (" LIMIT " + flipper.getLimit() + " OFFSET " + flipper.getOffset()));
                }
                if (sinfo.isEmptyNamed()) {
                    Statement stmt = conn.createQueryStatement();
                    ResultSet set = stmt.executeQuery(listSql);
                    list = EntityBuilder.getListValue(type, createDataResultSet(null, set));
                    set.close();
                    conn.offerQueryStatement(stmt);
                } else {
                    final PreparedStatement prestmt = conn.prepareQueryStatement(listSql);
                    Map<String, Object> paramValues = sinfo.getParamValues();
                    int index = 0;
                    for (String n : sinfo.getParamNames()) {
                        prestmt.setObject(++index, paramValues.get(n));
                    }
                    ResultSet set = prestmt.executeQuery();
                    list = EntityBuilder.getListValue(type, createDataResultSet(null, set));
                    set.close();
                    conn.offerQueryStatement(prestmt);
                }
                slowLog(s, listSql);
            } else {
                list = new ArrayList();
            }
            return new Sheet<>(total, list);
        } catch (Exception ex) {
            throw new SourceException(ex);
        } finally {
            readPool.offerConnection(conn);
        }
    }

    @Override
    public <V> CompletableFuture<V> nativeQueryAsync(String sql, BiConsumer<Object, Object> consumer, Function<DataResultSet, V> handler, Map<String, Object> params) {
        return supplyAsync(() -> nativeQuery(sql, consumer, handler, params));
    }

    @Override
    public <V> CompletableFuture<V> nativeQueryAsync(String sql, BiConsumer<Object, Object> consumer, Function<DataResultSet, V> handler) {
        return supplyAsync(() -> nativeQuery(sql, consumer, handler));
    }

    @Override
    public CompletableFuture<Integer> nativeUpdateAsync(String sql, Map<String, Object> params) {
        return supplyAsync(() -> nativeUpdate(sql, params));
    }

    @Override
    public CompletableFuture<Integer> nativeUpdateAsync(String sql) {
        return supplyAsync(() -> nativeUpdate(sql));
    }

    @Override
    public CompletableFuture<int[]> nativeUpdatesAsync(String... sqls) {
        return supplyAsync(() -> nativeUpdates(sqls));
    }

    @Override
    public <V> CompletableFuture<Sheet<V>> nativeQuerySheetAsync(Class<V> type, String sql, Flipper flipper, Map<String, Object> params) {
        return supplyAsync(() -> nativeQuerySheet(type, sql, flipper, params));
    }

    @Deprecated
    public int directExecute(String sql) {
        return nativeUpdate(sql);
    }

    @Deprecated
    public int[] directExecute(String... sqls) {
        return nativeUpdates(sqls);
    }

    @Deprecated
    public <V> V directQuery(String sql, Function<DataResultSet, V> handler) {
        return nativeQuery(sql, handler);
    }

    public static DataResultSet createDataResultSet(@Nullable EntityInfo info, ResultSet set) {
        return new DataJdbcResultSet(info).resultSet(set);
    }

    protected static class DataJdbcResultSet implements DataResultSet {

        final EntityInfo info;

        ResultSet rr;

        public DataJdbcResultSet(EntityInfo info) {
            this.info = info;

        }

        public DataJdbcResultSet resultSet(ResultSet set) {
            this.rr = set;
            return this;
        }

        @Override
        public <T> Serializable getObject(Attribute<T, Serializable> attr, int index, String columnLabel) {
            Class t = attr.type();
            if (t == int.class) {
                return index > 0 ? getInt(index) : getInt(columnLabel);
            } else if (t == String.class) {
                return index > 0 ? getString(index) : getString(columnLabel);
            } else if (t == long.class) {
                return index > 0 ? getLong(index) : getLong(columnLabel);
            } else if (t == java.util.Date.class) {
                Object val = index > 0 ? getObject(index) : getObject(columnLabel);
                return val == null ? null : new java.util.Date(((java.sql.Date) val).getTime());
            } else if (t == java.time.LocalDate.class) {
                Object val = index > 0 ? getObject(index) : getObject(columnLabel);
                return val == null ? null : ((java.sql.Date) val).toLocalDate();
            } else if (t == java.time.LocalTime.class) {
                Object val = index > 0 ? getObject(index) : getObject(columnLabel);
                return val == null ? null : ((java.sql.Time) val).toLocalTime();
            } else if (t == java.time.LocalDateTime.class) {
                Object val = index > 0 ? getObject(index) : getObject(columnLabel);
                return val == null ? null : ((java.sql.Timestamp) val).toLocalDateTime();
            } else if (t.getName().startsWith("java.sql.")) {
                return index > 0 ? (Serializable) getObject(index) : (Serializable) getObject(columnLabel);
            }
            return DataResultSet.getRowColumnValue(this, attr, index, columnLabel);
        }

        @Override
        public boolean next() {
            try {
                return rr.next();
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        @Override
        public List<String> getColumnLabels() {
            try {
                ResultSetMetaData meta = rr.getMetaData();
                int count = meta.getColumnCount();
                List<String> labels = new ArrayList<>(count);
                for (int i = 1; i <= count; i++) {
                    labels.add(meta.getColumnLabel(i));
                }
                return labels;
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        @Override
        public boolean wasNull() {
            try {
                return rr.wasNull();
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        @Override
        public void close() {
            try {
                rr.close();
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        @Override
        public Object getObject(int index) {
            try {
                return rr.getObject(index);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        @Override
        public Object getObject(String column) {
            try {
                return rr.getObject(column);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public int getInt(int index) {
            try {
                return rr.getInt(index);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public int getInt(String column) {
            try {
                return rr.getInt(column);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public long getLong(int index) {
            try {
                return rr.getLong(index);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public long getLong(String column) {
            try {
                return rr.getLong(column);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public String getString(int index) {
            try {
                return rr.getString(index);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public String getString(String column) {
            try {
                return rr.getString(column);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        @Override
        public EntityInfo getEntityInfo() {
            return info;
        }

    }

    protected class JdbcConnectionPool implements AutoCloseable {

        protected final LongAdder closeCounter = new LongAdder(); //已关闭连接数

        protected final LongAdder usingCounter = new LongAdder(); //使用中连接数

        protected final LongAdder creatCounter = new LongAdder(); //已创建连接数

        protected final LongAdder cycleCounter = new LongAdder(); //已复用连接数

        protected final java.sql.Driver driver;

        protected final Properties connectAttrs;

        protected ArrayBlockingQueue<JdbcConnection> queue;

        protected int connectTimeoutSeconds;

        protected int maxConns;

        protected Semaphore newSemaphore;

        protected String url;

        protected final AtomicInteger urlVersion = new AtomicInteger();

        protected final boolean readFlag;

        public JdbcConnectionPool(boolean readFlag, Properties prop) {
            this.readFlag = readFlag;
            this.connectTimeoutSeconds = Integer.decode(prop.getProperty(DATA_SOURCE_CONNECT_TIMEOUT_SECONDS, "30"));
            int defMaxConns = Utility.cpus() * 4;
            if (workExecutor instanceof ThreadPoolExecutor) {
                defMaxConns = ((ThreadPoolExecutor) workExecutor).getCorePoolSize();
            } else if (workExecutor != null) { //maybe virtual thread pool
                defMaxConns = Math.min(1000, Utility.cpus() * 100);
            }
            this.maxConns = Math.max(1, Integer.decode(prop.getProperty(DATA_SOURCE_MAXCONNS, "" + defMaxConns)));
            this.newSemaphore = new Semaphore(this.maxConns);
            this.queue = new ArrayBlockingQueue<>(maxConns);
            this.url = prop.getProperty(DATA_SOURCE_URL);
            String username = prop.getProperty(DATA_SOURCE_USER, "");
            String password = prop.getProperty(DATA_SOURCE_PASSWORD, "");
            this.connectAttrs = new Properties();
            if (username != null) {
                this.connectAttrs.put("user", username);
            }
            if (password != null) {
                this.connectAttrs.put("password", password);
            }
            if ("postgresql".equals(dbtype())) {
                if (!url.contains("prepareThreshold=")) {
                    this.connectAttrs.put("prepareThreshold", "-1");
                }
            }
            try {
                this.driver = DriverManager.getDriver(this.url);
            } catch (SQLException e) {
                throw new SourceException(e);
            }
            resetMaxConnection();
        }

        @ResourceChanged
        public void onResourceChange(ResourceEvent[] events) {
            int newConnectTimeoutSeconds = this.connectTimeoutSeconds;
            int newMaxconns = this.maxConns;
            String newUrl = this.url;
            String newUser = this.connectAttrs.getProperty("user");
            String newPassword = this.connectAttrs.getProperty("password");
            for (ResourceEvent event : events) {
                if (event.name().equals(DATA_SOURCE_URL) || event.name().endsWith("." + DATA_SOURCE_URL)) {
                    newUrl = event.newValue().toString();
                } else if (event.name().equals(DATA_SOURCE_CONNECT_TIMEOUT_SECONDS) || event.name().endsWith("." + DATA_SOURCE_CONNECT_TIMEOUT_SECONDS)) {
                    newConnectTimeoutSeconds = Integer.decode(event.newValue().toString());
                } else if (event.name().equals(DATA_SOURCE_USER) || event.name().endsWith("." + DATA_SOURCE_USER)) {
                    newUser = event.newValue().toString();
                } else if (event.name().equals(DATA_SOURCE_PASSWORD) || event.name().endsWith("." + DATA_SOURCE_PASSWORD)) {
                    newPassword = event.newValue().toString();
                } else if (event.name().equals(DATA_SOURCE_MAXCONNS) || event.name().endsWith("." + DATA_SOURCE_MAXCONNS)) {
                    newMaxconns = Math.max(1, Integer.decode(event.newValue().toString()));
                }
            }
            if (!Objects.equals(newUser, this.connectAttrs.get("user"))
                || !Objects.equals(newPassword, this.connectAttrs.get("password")) || !Objects.equals(newUrl, url)) {
                this.urlVersion.incrementAndGet();
            }
            if ("postgresql".equals(dbtype())) {
                if (!newUrl.contains("prepareThreshold=")) {
                    this.connectAttrs.put("prepareThreshold", "-1");
                } else {
                    this.connectAttrs.remove("prepareThreshold");
                }
            }
            this.url = newUrl;
            this.connectTimeoutSeconds = newConnectTimeoutSeconds;
            this.connectAttrs.put("user", newUser);
            this.connectAttrs.put("password", newPassword);
            if (newMaxconns != this.maxConns) {
                changeMaxConns(newMaxconns);
            }
        }

        private void resetMaxConnection() {
            if ("mysql".equals(dbtype()) || "postgresql".equals(dbtype())) {
                int newMaxconns = this.maxConns;
                try {
                    Connection conn = driver.connect(url, connectAttrs);
                    Statement stmt = conn.createStatement();
                    if ("mysql".equals(dbtype())) {
                        ResultSet rs = stmt.executeQuery("SHOW VARIABLES LIKE 'max_connections'");
                        if (rs.next()) {
                            newMaxconns = rs.getInt(2);
                        }
                    } else if ("postgresql".equals(dbtype())) {
                        ResultSet rs = stmt.executeQuery("SHOW max_connections");
                        if (rs.next()) {
                            newMaxconns = rs.getInt(1);
                        }
                    }
                    stmt.close();
                    conn.close();
                } catch (Exception e) {
                    //do nothing
                }
                if (this.maxConns > newMaxconns) { //配置连接数过大
                    changeMaxConns(newMaxconns);
                }
            }
        }

        private void changeMaxConns(int newMaxconns) {
            ArrayBlockingQueue<JdbcConnection> newQueue = new ArrayBlockingQueue<>(newMaxconns);
            ArrayBlockingQueue<JdbcConnection> oldQueue = this.queue;
            Semaphore oldSemaphore = this.newSemaphore;
            this.queue = newQueue;
            this.maxConns = newMaxconns;
            this.newSemaphore = new Semaphore(this.maxConns);
            JdbcConnection c;
            while ((c = oldQueue.poll()) != null) {
                c.version = -1;
                offerConnection(c, oldSemaphore, this.queue);
            }
        }

        public JdbcConnection pollConnection() {
            JdbcConnection conn = queue.poll();
            if (conn == null) {
                return newConnection(this.queue);
            }
            usingCounter.increment();
            if (checkValid(conn)) {
                cycleCounter.increment();
                return conn;
            } else {
                offerConnection(conn);
                conn = null;
            }
            return newConnection(this.queue);
        }

        //用于事务的连接
        public JdbcConnection pollTransConnection() {
            return pollTransConnection();
        }

        private JdbcConnection newConnection(ArrayBlockingQueue<JdbcConnection> queue) {
            Semaphore semaphore = this.newSemaphore;
            JdbcConnection conn = null;
            if (semaphore.tryAcquire()) {
                try {
                    conn = new JdbcConnection(driver.connect(url, connectAttrs), readFlag, this.urlVersion.get());
                } catch (SQLException ex) {
                    semaphore.release();
                    throw new SourceException(ex);
                }
                usingCounter.increment();
                creatCounter.increment();
                return conn;
            } else {
                try {
                    conn = queue.poll(connectTimeoutSeconds, TimeUnit.SECONDS);
                } catch (InterruptedException t) {
                    logger.log(Level.WARNING, "take pooled jdbc connection error", t);
                }
                if (conn == null) {
                    throw new SourceException("create pooled jdbc connection timeout");
                }
                return conn;
            }
        }

        public <C> void offerConnection(final C connection) {
            offerConnection(connection, this.newSemaphore, this.queue);
        }

        public <C> void offerTransConnection(final C connection) {
            offerConnection(connection, this.newSemaphore, this.queue);
        }

        private <C> void offerConnection(final C connection, Semaphore semaphore, Queue<JdbcConnection> queue) {
            JdbcConnection conn = (JdbcConnection) connection;
            if (conn != null) {
                conn.commiting = false;
                try {
                    if (checkValid(conn) && queue.offer(conn)) {
                        usingCounter.decrement();
                    } else {
                        usingCounter.decrement();
                        closeCounter.increment();
                        semaphore.release();
                        conn.close();
                    }
                } catch (Exception e) {
                    logger.log(Level.WARNING, "closeSQLConnection abort", e);
                }
            }
        }

        protected boolean checkValid(JdbcConnection conn) {
            try {
                return !conn.conn.isClosed() && conn.version == this.urlVersion.get();
            } catch (SQLException ex) {
                if (!"08S01".equals(ex.getSQLState())) {//MySQL特性， 长时间连接没使用会抛出com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
                    logger.log(Level.FINER, "result.getConnection from pooled connection abort [" + ex.getSQLState() + "]", ex);
                }
                return false;
            }
        }

        @Override
        public void close() {
            queue.stream().forEach(x -> {
                try {
                    x.close();
                } catch (Exception e) {
                    //do nothing
                }
            });
        }
    }

    protected class JdbcConnection extends DataJdbcConnection {

        public int version;

        public final Connection conn;

        boolean commiting;

        public JdbcConnection(Connection conn, boolean readFlag, int version) {
            super(readFlag);
            Objects.requireNonNull(conn);
            this.conn = conn;
            this.version = version;
        }

        @Override
        public Connection getConnection() {
            return conn;
        }

        public Statement createStreamStatement() throws SQLException {
            Statement statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            statement.setFetchSize(Integer.MIN_VALUE);
            return statement;
        }

        public void offerStreamStatement(final Statement stmt) throws SQLException {
            if (stmt != null) {
                stmt.close();
            }
        }

        public Statement createQueryStatement() throws SQLException {
            return conn.createStatement();
        }

        public void offerQueryStatement(final Statement stmt) throws SQLException {
            if (stmt != null) {
                stmt.close();
            }
        }

        public Statement createUpdateStatement() throws SQLException {
            return conn.createStatement();
        }

        public void offerUpdateStatement(final Statement stmt) throws SQLException {
            if (stmt != null) {
                stmt.close();
            }
        }

        public PreparedStatement prepareQueryStatement(String sql) throws SQLException {
            return conn.prepareStatement(sql);
        }

        public void offerQueryStatement(final PreparedStatement stmt) throws SQLException {
            if (stmt != null) {
                stmt.close();
            }
        }

        public PreparedStatement prepareUpdateStatement(String sql) throws SQLException {
            return conn.prepareStatement(sql);
        }

        public PreparedStatement prepareUpdateStatement(String sql, int resultSetType) throws SQLException {
            return conn.prepareStatement(sql, resultSetType);
        }

        public void offerUpdateStatement(final PreparedStatement stmt) throws SQLException {
            if (stmt != null) {
                stmt.close();
            }
        }

        public void offerUpdateStatement(List<? extends Statement> stmts) throws SQLException {
            if (stmts != null) {
                for (Statement s : stmts) {
                    s.close();
                }
            }
        }

        public void offerUpdateStatement(final Statement stmt, List<? extends Statement> stmts) throws SQLException {
            if (stmt != null) {
                stmt.close();
            }
            if (stmts != null) {
                for (Statement s : stmts) {
                    s.close();
                }
            }
        }

        public void setAutoCommit(boolean autoCommit) throws SQLException {
            conn.setAutoCommit(autoCommit);
        }

        public void commit() throws SQLException {
            commiting = true;
            conn.commit();
        }

        public void rollback(Statement stmt) {
            try {
                if (commiting) {
                    conn.rollback();
                    commiting = false;
                }
                if (stmt != null) {
                    stmt.close();
                }
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public void rollback(List<? extends Statement> stmts) {
            try {
                if (commiting) {
                    conn.rollback();
                    commiting = false;
                }
                if (stmts != null) {
                    for (Statement s : stmts) {
                        s.close();
                    }
                }
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public void rollback(final Statement stmt, List<? extends Statement> stmts) {
            try {
                if (commiting) {
                    conn.rollback();
                    commiting = false;
                }
                if (stmt != null) {
                    stmt.close();
                }
                if (stmts != null) {
                    for (Statement s : stmts) {
                        s.close();
                    }
                }
            } catch (SQLException e) {
                throw new SourceException(e);
            }
        }

        public DatabaseMetaData getMetaData() throws SQLException {
            return conn.getMetaData();
        }

        public Blob createBlob() throws SQLException {
            return conn.createBlob();
        }

        public void close() throws SQLException {
            conn.close();
        }

    }

}
