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

1、MysqlIO.java:Connection和mysql通訊的IO類

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

  // 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);
    }
}

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

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

  // 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);
    }
  }

3、demo關鍵步驟

根據上述代碼runThroughExceptionInterceptor中可以實現對SQLException擴展
3.1、在獲取Connection時設置exceptionInterceptors屬性

  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.2、實現自定義攔截器,即可以將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);
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章