本文次要钻研一下druid的handleException

prepareStatement

com/alibaba/druid/pool/DruidPooledConnection.java

    public PreparedStatement prepareStatement(String sql) throws SQLException {        checkState();        PreparedStatementHolder stmtHolder = null;        PreparedStatementKey key = new PreparedStatementKey(sql, getCatalog(), MethodType.M1);        boolean poolPreparedStatements = holder.isPoolPreparedStatements();        if (poolPreparedStatements) {            stmtHolder = holder.getStatementPool().get(key);        }        if (stmtHolder == null) {            try {                stmtHolder = new PreparedStatementHolder(key, conn.prepareStatement(sql));                holder.getDataSource().incrementPreparedStatementCount();            } catch (SQLException ex) {                handleException(ex, sql);            }        }        initStatement(stmtHolder);        DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement(this, stmtHolder);        holder.addTrace(rtnVal);        return rtnVal;    }
DruidPooledConnection的prepareStatement会catch住SQLException而后执行handleException

executeQuery

com/alibaba/druid/pool/DruidPooledPreparedStatement.java

    public ResultSet executeQuery() throws SQLException {        checkOpen();        incrementExecuteQueryCount();        transactionRecord(sql);        oracleSetRowPrefetch();        conn.beforeExecute();        try {            ResultSet rs = stmt.executeQuery();            if (rs == null) {                return null;            }            DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);            addResultSetTrace(poolableResultSet);            return poolableResultSet;        } catch (Throwable t) {            errorCheck(t);            throw checkException(t);        } finally {            conn.afterExecute();        }    }
executeQuery在catch到Throwable时会执行throw checkException(t)

checkException

com/alibaba/druid/pool/DruidPooledStatement.java

    protected SQLException checkException(Throwable error) throws SQLException {        String sql = null;        if (this instanceof DruidPooledPreparedStatement) {            sql = ((DruidPooledPreparedStatement) this).getSql();        }        handleSocketTimeout(error);        exceptionCount++;        return conn.handleException(error, sql);    }
checkException这里会执行conn.handleException(error, sql)

handleException

    public SQLException handleException(Throwable t, String sql) throws SQLException {        final DruidConnectionHolder holder = this.holder;        //        if (holder != null) {            DruidAbstractDataSource dataSource = holder.getDataSource();            dataSource.handleConnectionException(this, t, sql);        }        if (t instanceof SQLException) {            throw (SQLException) t;        }        throw new SQLException("Error", t);    }
handleException这里是委托给了dataSource.handleConnectionException(this, t, sql);

handleConnectionException

com/alibaba/druid/pool/DruidDataSource.java

    public void handleConnectionException(DruidPooledConnection pooledConnection,                                          Throwable t,                                          String sql) throws SQLException {        final DruidConnectionHolder holder = pooledConnection.getConnectionHolder();        if (holder == null) {            return;        }        errorCountUpdater.incrementAndGet(this);        lastError = t;        lastErrorTimeMillis = System.currentTimeMillis();        if (t instanceof SQLException) {            SQLException sqlEx = (SQLException) t;            // broadcastConnectionError            ConnectionEvent event = new ConnectionEvent(pooledConnection, sqlEx);            for (ConnectionEventListener eventListener : holder.getConnectionEventListeners()) {                eventListener.connectionErrorOccurred(event);            }            // exceptionSorter.isExceptionFatal            if (exceptionSorter != null && exceptionSorter.isExceptionFatal(sqlEx)) {                handleFatalError(pooledConnection, sqlEx, sql);            }            throw sqlEx;        } else {            throw new SQLException("Error", t);        }    }
handleConnectionException办法在exceptionSorter.isExceptionFatal(sqlEx)为true时会执行handleFatalError

isExceptionFatal

com/alibaba/druid/pool/vendor/MySqlExceptionSorter.java

public class MySqlExceptionSorter implements ExceptionSorter {    @Override    public boolean isExceptionFatal(SQLException e) {        if (e instanceof SQLRecoverableException) {            return true;        }        final String sqlState = e.getSQLState();        final int errorCode = e.getErrorCode();        if (sqlState != null && sqlState.startsWith("08")) {            return true;        }        switch (errorCode) {            // Communications Errors            case 1040: // ER_CON_COUNT_ERROR            case 1042: // ER_BAD_HOST_ERROR            case 1043: // ER_HANDSHAKE_ERROR            case 1047: // ER_UNKNOWN_COM_ERROR            case 1081: // ER_IPSOCK_ERROR            case 1129: // ER_HOST_IS_BLOCKED            case 1130: // ER_HOST_NOT_PRIVILEGED                // Authentication Errors            case 1045: // ER_ACCESS_DENIED_ERROR                // Resource errors            case 1004: // ER_CANT_CREATE_FILE            case 1005: // ER_CANT_CREATE_TABLE            case 1015: // ER_CANT_LOCK            case 1021: // ER_DISK_FULL            case 1041: // ER_OUT_OF_RESOURCES                // Out-of-memory errors            case 1037: // ER_OUTOFMEMORY            case 1038: // ER_OUT_OF_SORTMEMORY                // Access denied            case 1142: // ER_TABLEACCESS_DENIED_ERROR            case 1227: // ER_SPECIFIC_ACCESS_DENIED_ERROR            case 1023: // ER_ERROR_ON_CLOSE            case 1290: // ER_OPTION_PREVENTS_STATEMENT                return true;            default:                break;        }        // for oceanbase        if (errorCode >= -9000 && errorCode <= -8000) {            return true;        }        String className = e.getClass().getName();        if (className.endsWith(".CommunicationsException")) {            return true;        }        String message = e.getMessage();        if (message != null && message.length() > 0) {            if (message.startsWith("Streaming result set com.mysql.jdbc.RowDataDynamic")                    && message.endsWith("is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.")) {                return true;            }            final String errorText = message.toUpperCase();            if ((errorCode == 0 && (errorText.contains("COMMUNICATIONS LINK FAILURE")) //                    || errorText.contains("COULD NOT CREATE CONNECTION")) //                    || errorText.contains("NO DATASOURCE") //                    || errorText.contains("NO ALIVE DATASOURCE")) {                return true;            }        }        Throwable cause = e.getCause();        for (int i = 0; i < 5 && cause != null; ++i) {            if (cause instanceof SocketTimeoutException) {                return true;            }            className = cause.getClass().getName();            if (className.endsWith(".CommunicationsException")) {                return true;            }            cause = cause.getCause();        }        return false;    }    @Override    public void configFromProperties(Properties properties) {    }}
MySqlExceptionSorter针对指定错误码来判断是否fatal

handleFatalError

com/alibaba/druid/pool/DruidDataSource.java

    protected final void handleFatalError(DruidPooledConnection conn,                                          SQLException error,                                          String sql) throws SQLException {        final DruidConnectionHolder holder = conn.holder;        if (conn.isTraceEnable()) {            activeConnectionLock.lock();            try {                if (conn.isTraceEnable()) {                    activeConnections.remove(conn);                    conn.setTraceEnable(false);                }            } finally {                activeConnectionLock.unlock();            }        }        long lastErrorTimeMillis = this.lastErrorTimeMillis;        if (lastErrorTimeMillis == 0) {            lastErrorTimeMillis = System.currentTimeMillis();        }        if (sql != null && sql.length() > 1024) {            sql = sql.substring(0, 1024);        }        boolean requireDiscard = false;        final ReentrantLock lock = conn.lock;        lock.lock();        try {            if ((!conn.isClosed()) || !conn.isDisable()) {                conn.disable(error);                requireDiscard = true;            }            lastFatalErrorTimeMillis = lastErrorTimeMillis;            fatalErrorCount++;            if (fatalErrorCount - fatalErrorCountLastShrink > onFatalErrorMaxActive) {                onFatalError = true;            }            lastFatalError = error;            lastFatalErrorSql = sql;        } finally {            lock.unlock();        }        if (onFatalError && holder != null && holder.getDataSource() != null) {            ReentrantLock dataSourceLock = holder.getDataSource().lock;            dataSourceLock.lock();            try {                emptySignal();            } finally {                dataSourceLock.unlock();            }        }        if (requireDiscard) {            if (holder.statementTrace != null) {                holder.lock.lock();                try {                    for (Statement stmt : holder.statementTrace) {                        JdbcUtils.close(stmt);                    }                } finally {                    holder.lock.unlock();                }            }            this.discardConnection(holder);        }        // holder.        LOG.error("{conn-" + holder.getConnectionId() + "} discard", error);    }
handleFatalError办法这里会执行conn.disable(error),而后标记requireDiscard为true,最初执行discardConnection

discardConnection

com/alibaba/druid/pool/DruidDataSource.java

    public void discardConnection(DruidConnectionHolder holder) {        if (holder == null) {            return;        }        Connection conn = holder.getConnection();        if (conn != null) {            JdbcUtils.close(conn);        }        lock.lock();        try {            if (holder.discard) {                return;            }            if (holder.active) {                activeCount--;                holder.active = false;            }            discardCount++;            holder.discard = true;            if (activeCount <= minIdle) {                emptySignal();            }        } finally {            lock.unlock();        }    }
discardConnection这里会通过JdbcUtils.close(conn)敞开连贯,而后加锁判断是否小于等于minIdle,若为true则执行emptySignal

小结

druid会在prepareStatement或者执行prepareStatement出现异常的时候执行conn.handleException,它委托给dataSource.handleConnectionException,后者会在exceptionSorter.isExceptionFatal(sqlEx)为true时会执行handleFatalError,handleFatalError办法这里会执行conn.disable(error),而后标记requireDiscard为true,最初执行discardConnection来敞开连贯。通过这样子来疾速清理不可用的连贯,防止连接池的连贯不可用。