一、Hibernate 提供了以下几种检索对象的方式:
- 导航对象图检索方式: 根据已经加载的对象导航到其他对象 OID 检索方式: 按照对象的 OID 来检索对象
- HQL 检索方式:使用面向对象的 HQL 查询语言
- QBC 检索方式: 使用 QBC(Query By Criteria) API 来检索对象. 这种
API 封装了基于字符串形式的查询语句, 提供了更加面向对象的查询接口. - 本地 SQL 检索方式: 使用本地数据库的 SQL 查询语句
二、HIbernate的HQL查询
1. HQL(Hibernate Query Language) 是面向对象的查询语言, 它和 SQL 查询语言有些相似. 在 Hibernate 提供的各种检索方式中, HQL 是使用最广的一种检索方式. 它有如下功能:
在查询语句中设定各种查询条件
- 支持投影查询, 即仅检索出对象的部分属性
- 支持分页查询
- 支持连接查询
- 支持分组查询, 允许使用 HAVING 和 GROUP BY 关键字
- 提供内置聚集函数, 如 sum(), min() 和 max()
- 支持子查询
- 支持动态绑定参数
- 能够调用 用户定义的 SQL 函数或标准的 SQL 函数
2. HQL 检索方式包括以下步骤:
a、通过 Session 的 createQuery() 方法创建一个 Query 对象, 它包括一个 HQL 查询语句. HQL 查询语句中可以包含命名参数
b、动态绑定参数
c、调用 Query 相关方法执行查询语句.
三、各种查询示例代码:
1、首先搭建测试环境:
两个测试实体类:
Employee类:
public class Employee {
private Integer id;
private String name;
private float salary;
private String email;
private Department dept;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
public Employee(float salary, String email, Department dept) {
super();
this.salary = salary;
this.email = email;
this.dept = dept;
}
public Employee() {
}
@Override
public String toString() {
return "Employee [id=" + id + "]";
}
}
Department类:
public class Department {
private Integer id;
private String name;
private Set<Employee> emps=new HashSet<Employee>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Employee> getEmps() {
return emps;
}
public void setEmps(Set<Employee> emps) {
this.emps = emps;
}
@Override
public String toString() {
return "Department [id=" + id + ", name=" + name + "]";
}
}
2个实体类对应的hbm配置文件:
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2015-10-31 23:07:48 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="com.elgin.hibernate.entity">
<class name="Employee" table="EMPLOYEE">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="salary" type="float">
<column name="SALARY" />
</property>
<property name="email" type="java.lang.String">
<column name="EMAIL" />
</property>
<many-to-one name="dept" class="Department" >
<column name="DEPT_ID" />
</many-to-one>
</class>
<query name="salaryEmp">
<![CDATA[
from Employee e where e.salary> :minSal and e.salary < :maxSal
]]>
</query>
</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2015-10-31 23:07:48 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="com.elgin.hibernate.entity">
<class name="Department" table="DEPARTMENT">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<set name="emps" table="EMPLOYEE" inverse="true" lazy="true">
<key>
<column name="DEPT_ID" />
</key>
<one-to-many class="Employee" />
</set>
</class>
</hibernate-mapping>
Hibernate配置文件:hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- hibernate数据库连接信息配置 -->
<property name="connection.username">root</property>
<property name="connection.password">root123</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- hibernate基本配置 -->
<!-- hibernate的数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hbm2ddl.auto">update</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<!--设置hibernate事务的隔离级别 -->
<property name="connection.isolation">2</property>
<!-- 需要关联的hibernate映射文件 hbm.xml文件 -->
<mapping resource="com/elgin/hibernate/entity/Department.hbm.xml"/>
<mapping resource="com/elgin/hibernate/entity/Employee.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Hibernate查询单元测试类:
public class HibernateTest2 {
//如此声明只为方便测试,生产环境不能这么用
private SessionFactory sessionFactory;
private Session session;
private Transaction transcation;
@Before
public void init(){
Configuration cfg=new Configuration().configure();
ServiceRegistry serviceRegistry=new ServiceRegistryBuilder().applySettings(cfg.getProperties()).buildServiceRegistry();
sessionFactory=cfg.buildSessionFactory(serviceRegistry);
session=sessionFactory.openSession();
transcation=session.beginTransaction();
}
public void insert(int i){
Employee employee=new Employee();
employee.setName("name"+i);
employee.setEmail("name"+i+"@qq.com");
employee.setSalary(1000*i);
session.save(employee);
}
@Test
//初始化2个表中的数据,方便查询
public void test(){
for (int i = 14; i < 21; i++) {
insert(i);
}
}
@After
public void destory(){
transcation.commit();
session.close();
sessionFactory.close();
}
}
上述类为基础测试类,如下的测试代码均需加入到上述类中运行。至此,测试环境搭建完成,下面逐一进行测试:
2 绑定参数:
- Hibernate 的参数绑定机制依赖于 JDBC API 中的 PreparedStatement 的预定义 SQL 语句功能.
- HQL 的参数绑定由两种形式:
按参数名字绑定: 在 HQL 查询语句中定义命名参数, 命名参数以 “:” 开头.
按参数位置绑定: 在 HQL 查询语句中用 “?”来定义参数位置
- 相关方法:
setEntity(): 把参数与一个持久化类绑定。
setParameter(): 绑定任意类型的参数. 该方法的第三个参数显式指定 Hibernate 映射类型。
测试代码:
@Test
public void testHQLNamedParameters(){
//1.创建 Query 对象
// 基于命名参数
String HQL="FROM Employee e WHERE e.salary> :salary AND e.email LIKE :email";
Query query=session.createQuery(HQL);
//2. 动态绑定参数
query.setFloat("salary", 6000).setString("email", "%a%");
//3. 执行查询
List<Employee> emps = query.list();
System.out.println(emps.size());
}
@Test
public void testHQL(){
//1.创建 Query 对象
// 基于位置的参数
String HQL="FROM Employee e WHERE e.salary> ? AND e.email LIKE ?";
Query query=session.createQuery(HQL);
//2. 动态绑定参数
// Query对象调用setXxx方法,支持方法链的编程
query.setFloat(0, 6000).setString(1, "%a%");
//3. 执行查询
List<Employee> emps = query.list();
System.out.println(emps.size());
}
3 分页查询:
- setFirstResult(int firstResult): 设定从哪一个对象开始检索, 参数 firstResult表示这个对象在查询结果中的索引位置, 索引位置的起始值为 0. 默认情况下, Query 从查询结果中的第一个对象开始检索
- setMaxResults(int maxResults): 设定一次最多检索出的对象的数目. 在默认情况下, Query 和 Criteria 接口检索出查询结果中所有的对象
/**
* HQL分页查询
*
*/
@Test
public void testPageQuery(){
String HQL="from Employee";
int pageNo=2;
int pageSize=5;
List<Employee> emps= session.createQuery(HQL)
.setFirstResult((pageNo-1)*pageSize)
.setMaxResults(pageSize)
.list();
System.out.println(emps);
}
4.在映射文件中定义命名查询语句
Hibernate 允许在映射文件中定义字符串形式的查询语句.
元素用于定义一个 HQL 查询语句, 它和 元素并列.
在程序中通过 Session 的 getNamedQuery() 方法获取查询语句对应的 Query 对象.
本例在Employee.hbm.xml映射文件中定义了如下:
<query name="salaryEmp">
<![CDATA[
from Employee e where e.salary> :minSal and e.salary < :maxSal
]]>
</query>
之后就可以使用如下代码来使用次查询语句:
/**
* HQL命名查询(HQL语句配置在hbm文件中的query标签中,使用CDATA包裹)
*
*/
@Test
public void testNamedQuery(){
Query query=session.getNamedQuery("salaryEmp");
List<Employee> emps=query.setFloat("minSal", 2000)
.setFloat("maxSal", 5000)
.list();
System.out.println(emps);
}
5.投影查询:
- 投影查询: 查询结果仅包含实体的部分属性. 通过 SELECT 关键字实现.
- Query 的 list() 方法返回的集合中包含的是数组类型的元素, 每个对象数组代表查询结果的一条记录
- 可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录, 使程序代码能完全运用面向对象的语义来访问查询结果集.
- 可以通过 DISTINCT 关键字来保证查询结果不会返回重复元素
测试代码:
/**
* HQL投影查询,结果类型为List<Object[]>
*
*/
@Test
public void testFieldQuery(){
String hql="select e.email,e.salary,e.dept from Employee e where e.dept=:dept";
Department dept=new Department();
dept.setId(6);
Query query=session.createQuery(hql).setEntity("dept", dept);
List<Object[]> emps=query.list();
for (Object[] employee : emps) {
System.out.println(Arrays.asList(employee));
}
}
若想要把投影查询的结果映射到对象上,则需要在Employee对象加入相应的构造方法,映射对象示例:
/**
* HQL投影查询,结果类型为List<Employee>
* 1.HQL语句中使用new关键字将结果映射到对象上,
* 前提是Employee对象中有对应的构造方法
*/
@Test
public void testFieldQuery2(){
String hql="select new Employee(e.salary,e.email,e.dept) from Employee e where e.dept=:dept";
Department dept=new Department();
dept.setId(6);
Query query=session.createQuery(hql).setEntity("dept", dept);
List<Employee> emps=query.list();
for (Employee employee : emps) {
System.out.println(employee.getEmail());
System.out.println(employee.getSalary());
System.out.println(employee.getDept());
}
}
6.报表查询
报表查询用于对数据分组和统计, 与 SQL 一样, HQL 利用 GROUP BY 关键字对数据分组, 用 HAVING 关键字对分组数据设定约束条件.
在 HQL 查询语句中可以调用以下聚集函数:
count()
min()
max()
sum()
avg()
示例:
/**
* 报表查询,可以使用相关聚集函数
*
*/
@Test
public void testGroupBy(){
String hql="select min(e.salary),max(e.salary),e.dept from Employee e"
+ " group by e.dept"
+ " having min(e.salary) > :min";
Query query=session.createQuery(hql).setFloat("min", 3000);
List<Object[]> emps=query.list();
for (Object[] objects : emps) {
System.out.println(Arrays.asList(objects));
}
}
7.HQL迫切左外连接:
- LEFT JOIN FETCH 关键字表示迫切左外连接检索策略.
- list() 方法返回的集合中存放实体对象的引用, 每个 Department 对象关联的 Employee 集合都被初始化,存放所有关联的 Employee 的实体对象.
- 查询结果中可能会包含重复元素, 可以通过一个 HashSet 来过滤重复元素
/**
* HQL 迫切左外连接
*/
@Test
public void testLeftJoinFetch(){
String hql="select distinct d from Department d left join fetch d.emps";
Query query=session.createQuery(hql);
List<Department> depts=query.list();
for (Department dept : depts) {
System.out.println(dept.getName()+"-"+dept.getEmps().size());
}
}
7.HQL 左外连接:
- LEFT JOIN 关键字表示左外连接查询.
- list() 方法返回的集合中存放的是对象数组类型
- 根据配置文件来决定 Employee 集合的检索策略(是否延迟加载)
- 如果希望 list() 方法返回的集合中仅包含 Department 对象, 可以在HQL 查询语句中使用 SELECT 关键字
/**
* HQL 左外连接
*/
@Test
public void testLeftJoin(){
String hql="select distinct d from Department d left join d.emps";
Query query=session.createQuery(hql);
List<Department> depts=query.list();
for (Department dept : depts) {
System.out.println(dept.getName()+"-"+dept.getEmps().size());
}
}
综上迫切左外连接和左外连接:
- 如果在 HQL 中没有显式指定检索策略, 将使用映射文件配置的检索策略.
- HQL 会忽略映射文件中设置的迫切左外连接检索策略, 如果希望 HQL 采用迫切左外连接策略, 就必须在 HQL 查询语句中显式的指定它
- 若在 HQL 代码中显式指定了检索策略, 就会覆盖映射文件中配置的检索策略