全部到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); } }
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); } }
根據上述代碼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); } } }