首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Hibernate 關聯映射與高級查詢完全指南:從一對多到原生 SQL 實戰

     編輯:Java 綜合教學

Hibernate 關聯關係映射全景

Hibernate 支援五種核心關聯映射,覆蓋關係資料庫所有外鍵關係場景:

1. 單向一對多

場景:部門擁有多名員工,但員工不需要知道所屬部門對象。

映射關鍵:在「一」的一方使用 <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。

2. 單向多對一

場景:員工屬於某部門,需導航到部門對象。

映射關鍵:在「多」的一方使用 <many-to-one>

<many-to-one name="department" class="com.entity.Department">
    <column name="DEPT_ID"/>
</many-to-one>

3. 雙向一對多

同時配置上述兩種映射,並在「一」的一方設定 inverse="true",由「多」的一方負責關係維護。注意:雙向關聯在 Java 端需手動維護雙向引用一致性(如 emp.setDept(dept); dept.getEmps().add(emp);)。

4. 一對一

主鍵關聯或外鍵關聯(推薦外鍵+唯一約束)。

<!-- 員工端 -->
<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>

5. 多對多

透過中間表實現。兩種策略:

  1. 無中間實體類:雙向 <set> + <many-to-many> 指向中間表
  2. 有中間實體類:將多對多拆解為兩個一對多
<!-- 學生端 -->
<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

Hibernate 五大檢索方式

  1. 導航對象圖:從已載入對象遍歷關聯
  2. OID 檢索:session.get/load(id)
  3. HQL:面向對象查詢語言,獨立資料庫
  4. QBC (Criteria):完全物件化查詢 API
  5. 原生 SQL:複雜查詢、資料庫特有功能

HQL 核心技術詳解

基本查詢與 Query 接口

// 工具類: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() vs iterate() 效能抉擇

  • 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();

參數綁定:防止 SQL 注入

// 位置參數
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 API:完全物件化查詢

// 基本查詢
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()

DetachedCriteria:離線查詢

無需 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()

原生 SQL 查詢

複雜報表、資料庫特有函數、存儲過程調用:

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);

最佳實踐總結

  1. 優先使用 HQL/Criteria:可移植、面向對象、享受緩存優勢
  2. 複雜報表、大數據統計用原生 SQL:發揮資料庫引擎優勢
  3. 合理配置 fetch/lazy:避免 N+1 問題,按需加載關聯
  4. 級聯操作謹慎使用:cascade="delete" 可能誤刪關聯數據
  5. 雙向關聯必須設置 inverse="true":由集合端維護關係,減少 UPDATE 語句
  6. 分頁必須在數據庫層面完成:setFirstResult/setMaxResults 而非記憶體截取
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved