傳統 JDBC 代碼存在兩大痛點:
關聯式資料表與 Java 類別結構同構:
若遵循「類別名 = 表名」、「屬性名 = 欄位名」慣例,即可利用反射自動映射。
// 從資料庫取得表名,直接對應類別名
Class c = Class.forName("Person"); // 表名 Person
Person p = (Person) c.newInstance(); // 建立實例
優勢:原始碼無需 import 具體實體類別,新增表格只需對應新增類別檔。
因 newInstance() 僅呼叫無參建構子,需額外初始化方法:
public abstract class DBEntity {
// 由子類實作或框架透過反射自動填充
public abstract void initFromResultSet(ResultSet rs) throws SQLException;
}
// 初始化階段:快取所有表的主鍵/索引欄位
for (each table) {
if (對應類別存在) {
loadClass(tableName);
cachePrimaryKeys(tableName, metaData.getPrimaryKeys());
cacheIndexColumns(tableName, metaData.getIndexInfo());
}
}
// 查詢階段:根據傳入的 (name,value) 對自動生成 SQL
Object query(Object criteria) {
for (each table) {
List matchedKeys = matchCriteriaWithKeys(criteria, table);
if (matchedKeys not empty) {
String sql = buildSelectSQL(table, matchedKeys);
ResultSet rs = executeQuery(sql);
while (rs.next()) {
Object entity = createEntity(table);
entity.initFromResultSet(rs);
addToResults(entity);
}
}
}
}
// 利用 Introspector 取得 Bean 所有屬性
BeanInfo info = Introspector.getBeanInfo(entityClass);
PropertyDescriptor[] props = info.getPropertyDescriptors();
// 利用 DatabaseMetaData 取得 Table 所有欄位
ResultSet columns = metaData.getColumns(null, null, tableName, null);
// 比對:屬性名、型別、可空性是否與欄位定義一致
// 發現不一致即拋出異常,部署階段即可攔截錯誤
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Client │────▶│ GenericDAO │────▶│ Database │
│ (Criteria) │ │ (Reflection)│ │ (MetaData) │
└─────────────┘ └──────────────┘ └─────────────┘
▲ │
│ ▼
│ ┌──────────────┐
└────────────│ Entity Bean │
│ (JavaBean) │
└──────────────┘
| 優點 | 缺點/挑戰 |
|---|---|
| 零配置映射,慣例優於配置 | 命名慣例嚴格,舊資料庫不符需適配層 |
| 資料庫結構變更只需修改 Bean 類別 | 效能較手寫 JDBC 低(反射開銷) |
| 通用 DAO 可服務所有實體,減少重複代碼 | 複雜關聯查詢、多表 Join 難以通用化 |
| 部署期透過反射驗證結構一致性 | Blob/Clob、自訂型別需額外處理 |
此思想直接催生了 Hibernate、JPA、MyBatis 等 ORM 框架:
JavaBeans 結合反射與 DatabaseMetaData,實現了「寫一次通用 DAO,服務所有資料表」的願景。雖然現代專案直接使用成熟 ORM 框架,但理解這層基礎原理,有助於:排查框架底層行為、設計客製化映射策略、在輕量級專案中自行實作簡易資料存取層。