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

Spring JDBC/DAO 框架實戰:從傳統 DAO 痛點到現代資料存取最佳實踐

     編輯:Java 程式設計入門

前言:為什麼需要 Spring DAO?

傳統 JDBC 程式碼充滿樣板碼:取得連線、準備 Statement、執行查詢、遍歷 ResultSet、釋放資源、處理 SQLException。這導致業務邏輯與持久層耦合、資源洩漏風險高、錯誤處理繁瑣且不具可攜性。Spring JDBC 框架透過 JdbcTemplate 封裝固定流程,配合 IoC 容器 管理資源,徹底解決這些痛點。

傳統 DAO 設計與痛點

經典架構:介面 + 實作 + 工廠

// 1. DAO 介面 (Programming to Interface)
public interface IEmployeeDAO {
    String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, SALARY FROM EMP WHERE SALARY >= ? AND SALARY <= ?";
    List findBySalaryRange(Map salaryMap);
}

// 2. JDBC 實作 - 充滿樣板碼
public class EmployeeDAOImpl implements IEmployeeDAO {
    public List findBySalaryRange(Map salaryMap) {
        Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();  // 從連線池取得
            pstmt = conn.prepareStatement(FIND_BY_SAL_RNG);
            pstmt.setDouble(1, Double.valueOf((String)salaryMap.get("MIN_SALARY")));
            pstmt.setDouble(2, Double.valueOf((String)salaryMap.get("MAX_SALARY")));
            rs = pstmt.executeQuery();
            // 手動映射 ResultSet 到 Transfer Object...
        } catch (SQLException e) { throw new DBException(e); }
        finally { /* 繁瑣的資源關閉邏輯 */ }
    }
}

// 3. DAO 工廠
public class DAOFactory {
    public IEmployeeDAO getEmployeeDAO() { return new EmployeeDAOImpl(); }
}

五大核心痛點

  1. 程式碼重複:每個 DAO 方法重複連線/Statement/ResultSet/資源釋放邏輯
  2. 緊密耦合:大量 import java.sql.*、java.util.*,難以切換 O/R Mapping 框架
  3. 資源洩漏風險:手動關閉資源,新手易遺漏導致連線池耗盡
  4. 錯誤處理困擾:SQLException 為受檢異常,錯誤碼廠商專屬,難寫可攜程式碼
  5. 脆弱的綁定變數:SQL 修改(欄位順序/數量變動)強制修改 Java 程式碼、重測、重部署

Spring JDBC 框架核心組件

Business Service → IEmployeeDAO → EmployeeDAOImpl (extends JdbcDaoSupport)
                                      ↑
                              JdbcTemplate (核心執行引擎)
                                      ↑
                              DataSource (由 IoC 容器注入)

JdbcTemplate 職責

  • 取得連線、準備 Statement、執行 SQL CRUD
  • 遍歷 ResultSet,填充標準集合
  • 處理 SQLException → 轉換為 Spring 階層化非受檢異常(DataAccessException 子類)
  • 確保資源正確釋放

重構實戰:四步驟遷移至 Spring DAO

步驟 1:DAO 實作繼承 JdbcDaoSupport

public class EmployeeDAOImpl extends JdbcDaoSupport implements IEmployeeDAO {
    public List findBySalaryRange(Map salaryMap) {
        Object[] params = {
            Double.valueOf((String)salaryMap.get("MIN_SALARY")),
            Double.valueOf((String)salaryMap.get("MAX_SALARY"))
        };
        // queryForList 回傳 List,每個 Map 代表一行 (欄位名->值)
        return getJdbcTemplate().queryForList(FIND_BY_SAL_RNG, params);
    }
}

效益:程式碼從 50+ 行縮減至 10 行,消除 JDBC/Collection 耦合,資源管理自動化,異常轉換為非受檢 DataAccessException 階層(如 DataIntegrityViolationException),廠商中立。

步驟 2:業務服務採用 Setter Injection

public class EmployeeBusinessServiceImpl implements IEmployeeBusinessService {
    private IEmployeeDAO empDAO;  // 不再直接 new DAOFactory
    
    public List getEmployeesWithinSalaryRange(Map salaryMap) {
        return empDAO.findBySalaryRange(salaryMap);
    }
    public void setDao(IEmployeeDAO empDAO) { this.empDAO = empDAO; }
}

步驟 3:Spring Bean 設定檔 (XML)

<beans>
  <!-- 資料來源:JNDI 取得 WebLogic 連線池 -->
  <bean id="FIREBIRD_DATASOURCE" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiEnvironment">
      <props>
        <prop key="java.naming.factory.initial">weblogic.jndi.WLInitialContextFactory</prop>
        <prop key="java.naming.provider.url">t3://localhost:7001</prop>
      </props>
    </property>
    <property name="jndiName"><value>jdbc/DBPool</value></property>
  </bean>

  <!-- DAO 注入 DataSource -->
  <bean id="EMP_DAO" class="com.bea.dev2dev.dao.EmployeeDAOImpl">
    <property name="dataSource"><ref bean="FIREBIRD_DATASOURCE"/></property>
  </bean>

  <!-- 業務服務注入 DAO -->
  <bean id="EMP_BUSINESS" class="com.bea.dev2dev.sampleapp.business.EmployeeBusinessServiceImpl">
    <property name="dao"><ref bean="EMP_DAO"/></property>
  </bean>
</beans>

步驟 4:JUnit 整合測試

public class EmployeeBusinessServiceImplTest extends TestCase {
    private IEmployeeBusinessService empBusiness;
    private Map salaryMap; List expResult;

    protected void setUp() throws Exception {
        ApplicationContext ac = new FileSystemXmlApplicationContext("C:/SpringConfig/Spring-Config.xml");
        empBusiness = (IEmployeeBusinessService) ac.getBean("EMP_BUSINESS");
        salaryMap = new HashMap(); salaryMap.put("MIN_SALARY","1"); salaryMap.put("MAX_SALARY","50");
        expResult = new ArrayList();
        Map row = new HashMap(); row.put("EMP_NO",1); row.put("EMP_NAME","John"); row.put("SALARY",46.11);
        expResult.add(row);
    }
    public void testGetEmployeesWithinSalaryRange() {
        List result = empBusiness.getEmployeesWithinSalaryRange(salaryMap);
        assertEquals(expResult, result);
    }
}

進階技巧:命名參數與 RowMapper 回調

問題:位置參數 (?) 導致 SQL 修改時 Java 程式碼同步異動

解決:NamedParameterJdbcTemplate 支援 :name 風格參數

// 介面修改 SQL 使用命名參數
public interface IEmployeeDAO {
    String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, SALARY FROM EMP WHERE SALARY >= :max AND SALARY <= :min";
    List findBySalaryRange(Map salaryMap);  // Map key 直接對應 :max, :min
}

// 實作使用 NamedParameterJdbcDaoSupport
public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport implements IEmployeeDAO {
    public List findBySalaryRange(Map salaryMap) {
        return getNamedParameterJdbcTemplate().queryForList(FIND_BY_SAL_RNG, salaryMap);
    }
}

運行時自動轉換為位置參數,SQL 欄位順序調整不再影響 Java 程式碼。

RowMapper 回調:將 ResultSet 映射為領域物件

// 1. 實作 RowMapper
public class EmployeeTOMapper implements RowMapper {
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new EmployeeTO(rs.getInt(1), rs.getString(2), rs.getDouble(3));
    }
}

// 2. DAO 使用 query 方法註冊 Mapper
public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport implements IEmployeeDAO {
    public List findBySalaryRange(Map salaryMap) {
        return getNamedParameterJdbcTemplate().query(FIND_BY_SAL_RNG, salaryMap, new EmployeeTOMapper());
    }
}

關鍵點:

  • mapRow 不可 呼叫 rs.next(),框架負責迭代
  • Mapper 中的 SQLException 由框架統一轉換
  • 映射邏輯與 DAO 核心邏輯分離,符合單一職責原則

總結:Spring DAO 帶來的價值

傳統 DAO 痛點Spring DAO 解決方案
大量樣板碼重複JdbcTemplate 封裝固定流程,DAO 方法只需核心邏輯
JDBC API 緊耦合依賴介面而非實作,易切換 Hibernate/JPA/O/R Mapping
資源洩漏風險模板方法保證資源正確釋放
SQLException 處理困擾轉換為非受檢 DataAccessException 階層,廠商中立
位置參數脆弱命名參數支援,SQL 重構不影響 Java 程式碼
ResultSet 映射分散RowMapper 回調集中封裝映射邏輯

Spring JDBC 框架以極簡 API 解決傳統 JDBC 開發的核心難題,是遷移既有應用、建立新專案的最佳選擇。建議搭配 重構工具(Extract Interface、Introduce Parameter Object)漸進式導入,參考官方文件與社群最佳實踐持續優化。

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