數據庫異常的五層封裝和定製

1、數據庫異常的五層封裝

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

2、數據庫異常的定製

詳見筆者以前的博客

一、數據庫驅動層

http://www.javashuo.com/article/p-zgnderxf-b.html

二、MyBatis層

http://www.javashuo.com/article/p-qntkinyo-o.html

三、MyBatis-Spring層

http://www.javashuo.com/article/p-ynrqqqiw-v.html

相關文章
相關標籤/搜索