Spring 統一的異常訪問體系

要了解Spring爲何要提供統一的異常訪問層次體系,得先從DAO模式提及.java

不論是一個邏輯簡單的小軟件系統,仍是一個關係複雜的大型軟件系統,都極可能涉及到對數據的訪問和存儲,而這些對數據的訪問和存儲每每隨着場景的不一樣而各異。爲了統一和簡化相關的數據訪問操做,J2EE核心模式提出了DAO(Data Access Object,數據訪問對象)模式。使用DAO模式,能夠徹底分離數據的訪問和存儲,很好的屏蔽了數據訪問的差別性。不論數據存儲在普通的文本文件或者csv文件,仍是關係數據庫(RDBMS)或者LDAP(Lightweight Derectory Access Protocol 輕量級目錄訪問協議),使用DAO模式訪問數據的客戶端代碼徹底能夠忽視這種差別,用統一的接口訪問相關的數據。mysql

看一具體的場景:
對於大部分軟件系統來講,訪問用戶信息是你們常常接觸到的。以訪問用戶信息爲例,使用DAO模式的話,須要先聲明一個數據訪問的接口,以下所示:spring

package com.google.spring.jdbc;

public interface IUserDao
{
	public User findUserByPK(Integer id);
	
	public void updateUser(User user);
}

 

對於客戶端代碼,即一般的服務層代碼來講,只須要聲明依賴的DAO接口便可,即便數據訪問方式方式發生了改變,只須要改變相關的DAO實現方式,客戶端代碼不須要作任何的調整。 sql

package com.google.spring.jdbc;

public class UserService
{
	private IUserDao userDao;

	public IUserDao getUserDao()
	{
		return userDao;
	}

	public void setUserDao(IUserDao userDao)
	{
		this.userDao = userDao;
	}
	
	public void disableUser(Integer userId)
	{
		User user = this.userDao.findUserByPK(userId);
		userDao.updateUser(user);
	}
}

一般狀況下,用戶信息存儲在關係數據庫中,因此,相應的咱們會提供一個基於JDBC的DAO接口實現類: 數據庫

package com.google.spring.jdbc;

public class JDBCUserDao implements IUserDao
{

	@Override
	public User findUserByPK(Integer id)
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void updateUser(User user)
	{
		// TODO Auto-generated method stub

	}

}

 

可能隨着系統需求的變動,顧客信息須要轉移到LDAP服務,或者轉而使用其它的LDAP服務,又或者別人須要使用咱們的Service,可是他們用的是另外的數據訪問機制,這時就須要提供一個基於LDAP的數據訪問對象,以下所示: 服務器

package com.google.spring.jdbc;

public class LdapUserDao implements IUserDao
{

	@Override
	public User findUserByPK(Integer id)
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void updateUser(User user)
	{
		// TODO Auto-generated method stub

	}

}

 

即便具體的實現類發生了變化,客戶端代碼徹底能夠忽視這種變化,惟一須要變化的是factory中幾行代碼的改變,或者是IOC容器中幾行簡單的替換而已,因此DAO模式能夠很好的屏蔽不一樣的數據訪問的差別。併發

爲了簡化描述,上述省略了最基本的數據訪問代碼,當引入具體的數據訪問代碼的時候,問題就出現了。oracle

package com.google.spring.jdbc;

import java.sql.Connection;

import javax.sql.DataSource;

public class JDBCUserDao implements IUserDao
{
	private DataSource dataSource ;
	

	public DataSource getDataSource()
	{
		return dataSource;
	}

	public void setDataSource(DataSource dataSource)
	{
		this.dataSource = dataSource;
	}

	@Override
	public User findUserByPK(Integer id)
	{
		Connection conn = null;
		try
		{
			conn = getDataSource().getConnection();
			//....
			User user = new User();
			//........
			return user;
		} 
		catch (Exception e)
		{
			//是拋出異常,仍是在當前位置處理。。。
		}
		finally
		{
			releaseConnection(conn);
		}
		return null;
	}

	@Override
	public void updateUser(User user)
	{
		// TODO Auto-generated method stub

	}
	
	public void releaseConnection(Connection conn)
	{
		
	}
}

 

使用JDBC進行數據庫訪問,當其間出現問題的時候,JDBC API會拋出SQLException來代表問題的發生。而SQLException屬於checked     exception,因此,咱們的DAO實現類要捕獲這種異常並處理。 
那如何處理DAO中捕獲的SQLException呢,直接在DAO實現類處理掉?若是這樣的話,客戶端代碼就沒法得知在數據訪問期間發生了什麼變化?因此只好將SQLException拋給客戶端,進而,DAO實現類的相應的簽名 框架

public User findUserByPK(Integer id) throws SQLException

 

相應的,DAO接口中的相應的方法簽名也須要修改: ide

public User findUserByPK(Integer id) throws SQLException;

 

可是,這樣並無解決問題: 
一、咱們的數據訪問接口對客戶端應該是通用的,無論數據訪問的機制發生瞭如何的變化,客戶端代碼都不該該受到牽連。可是,由於如今用的JDBC訪問數據庫,須要拋出特定的SQLException,這與數據訪問對象模式的初衷是背離的。 
二、當引入另一種數據訪問的模式的時候,好比,當加入LdapUserDao的時候,會拋出NamingException,若是要實現該接口,那麼該方法簽名又要發生改變,以下所示: 

public User findUserByPK(Integer id) throws SQLException,NamingException;

 

這是很糟糕的解決方案,若是不一樣的數據訪問的對象的實現愈來愈多,以及考慮到數據訪問對象中的其它的數據訪問的方法,這種糟糕的問題還得繼續下去嗎? 
也就是說,由於數據訪問的機制有所不一樣,咱們的數據訪問接口的定義如今變成了空中樓閣,咱們沒法最終肯定這個接口!好比,有的數據庫提供商採用SQLException的ErrorCode做爲具體的錯誤信息標準,有的數據庫提供商則經過SQLException的SqlState來返回相信的錯誤信息。即便將SQLException封裝後拋給客戶端對象,當客戶端要了解具體的錯誤信息的時候,依然要根據數據庫提供商的不一樣採起不一樣的信息提取方式,這種客戶端處理起來將是很是的糟糕,咱們應該向客戶端對象屏蔽這種差別性。能夠採用分類轉譯(Exception Translation) 
a>首先,不該該將特定的數據訪問異常的錯誤信息提取工做留給客戶端對象,而是應該由DAO實現類,或者某個工具類進行統一的處理。假如咱們讓具體的DAO實現類來作這個工做,那麼,對於JdbcUserDao來講,代碼以下: 

try
		{
			conn = getDataSource().getConnection();
			//....
			User user = new User();
			Statement stmt = conn.createStatement();
			stmt.execute("");
			//........
			return user;
		} 
		catch (SQLException e)
		{
			//是拋出異常,仍是在當前位置處理。。。
			if(isMysqlVendor())
			{
				//按照mysql數據庫的規則分析錯誤信息而後拋出
				throw new RuntimeException(e);
			}
			if(isOracleVendor())
			{
				//按照oracle數據庫的規則分析錯誤信息並拋出
				throw new RuntimeException(e);
			}
			throw new RuntimeException(e);
		}

 

b>信息提出出來了,但是,只經過RuntimeException一個異常類型,還不足以區分不一樣的錯誤類型,咱們須要將數據訪問期間發生的錯誤進行分類,而後爲具體的錯誤分類分配一個對應的異常類型。好比,數據庫鏈接不上、ldap服務器鏈接失敗,他們被認爲是資源獲取失敗;而主鍵衝突或者是其它的資源衝突,他們被認爲是數據訪問一致性衝突。針對這些狀況,能夠爲RuntimeException爲基準,爲獲取資源失敗這種狀況分配一個RuntimeException子類型,稱其爲ResourceFailerException,而數據一致性衝突對應另一個子類型DataIntegrityViolationException,其它的分類異常能夠加以類推,因此咱們須要的只是一套unchecked exception類型的面向數據訪問領域的異常層次類型。

不須要從新發明輪子
咱們知道unchecked exception類型的面向數據訪問領域的異常層次體系存在的必要性,不需咱們設計,spring已經提供了異常訪問體系。
spring框架中的異常層次體系所涉及的大部分的異常類型均定義在org.springframework.dao包中,處於這個異常體系中的異常類型均是以org.springframework.dao.DataAccessException爲統領,而後根據職能劃分爲不一樣的子類型,整體上看,整個異常體系以下所示:

CleanupFailureDataAccessException:當成功完成數據訪問要對資源進行清理的時候,將拋出該異常,好比使用jdbc進行數據庫進行訪問的時候,查詢或者更新完成以後須要關閉相應的數據庫鏈接,若是在關閉的過程當中出現了SQLException,那麼致使數據庫鏈接沒有被釋放,致使資源清理失敗。
DataAccessResourceFailureException:在沒法訪問相應的數據資源的狀況下,將拋出DataAccessResourceFailureException。對應這種異常出現最多見的場景就是數據庫服務器掛掉的狀況,這時,鏈接數據庫的應用程序經過捕獲該異常須要了解到是數據庫服務器出現了問題。對於JDBC來講,服務器掛掉會拋出該類型的子類型,即org.springframework.dao.CannotGetJdbcConnectionException。
DataSourceLookupFailureException:當嘗試對jndi服務或者是其它位置上的DataSource進行查找的時候,能夠拋出DataSourceLookupFailureException。
ConcurrencyFailureException:併發訪問失敗的時候,能夠拋出ConcurrencyFailureException 好比沒法取得相應的數據庫的鎖,或者樂觀鎖更新衝突。根據不一樣的併發數據訪問失敗的狀況,ConcurrencyFailureException細分爲所個子類:

OptimisticLockingFailureException對應數據更新的時候出現樂觀鎖衝突的狀況。PessimisticLockingFailureException對應的是悲觀鎖衝突,PessimisticLockingFailureException還能夠細分爲CannotAcquireLockException和DeadlockLoserDataAccessException子類型。 InvalidDataAccessApiUsageException:該異常不是由於數據庫資源出現了問題,而是咱們以錯誤的方式,使用了特定的數據訪問API,好比使用Spring的JdbcTemplate的queryForObject()語義上只返回一個結果對象,因此咱們在查詢多行的時候不能使用此方法。 InvalidDataAccessResourceUsageException:以錯誤的方式訪問數據資源,會拋出該異常,好比要訪問數據庫資源,卻傳入錯誤的sql語句,分爲不一樣的子類,基於JDBC的訪問會拋出BadSqlGrammarException 基於hibernate的會拋出HibernateQueryException異常。 DataRetrievalFailureException:在要獲取預期的數據卻失敗的時候。 PermissionDeniedDataAccessException:要訪問相關的數據資源卻沒相應的權限的時候。 DataIntegrityViolationException:數據一致性衝突異常,好比主鍵衝突。 UncategorizedDataAccessException:沒法細分的其它的異常,能夠子類化定義具體的異常。

相關文章
相關標籤/搜索