tulip notes
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Star-Lord

希望一天成为大师的学徒
首页
  • 学习笔记

    • 《Vue》
  • 踩坑日记

    • JavaScript
  • MQ
  • Nginx
  • IdentityServer
  • Redis
  • Linux
  • Java
  • SpringBoot
  • SpringCloud
  • MySql
  • docker
  • 算法与设计模式
  • 踩坑与提升
  • Git
  • GitHub技巧
  • Mac
  • 网络
  • 项目构建合集
  • 一些技巧
  • 面试
  • 一些杂货
  • 友情链接
  • 项目发布
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 设计相关

  • 运行相关

  • 事务相关

  • 表设计实战系列

  • 分库分表

  • 框架使用

    • hibernate的hql查询
      • HQL查询
        • HQL 基本语法
        • 基本查询
        • 带条件的查询
        • 使用 `?` 的示例代码
        • 分页查询
        • 关联查询
        • 投影查询
        • 其他方式-1:使用 `Tuple`(JPA 标准)
        • 其他方式-2:使用动态实例化(简化 DTO 投影)
        • 返回单个字段(直接指定类型)
        • 查询单个结果【注】
        • 查询单个对象【注】
        • 聚合函数
        • 扩展-使用Map映射结果
        • 使用 `Tuple`(JPA 标准,类型安全)
        • 使用自定义 DTO 投影(面向对象封装)
      • SQL和HQL的区别
        • 面向对象 vs 面向关系
        • 安全性与参数绑定
        • 功能特性
      • 练习与扩展
        • 1. 按天统计入馆和出馆人数(通用场景)
        • 2. 查询指定某天的人流
        • 查询最近 10 天的人流
        • 4.按年龄和性别分组统计某天的人流
        • 5.根据
  • 常用sql跟关键字

  • 安装与卸载相关

  • 《MySql》笔记
  • 框架使用
EffectTang
2025-04-21
目录

hibernate的hql查询

# hibernate的hql查询

# HQL查询

Hibernate HQL(Hibernate Query Language)是 Hibernate 框架提供的一种面向对象的查询语言,其语法类似于 SQL,但操作的是实体类(Entity)和属性(Property)而不是数据库表和列。HQL 支持继承、多态和关联等面向对象特性,是 Hibernate 的核心功能之一。

# HQL 基本语法

  1. 面向对象:HQL 操作的是实体类和属性,例如 FROM User(User 是实体类名)。
  2. 支持 SQL 特性:包括 SELECT、FROM、WHERE、JOIN、GROUP BY、HAVING、ORDER BY 等子句。
  3. 参数绑定:
    1. 支持命名参数(:param)
    2. 和位置参数(?),避免 SQL 注入。
  4. 分页查询:通过 setFirstResult() 和 setMaxResults() 实现分页。

# 基本查询

场景:查询所有实体对象。 示例:查询所有用户。

String hql = "FROM User";
Query<User> query = session.createQuery(hql, User.class);
List<User> users = query.list();
1
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();
1
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();
1
2
3
4

如果多个参数,则是以下的情况:

String hql = "FROM User WHERE age > ? AND name LIKE ?";
query.setParameter(0, 18);      // 第一个 ? 对应索引 0
query.setParameter(1, "%John%"); // 第二个 ? 对应索引 1
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();
1
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();  // 用户及其订单会被一次性加载
1
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];
}
1
2
3
4
5
6
7
8

# 其他方式-1:使用 Tuple(JPA 标准)

适用场景:需要按字段别名访问结果,类型安全且代码可读性高。

步骤:

  1. 在查询中为字段指定别名(如 username as userName)。
  2. 使用 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);
}
1
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");
}
1
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();
1
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");
1
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 实体
1
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();
1
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);
1
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);
1
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; }
}
1
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());
1
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();
1
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();
1
2
3
4

# 安全性与参数绑定

  • HQL:通过命名参数或位置参数的方式进行参数绑定,这种方式有助于防止SQL注入攻击。

    Query<User> query = session.createQuery("FROM User WHERE id = :id", User.class);
    query.setParameter("id", userId);
    
    1
    2
  • SQL:同样支持参数绑定,但是需要特别注意避免手动拼接字符串导致的SQL注入风险。

# 功能特性

  • HQL:提供了丰富的功能,如继承映射的支持、多态查询等,可以直接利用Hibernate的映射关系来简化复杂的查询逻辑。

    // 多态查询示例
    String hql = "FROM Model";
    Query<Object> query = session.createQuery(hql);
    List<Object> models = query.list(); // 可能返回Model及其子类的实例
    
    1
    2
    3
    4
  • SQL:提供对数据库底层特性的直接访问,比如存储过程调用、特定数据库的功能(如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='人流记录接收信息';
1
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 + " 人");
}
1
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);
1
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();
1
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

注意:

GROUP BY 后可以跟多个列:按照指定顺序依次进行分组。

  • 分组逻辑:先按第一个列分组,再在每个分组中按第二个列分组,依此类推。
  • 实际意义:多列分组允许对数据进行更细粒度的分类和统计。

# 5.根据

上次更新: 2025/05/21, 15:29:11
Sharding-JDBC初步认识
MySql查找指定数据

← Sharding-JDBC初步认识 MySql查找指定数据→

最近更新
01
springboot中发送http
06-17
02
集成Redis及注意事项
06-08
03
Vue项目发布
06-04
更多文章>
Theme by Vdoing | Copyright © 2023-2025 EffectTang
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式