Hibernate的6种查询方式对比

Hibernate 有 6 种查询方式:HQL(面向对象的查询语言,类似 SQL)、SQL(原生 SQL)、命名查询(XML 配置 SQL/HQL)、Criteria(完全面向对象)、DetachedCriteria(分离查询条件)、Example(按实体示例查询)。推荐 HQL 日常查询、Criteria 动态条件、SQL 复杂场景。Example 代码复杂不推荐。

上一篇把 Hibernate 的入门配置走通了,这篇接着聊它的查询。

Hibernate 查数据的方式不止一种,前前后后有六种玩法。刚接触的时候容易懵:这么多种到底该用哪个?

我按平时的使用习惯和适用面给它们排了个序,每种配个最简实例,顺便说说各自的优缺点。看完你心里就有谱了。

六种查询,先排个序

按我自己的使用频率,大致是这个顺序:

  1. HQL 查询
  2. SQL 查询
  3. 命名查询
  4. 对象化查询(Criteria)
  5. 动态查询(DetachedCriteria)
  6. 例子查询(不推荐)

下面一个个过,代码都用上一篇那个 User 实体。

HQL 查询

HQL 是 Hibernate 自己的查询语言,长得像 SQL,但有个关键区别:它操作的是对象和属性,不是表和字段。

static void query(String name) {
    Session s = null;
    try {
        s = HibernateUtil.getSession();
        // from 后面跟的是对象 User,不是表名
        // 冒号开头的是命名参数,可读性比 ? 好,推荐
        String hql = "from User as user where user.username = :name";
        Query query = s.createQuery(hql);
        query.setString("name", name);  // 给命名参数赋值
        List<User> list = query.list();
        for (User user : list) {
            // 遍历结果
        }
    } finally {
        if (s != null) s.close();
    }
}
​
  • 优点:最常用、最传统,结构类似 JDBC 容易理解,而且因为写的是对象,天然跨数据库。
  • 缺点:是一门全新的查询语言,只在 Hibernate 里管用,换框架就白学。

日常八成查询用它就对了。

SQL 查询

就是直接写原生 SQL,没啥好绕的。

static void query(String name) {
    Session s = null;
    try {
        s = HibernateUtil.getSession();
        // createSQLQuery 走原生 SQL,addEntity 指定结果封装成哪个实体
        // 注意:这里为演示用了拼接,真实代码要用参数绑定防注入
        Query query = s.createSQLQuery("select * from user where name = :name")
                       .addEntity(User.class);
        query.setString("name", name);
        List<User> list = query.list();
    } finally {
        if (s != null) s.close();
    }
}
​
  • 优点:万能,不想学 HQL 的直接写 SQL,复杂查询(多表、特殊函数)也能硬上。
  • 缺点:破坏了跨数据库能力,也不面向对象,等于绕开了 Hibernate 的大半好处。

多说一句安全:原底稿里是把变量直接拼进 SQL 字符串的,那样有注入风险。正确做法是用 :name 这种命名参数绑定,别字符串拼接。

命名查询

把查询语句写到配置文件里,代码里按名字调用。思路有点像 MyBatis 那种"SQL 和代码分离"。

代码这边:

static void query(String name) {
    Session s = null;
    try {
        s = HibernateUtil.getSession();
        // 按名字取出配置里定义好的查询
        Query query = s.getNamedQuery("getUserByName");
        query.setString("name", name);
        List<User> list = query.list();
    } finally {
        if (s != null) s.close();
    }
}
​

配置文件里定义,HQL 和 SQL 都支持:

<!-- 命名查询,写 HQL -->
<query name="getUserByName">
    <![CDATA[from User where username = :name]]>
</query>

<!-- 命名查询,写原生 SQL -->
<sql-query name="getUserByNameSql">
    <return alias="user" class="com.mebugs.daoBean.User"></return>
    <![CDATA[select * from user where name = :name]]>
</sql-query>
​
  • 优点:SQL 统一管理,集中维护,改语句不用动 Java 代码。
  • 缺点:本质还是 HQL/SQL,不面向对象;语句散在配置里,调试时得两头翻。

对象化查询(Criteria)

这个就纯面向对象了,不用写任何查询语句字符串,全靠 API 拼条件。

static void query(String name) {
    Session s = null;
    try {
        s = HibernateUtil.getSession();
        Criteria c = s.createCriteria(User.class);
        // Restrictions 提供各种条件:eq 等于、gt 大于、lt 小于、or 或 ...
        c.add(Restrictions.eq("username", name));
        List<User> list = c.list();
    } finally {
        if (s != null) s.close();
    }
}
​
  • 优点:完全面向对象,条件用 API 堆,可读性好,特别适合"条件个数不固定"的动态查询。
  • 缺点:适用面不如 HQL 广,碰到复杂的连表、子查询会比较别扭。

提一句:Criteria 在 Hibernate 新版本(5.2 之后)已经被标记废弃,官方推荐用 JPA 的 CriteriaQuery 替代。所以新项目别再用这套老 Criteria 了,了解它的思路就行。

动态查询(DetachedCriteria)

它是 Criteria 的进阶版,把"拼条件"和"执行查询"拆开了。

条件可以在业务层先拼好,再丢给 DAO 层执行,DAO 层不用关心具体查什么字段。

// DAO 层:只负责执行,不关心条件细节
static List<User> query(DetachedCriteria dc) {
    Session s = null;
    try {
        s = HibernateUtil.getSession();
        // DetachedCriteria 绑定到当前 session 才能执行
        Criteria c = dc.getExecutableCriteria(s);
        return c.list();
    } finally {
        if (s != null) s.close();
    }
}

// 业务层:在这里组装条件
DetachedCriteria dc = DetachedCriteria.forClass(User.class);
dc.add(Restrictions.eq("username", "mebugs"));
List<User> list = query(dc);
​
  • 优点:面向对象,还把业务层和持久层解耦了,DAO 层不用为每种条件写一个方法。
  • 缺点:和 Criteria 一样,适用面有限,同样面临被废弃的问题。

例子查询(不推荐)

最后这个,拿一个"样例对象"去查:把对象里非空的属性当成查询条件。

static void query(User example) {
    Session s = null;
    try {
        s = HibernateUtil.getSession();
        // Example.create 把 example 对象里非空字段拼成查询条件
        List<User> list = s.createCriteria(User.class)
                           .add(Example.create(example))
                           .list();
    } finally {
        if (s != null) s.close();
    }
}
​

从代码复杂度和可控性来看,我觉得它不值得推荐,了解一下就够了。

空值怎么处理、模糊匹配怎么配,这些细节绕来绕去,还不如直接写 Criteria 或 HQL 来得清楚。

怎么选

六种看着多,实际日常就记三个主力:

  • 普通查询:用 HQL,最通用,面向对象又好读。
  • 条件不固定的动态查询:用 Criteria(老项目),新项目换成 JPA CriteriaQuery。
  • HQL 和 Criteria 都搞不定的复杂场景:退回 原生 SQL,记得参数绑定。

命名查询看团队习惯,想集中管理语句可以用;例子查询基本可以忽略。

小结

Hibernate 的六种查询,核心矛盾是"面向对象的方便"和"原生 SQL 的灵活"之间的权衡。

HQL 是中间最平衡的选择,日常首选;Criteria 系列胜在动态拼条件,但已经在被淘汰;原生 SQL 是复杂场景的兜底。

要提醒的是,这套 API 是 Hibernate 老版本的玩法,新项目大多走 JPA 规范(Spring Data JPA)了,Criteria、Example 这些能不碰就不碰。

但搞懂这六种的差异,对理解 ORM 查询"到底能怎么查、各自代价是什么"很有价值。知道有哪些牌,才知道该出哪张。

更多推荐

章节目录