網頁

2020/6/13

Spring Web 處理存取資料庫例外錯誤 handle database access exception

在Spring Web專案中,通常在DAO或Repository層存取資料庫,而存取資料庫時拋出例外錯誤該在哪處理?

有些人偏好在DAO層以try-catch捕捉所有異常,然後在catch區塊紀錄log並拋出自訂例外錯誤。例如下面是存取資料表EMPLOYEEDemoDao及Spring Data JPA的Repository DemoRepo

DemoDao

package com.abc.demo.dao;

import com.abc.demo.entity.Employee;
import com.abc.demo.exception.DemoException;
import com.abc.demo.repo.DemoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class DemoDao {

    @Autowired
    private DemoRepository demoRepository;

    public void add(Employee employee) {
        try {
            demoRepository.save(employee);
        } catch (Exception e) {
            String message = "access database error";
            log.error(message, e);
            throw new DemoException(message); // 拋出自訂例外錯誤
        }
    }
}

DemoRepository

package com.abc.demo.repo;

import com.abc.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DemoRepository extends JpaRepository<Employee, Long> {}

自訂的例外錯誤類別。

DemoException

package com.abc.demo.exception;

public class DemoException extends RuntimeException {

    private String message;
    
    public DemoException(String message) {
        this.message = message;
    }

    // getters and setters
}

但這樣的做法導致每個DAO都會有一堆類似的try-catch,且只是要把原始錯誤訊息轉譯為自訂訊息而已。

更甚者還會把DAO拋出的自訂例外錯誤繼承Exception而非RuntimeException,也就代表著在Service層又要在處理一次自訂例外,然後才拋出另一個繼承RuntimeException的自訂例外,最後在@ControllerAdvice類處理錯誤及回應結果給外部。



然而Spring存取資料庫發生錯誤時會拋出以DataAccessException為父類別所對應的子類,而DataAccessException繼承RuntimeException。這意味著Spring並不要求在執行資料庫存取的地方處理例外,而是讓錯誤一直往外拋直到可處理的地方,也就代表以上的做法不好。

若要把原始例外錯誤轉成自訂例外錯誤,可統一在@ControllerAdvice類處理,也就是DAO應改成下面。

package com.abc.demo.dao;

import com.abc.demo.entity.Employee;
import com.abc.demo.exception.DemoException;
import com.abc.demo.repo.DemoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class DemoDao {

    @Autowired
    private DemoRepository demoRepository;

    public void add(Employee employee) {
        demoRepository.save(employee); // 把try-catch拿掉,不在此處理例外錯誤
    }
}

所有Spring Web資料庫存取拋出的錯誤統一由@ControllerAdvice捕捉DataAccessException來處理。

DemoExceptionHandler

package com.abc.demo.controller;

import lombok.extern.log4j.Log4j2;
import org.springframework.dao.DataAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Log4j2
@ControllerAdvice
public class DemoExceptionHandler {

    @ExceptionHandler({DataAccessException.class})
    public final ResponseEntity<String> handleDataAccessException(DataAccessException e) {
        String message = "access database error";
        log.error(message, e);
        return ResponseEntity.badRequest().body(message);
    }

}

結論是若無特殊需求不應在Spring的Service,DAO或Repository層個別用try-catch處理存取資料庫造成的例外,原因為:

  1. Spring存取資料庫發生的例外錯誤設計不要求立即處理。
  2. try-catch區塊讓程式碼看起來比較髒。
  3. 團隊成員一多就會有人忘記加try-catch
  4. 拋出的錯誤訊息格式不統一。

沒有留言:

張貼留言