Spring訪問數(shù)據(jù)庫異常的處理方法
使用JDBC API時(shí),很多操作都要聲明拋出java.sql.SQLException異常,通常情況下是要制定異常處理策略。而Spring的JDBC模塊為我們提供了一套異常處理機(jī)制,這套異常系統(tǒng)的基類是DataAccessException,它是RuntimeException的一種類型,那么就不用強(qiáng)制去捕捉異常了,Spring的異常體系如下: 
 
目前為止我們還沒有明確地處理Spring中JDBC模塊的異常。要理解它的異常處理機(jī)制,我們來做幾個(gè)測試??聪旅娴臏y試代碼:
- public void insert(final Vehicle vehicle) {
 - String sql = "insert into vehicle
 - (ID,PLATE,CHASSIS,COLOR,WHEEL,SEAT) values
 - (:id,:plate,:chassis,:color,:wheel,:seat)";
 - SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(
 - vehicle);
 - getSimpleJdbcTemplate().update(sql, parameterSource);
 - }
 - public void insert(final Vehicle vehicle) {
 - String sql = "insert into vehicle(ID,PLATE,CHASSIS,COLOR,WHEEL,SEAT)
 - values(:id,:plate,:chassis,:color,:wheel,:seat)";
 - SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(
 - vehicle);
 - getSimpleJdbcTemplate().update(sql, parameterSource);
 - }
 
- public static void main(String[] args) {
 - ApplicationContext ctx = new ClassPathXmlApplicationContext(
 - "classpath:org/ourpioneer/vehicle/spring/applicationContext.xml");
 - VehicleDAO vehicleDAO = (VehicleDAO) ctx.getBean("vehicleDAO");
 - Vehicle vehicle = new Vehicle("遼B-000000", "1A00000001", "RED", 4, 4);
 - vehicle.setId(1);
 - vehicleDAO.insert(vehicle);
 - }
 - public static void main(String[] args) {
 - ApplicationContext ctx = new ClassPathXmlApplicationContext(
 - "classpath:org/ourpioneer/vehicle/spring/applicationContext.xml");
 - VehicleDAO vehicleDAO = (VehicleDAO) ctx.getBean("vehicleDAO");
 - Vehicle vehicle = new Vehicle("遼B-000000", "1A00000001", "RED", 4, 4);
 - vehicle.setId(1);
 - vehicleDAO.insert(vehicle);
 - }
 
修改SQL語句,不使用自增主鍵的特性,并在這里設(shè)置重復(fù)的主鍵,那么運(yùn)行程序,就會(huì)報(bào)出字段重復(fù)的異常。下面來捕捉這個(gè)異常:
- try {
 - vehicleDAO.insert(vehicle);
 - } catch (DataAccessException e) {
 - SQLException sqle = (SQLException) e.getCause();
 - System.out.println("Error code: " + sqle.getErrorCode());
 - System.out.println("SQL state: " + sqle.getSQLState());
 - }
 - try {
 - vehicleDAO.insert(vehicle);
 - } catch (DataAccessException e) {
 - SQLException sqle = (SQLException) e.getCause();
 - System.out.println("Error code: " + sqle.getErrorCode());
 - System.out.println("SQL state: " + sqle.getSQLState());
 - }
 
此時(shí),我們就可以獲得錯(cuò)誤碼和SQL狀態(tài)(不同的數(shù)據(jù)庫系統(tǒng)會(huì)有不同):
 
關(guān)于HSQL數(shù)據(jù)庫的錯(cuò)誤碼可以到org.hsqldb.Trace類中查看,只要注意運(yùn)行結(jié)果會(huì)有一個(gè)負(fù)號,而類中定義的是沒有負(fù)號的。這樣就知道了這個(gè)錯(cuò)誤的具體含義,比如104:***約束驗(yàn)證失敗。這就是我們故意設(shè)置的重復(fù)主鍵問題。
Spring的JDBC模塊為我們預(yù)定義了一些錯(cuò)誤代碼,它存儲(chǔ)在org.springframework.jdbc.support包下的sql-error-codes.xml文件中,其中描述HSQL的內(nèi)容為:
- <bean id="HSQL" class="org.springframework.jdbc.support.SQLErrorCodes">
 - <property name="databaseProductName">
 - <value>HSQL Database Engine</value>
 - </property>
 - <property name="badSqlGrammarCodes">
 - <value>-22,-28</value>
 - </property>
 - <property name="duplicateKeyCodes">
 - <value>-104</value>
 - </property>
 - <property name="dataIntegrityViolationCodes">
 - <value>-9</value>
 - </property>
 - <property name="dataAccessResourceFailureCodes">
 - <value>-80</value>
 - </property>
 - </bean>
 - <bean id="HSQL" class="org.springframework.jdbc.support.SQLErrorCodes">
 - <property name="databaseProductName">
 - <value>HSQL Database Engine</value>
 - </property>
 - <property name="badSqlGrammarCodes">
 - <value>-22,-28</value>
 - </property>
 - <property name="duplicateKeyCodes">
 - <value>-104</value>
 - </property>
 - <property name="dataIntegrityViolationCodes">
 - <value>-9</value>
 - </property>
 - <property name="dataAccessResourceFailureCodes">
 - <value>-80</value>
 - </property>
 - </bean>
 
其余數(shù)據(jù)庫的錯(cuò)誤碼內(nèi)容也可以從這個(gè)文件之中獲得。下面我們來看看如何自定義異常處理。上面我們已經(jīng)知道在org.springframework.jdbc.support包下有sql-error-codes.xml文件,在Spring啟動(dòng)時(shí)會(huì)自動(dòng)讀取這個(gè)文件中的錯(cuò)誤碼,它為我們預(yù)分類了一些錯(cuò)誤碼,而我們可以加強(qiáng)它,來使用我們自定義的異常。首先,定義一個(gè)異常類,我們就來自定義一下前面的-104錯(cuò)誤,就是HSQL的重復(fù)鍵的問題:
- package org.ourpioneer.vehicle.exception;
 - import org.springframework.dao.DataIntegrityViolationException;
 - public class VehicleDuplicateKeyException extends
 - DataIntegrityViolationException {
 - public VehicleDuplicateKeyException(String msg) {
 - super(msg);
 - }
 - public VehicleDuplicateKeyException(String msg, Throwable cause) {
 - super(msg, cause);
 - }
 - }
 - package org.ourpioneer.vehicle.exception;
 - import org.springframework.dao.DataIntegrityViolationException;
 - public class VehicleDuplicateKeyException extends
 - DataIntegrityViolationException {
 - public VehicleDuplicateKeyException(String msg) {
 - super(msg);
 - }
 - public VehicleDuplicateKeyException(String msg, Throwable cause) {
 - super(msg, cause);
 - }
 - }
 
之后我們重新新建一個(gè)sql-error-codes.xml代碼,并將它放到類路徑的根目錄下,這樣Spring會(huì)發(fā)現(xiàn)它并使用我們自定義的文件,在配置中定義如下:
- <bean id="HSQL" class="org.springframework.jdbc.support.SQLErrorCodes">
 - <property name="databaseProductName" value="HSQL Database Engine" />
 - <property name="useSqlStateForTranslation" value="false" />
 - <property name="customTranslations">
 - <list>
 - <ref local="vehicleDuplicateKeyTranslation" />
 - </list>
 - </property>
 - </bean>
 - <bean id="vehicleDuplicateKeyTranslation"
 - class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation">
 - <property name="errorCodes" value="-104" />
 - <property name="exceptionClass"
 - value="org.ourpioneer.vehicle.exception.VehicleDuplicateKeyException" />
 - </bean>
 - <bean id="HSQL" class="org.springframework.jdbc.support.SQLErrorCodes">
 - <property name="databaseProductName" value="HSQL Database Engine" />
 - <property name="useSqlStateForTranslation" value="false" />
 - <property name="customTranslations">
 - <list>
 - <ref local="vehicleDuplicateKeyTranslation" />
 - </list>
 - </property>
 - </bean>
 - <bean id="vehicleDuplicateKeyTranslation"
 - class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation">
 - <property name="errorCodes" value="-104" />
 - <property name="exceptionClass"
 - value="org.ourpioneer.vehicle.exception.VehicleDuplicateKeyException" />
 - </bean>
 
HSQL的bean的名稱不要改,并將useSqlStateForTranslation置為false,就可以使用我們自己定義的異常類了。在主函數(shù)中移除try/catch塊,啟動(dòng)程序,我們就可以看到如下內(nèi)容:
 
從啟動(dòng)信息中可以發(fā)現(xiàn)Spring發(fā)現(xiàn)了我們自定義的sql-error-codes.xml,并替換其中的HSQL數(shù)據(jù)庫處理部分,使用了我們定義的異常,模擬出主鍵重復(fù)的異常后,VehicleDuplicateKeyException就拋出了。除此之外,還可以實(shí)現(xiàn)SQLExceptionTranslator接口,并在JDBC模板中注入其實(shí)例來實(shí)現(xiàn)異??刂?,我們來看一下,首先創(chuàng)建一個(gè)Translator類:
- package org.ourpioneer.vehicle.exception;
 - import java.sql.SQLException;
 - import org.springframework.dao.DataAccessException;
 - import org.springframework.jdbc.UncategorizedSQLException;
 - import org.springframework.jdbc.support.SQLExceptionTranslator;
 - public class VehicleDuplicateKeyTranslator implements SQLExceptionTranslator {
 - public DataAccessException translate(String task, String sql,
 - SQLException ex) {
 - if (task == null) {
 - task = "";
 - }
 - if (sql == null) {
 - }
 - if (ex.getErrorCode() == -104) {
 - return new VehicleDuplicateKeyException(buildMessage(task, sql, ex));
 - } else {
 - return new UncategorizedSQLException(task, sql, ex);
 - }
 - }
 - private String buildMessage(String task, String sql, SQLException ex) {
 - return "數(shù)據(jù)庫操作異常:" + task + "; SQL [" + sql + "]; " + ex.getMessage();
 - }
 - }
 - package org.ourpioneer.vehicle.exception;
 - import java.sql.SQLException;
 - import org.springframework.dao.DataAccessException;
 - import org.springframework.jdbc.UncategorizedSQLException;
 - import org.springframework.jdbc.support.SQLExceptionTranslator;
 - public class VehicleDuplicateKeyTranslator implements SQLExceptionTranslator {
 - public DataAccessException translate(String task, String sql,
 - SQLException ex) {
 - if (task == null) {
 - task = "";
 - }
 - if (sql == null) {
 - }
 - if (ex.getErrorCode() == -104) {
 - return new VehicleDuplicateKeyException(buildMessage(task, sql, ex));
 - } else {
 - return new UncategorizedSQLException(task, sql, ex);
 - }
 - }
 - private String buildMessage(String task, String sql, SQLException ex) {
 - return "數(shù)據(jù)庫操作異常:" + task + "; SQL [" + sql + "]; " + ex.getMessage();
 - }
 - }
 
其中,要覆蓋translate方法,方法有三個(gè)參數(shù),task表示當(dāng)前操作要進(jìn)行的任務(wù)是什么,sql就是執(zhí)行的sql語句,ex表示SQLException,我們可以從中獲取異常信息,其處理代碼僅僅捕捉了錯(cuò)誤碼為-104(HSQL數(shù)據(jù)庫)的錯(cuò)誤,其余的配置信息可以根據(jù)需要來自行添加。之后要在Spring中重新配置它們:
- <bean id="vehicleDuplicateKeyTranslator"
 - class="org.ourpioneer.vehicle.exception.VehicleDuplicateKeyTranslator"></bean>
 - <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
 - <property name="exceptionTranslator" ref="vehicleDuplicateKeyTranslator" />
 - <property name="dataSource" ref="dataSource" />
 - </bean>
 - <bean id="vehicleDAO" class="org.ourpioneer.vehicle.dao.VehicleDAOImpl">
 - <property name="jdbcTemplate" ref="jdbcTemplate" />
 - </bean>
 - <bean id="vehicleDuplicateKeyTranslator"
 - class="org.ourpioneer.vehicle.exception.VehicleDuplicateKeyTranslator"></bean>
 - <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
 - <property name="exceptionTranslator" ref="vehicleDuplicateKeyTranslator" />
 - <property name="dataSource" ref="dataSource" />
 - </bean>
 - <bean id="vehicleDAO" class="org.ourpioneer.vehicle.dao.VehicleDAOImpl">
 - <property name="jdbcTemplate" ref="jdbcTemplate" />
 - </bean>
 
調(diào)整DAO實(shí)現(xiàn)類的代碼:
- public class VehicleDAOImpl extends SimpleJdbcDaoSupport implements VehicleDAO {
 - … …
 - public void insert(final Vehicle vehicle) {
 - String sql = "insert into vehicle(ID,PLATE,CHASSIS,COLOR,WHEEL,SEAT) values(?,?,?,?,?,?)";
 - getJdbcTemplate().update(sql, vehicle.getId(),vehicle.getPlate(),vehicle.getChassis(),vehicle.getColor(),vehicle.getWheel(),vehicle.getSeat());
 - }
 - … …
 - }
 - public class VehicleDAOImpl extends SimpleJdbcDaoSupport implements VehicleDAO {
 - … …
 - public void insert(final Vehicle vehicle) {
 - String sql = "insert into vehicle(ID,PLATE,CHASSIS,COLOR,WHEEL,SEAT) values(?,?,?,?,?,?)";
 - getJdbcTemplate().update(sql, vehicle.getId(),vehicle.getPlate(),vehicle.getChassis(),vehicle.getColor(),vehicle.getWheel(),vehicle.getSeat());
 - }
 - … …
 - }
 
為了進(jìn)行測試,其它代碼可不用修改,這樣繼續(xù)運(yùn)行測試程序,同時(shí)將sql-error-codes.xml文件從類路徑的根路徑下去除,就可以得到如下結(jié)果:
 
Spring的JDBC模塊在自定義異常處理上也非常靈活,可以選擇自己喜歡的方式來實(shí)現(xiàn)。希望對使用者有用,歡迎交流,下一部分開始介紹Spring的ORM。
原文鏈接:http://sarin.javaeye.com/blog/888458
【編輯推薦】
- Java持久化框架 DataNucleus 2.1發(fā)布
 - 淺談Spring框架中的JDBC應(yīng)用
 - Spring框架的7個(gè)模塊
 - 詳細(xì)介紹Spring框架
 - 將Flex與Spring框架集成
 















 
 
 
 
 
 
 