Hibernate 的 O/R 映射機制在處理多表關聯、分組統計、排序等複雜查詢時,會產生大量冗余 SQL(N+1 問題),導致效能遠遜於原生 JDBC。本文提出在 Hibernate 框架內直接獲取 JDBC Connection 的輕量級方案,兼顧 ORM 便利性與 SQL 靈活性。
假設主表 A、子表 B、C 為一對多關聯。Hibernate 以 lazy=false 載入 A 時,會對每條 A 記錄分別查詢 B、C,產生 2N+1 條 SQL。若 A 有 1000 筆、B 3 萬筆、C 2000 筆,查詢+排序耗時超過 2 秒;隨資料增長呈線性惡化。
改用單條優化 SQL(含子查詢與 Left Join)僅需 1 條 SQL,耗時穩定在 1 秒內,且不隨資料量劇增。
Hibernate Session 實際上封裝了 JDBC Connection,二者生命週期一致。透過 HibernateTemplate.getSessionFactory().getCurrentSession().connection() 即可取得底層 Connection,進而操作 PreparedStatement、ResultSet。
AbstractHibernateDao // 保留原有 save/update/delete/loadAll
↑
BaseHibernateDao // 新增 JDBC 直連介面
- getConnection() : Connection
- getObjectsBySql(sql) : List<Object>
- fetchObjects(ResultSet) : List<Object> // 需子類實作映射邏輯
public abstract class BaseHibernateDao extends AbstractHibernateDao {
public Connection getConnection() {
try {
Session session = getHibernateTemplate().getSessionFactory().getCurrentSession();
return session.connection();
} catch (Exception e) { return null; }
}
public ArrayList<Object> getObjectsBySql(String sql) {
Connection con = null; PreparedStatement ps = null; ResultSet rs = null;
try {
con = getConnection();
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
return fetchObjects(rs);
} catch (Exception e) { return null; }
finally { /* 關閉資源 */ }
}
// 由具體 DAO 實作 ResultSet → Domain Object
public abstract ArrayList<Object> fetchObjects(ResultSet rs);
}
public class Top10Dao extends BaseHibernateDao {
private String sql = "SELECT tab1.ID, tab1.sumCol+tab2.sumCol FROM "
+ "(SELECT a.ID, COUNT(b.ID) sumCol FROM A a LEFT JOIN B b ON a.ID=b.ID GROUP BY a.ID) tab1, "
+ "(SELECT a.ID, COUNT(c.ID) sumCol FROM A a LEFT JOIN C c ON a.ID=c.ID GROUP BY a.ID) tab2 "
+ "WHERE tab1.ID=tab2.ID ORDER BY tab1.sumCol+tab2.sumCol DESC";
@Override
public ArrayList<A> fetchObjects(ResultSet rs) {
ArrayList<A> list = new ArrayList<A>();
int rank = 1;
while (rs.next()) {
A a = new A();
a.setId(rs.getLong(1));
System.out.println("top" + rank++ + " amount:" + rs.getLong(2));
list.add(a);
}
return list;
}
}
| 資料量 | 方案 | 查詢(ms) | 排序(ms) | 總計(ms) |
|---|---|---|---|---|
| A:1000 B:3W C:2000 | Hibernate | 1475 | 1035 | 2110 |
| A:1000 B:3W C:2000 | JDBC 直連 | 1096 | 0 | 1096 |
| A:2000 B:6W C:4000 | Hibernate | 2836 | 1568 | 4404 |
| A:2000 B:6W C:4000 | JDBC 直連 | 1657 | 0 | 1657 |
結論:JDBC 直連方案在資料增長時效能穩定,且可靈活運用資料庫索引、子查詢、Join 調優。
Connection 關閉,避免洩漏;事務邊界仍由 Hibernate 管理(同一 Session 內)。