Hibernate 支援五種核心關聯映射,覆蓋關係資料庫所有外鍵關係場景:
場景:部門擁有多名員工,但員工不需要知道所屬部門對象。
映射關鍵:在「一」的一方使用 <set> + <one-to-many>
<set name="employees" cascade="save-update" inverse="true">
<key column="DEPT_ID"/> <!-- 員工表外鍵列 -->
<one-to-many class="com.entity.Employee"/>
</set>
inverse="true" 表示由「多」的一方維護關係,避免雙向更新產生冗餘 SQL。
場景:員工屬於某部門,需導航到部門對象。
映射關鍵:在「多」的一方使用 <many-to-one>
<many-to-one name="department" class="com.entity.Department">
<column name="DEPT_ID"/>
</many-to-one>
同時配置上述兩種映射,並在「一」的一方設定 inverse="true",由「多」的一方負責關係維護。注意:雙向關聯在 Java 端需手動維護雙向引用一致性(如 emp.setDept(dept); dept.getEmps().add(emp);)。
主鍵關聯或外鍵關聯(推薦外鍵+唯一約束)。
<!-- 員工端 -->
<one-to-one name="idCard" class="com.entity.IdCard"
cascade="all" property-ref="employee"/>
<!-- 身份證端 -->
<many-to-one name="employee" class="com.entity.Employee"
unique="true"> <!-- unique="true" 效等於 one-to-one -->
<column name="EMP_ID"/>
</many-to-one>
透過中間表實現。兩種策略:
<set> + <many-to-many> 指向中間表<!-- 學生端 -->
<set name="courses" table="STUDENT_COURSE" cascade="save-update">
<key column="STUDENT_ID"/>
<many-to-many class="com.entity.Course" column="COURSE_ID"/>
</set>
<!-- 課程端 -->
<set name="students" table="STUDENT_COURSE" cascade="save-update" inverse="true">
<key column="COURSE_ID"/>
<many-to-many class="com.entity.Student" column="STUDENT_ID"/>
</set>
| 屬性 | 適用元素 | 核心作用 | 常用值 |
|---|---|---|---|
| cascade | <set>, <many-to-one>, <one-to-one> | 級聯操作傳播 | all, save-update, delete, none |
| inverse | <set>, <bag>, <list> | 關係維護權反轉 | true/false (默認 false) |
| fetch | <set>, <many-to-one> | 抓取策略 | select(默認), join, subselect |
| lazy | <set>, <class> | 延遲載入開關 | true(默認)/false |
session.get/load(id)// 工具類:ThreadLocal 綁定 Session,保證線程隔離
public class HibernateUtil {
private static final ThreadLocal<Session> TL = new ThreadLocal<>();
private static SessionFactory SF;
static { SF = new Configuration().configure().buildSessionFactory(); }
public static Session getCurrentSession() {
Session s = TL.get();
if (s == null) { s = SF.openSession(); TL.set(s); }
return s;
}
public static void closeSession() {
Session s = TL.get(); TL.set(null); if (s != null) s.close();
}
}
// DAO 使用範例
public List<Dept> findAll() {
Session s = HibernateUtil.getCurrentSession();
Query q = s.createQuery("from Dept");
return q.list(); // 一次性載入所有結果並寫入緩存
}
list():直接查詢所有符合條件數據,寫入一級緩存,適合小結果集或需立即使用所有數據iterate():先查 ID,再按需從緩存/資料庫載入對象,適合大結果集且緩存命中率高場景// 方式 1:直接選擇屬性,返回 Object[]
Query q = s.createQuery("select deptName, location from Dept");
List<Object[]> list = q.list();
// 方式 2:構造函數注入,返回實體對象(需實體有對應構造函數)
Query q = s.createQuery("select new Dept(deptName, location) from Dept");
List<Dept> list = q.list();
// 位置參數
Query q = s.createQuery("from Dept where deptName like ?");
q.setString(0, "%" + name + "%");
// 命名參數(推薦,可讀性強)
Query q = s.createQuery("from Dept where deptName = :name");
q.setParameter("name", name);
public List<Dept> findByPage(int pageNo, int pageSize) {
Query q = s.createQuery("from Dept");
q.setFirstResult((pageNo - 1) * pageSize);
q.setMaxResults(pageSize);
return q.list();
}
public int getTotalPages(String keyword) {
StringBuilder hql = new StringBuilder("select count(*) from Dept where 1=1");
List params = new ArrayList();
if (keyword != null) {
hql.append(" and deptName like ?");
params.add("%" + keyword + "%");
}
Query q = s.createQuery(hql.toString());
for (int i = 0; i < params.size(); i++) q.setParameter(i, params.get(i));
long count = (Long) q.uniqueResult();
return (int) Math.ceil((double) count / PAGE_SIZE);
}
// 基本查詢
Criteria c = s.createCriteria(Dept.class);
c.add(Restrictions.eq("deptNo", "101"));
Dept dept = (Dept) c.uniqueResult();
// 示例查詢:根據對象模板查詢
Dept template = new Dept();
template.setLocation("上海");
c.add(Example.create(template)); // 自動忽略 null 屬性
List<Dept> list = c.list();
// 統計、分組、分頁
c.setProjection(Projections.projectionList()
.add(Projections.groupProperty("deptName"))
.add(Projections.rowCount())
.add(Projections.avg("salary"))
.add(Projections.max("deptNo")));
c.setFirstResult(0).setMaxResults(10);
在 .hbm.xml 中預定義:
<query name="findDeptByNo">
<![CDATA[ from Dept d where d.deptNo = :deptNo ]]>
</query>
調用:s.getNamedQuery("findDeptByNo").setParameter("deptNo", 101).list()
無需 Session 即可構建查詢條件,適合 Web 層組裝查詢條件後傳給 DAO:
DetachedCriteria dc = DetachedCriteria.forClass(StudentExam.class);
dc.add(Restrictions.eq("exam.id", examId));
dc.add(Restrictions.in("classId", classIds));
List<StudentExam> list = findList(dc); // DAO 內執行 dc.getExecutableCriteria(session).list()
複雜報表、資料庫特有函數、存儲過程調用:
String sql = "SELECT * FROM Z_ANSWER WHERE RECRUIT_ID = :rid AND EXAM_ID = :eid ORDER BY CREATE_TIME DESC";
Query q = s.createSQLQuery(sql).addEntity(ZAnswer.class);
q.setParameter("rid", recruitId).setParameter("eid", examId);
List<ZAnswer> list = q.list();
// 有結果集
CallableStatement cs = session.connection().prepareCall("{call findEmpById(?)}");
cs.setInt(1, 2);
ResultSet rs = cs.executeQuery();
// 有輸出參數
CallableStatement cs = session.connection().prepareCall("{call getNameById(?, ?)}");
cs.setInt(1, 3);
cs.registerOutParameter(2, Types.VARCHAR);
cs.execute();
String name = cs.getString(2);
cascade="delete" 可能誤刪關聯數據setFirstResult/setMaxResults 而非記憶體截取