《Spring in Action》第四章筆記,聚焦 Spring JDBC 核心抽象:模板方法模式封裝固定流程、回調介面處理變化邏輯,詳解 JdbcTemplate 核心操作、批量更新、結果集映射、存儲過程調用、以及 Hibernate/iBatis/JDO 整合要點。
Spring 將 JDBC 固定流程(獲取連接、創建 Statement、事務控制、資源釋放、異常轉換)封裝於 JdbcTemplate,變化邏輯(SQL、參數綁定、結果整理)委派給回調介面:
PreparedStatementCreator:創建 PreparedStatement(含 SQL 與參數)。PreparedStatementSetter:綁定參數值。RowCallbackHandler:逐行處理 ResultSet(無返回值)。RowMapper:將單行映射為領域對象(推薦,可返回 List)。ResultSetExtractor:整個結果集提取單一結果(如聚合、分頁)。CallableStatementCallback:處理存儲過程 CallableStatement。JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); // 線程安全,可作 Bean 注入
// 或繼承 JdbcDaoSupport,用 getJdbcTemplate() 獲取
// 簡單更新
jdbcTemplate.update("UPDATE user SET name=? WHERE id=?", "John", 1L);
// 批量更新:高效處理大量數據
List
注意:execute() 無參數重載;帶參數更新請用 update(sql, args) 或 batchUpdate。
// 單行查詢
User user = jdbcTemplate.queryForObject(
"SELECT id, name, email FROM user WHERE id=?",
new RowMapper() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));
u.setEmail(rs.getString("email"));
return u;
}
}, 1L);
// 多行查詢
List users = jdbcTemplate.query(
"SELECT id, name, email FROM user WHERE active=1",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"))); // Java 8 Lambda
// 簡單值查詢
int count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM user", Integer.class);
String name = jdbcTemplate.queryForObject("SELECT name FROM user WHERE id=?", String.class, 1L);
jdbcTemplate.execute("{call ARCHIVE_USERS(?, ?)}", new CallableStatementCallback() {
public Integer doInCallableStatement(CallableStatement cs) throws SQLException {
cs.setInt(1, 2023); // IN 參數
cs.registerOutParameter(2, Types.INTEGER); // OUT 參數
cs.execute();
return cs.getInt(2); // 返回 OUT 值
}
});
將 SQL 操作封裝為可重用對象,近似 DAO 方法:
public class InsertUser extends SqlUpdate {
public InsertUser(DataSource ds) {
super(ds, "INSERT INTO user(name, email) VALUES(?, ?)");
declareParameter(new SqlParameter("name", Types.VARCHAR));
declareParameter(new SqlParameter("email", Types.VARCHAR));
compile();
}
public int execute(String name, String email) {
return update(new Object[]{name, email});
}
}
優點:類型安全、可重用、隱藏 JDBC 細節。缺點:增加類數量,簡單項目用 JdbcTemplate 更直接。
抽象不同數據庫序列/自增機制:
MySQLMaxValueIncrementer:SELECT LAST_INSERT_ID()OracleSequenceMaxValueIncrementer:序列 NEXTVALPostgreSQLSequenceMaxValueIncrementerHsqlMaxValueIncrementer / DerbyMaxValueIncrementerDataFieldMaxValueIncrementer inc = new MySQLMaxValueIncrementer(dataSource, "user");
long newId = inc.nextLongValue(); // 插入前獲取 ID,或插入後獲取
| ORM | 核心 FactoryBean | 模板類 | 關鍵配置 |
|---|---|---|---|
| Hibernate | LocalSessionFactoryBean | HibernateTemplate | mappingDirectoryLocations 掃描 *.hbm.xml,不再需 hibernate.cfg.xml |
| iBatis 2.x | SqlMapClientFactoryBean | SqlMapClientTemplate | configLocation=sqlMap-config.xml,保留原配置檔 |
| iBatis 1.3 | SqlMapFactoryBean | SqlMapTemplate | 類名 SqlMapXxx |
| JDO | LocalPersistenceManagerFactoryBean | JdoTemplate | 較少用 |
| TopLink | TopLinkFactoryBean | TopLinkTemplate | Oracle 產品 |
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingDirectoryLocations">
<list><value>classpath:com/example/domain/</value></list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
DAO 繼承 HibernateDaoSupport 或注入 HibernateTemplate,使用 HibernateCallback 執行複雜查詢/批量操作。
JdbcTemplate 封裝 JDBC 固定流程,回調介面分離變化邏輯,核心掌握 update/query/batchUpdate/RowMapper。batchUpdate(sql, List,效能遠超迴圈單條執行。RowMapper(Java 8 Lambda 極簡潔),避免舊版 RowCallbackHandler+RowMapperResultReader。FactoryBean,統一事務、異常轉換、資源管理,DAO 層幾乎無 ORM 專用代碼。