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

 

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