mysql驅動對SQLException的擴展點(Interceptor)

一、MysqlIO.java:Connection和mysql通信的IO類

全部到DB執行的sql語句都會Encode成mysql協議的字節序列(com.mysql.jdbc.Buffer類),MysqlIO在發送完queryPacket以後會對接收到數據庫返回的resultPacket進行checkError,即進行SQLException的包裝,代碼以下:java

// MysqlIO.java
  // 對mysql返回的SQLException進行warp
  private void checkErrorPacket(Buffer resultPacket) throws SQLException {
    int statusCode = resultPacket.readByte();

    // Error handling,若是是error package才處理
    if (statusCode == (byte) 0xff) {
      String serverErrorMessage;
      int errno = 2000;
      // 判斷協議的版本號,咱們值是10,走這個分支
      if (this.protocolVersion > 9) {
        // 數據庫或分庫中間件返回的錯誤碼,如1064
        errno = resultPacket.readInt();
        String xOpen = null;
        // 返回的錯誤信息,如:#HY000octopus route table info is not exit!
        serverErrorMessage = resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());

        if (serverErrorMessage.charAt(0) == '#') {
          // we have an SQLState
          if (serverErrorMessage.length() > 6) {
            xOpen = serverErrorMessage.substring(1, 6);
            serverErrorMessage = serverErrorMessage.substring(6);
            // 使用XOPEN標準
            if (xOpen.equals("HY000")) {
              // 將數據庫或數據庫中間件返回的1064錯誤碼轉換成X/Open or SQL-92 errorCodes,1062對應的是42000
              xOpen = SQLError.mysqlToSqlState(errno, this.connection.getUseSqlStateCodes());
            }
          } else {
              xOpen = SQLError.mysqlToSqlState(errno, this.connection.getUseSqlStateCodes());
          }
        } else {
          xOpen = SQLError.mysqlToSqlState(errno, this.connection.getUseSqlStateCodes());
        }

        clearInputStream();

        StringBuilder errorBuf = new StringBuilder();
        // 後期42000代碼的message:Syntax error or access violation
        String xOpenErrorMessage = SQLError.get(xOpen);
        // useOnlyServerErrorMessages爲true,不會拼接錯誤信息
        if (!this.connection.getUseOnlyServerErrorMessages()) {
          if (xOpenErrorMessage != null) {
            errorBuf.append(xOpenErrorMessage);
            errorBuf.append(Messages.getString("MysqlIO.68"));
          }
        }
        // serverErrorMessage:octopus route table info is not exit!
        errorBuf.append(serverErrorMessage);

        if (!this.connection.getUseOnlyServerErrorMessages()) {
          if (xOpenErrorMessage != null) {
            errorBuf.append("\"");
          }
        }

        appendDeadlockStatusInformation(xOpen, errorBuf);

        if (xOpen != null && xOpen.startsWith("22")) {
           throw new MysqlDataTruncation(errorBuf.toString(), 0, true, false, 0, 0, errno);
        }
        // 根據serverErrorMessage,xOpen即sqlState,error,將SQLException warp爲mysql預設的異常類
        throw SQLError.createSQLException(errorBuf.toString(), xOpen, errno, false, getExceptionInterceptor(), this.connection);
      }

     serverErrorMessage = resultPacket.readString(this.connection.getErrorMessageEncoding(), getExceptionInterceptor());
     clearInputStream();

     if (serverErrorMessage.indexOf(Messages.getString("MysqlIO.70")) != -1) {
       throw SQLError.createSQLException(SQLError.get(SQLError.SQL_STATE_COLUMN_NOT_FOUND) + ", " + serverErrorMessage,
       SQLError.SQL_STATE_COLUMN_NOT_FOUND, -1, false, getExceptionInterceptor(), this.connection);
     }

     StringBuilder errorBuf = new StringBuilder(Messages.getString("MysqlIO.72"));
     errorBuf.append(serverErrorMessage);
     errorBuf.append("\"");

     throw SQLError.createSQLException(SQLError.get(SQLError.SQL_STATE_GENERAL_ERROR) + ", " + errorBuf.toString(), SQLError.SQL_STATE_GENERAL_ERROR, -1,
                    false, getExceptionInterceptor(), this.connection);
    }
}

二、SQLError.java:將mysql的錯誤碼映射到X/Open錯誤碼的工具類(jdbc規範所要求的)

SQLException中的屬性說明:
reason:異常信息描述
sqlState:X/Open或SQL:2003規範的錯誤碼字段
vendorCode:數據庫廠商(如mysql、oracle)本身的錯誤碼字段
cause:引發此異常的潛在緣由(可能爲null),經過getCause()獲取mysql

// SQLError.java
  public static SQLException createSQLException(String message, String sqlState, int      vendorErrorCode, boolean isTransient, ExceptionInterceptor interceptor, Connection conn) {
    try {
      SQLException sqlEx = null;
      // sqlState:42000
      if (sqlState != null) {
        if (sqlState.startsWith("08")) {
          if (isTransient) {
            if (!Util.isJdbc4()) {
              sqlEx = new MySQLTransientConnectionException(message, sqlState, vendorErrorCode);
            } else {
              sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLTransientConnectionException",
                                    new Class[] { String.class, String.class, Integer.TYPE },
                                    new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) }, interceptor);
                        }
          } else if (!Util.isJdbc4()) {
            sqlEx = new MySQLNonTransientConnectionException(message, sqlState, vendorErrorCode);
          } else {
            sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException",
                                new Class[] { String.class, String.class, Integer.TYPE }, new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) },
                                interceptor);
          }
        else if (sqlState.startsWith("22")) {
          if (!Util.isJdbc4()) {
            sqlEx = new MySQLDataException(message, sqlState, vendorErrorCode);
          } else {
            sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLDataException",
                                new Class[] { String.class, String.class, Integer.TYPE }, new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) },
                                interceptor);
          }
        } else if (sqlState.startsWith("23")) {

          if (!Util.isJdbc4()) {
            sqlEx = new MySQLIntegrityConstraintViolationException(message, sqlState, vendorErrorCode);
          } else {
           sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException",
                                new Class[] { String.class, String.class, Integer.TYPE }, new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) },
                                interceptor);
          }
        // 以42開頭
        } else if (sqlState.startsWith("42")) {
          if (!Util.isJdbc4()) {
            sqlEx = new MySQLSyntaxErrorException(message, sqlState, vendorErrorCode);
          } else {
            // Class.forName("com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException),經過反射獲取構造方法,將message,sqlState,vendorErrorCode做爲參數建立異常對象
           sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException",
                                new Class[] { String.class, String.class, Integer.TYPE }, new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) },
                                interceptor);
          }
        } else if (sqlState.startsWith("40")) {
           if (!Util.isJdbc4()) {
            sqlEx = new MySQLTransactionRollbackException(message, sqlState, vendorErrorCode);
           } else {
            sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException",
                                new Class[] { String.class, String.class, Integer.TYPE }, new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) },
                                interceptor);
           }
        } else if (sqlState.startsWith("70100")) {
           if (!Util.isJdbc4()) {
            sqlEx = new MySQLQueryInterruptedException(message, sqlState, vendorErrorCode);
           } else {
            sqlEx = (SQLException) Util.getInstance("com.mysql.jdbc.exceptions.jdbc4.MySQLQueryInterruptedException",
                                new Class[] { String.class, String.class, Integer.TYPE }, new Object[] { message, sqlState, Integer.valueOf(vendorErrorCode) },
                                interceptor);
           }
        } else {
          sqlEx = new SQLException(message, sqlState, vendorErrorCode);
        }
      } else {
        sqlEx = new SQLException(message, sqlState, vendorErrorCode);
      }
      // SQLException攔截器,一個擴展點,能夠實現自定義異常處理
      return runThroughExceptionInterceptor(interceptor, sqlEx, conn);
    } catch (SQLException sqlEx) {
      SQLException unexpectedEx = new SQLException(
                    "Unable to create correct SQLException class instance, error class/codes may be incorrect. Reason: " + Util.stackTraceToString(sqlEx),
                    SQL_STATE_GENERAL_ERROR);

      return runThroughExceptionInterceptor(interceptor, unexpectedEx, conn);
    }
  }

三、demo關鍵步驟

根據上述代碼runThroughExceptionInterceptor中能夠實現對SQLException擴展
3.一、在獲取Connection時設置exceptionInterceptors屬性sql

Properties pro = new Properties();
  // 設置異常攔截器
  pro.setProperty("exceptionInterceptors", "com.pinganfu.test.MySqlSqlIntecept");
  pro.setProperty("user", user);
  pro.setProperty("password", password);
  Connection con = DriverManager.getConnection(url, pro);

3.二、實現自定義攔截器,便可以將SQLException攔截轉換成自定義的異常數據庫

// 實現ExceptionInterceptor接口
  public class MySqlSqlIntecept implements ExceptionInterceptor {
    // 自定義異常處理邏輯
    @Override
    public SQLException interceptException(SQLException sqlEx, Connection conn) {
        return new MyTestException("test");
    }

    public class MyTestException extends  SQLException {

        public MyTestException(String msg) {
            super(msg);
        }
    }
}
相關文章
相關標籤/搜索