傳統 JDBC 程式碼充滿樣板碼:取得連線、準備 Statement、執行查詢、遍歷 ResultSet、釋放資源、處理 SQLException。這導致業務邏輯與持久層耦合、資源洩漏風險高、錯誤處理繁瑣且不具可攜性。Spring JDBC 框架透過 JdbcTemplate 封裝固定流程,配合 IoC 容器 管理資源,徹底解決這些痛點。
// 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(); }
}
java.sql.*、java.util.*,難以切換 O/R Mapping 框架SQLException 為受檢異常,錯誤碼廠商專屬,難寫可攜程式碼Business Service → IEmployeeDAO → EmployeeDAOImpl (extends JdbcDaoSupport)
↑
JdbcTemplate (核心執行引擎)
↑
DataSource (由 IoC 容器注入)
DataAccessException 子類)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
效益:程式碼從 50+ 行縮減至 10 行,消除 JDBC/Collection 耦合,資源管理自動化,異常轉換為非受檢 DataAccessException 階層(如 DataIntegrityViolationException),廠商中立。
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; }
}
<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>
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);
}
}
// 介面修改 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 程式碼。
// 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(),框架負責迭代| 傳統 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)漸進式導入,參考官方文件與社群最佳實踐持續優化。