下圖左邊展現的是分庫分表前的數據訪問層(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層