項目從單機遷移至集群,要求多數據庫最終一致性 (允許短暫不一致),網絡不穩定 (鐵路內網)。方案:正常走分佈式事務/同步調用;斷網時 Hibernate Interceptor 攔截持久化操作,序列化到本地表 z_jcyy_tb;網絡恢復由後台線程讀取本地隊列重放同步。
[應用服務] --正常網絡--> [主庫] + [從庫1] + [從庫2] (同步寫入)
|
|--斷網--> [Hibernate Interceptor] --> [本地隊列表 z_jcyy_tb]
(id, inputdate, object[Blob], action)
|
|--定時線程 (網絡恢復)--> 反序列化 --> 重放同步到遠程庫
private static String url_b, user_b, pass_b, driver_b;
static {
try {
// 反射讀取 SessionManager 私有常量 CONFIG_FILE_LOCATION
Field field = SessionManager.class.getDeclaredField("CONFIG_FILE_LOCATION");
field.setAccessible(true);
String path = (String) field.get(SessionManager.class);
// dom4j 解析 hibernate.cfg.xml
Document doc = new SAXReader().read(TBDao.class.getResourceAsStream(path));
// XPath + 正則提取 *_b 屬性
String xpath = "/hibernate-configuration/session-factory/property[@name='XPATH_I']";
Pattern p = Pattern.compile("(XPATH_I)");
Matcher m = p.matcher(xpath);
url_b = DocumentHelper.createXPath(m.replaceAll("connection.url_b")).selectSingleNode(doc).getText();
user_b = DocumentHelper.createXPath(m.replaceAll("connection.username_b")).selectSingleNode(doc).getText();
pass_b = DocumentHelper.createXPath(m.replaceAll("connection.password_b")).selectSingleNode(doc).getText();
driver_b = DocumentHelper.createXPath(m.replaceAll("connection.driver_class")).selectSingleNode(doc).getText();
} catch (Exception e) { e.printStackTrace(); }
}
public Session openSessionb() {
try {
Class.forName(driver_b);
Connection conn = DriverManager.getConnection(url_b, user_b, pass_b);
// 反射獲取已構建的 SessionFactory
Field sfField = Arrays.stream(SessionManager.class.getDeclaredFields())
.filter(f -> SessionFactory.class.equals(f.getType())).findFirst().orElseThrow();
sfField.setAccessible(true);
SessionFactory sf = (SessionFactory) sfField.get(SessionManager.class);
return sf.openSession(conn); // 關鍵:使用外部 Connection 打開 Session
} catch (Exception e) { return null; }
}
public void save(Object obj, String action) { // action: INSERT/UPDATE/DELETE
Session session = null;
try {
session = SessionManager.currentSession(null, null);
Transaction tr = session.beginTransaction();
ZJcyyTb zj = new ZJcyyTb();
zj.setAction(action);
zj.setInputdate(new Date());
session.save(zj);
session.flush();
session.refresh(zj, LockMode.UPGRADE); // 獲取自增 ID
// 序列化物件到 Blob
zj.setObject(new ObjectConvert().ObjectToBlob(obj));
tr.commit();
} catch (Exception e) { e.printStackTrace(); }
finally { if (session != null && session.isOpen()) session.close(); }
}
ZJcyyTb 表結構:id (PK), inputdate, object (BLOB), action (VARCHAR)。
public boolean action() {
ObjectConvert oc = new ObjectConvert();
Session local = null, remote = null;
try {
if (isSql() > 0) { // 本地有待同步記錄
local = SessionManager.currentSession(null, null);
remote = openSessionb(); // 嘗試連接遠程
if (remote != null) {
Transaction trL = local.beginTransaction();
Transaction trR = remote.beginTransaction();
Query q = local.createQuery("FROM ZJcyyTb ORDER BY inputdate");
// 分頁處理,避免一次性載入過多
for (int page = 0; ; page++) {
q.setFirstResult(page * 10).setMaxResults(10);
List list = q.list();
if (list.isEmpty()) break;
for (Object o : list) {
ZJcyyTb tb = (ZJcyyTb) o;
Object entity = oc.BlobToObject(tb.getObject(), null);
if (entity != null) {
switch (tb.getAction()) {
case DELETE: remote.delete(entity); break;
case INSERT: remote.save(entity); break;
case UPDATE: remote.update(entity); break;
}
}
local.delete(tb); // 同步成功刪除本地記錄
}
trL.commit(); trR.commit();
}
}
}
return true;
} catch (Exception e) { return false; }
finally { /* 關閉 Session */ }
}
private int isSql() { /* 查詢 z_jcyy_tb count(*) */ }
在 hibernate.cfg.xml 註冊自定義 Interceptor:
com.example.sync.SyncInterceptor
SyncInterceptor 實現 onSave/onUpdate/onDelete,判斷遠程連接可用性:
- 可用 → 直接在遠程 Session 同步操作
- 不可用 → 調用 TBDao.save(entity, action) 入本地隊列
| 環節 | 技術 | 作用 |
|---|---|---|
| 遠程連接 | 反射獲取 SessionFactory + openSession(Connection) | 復用主庫 SessionFactory,動態切換遠程數據源 |
| 本地隊列 | Hibernate Interceptor + BLOB 序列化 | 零侵入業務代碼,自動捕獲所有持久化操作 |
| 序列化 | ObjectConvert (ObjectOutputStream/ObjectInputStream) | 支援任意可序列化實體,含關聯對象圖 |
| 重放策略 | 按 inputdate 升序分頁重放 + 事務保證 | 保證操作順序、原子性、防止 OOM |
| 網絡檢測 | 定時線程嘗試 openSessionb() | 自適應網絡恢復,無需外部監控 |
version (樂觀鎖) 或 timestamp,發現衝突記錄人工介入。