全部到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); } } }