数据库异常的五层封装和定制

一、数据库异常的五层封装

下图左边展示的是分库分表前的数据访问层(3层),右边是在引入了分库分表(如Mycat)之后的数据访问层(5层)。本文我们将以分库分表之后的5层结构为例,由下至上逐层分析数据库的异常是如何返回到业务的DAO层。

第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 throwable

第2层:分库分表层(以Mycat为例)
将调用JDBC执行SQL过程中抛出的SQLException Catch住,根据errno(vendorCode),sqlState(SQLState,Mycat默认的sqlState是HY000),message(reason)创建一个ErrorPacket对象,并序列号成一个MySQL协议发送出去。

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,如下图索引:


此时mysql驱动根据sqlState把SQLException封装成了特定的MySQL的异常(SQLException的子类)抛出

第4层:MyBatis
MyBatis抛出的异常都是PersistenceException类型,将调用mysql驱动执行SQL过程中抛出的SQLException或其子类 Catch住,

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文件片段

<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异常。

思考:
JDBC抛出的都是SQLException类型
MyBatis抛出的都是PersistenceException类型
MyBatis-Spring抛出的都是DataAccessException类型,我们在设计一个系统或框架的时候,可以统一我们的异常类型。

二、数据库异常的定制

详见笔者之前的博客

1、数据库驱动层

https://my.oschina.net/u/3787772/blog/2980222

2、MyBatis层

https://my.oschina.net/u/3787772/blog/2962941

3、MyBatis-Spring层

https://my.oschina.net/u/3787772/blog/2963134

转载于:https://my.oschina.net/u/3787772/blog/3052090

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值