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