典型電商前後台系統:前台商品瀏覽/購物車/訂單/用戶中心,後台 EasyUI 管理後台。技術棧:Struts 2.5 + Spring 4 + Hibernate 5 + MySQL + EasyUI + json-lib。
| 維度 | JDBC + Servlet 時代 | SSH 整合後 |
| 表結構變更 | 手改 SQL、手改 Bean、手改 DAO | 改實體註解/映射文件,Hibernate 自動 DDL (hbm2ddl.auto=update) |
| DAO 代碼量 | 每表一套 CRUD、關聯查詢手寫 SQL | BaseDao 泛型封裝通用方法,僅寫特殊 HQL/QL |
| 參數綁定 | 手動 ps.setString(1, val) | Struts2 OGNL 自動注入 Action 屬性,Service 直接用對象 |
| 事務管理 | 手動 conn.commit/rollback | Spring @Transactional 聲明式,攔截器織入 |
核心感悟:框架讓開發者聚焦業務邏輯,但也拉遠了與底層的距離——理解原理仍是排查疑難雜症的前提。
代碼層面的三大進階技巧
技巧一:多條件查詢合一 — 單方法 + 動態 HQL
舊寫法:getByName()、getByType()、getAll()… 代碼冗餘。
新寫法:查詢參數封裝入實體對象,構建器模式拼 HQL:
public List<Product> getProductList(Product query, PageBean page) {
StringBuilder hql = new StringBuilder("FROM Product WHERE 1=1 ");
List<Object> params = new ArrayList<>();
if (query.getSpecialPrice() == 1) hql.append("AND specialPrice=1 ORDER BY specialPriceTime DESC ");
if (query.getHot() == 1) hql.append("AND hot=1 ORDER BY hotTime DESC ");
if (query.getBigType() != null) { hql.append("AND bigType.id=? "); params.add(query.getBigType().getId()); }
if (query.getSmallType() != null) { hql.append("AND smallType.id=? "); params.add(query.getSmallType().getId()); }
if (StringUtils.isNotBlank(query.getName())) { hql.append("AND name LIKE ? "); params.add("%" + query.getName() + "%"); }
return baseDao.find(hql.toString(), params, page);
}
優勢:單入口、邏輯集中、易維護、參數化防注入。
技巧二:json-lib 日期/關聯對象序列化 — 註冊 ValueProcessor
問題:Date 序列化為完整對象圖 {"date":1,"day":3...};關聯對象 (如 BigType) 遞歸序列化導致循環引用/數據過大。
解決:實現 JsonValueProcessor 並註冊:
// 日期處理器
public class DateJsonValueProcessor implements JsonValueProcessor {
private final String format;
public DateJsonValueProcessor(String format) { this.format = format; }
@Override public Object processArrayValue(Object v, JsonConfig cfg) { return process(v); }
@Override public Object processObjectValue(String key, Object v, JsonConfig cfg) { return process(v); }
private Object process(Object v) {
if (v == null) return "";
if (v instanceof Date) return new SimpleDateFormat(format).format((Date) v);
return v.toString();
}
}
// 關聯對象處理器 (僅保留 id/name)
public class ObjectJsonValueProcessor implements JsonValueProcessor {
private final String[] fields; private final Class<?> clazz;
public ObjectJsonValueProcessor(String[] fields, Class<?> clazz) { this.fields = fields; this.clazz = clazz; }
@Override public Object processArrayValue(Object v, JsonConfig cfg) { return process(v); }
@Override public Object processObjectValue(String key, Object v, JsonConfig cfg) { return process(v); }
private Object process(Object v) {
if (v == null) return JSONObject.NULL;
JSONObject jo = new JSONObject();
for (String f : fields) {
try { jo.put(f, new PropertyDescriptor(f, clazz).getReadMethod().invoke(v)); }
catch (Exception e) { jo.put(f, ""); }
}
return jo;
}
}
// Action 中註冊
JsonConfig cfg = new JsonConfig();
cfg.setExcludes(new String[]{"orderProductList"}); // 排除循環引用集合
cfg.registerJsonValueProcessor(Date.class, new DateJsonValueProcessor("yyyy-MM-dd"));
cfg.registerJsonValueProcessor(BigType.class, new ObjectJsonValueProcessor(new String[]{"id","name"}, BigType.class));
cfg.registerJsonValueProcessor(SmallType.class, new ObjectJsonValueProcessor(new String[]{"id","name"}, SmallType.class));
JSONArray array = JSONArray.fromObject(list, cfg);
效果:"hotTime":"2024-01-15"、"bigType":{"id":1,"name":"手機"},乾淨可控。
技巧三:BaseDao 泛型封裝 — 消除重複 CRUD
public class BaseDao<T> {
private Class<T> entityClass;
public BaseDao() { this.entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; }
public T get(Serializable id) { return (T) getSession().get(entityClass, id); }
public void save(T entity) { getSession().save(entity); }
public void update(T entity) { getSession().update(entity); }
public void delete(T entity) { getSession().delete(entity); }
public List<T> find(String hql, List<Object> params) { /* ... */ }
public List<T> find(String hql, List<Object> params, PageBean page) { /* 分頁 */ }
}
子類零代碼繼承:public class ProductDao extends BaseDao<Product> {}。
遺留疑難:Session 屬性莫名消失 (非 Session 失效)
現象:存入 Session 的 對象類型屬性 (User、購物車 Map) 幾秒後讀取為 null,但 字符串/基本類型屬性 正常;Session 本身未銷毀 (session.getId() 不變)。
排查方向
- 序列化問題:對象未實現
Serializable,集群/持久化 Session 時反序列化失敗導致屬性丟失。
- 類加載器隔離:熱部署/重新部署導致舊 Session 中對象類與新類加載器不一致,
ClassCastException 被吞沒返回 null。
- Session 屬性被攔截器/濾波器清理:檢查
session.removeAttribute() 調用點。
- Tomcat 配置:
context.xml <Manager pathname="" /> 禁用持久化,或 sessionAttributeValueClassNameFilter 限制可序列化類。
- 並發修改:多線程並發讀寫同一 Session 屬性 (如購物車),未同步導致數據不一致。
建議驗證步驟
- 確保所有存入 Session 的對象 實現 Serializable 並定義
serialVersionUID。
- 啟用 Tomcat Session 存活監控:
manager/html/sessions 觀察屬性變化。
- 臨時在濾波器記錄
session.getAttributeNames() 追蹤消失時刻。
- 若為集群環境,檢查 Redis/Database Session 存儲的序列化配置 (如
spring.session.serializer)。
此問題極具迷惑性,歡迎有確切根因的同學分享解決方案。
總結
- SSH 整合核心在 Spring 管理 Bean、Hibernate 管 ORM、Struts2 接請求,配置文件模式固定 (applicationContext.xml, struts.xml, hibernate.cfg.xml)。
- 工程化關鍵:模板復用、啟動緩存、動態 HQL、JSON 精準序列化、泛型 BaseDao。
- Session 異常丟失多因序列化/類加載器/集群配置,需系統化排查。
- 框架是加速器,原理理解是剎車——只會用不懂原理,遇到坑只能干瞪眼。
- 上一篇文章: Tomcat 7/8 + MyEclipse 2015 安裝配置全流程圖解:環境變量、JRE、Runtime Environment 一站式指南
- 下一篇文章: 深入理解電腦中斷請求(IRQ):原理、分配與衝突解決
Copyright ©
程式碼之家 All Rights Reserved