Hibernate 3.2 內建多種主鍵生成策略(increment、identity、sequence、native、UUID 等),但在實務開發中常遇到無法滿足需求的情境:
increment:單機可用,叢集環境下會產生重複鍵identity/sequence/native:依賴資料庫特定機制,跨資料庫移植性差若需求為 Long 型別、自動遞增、支援叢集,則必須實作自訂 IdentifierGenerator。
package hibernate;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.type.Type;
public class IncrementGenerator implements IdentifierGenerator, Configurable {
private static final Log log = LogFactory.getLog(IncrementGenerator.class);
private Long next;
private String sql;
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
if (sql != null) {
getNext(session.connection());
}
return next;
}
public void configure(Type type, Properties params, Dialect d)
throws MappingException {
String table = params.getProperty("table");
if (table == null) table = params.getProperty(PersistentIdentifierGenerator.TABLE);
String column = params.getProperty("column");
if (column == null) column = params.getProperty(PersistentIdentifierGenerator.PK);
String schema = params.getProperty(PersistentIdentifierGenerator.SCHEMA);
sql = "select max(" + column + ") from "
+ (schema == null ? table : schema + '.' + table);
log.info("ID Generator SQL: " + sql);
}
private void getNext(Connection conn) throws HibernateException {
PreparedStatement st = null;
ResultSet rs = null;
try {
st = conn.prepareStatement(sql);
rs = st.executeQuery();
if (rs.next()) {
next = rs.getLong(1) + 1;
} else {
next = 1L;
}
} catch (SQLException e) {
throw new HibernateException(e);
} finally {
try { if (rs != null) rs.close(); } catch (SQLException ignore) {}
try { if (st != null) st.close(); } catch (SQLException ignore) {}
// 注意:此處不應關閉 connection,由 Hibernate 管理
}
}
}
IdentifierGenerator(核心生成邏輯)+ Configurable(接收映射檔參數)params 取得 table、column、schema,動態組裝 SELECT MAX(...) SQLStatement/ResultSet,不可關閉 Connection(交由 Session 管理)<id name="id" type="long" column="id">
<generator class="hibernate.IncrementGenerator" />
</id>
若需指定資料表/欄位,可在 <generator> 內添加 <param name="table">MY_TABLE</param> 等參數。
上述實作為「查詢 MAX + 1」模式,在高併發叢集下仍可能產生競態條件。生產環境建議採用以下任一進階方案:
native 策略hilo/seqhilo,透過「高位從資料庫取號、低位記憶體自增」減少資料庫往返自訂 IdentifierGenerator 讓 Hibernate 能適應特殊業務需求。本文示範的「MAX+1」策略實作簡單、跨資料庫相容,適合中低併發場景;高併發叢集建議改用資料庫原生序列或 Hi/Lo 機制。掌握擴充點,才能在 ORM 框架中遊刃有餘。