hibernate的hql查询
# hibernate的hql查询
# HQL查询
Hibernate HQL(Hibernate Query Language)是 Hibernate 框架提供的一种面向对象的查询语言,其语法类似于 SQL,但操作的是实体类(Entity)和属性(Property)而不是数据库表和列。HQL 支持继承、多态和关联等面向对象特性,是 Hibernate 的核心功能之一。
# HQL 基本语法
- 面向对象:HQL 操作的是实体类和属性,例如
FROM User
(User
是实体类名)。 - 支持 SQL 特性:包括
SELECT
、FROM
、WHERE
、JOIN
、GROUP BY
、HAVING
、ORDER BY
等子句。 - 参数绑定:
- 支持命名参数(
:param
) - 和位置参数(
?
),避免 SQL 注入。
- 支持命名参数(
- 分页查询:通过
setFirstResult()
和setMaxResults()
实现分页。
# 基本查询
场景:查询所有实体对象。 示例:查询所有用户。
String hql = "FROM User";
Query<User> query = session.createQuery(hql, User.class);
List<User> users = query.list();
2
3
# 带条件的查询
场景:根据条件筛选数据。 示例:查询年龄大于 18 的用户。
String hql = "FROM User WHERE age > :age";
Query<User> query = session.createQuery(hql, User.class);
query.setParameter("age", 18);
List<User> users = query.list();
2
3
4
# 使用 ?
的示例代码
场景:查询年龄大于 18 的用户,使用位置参数(?
)代替命名参数(:age
)。
String hql = "FROM User WHERE age > ?"; // HQL 中使用 ?
Query<User> query = session.createQuery(hql, User.class);
query.setParameter(0, 18); // 参数索引从 0 开始
List<User> users = query.list();
2
3
4
如果多个参数,则是以下的情况:
String hql = "FROM User WHERE age > ? AND name LIKE ?";
query.setParameter(0, 18); // 第一个 ? 对应索引 0
query.setParameter(1, "%John%"); // 第二个 ? 对应索引 1
2
3
# 分页查询
场景:分页显示数据。 示例:查询第 2 页(每页 10 条)的用户。
String hql = "FROM User ORDER BY id";
Query<User> query = session.createQuery(hql, User.class);
query.setFirstResult(10); // 从第 11 条开始
query.setMaxResults(10); // 每页 10 条
List<User> users = query.list();
2
3
4
5
# 关联查询
场景:查询关联对象(如一对多、多对一)。
示例:查询用户及其订单(使用 JOIN FETCH
避免 N+1 查询问题)。
String hql = "SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :userId";
Query<User> query = session.createQuery(hql, User.class);
query.setParameter("userId", 1L);
User user = query.getSingleResult(); // 用户及其订单会被一次性加载
2
3
4
# 投影查询
场景:仅查询部分字段。 示例:查询用户名和邮箱。
String hql = "SELECT username, email FROM User";
Query<Object[]> query = session.createQuery(hql, Object[].class);
List<Object[]> results = query.list();
// 遍历结果
for (Object[] row : results) {
String username = (String) row[0];
String email = (String) row[1];
}
2
3
4
5
6
7
8
# 其他方式-1:使用 Tuple
(JPA 标准)
适用场景:需要按字段别名访问结果,类型安全且代码可读性高。
步骤:
- 在查询中为字段指定别名(如
username as userName
)。 - 使用
Tuple
接口通过别名获取值。
String hql = "SELECT username as userName, email as userEmail FROM User";
Query<Tuple> query = session.createQuery(hql, Tuple.class);
List<Tuple> results = query.list();
for (Tuple tuple : results) {
String username = tuple.get("userName", String.class); // 通过别名和类型获取
String email = tuple.get("userEmail", String.class);
}
2
3
4
5
6
7
8
# 其他方式-2:使用动态实例化(简化 DTO 投影)
适用场景:无需显式定义 DTO 类,直接返回 Map
或 List<Object>
。
String hql = "SELECT new map(username as userName, email as userEmail) FROM User";
Query<Map<String, Object>> query = session.createQuery(hql, Map.class);
List<Map<String, Object>> results = query.list();
for (Map<String, Object> row : results) {
String username = (String) row.get("userName");
String email = (String) row.get("userEmail");
}
2
3
4
5
6
7
8
# 返回单个字段(直接指定类型)
如果查询仅返回单个字段,可以直接指定返回类型
String hql = "SELECT username FROM User";
Query<String> query = session.createQuery(hql, String.class);
List<String> usernames = query.list();
2
3
# 查询单个结果【注】
场景:查询返回单行多列数据(例如通过唯一条件查询),使用 new map
映射到 Map
。
示例:根据用户 ID 查询用户名和邮箱。
String hql = "SELECT new map(username as userName, email as userEmail) FROM User WHERE id = :id";
Query<Map<String, Object>> query = session.createQuery(hql, Map.class);
query.setParameter("id", 1L);
// 直接获取单个 Map(确保查询结果唯一)
Map<String, Object> result = query.getSingleResult();
String username = (String) result.get("userName");
String email = (String) result.get("userEmail");
2
3
4
5
6
7
8
# 查询单个对象【注】
场景:根据唯一条件(如主键)查询完整实体对象。 示例:通过用户 ID 查询用户实体。
String hql = "FROM User WHERE id = :id";
Query<User> query = session.createQuery(hql, User.class);
query.setParameter("id", 1L);
User user = query.getSingleResult(); // 直接返回 User 实体
2
3
4
5
使用
getSingleResult()
时需注意:
- 无结果:抛出
NoResultException
。- 多个结果:抛出
NonUniqueResultException
。
# 聚合函数
场景:统计、求和、平均值等。 示例:统计用户数量。
String hql = "SELECT COUNT(*) FROM User";
Query<Long> query = session.createQuery(hql, Long.class);
Long count = query.getSingleResult();
2
3
当 HQL 查询中同时包含多个聚合函数(如 COUNT
、SUM
、AVG
等)时,可以通过以下两种方式处理返回结果:
# 扩展-使用Map<String, Object>映射结果
这是最简洁的方式,适合需要快速获取多个聚合值的场景。通过为每个聚合字段指定别名,可以直接通过别名访问结果。
String hql = "SELECT new map(" +
"COUNT(*) as userCount, " +
"AVG(age) as avgAge, " +
"MAX(age) as maxAge) " +
"FROM User";
Query<Map<String, Object>> query = session.createQuery(hql, Map.class);
Map<String, Object> result = query.getSingleResult(); // 确保结果唯一
// 从 Map 中按别名提取值(注意类型转换)
Long userCount = (Long) result.get("userCount");
Double avgAge = (Double) result.get("avgAge");
Integer maxAge = (Integer) result.get("maxAge"); // 假设 age 是 Integer 类型
System.out.println("用户总数: " + userCount);
System.out.println("平均年龄: " + avgAge);
System.out.println("最大年龄: " + maxAge);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
注意:
别名:COUNT(*) as userCount
中的 userCount
是别名,对应 Map
的键。
类型转换:聚合函数返回值类型需与 Java 类型匹配:
COUNT(*)
→Long
AVG(age)
→Double
(即使age
是Integer
)MAX(age)
→Integer
(与age
字段类型一致)
# 使用 Tuple
(JPA 标准,类型安全)
如果需要更严格的类型检查和 IDE 支持,可以使用 JPA 标准的 Tuple
接口。
String hql = "SELECT " +
"COUNT(*) as userCount, " +
"AVG(age) as avgAge, " +
"MAX(age) as maxAge " +
"FROM User";
Query<Tuple> query = session.createQuery(hql, Tuple.class);
Tuple tuple = query.getSingleResult();
Long userCount = tuple.get("userCount", Long.class);
Double avgAge = tuple.get("avgAge", Double.class);
Integer maxAge = tuple.get("maxAge", Integer.class);
System.out.println("用户总数: " + userCount);
System.out.println("平均年龄: " + avgAge);
System.out.println("最大年龄: " + maxAge);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意:
该用法,需 Hibernate 5.4+
# 使用自定义 DTO 投影(面向对象封装)
如果查询结果需要复用或参与业务逻辑,可定义一个 DTO 类来封装结果。
public class UserStatsDTO {
private Long userCount;
private Double avgAge;
private Integer maxAge;
// 必须包含与 HQL 字段顺序和类型匹配的构造函数
public UserStatsDTO(Long userCount, Double avgAge, Integer maxAge) {
this.userCount = userCount;
this.avgAge = avgAge;
this.maxAge = maxAge;
}
// Getter 方法
public Long getUserCount() { return userCount; }
public Double getAvgAge() { return avgAge; }
public Integer getMaxAge() { return maxAge; }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
查询
String hql = "SELECT NEW com.example.UserStatsDTO(" +
"COUNT(*), AVG(age), MAX(age)) " +
"FROM User";
Query<UserStatsDTO> query = session.createQuery(hql, UserStatsDTO.class);
UserStatsDTO stats = query.getSingleResult();
System.out.println("用户总数: " + stats.getUserCount());
System.out.println("平均年龄: " + stats.getAvgAge());
System.out.println("最大年龄: " + stats.getMaxAge());
2
3
4
5
6
7
8
9
# SQL和HQL的区别
# 面向对象 vs 面向关系
HQL:是面向对象的查询语言,操作的是实体类和属性而不是表和字段。这意味着你可以直接使用你的 Java 类名(实体类)和属性名来构建查询,而不需要考虑底层数据库的表结构。
String hql = "FROM User WHERE status = :status";
Query<User> query = session.createQuery(hql, User.class);
query.setParameter("status", "active");
List<User> users = query.list();
2
3
4
SQL:是面向关系型数据库的语言,操作的是数据库中的表和列。你需要明确地指定表名和列名。
String sql = "SELECT * FROM users WHERE status = ?";
SQLQuery<?> query = session.createSQLQuery(sql).addEntity(User.class);
query.setString(0, "active");
List<User> users = query.list();
2
3
4
# 安全性与参数绑定
HQL:通过命名参数或位置参数的方式进行参数绑定,这种方式有助于防止SQL注入攻击。
Query<User> query = session.createQuery("FROM User WHERE id = :id", User.class); query.setParameter("id", userId);
1
2SQL:同样支持参数绑定,但是需要特别注意避免手动拼接字符串导致的SQL注入风险。
# 功能特性
HQL:提供了丰富的功能,如继承映射的支持、多态查询等,可以直接利用Hibernate的映射关系来简化复杂的查询逻辑。
// 多态查询示例 String hql = "FROM Model"; Query<Object> query = session.createQuery(hql); List<Object> models = query.list(); // 可能返回Model及其子类的实例
1
2
3
4SQL:提供对数据库底层特性的直接访问,比如存储过程调用、特定数据库的功能(如MySQL的
LIMIT
子句),以及更细粒度的数据控制能力。
# 练习与扩展
实现按照天的维度来查询每天的入馆人数和出馆人数,RY_NUM值为-1为出馆,为1则为入馆,该怎么实现,对应sql表为,
CREATE TABLE `rl_record_info` (
`AUTO_ID` bigint(18) NOT NULL AUTO_INCREMENT COMMENT '主键',
`CHN_ID` int(5) DEFAULT NULL COMMENT '通道ID',
`CHN_NAME` varchar(40) DEFAULT NULL COMMENT '通道名称',
`RULE_TYPE` int(5) DEFAULT NULL COMMENT '规则类型',
`EVENT_TYPE` varchar(40) DEFAULT NULL COMMENT '事件类型',
`GENDER` varchar(20) DEFAULT NULL COMMENT '性别',
`AGE` int(5) DEFAULT NULL COMMENT '年龄',
`EVENT_TIME` varchar(19) DEFAULT NULL COMMENT '事件触发时间',
`RY_NUM` int(2) DEFAULT NULL COMMENT '计数',
PRIMARY KEY (`AUTO_ID`) USING BTREE,
KEY `EVENT_TIME` (`EVENT_TIME`) USING BTREE )
ENGINE=InnoDB AUTO_INCREMENT=22796 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='人流记录接收信息';
2
3
4
5
6
7
8
9
10
11
12
13
该怎么实现,如果要查询指定天的人流又该如何实现,查询最近10天的人流该如何实现,查询某天的人流情况,并根据年龄和性别进行分组,HQL如何实现
# 1. 按天统计入馆和出馆人数(通用场景)
目标:按天分组,统计每天的入馆人数(RY_NUM=1
)和出馆人数(RY_NUM=-1
)。
String hql = "SELECT " +
"SUBSTRING(r.eventTime, 1, 10) as date, " + // 提取日期部分(假设 eventTime 格式为 'yyyy-MM-dd HH:mm:ss')
"SUM(CASE WHEN r.ryNum = 1 THEN 1 ELSE 0 END) as enterCount, " +
"SUM(CASE WHEN r.ryNum = -1 THEN 1 ELSE 0 END) as exitCount " +
"FROM RlRecordInfo r " +
"GROUP BY SUBSTRING(r.eventTime, 1, 10) " +
"ORDER BY date DESC";
Query<Object[]> query = session.createQuery(hql, Object[].class);
List<Object[]> results = query.list();
// 遍历结果
for (Object[] row : results) {
String date = (String) row[0];
Long enterCount = (Long) row[1];
Long exitCount = (Long) row[2];
System.out.println(date + ": 入馆 " + enterCount + " 人,出馆 " + exitCount + " 人");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2. 查询指定某天的人流
目标:查询某一天(如 2023-10-01
)的入馆和出馆人数。
String hql = "SELECT " +
"SUM(CASE WHEN r.ryNum = 1 THEN 1 ELSE 0 END) as enterCount, " +
"SUM(CASE WHEN r.ryNum = -1 THEN 1 ELSE 0 END) as exitCount " +
"FROM RlRecordInfo r " +
"WHERE SUBSTRING(r.eventTime, 1, 10) = :date";
Query<Object[]> query = session.createQuery(hql, Object[].class);
query.setParameter("date", "2023-10-01"); // 传入日期参数
Object[] result = query.getSingleResult();
Long enterCount = (Long) result[0];
Long exitCount = (Long) result[1];
System.out.println("入馆人数: " + enterCount + ", 出馆人数: " + exitCount);
2
3
4
5
6
7
8
9
10
11
12
13
# 查询最近 10 天的人流
目标:统计最近 10 天的每日人流(假设 eventTime
是日期字符串)。
String hql = "SELECT " +
"SUBSTRING(r.eventTime, 1, 10) as date, " +
"SUM(CASE WHEN r.ryNum = 1 THEN 1 ELSE 0 END) as enterCount, " +
"SUM(CASE WHEN r.ryNum = -1 THEN 1 ELSE 0 END) as exitCount " +
"FROM RlRecordInfo r " +
"WHERE SUBSTRING(r.eventTime, 1, 10) >= :startDate " + // 假设 startDate 是 10 天前的日期
"GROUP BY date " +
"ORDER BY date DESC";
// 计算日期范围(示例代码)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -9); // 包含今天共 10 天
String startDate = sdf.format(calendar.getTime());
Query<Object[]> query = session.createQuery(hql, Object[].class);
query.setParameter("startDate", startDate);
List<Object[]> results = query.list();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SUBSTRING(str, pos, len)
:这是一个用于提取字符串部分的函数。str
是要操作的字符串表达式,在这里是r.eventTime
。pos
是开始提取字符的位置,MySQL中的位置是从1开始计数的。在你的例子中,开始位置是1,意味着从字符串的第一个字符开始提取。len
是你想要提取的字符长度。在你的例子中,长度是10,即从第一个字符开始提取接下来的10个字符。
r.eventTime
:这通常指的是某个表(或别名为r
的表)中的一个字段,该字段存储了事件发生的时间信息。时间信息可能以“YYYY-MM-DD HH:MM:SS”的格式存储,或者其他包含日期和时间的格式。
因此,SUBSTRING(r.eventTime, 1, 10)
的作用是从 eventTime
字段的值中提取前10个字符。如果 eventTime
的格式为“YYYY-MM-DD HH:MM:SS”,那么使用 SUBSTRING(r.eventTime, 1, 10)
将会提取出日期部分“YYYY-MM-DD”。
# 4.按年龄和性别分组统计某天的人流
目标:查询某一天的人流,并按年龄(age
)和性别(gender
)分组。
String hql = "SELECT " +
"r.age, r.gender, " +
"SUM(CASE WHEN r.ryNum = 1 THEN 1 ELSE 0 END) as enterCount, " +
"SUM(CASE WHEN r.ryNum = -1 THEN 1 ELSE 0 END) as exitCount " +
"FROM RlRecordInfo r " +
"WHERE SUBSTRING(r.eventTime, 1, 10) = :date " +
"GROUP BY r.age, r.gender " +
"ORDER BY r.age, r.gender";
Query<Object[]> query = session.createQuery(hql, Object[].class);
query.setParameter("date", "2023-10-01");
List<Object[]> results = query.list();
for (Object[] row : results) {
Integer age = (Integer) row[0];
String gender = (String) row[1];
Long enterCount = (Long) row[2];
Long exitCount = (Long) row[3];
System.out.println("年龄: " + age + ", 性别: " + gender +
", 入馆: " + enterCount + ", 出馆: " + exitCount);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注意:
GROUP BY
后可以跟多个列:按照指定顺序依次进行分组。
- 分组逻辑:先按第一个列分组,再在每个分组中按第二个列分组,依此类推。
- 实际意义:多列分组允许对数据进行更细粒度的分类和统计。