九月 11, 2024
摘要:在本教程中,我们将讨论如何在 PostgreSQL 中使用 Hibernate 查询语言(HQL)。
目录
介绍
Hibernate 是一个可以使用在 PostgreSQL 上的 ORM 框架。在这里,我们将讨论它的查询语言 HQL。
HQL 的语法与 SQL 的语法非常接近,因此任何了解 SQL 的人都应该能够很快上手。主要区别在于,HQL 处理的是对象及其属性,而不是处理表和列。从本质上讲,它是一种完整的面向对象语言,用于使用 Java 对象及其属性来查询数据库。与 SQL 不同的是,HQL 能够理解对象的继承、多态性和关联。用 HQL 编写的代码,在运行时会由 Hibernate 转换为 SQL,并在 PostgreSQL 数据库上面执行。
这里需要注意的重要一点是,在 HQL 中对对象及其属性的引用是区分大小写的;所有其他结构均不区分大小写。
为什么使用 HQL?
使用 HQL 的主要动力是数据库可移植性。由于它的实现设计为与数据库无关,因此,如果您的应用程序使用 HQL 查询数据库,则可以通过对 XML 配置文件进行简单的更改,来更换底层数据库。与原生 SQL 不一样,如果您的应用程序开始与不同的数据库通信,实际代码将基本保持不变。
亮点特性
HQL 实现的完整特性列表可以在他们的网站上找到。在这里,我们提供了一些基本和亮点特性的示例,这些特性可帮助您开始使用 HQL。这些示例使用一个名为 ’largecities’ 的表,其中列出了世界上最大的 10 个大都市。表的描述信息和数据如下:
postgres=# \d largecities
Table "public.largecities"
Column | Type | Modifiers
--------+------------------------+-----------
rank | integer | not null
name | character varying(255) |
Indexes:
"largecities_pkey" PRIMARY KEY, btree (rank)
select * from largecities;
rank | name
------+-------------
1 | Tokyo
2 | Seoul
3 | Shanghai
4 | Guangzhou
5 | Karachi
6 | Delhi
7 | Mexico City
8 | Beijing
9 | Lagos
10 | Sao Paulo
(10 rows)
HQL 作用在此表映射到的类上面,以便使用其数据在内存中创建对象。该类定义为:
@Entity
public class LargeCities {
@Id
private int rank;
private String name;
public int getRank() {
return rank;
}
public String getName() {
return name;
}
public void setRank(int rank) {
this.rank = rank;
}
public void setName(String name) {
this.name = name;
}
}
请注意 @Entity 和 @Id 注释,它们将类 ‘LargeCities’ 声明为实体,将属性 ‘rank’ 声明为标识符。
FROM 子句
如果要将表的所有行作为内存中的对象加载,可使用 FROM 子句。下面给出的示例代码,从表 ’largecities’ 中检索所有行,并列出对象的数据到 stdout。
try {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Query query = session.createQuery("FROM LargeCities");
List<LargeCities> cities = (List<LargeCities>)query.list();
session.close();
for (LargeCities c : cities)
System.out.println(c.getRank() + " " + c.getName());
} catch (Exception e) {
System.out.println(e.getMessage());
}
请注意,HQL 查询中引用的 ‘LargeCities’ 不是 ’largecities’ 表,而是 ‘LargeCities’ 类。这就是 HQL 面向对象的本质。
上面程序的输出如下:
1 Tokyo
2 Seoul
3 Shanghai
4 Guangzhou
5 Karachi
6 Delhi
7 Mexico City
8 Beijing
9 Lagos
10 Sao Paulo
WHERE 子句
在某些情况下,您可能希望为要查看的对象指定筛选条件。以上面的例子为例,您可能只想查看世界上排名前 5 的大都市。WHERE 子句可以帮助您实现此目的,如下所示:
try {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Query query = session.createQuery("FROM LargeCities WHERE rank < 6");
List<LargeCities> cities = (List<LargeCities>)query.list();
session.close();
for (LargeCities c : cities)
System.out.println(c.getRank() + " " + c.getName());
} catch (Exception e) {
System.out.println(e.getMessage());
}
上述代码的输出为:
1 Tokyo
2 Seoul
3 Shanghai
4 Guangzhou
5 Karachi
SELECT 子句
默认的 FROM 子句,检索表中的所有列,以作为 Java 中对象的属性。在某些情况下,您只想检索选定的属性,而不是所有属性。在这种情况下,您可以指定一个 SELECT 子句来标识要检索的特定列。
下面的代码仅选定了城市名称进行检索。注意,因为它现在只检索了一列,所以 Hibernate 会把它加载为一个 String 列表,而不是一个 LargeCities 对象的列表。
try {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Query query = session.createQuery("SELECT name FROM LargeCities");
List<String> cities = (List<String>)query.list();
session.close();
for (String c : cities)
System.out.println(c);
} catch (Exception e) {
System.out.println(e.getMessage());
}
该代码的输出为:
Tokyo
Seoul
Shanghai
Guangzhou
Karachi
Delhi
Mexico City
Beijing
Lagos
Sao Paulo
命名参数
与预备语句非常相似,您可以拥有命名参数,通过这些参数,您可以在运行时使用变量为 HQL 查询分配值。下面的示例使用了命名参数,来查找 ‘Beijing’ 的排名。
try {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Query query = session.createQuery("SELECT rank FROM LargeCities WHERE name = :city_name");
query.setParameter("city_name", "Beijing");
List<Integer> rank = (List<Integer>)query.list();
session.getTransaction().commit();
session.close();
for (Integer c : rank)
System.out.println("Rank is: " + c.toString());
} catch (Exception e) {
System.out.println(e.getMessage());
}
此代码的输出:
Rank is: 8
分页
编程时,会出现许多需要按块或页的形式处理数据的场景。这个过程称为数据分页,HQL 提供了一种机制来处理这个问题,这需要结合使用 Query 接口的 setFirstResult 和 setMaxResults 方法。顾名思义,setFirstResult 允许您指定哪条记录应作为记录检索的起点,而 setMaxResults 允许您指定要检索的最大记录数。这种组合在 Java 或 Web 应用程序中非常有用,在这些应用程序中,大型结果集被拆分为多个页面,并且用户能够指定页面大小。
下面代码将我们的 ’largecities’ 示例分成 2 个页面,并按页检索数据。
try {
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Query query = session.createQuery("FROM LargeCities");
query.setFirstResult(0);
query.setMaxResults(5);
List<LargeCities> cities = (List<LargeCities>)query.list();
System.out.println("*** Page 1 ***");
for (LargeCities c : cities)
System.out.println(c.getRank() + " " + c.getName());
query.setFirstResult(5);
cities = (List<LargeCities>)query.list();
System.out.println("*** Page 2 ***");
for (LargeCities c : cities)
System.out.println(c.getRank() + " " + c.getName());
session.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
这里要记住的一个重点是,Hibernate 通常是在内存中而不是在数据库查询级别进行分页。这意味着对于大型数据集,使用游标、临时表或其他一些构造进行分页可能会更高效。
其他特性
Hibernate 网站上提供了完整的特性列表,在这里值得一提的有:
- UPDATE 子句
- DELETE 子句
- INSERT 子句
- JOIN
聚合方法
- avg
- count
- max
- min
- sum
使用 HQL 的缺点
HQL 为其用户提供了很大的灵活性和丰富的功能集,供用户在与数据库通信时使用。然而,灵活性确实是有代价的。由于 HQL 设计为通用的,并且在很大程度上与数据库无关,因此在使用 HQL 时应注意以下事项。
- 有时,您需要使用 PostgreSQL 特定的高级特性和功能。例如,您可能希望利用新引入的 JSONB 数据类型的强大功能。或者,您可能希望使用窗口函数来分析数据。因为 HQL 试图尽可能地通用,所以为了使用这些高级功能,你需要退回到原生 SQL。
- 由于左连接的设计方式,如果您以一对多或多对多的形式,将一个对象连接到另一个表/对象,则可能会获得重复数据。在有级联的左连接情况下,这个问题会更加严重,HQL 必须保留对这些重复项的引用,最终基本上会传输大量的重复数据。这可能会显著影响性能。
- 因为 HQL 自身会做对象关系映射,所以你无法完全控制如何获取数据以及获取哪些数据。有这样一个臭名昭著的问题,那就是 N+1 问题。尽管您可以在 HQL 中找到解决方法,但有时候问题定位会变得非常棘手。