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