本系列第三部分,將延續第二部分的骨架應用,引入 Spring JDBC、DAO 抽象層與 iBATIS SQL Maps,實現從 Apache Derby 資料庫動態讀取電話簿資料。您將見證依賴注入(IoC)的威力:僅修改 XML 組態即可切換資料來源,完全不動 Java 程式碼。
public class PhonebookEntry {
private int entryID, rowID;
private String fName, lName, homeNumber, workNumber, cellNumber, email;
// getter/setter 略
}
CREATE TABLE PB_ENTRY (
ENTRY_ID INT NOT NULL PRIMARY KEY,
ENTRY_FNAME VARCHAR(80), ENTRY_LNAME VARCHAR(80)
);
CREATE TABLE PB_DETAILS (
ENTRY_ID INT NOT NULL, ROW_ID INT NOT NULL,
HOME_NUMBER VARCHAR(20), WORK_NUMBER VARCHAR(20),
CELL_NUMBER VARCHAR(20), EMAIL VARCHAR(200),
PRIMARY KEY (ENTRY_ID, ROW_ID),
FOREIGN KEY (ENTRY_ID) REFERENCES PB_ENTRY(ENTRY_ID)
);
在 Geronimo 主控台 DB Manager 中建立 phonebook 資料庫,執行上述 DDL 並匯入範例資料(loadTables.sql)。
public interface IPhonebookDAO {
List getPhonebookEntries() throws Exception;
}
public class PhonebookJdbcDao extends JdbcDaoSupport implements IPhonebookDAO {
public List getPhonebookEntries() throws Exception {
JdbcTemplate jt = getJdbcTemplate();
return jt.query(
"SELECT * FROM PB_ENTRY A, PB_DETAILS B WHERE A.ENTRY_ID = B.ENTRY_ID AND A.ENTRY_ID <> 0",
new RowMapperResultReader(new PhonebookRowMapper())
);
}
}
JdbcTemplate 自動處理連線建立/關閉、例外轉換,開發者專注 SQL 與映射邏輯。
<bean id="pbJdbcDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close">
<property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="url" value="jdbc:derby:phonebook"/>
<property name="username" value="APP"/>
<property name="password" value=""/>
</bean>
<bean id="phonebookDao" class="phonebook.dao.jdbc.PhonebookJdbcDao">
<property name="dataSource" ref="pbJdbcDataSource"/>
</bean>
<bean id="phonebookJdbcDataProvider" class="phonebook.dao.PhonebookDataProvider">
<property name="pbDao" ref="phonebookDao"/>
</bean>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/ApplicationContext.xml</param-value>
</context-param>
public class PhonebookDataProvider {
private IPhonebookDAO pbDao;
public void setPbDao(IPhonebookDAO pbDao) { this.pbDao = pbDao; }
public List getPhonebookEntries() throws Exception { return pbDao.getPhonebookEntries(); }
}
<%@ page import="phonebook.dao.*, java.util.List" %>
<%
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
PhonebookDataProvider pb = (PhonebookDataProvider) ctx.getBean("phonebookJdbcDataProvider");
List pbDetails = pb.getPhonebookEntries();
%>
<!-- 迭代 pbDetails 填充表格 -->
<sqlMap namespace="Phonebook">
<select id="getPhonebookEntries" resultClass="phonebook.dao.PhonebookEntry">
SELECT A.ENTRY_ID as entryID, A.ENTRY_FNAME as fName, A.ENTRY_LNAME as lName,
B.ROW_ID as rowID, B.HOME_NUMBER as homeNumber, B.WORK_NUMBER as workNumber,
B.CELL_NUMBER as cellNumber, B.EMAIL as email
FROM PB_ENTRY A, PB_DETAILS B
WHERE A.ENTRY_ID = B.ENTRY_ID AND A.ENTRY_ID != 0
</select>
</sqlMap>
<sqlMapConfig>
<settings cacheModelsEnabled="true" enhancementEnabled="true" lazyLoadingEnabled="true"
maxRequests="32" maxSessions="10" maxTransactions="5" useStatementNamespaces="false"/>
<sqlMap resource="/phonebook/dao/orm/Phonebook.xml"/>
</sqlMapConfig>
public class SqlMapPhonebookDao extends SqlMapClientDaoSupport implements IPhonebookDAO {
public List getPhonebookEntries() throws Exception {
return getSqlMapClientTemplate().queryForList("getPhonebookEntries", null);
}
}
<!-- 使用 Commons DBCP 連線池 -->
<bean id="pbOrmDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver"/>
<property name="url" value="jdbc:derby:phonebook"/>
<property name="username" value="APP"/>
<property name="password" value=""/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="/META-INF/sqlmap-config.xml"/>
<property name="dataSource" ref="pbOrmDataSource"/>
</bean>
<bean id="phonebookOrmDao" class="phonebook.dao.orm.SqlMapPhonebookDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="phonebookOrmDataProvider" class="phonebook.dao.PhonebookDataProvider">
<property name="pbDao" ref="phonebookOrmDao"/>
</bean>
只需修改一行組態,讓 iBATIS 共用 JDBC 定義的 DataSource:
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="/META-INF/sqlmap-config.xml"/>
<property name="dataSource" ref="pbJdbcDataSource"/> <!-- 改為引用 JDBC DataSource -->
</bean>
重新部署後訪問 home-orm.jsp,資料同樣正確顯示,證明資料來源完全解耦。
第四部分將引入 Spring AOP 實現方法攔截追蹤,並展示聲明式事務管理與 Spring Web Flow 宣告式流程定義。