首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

高階 DAO 設計模式實戰:事務邊界、異常處理與日誌記錄三大核心支柱

     編輯:Java 程式設計入門

Data Access Object (DAO) 模式是 J2EE 架構中分離資料存取邏輯與業務邏輯的標準模式。但真正生產級的 DAO 實作,遠不止 CRUD 方法這麼簡單。本文深入探討三大常被忽視卻至關重要的面向:事務界定、異常處理、日誌記錄。

DAO 架構組件回顧

  • DAO Factory:負責建立具體 DAO 實例
  • DAO Interface:定義資料操作契約
  • DAO Implementation:封裝特定資料源的存取邏輯
  • Data Transfer Object (DTO/Value Object):層間資料傳輸載體

事務界定:核心設計決策

關鍵設計問題

  1. 事務由誰發起?誰負責提交/回滾?
  2. DAO 是否應自行管理事務邊界?
  3. 單一業務操作是否跨越多個 DAO?
  4. 是否涉及多資料庫(分布式事務)?

策略一:DAO 內部管理事務(JDBC 本地事務)

適用於單一資料庫、單一 DAO 操作的簡單場景:

public class JdbcMovieDao implements MovieDao {
    private DataSource dataSource;
    
    public void createMovie(Movie movie) {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            conn.setAutoCommit(false); // 開啟手動提交
            
            PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO MOVIES (title, year, rating) VALUES (?, ?, ?)");
            ps.setString(1, movie.getTitle());
            ps.setString(2, movie.getYear());
            ps.setString(3, movie.getRating());
            ps.executeUpdate();
            
            conn.commit();
        } catch (SQLException e) {
            rollbackQuietly(conn);
            throw new DaoException("建立電影失敗", e);
        } finally {
            closeQuietly(conn);
        }
    }
}

缺點:無法跨 DAO 組合事務,事務範圍侷限於單一 Connection。

策略二:調用者管理事務(JTA 全域事務)

適用於跨多 DAO、多資料源、結合 JMS 等資源的企業級場景:

// 業務服務層 / Servlet / EJB
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();

try {
    Movie batman = dao.createMovie("Batman Reloaded", "2008", "R");
    jmsPublisher.publishTextMessage("New movie: " + batman.getTitle());
    dao.updateMovie(topgun.getId(), "Top Gun", "1986", "PG-13");
    dao.deleteMovie(legallyblonde.getId());
    
    utx.commit();
} catch (Exception e) {
    utx.rollback();
    throw new ServiceException("業務流程失敗", e);
}

JTA 關鍵配置

  • 資料源必須實作 javax.sql.XADataSource、XAConnection、XAResource
  • 應用服務器需配置 XA DataSource
  • 嚴禁對 XA Connection 調用 conn.commit()/rollback() 或使用 auto-commit
  • 統一使用 UserTransaction.begin/commit/rollback

UserTransaction 核心方法

方法用途
begin()啟動新事務
commit()提交事務(兩階段提交)
rollback()回滾事務
setRollbackOnly()標記僅能回滾
setTransactionTimeout(seconds)設定超時時間
getStatus()獲取事務狀態

日誌記錄:可觀測性基石

日誌框架選型

DAO 中整合 Commons Logging 範例

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DocumentDaoImpl implements DocumentDao {
    private static final Log log = LogFactory.getLog(DocumentDaoImpl.class);
    
    public void deleteDocument(String id) {
        log.debug("刪除文檔: {}", id);
        try {
            // 資料操作...
            log.info("文檔刪除成功: {}", id);
        } catch (DataAccessException e) {
            log.error("刪除文檔失敗: " + id, e); // 保留堆疊追蹤
            throw new DaoException("刪除失敗", e);
        }
    }
}

日誌等級運用指南

  • DEBUG:詳細診斷資訊(SQL 參數、流程節點)
  • INFO:關鍵業務里程碑(建立/刪除實體、批次處理完成)
  • WARN:潛在問題(重試、降級、接近閾值)
  • ERROR:失敗但應用仍可運行(單筆資料操作失敗)
  • FATAL:嚴重錯誤導致服務不可用

異常處理:健壯 DAO 的關鍵

七大設計原則

  1. 拋出有意義的檢查型異常:如 MovieNotFoundException、DuplicateKeyException
  2. 禁止拋出 java.lang.Exception:過於笼統,呼叫端無法針對性處理
  3. 禁止洩漏 SQLException:DAO 應封裝 JDBC 細節,轉換為領域異常
  4. 異常轉換保留因果鏈:使用 throw new DaoException("msg", cause) 建構鏈式異常
  5. 不可吞沒異常:catch 區塊不可為空,至少記錄日誌
  6. 區分檢查型與非檢查型:呼叫端可合理恢復用檢查型;不可恢復(如資料庫掛掉)用非檢查型
  7. 定義標準 DAO 異常體系:參考 Spring org.springframework.dao 套件設計

Spring DAO 異常層級結構參考

DataAccessException (非檢查型根異常)
├── NonTransientDataAccessException
│   ├── DataIntegrityViolationException (唯一鍵衝突、外鍵違反)
│   ├── InvalidDataAccessApiUsageException (API 誤用)
│   └── ...
├── TransientDataAccessException (暫時性錯誤,可重試)
│   ├── DeadlockLoserDataAccessException
│   ├── QueryTimeoutException
│   └── ...
└── RecoverableDataAccessException
    └── ...

完整實戰範例:MovieDAO 雙實作對比

核心介面定義

public interface MovieDao {
    Movie findById(String id) throws MovieNotFoundException;
    Collection<Movie> findByYear(String year);
    void delete(String id) throws MovieNotFoundException;
    Movie create(String rating, String year, String title);
    void update(String id, String rating, String year, String title) 
        throws MovieNotFoundException;
}

實作對比表

框架開源特點
java.util.logging否JDK 1.4+ 內建,功能較弱
Log4j是功能豐富、支援 1.3/1.4、業界標準
Commons Logging是抽象層,可切換底層實作,Struts/HttpClient 採用
特性MovieDaoImpl (JDBC)MovieDaoImplJTA (JTA)
事務邊界DAO 內部管理調用者管理 (外部)
事務類型JDBC 本地事務JTA 分布式事務
DataSource普通 DataSourceXA DataSource
Connection 獲取dao 內部 getConnection()調用者傳入或 JNDI 查找
跨 DAO 事務不支援支援
結合 JMS不可靠同一全域事務

Servlet 整合示範 (JTA 版)

public class DemoServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        
        MovieDao dao = MovieDaoFactory.getInstance("jta");
        UserTransaction utx = MovieUtil.getUserTransaction();
        
        try {
            utx.begin();
            
            Movie batman = dao.create("R", "2008", "Batman Reloaded");
            new MessagePublisher().publishTextMessage("I'll be back");
            dao.update(topgun.getId(), "PG-13", topgun.getYear(), topgun.getTitle());
            dao.delete(legallyblonde.getId());
            
            utx.commit();
            resp.getWriter().println("分布式事務提交成功");
        } catch (Exception e) {
            try { utx.rollback(); } catch (Exception ignored) {}
            throw new ServletException("事務失敗", e);
        }
    }
}

技術選型決策矩陣

應用特徵推薦策略理由
單資料庫、簡單 CRUD、無跨服務調用JDBC 事務 (DAO 內部)簡單、效能佳、無分布式協調開銷
多 DAO 組合業務、單資料庫JDBC 事務 (Service 層管理)傳遞 Connection 或使用 ThreadLocal 綁定
跨多資料庫、結合 JMS/EJB、需 ACIDJTA (調用者管理)唯一支援分布式 ACID 的標準方案
Spring 環境@Transactional (聲明式)最佳開發體驗,自動處理傳播行為

總結

優秀的 DAO 不僅是 SQL 的封裝器,更是事務邊界的守門人、異常語義的翻譯官、系統可觀測性的資料源。根據應用複雜度選擇適當的事務策略、建立領域化的異常體系、貫穿結構化日誌記錄,才能構建經得起生產環境考驗的資料存取層。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved