下图左边展现的是分库分表前的数据访问层(3层),右边是在引入了分库分表(如Mycat)以后的数据访问层(5层)。本文咱们将以分库分表以后的5层结构为例,由下至上逐层分析数据库的异常是如何返回到业务的DAO层。java
第1层:Oracle数据库驱动层
Oracle驱动是对JDBC接口的标准实现,经过Statement和PreparedStatement这两个接口咱们能够看出执行SQL时抛出的异常类型都是SQLException。
而SQLException中有几个关键的属性,了解这几个属性对咱们全盘理解异常的传递过程有很大帮助。
reason:a description of the exception
SQLState:an XOPEN or SQL:2003 code identifying the exception
vendorCode:a database vendor-specific exception code
cause:the cause of this throwablemysql
第2层:分库分表层(以Mycat为例)
将调用JDBC执行SQL过程当中抛出的SQLException Catch住,根据errno(vendorCode),sqlState(SQLState,Mycat默认的sqlState是HY000),message(reason)建立一个ErrorPacket对象,并序列号成一个MySQL协议发送出去。spring
private void handleSqlExceptionError(SQLException e) { String msg = e.getMessage(); ErrorPacket error = new ErrorPacket(); error.packetId = ++packetId; error.errno = e.getErrorCode(); error.sqlState = e.getSQLState().getBytes(); error.message = msg.getBytes(); // 发送给mysql driver this.respHandler.errorResponseWithNoInterrupt(error.writeToBytes(), this); }
第3层:MySQL数据库驱动层
MySQL Driver解析返回的mysql协议byte[],获取errno,sqlState,message根据sqlState判断是那种类型的SQLException,以下图索引:sql
此时mysql驱动根据sqlState把SQLException封装成了特定的MySQL的异常(SQLException的子类)抛出数据库
第4层:MyBatis
MyBatis抛出的异常都是PersistenceException类型,将调用mysql驱动执行SQL过程当中抛出的SQLException或其子类 Catch住,app
public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { // 封装SQLException throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } // 将SQLException做为PersistenceException的一个属性 public static RuntimeException wrapException(String message, Exception e) { return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e); }
第5层:MyBatis-Spring:MyBatis代码和Spring的整合
MyBatis-Spring针对Mybatis抛出的异常封装的都是DataAccessException类型框架
public DataAccessException translateExceptionIfPossible(RuntimeException e) { // Mybatis抛出异常 if (e instanceof PersistenceException) { // Batch exceptions come inside another PersistenceException // recursion has a risk of infinite loop so better make another if if (e.getCause() instanceof PersistenceException) { e = (PersistenceException) e.getCause(); } // MySQL驱动抛出的异常 if (e.getCause() instanceof SQLException) { this.initExceptionTranslator(); // 封装成DataAccessException return this.exceptionTranslator.translate(e.getMessage() + "\n", null, (SQLException) e.getCause()); } else if (e.getCause() instanceof TransactionException) { throw (TransactionException) e.getCause(); } return new MyBatisSystemException(e); } return null; }
sql-error-codes.xml文件片断ide
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes"> <property name="badSqlGrammarCodes"> <value>1054,1064,1146</value> </property> <property name="duplicateKeyCodes"> <value>1062</value> </property> <property name="dataIntegrityViolationCodes"> <value>630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557</value> </property> <property name="dataAccessResourceFailureCodes"> <value>1</value> </property> <property name="cannotAcquireLockCodes"> <value>1205</value> </property> <property name="deadlockLoserCodes"> <value>1213</value> </property> </bean>
根据SQLException中的错误码(db特定异常码)和sql-error-codes.xml文件中的定义,封装成具体的DataAccessException异常。oop
思考:
JDBC抛出的都是SQLException类型
MyBatis抛出的都是PersistenceException类型
MyBatis-Spring抛出的都是DataAccessException类型,咱们在设计一个系统或框架的时候,能够统一咱们的异常类型。ui
详见笔者以前的博客
一、数据库驱动层
http://www.javashuo.com/article/p-zgnderxf-b.html
二、MyBatis层
http://www.javashuo.com/article/p-qntkinyo-o.html
三、MyBatis-Spring层