Hibernate学习笔记(五)—— Hibernate查询方式
一、对象图导航查询
对象图导航查询方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来查询对象。比如要查找一个联系人对应的客户,就可以由联系人对象自动导航找到联系人所属的客户对象。当然,前提是必须在对象关系映射文件上配置了多对一的关系。其检索方式如下所示:
LinkMan linkMan = session.get(LinkMan.class, 1l); Customer customer = linkMan.getCustomer();
二、OID检索方式
OID检索方式主要指用Session的get()和load()方法加载某条记录对应的对象。如下面两种加载客户对象的方式,就是OID检索方式,具体如下:
Customer customer = session.get(Customer.class, 1l); Customer customer1 = session.load(Customer.class, 1l);
三、HQL检索
HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似,但它使用的是类、对象和属性的概念,而没有表和字段的概念。在Hibernate提供的各种检索方式中,HQL是官方推荐的查询语言,也是使用最广泛的一种检索方式,它具有如下功能。
- 在查询语句中设定各种查询条件;
- 支持投影查询,即仅检索出对象的部分属性;
- 支持分页查询;
- 支持分组查询,允许使用group by 和 having关键字;
- 提供内置聚集函数,如sum()、min()和max();
- 能够调用用户定义的SQL函数;
- 支持子查询,即嵌套查询;
- 支持动态绑定参数;
Hibernate提供的Query接口是专门的HQL查询接口,它能够执行各种复杂的HQL查询语句,完整的HQL语句结构如下:
select...from...where...group by...having...order by ... asc/desc
可以HQL查询非常类似于表中SQL查询。通常情况下,当检索数据表中的所有记录时,查询语句中可以省略select关键字,示例如下所示:
String hql = "from Customer";
如果执行该查询语句,则会返回应用程序中的所有Customer对象。需要注意的是:Customer是类名,而不是表名,类名需要区分大小写,而关键字from不区分大小写。
3.1 基本查询
/** * HQL:基本查询 */ @Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 基本查询 // Query query = session.createQuery("from Customer"); // 起别名 // Query query = session.createQuery("from Customer c"); Query query = session.createQuery("select c from Customer c"); List<Customer> list = query.list(); // query.uniqueResult(); //接收唯一的查询结果 for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
3.2 排序查询
/** * HQL:排序查询 */ @Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 排序查询 Query query = session.createQuery("from Customer order by cust_id desc"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
3.3 条件查询
/** * HQL:条件查询 */ @Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 按位置绑定参数——问号占位符 // Query query = session.createQuery("from Customer where cust_id = ? "); // 设置参数 // query.setLong(0, 1l); // query.setParameter(0, 1l); // 根据查询对象获得查询结果 // Customer customer = (Customer) query.uniqueResult(); // System.out.println(customer); // 按名称绑定参数 Query query = session.createQuery("from Customer where cust_id = :cust_id"); // 设置参数 query.setParameter("cust_id", 1l); // 根据查询对象获得查询结果 Customer customer = (Customer) query.uniqueResult(); System.out.println(customer); tx.commit(); session.close(); }
3.4 分页查询
/** * HQL:分页查询 */ @Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer"); // 设置分页信息 limit ?,? query.setFirstResult(1); query.setMaxResults(1); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
3.5 统计查询
/** * HQL:统计查询 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query1 = session.createQuery("select count(*) from Customer"); Query query2 = session.createQuery("select sum(cust_id) from Customer"); Query query3 = session.createQuery("select avg(cust_id) from Customer"); Query query4 = session.createQuery("select max(cust_id) from Customer"); Query query5 = session.createQuery("select min(cust_id) from Customer"); Number number = (Number) query1.uniqueResult(); System.out.println(number); tx.commit(); session.close(); }
3.6 投影查询
/** * 投影查询 */ @Test public void demo6() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 投影查询一列 /*Query query = session.createQuery("select cust_name from Customer"); List<String> list = query.list(); for (String string : list) { System.out.println(string); }*/ // 投影查询多列 /*Query query = session.createQuery("select cust_name,cust_id from Customer"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(Arrays.toString(objects)); }*/ // 投影的构造方式查询——Customer实体需要生成相应的带参构造 Query query = session.createQuery("select new Customer(cust_id,cust_name) from Customer"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
四、QBC查询
QBC(Query By Criteria)是Hibernate提供的另一种检索对象的方式,它主要有Criteria接口、Criterion接口和Expression类组成。Criteria接口是HibernateAPI中的一个查询接口,它需要由session进行创建。
Criterion是Criteria的查询条件,在Criteria中提供了add(Criterion criterion)方法添加查询条件。使用QBC检索对象的示例代码,如下所示:
// 创建criteria对象 Criteria criteria = session.createCriteria(Customer.class); // 设定查询条件 criteria.add(Restrictions.eq("id", 1l)); // 执行查询,返回查询结果 List<Customer> list = criteria.list();
上述代码中查询的是id为1的Customer对象。
QBC检索是使用Restrictions对象编写查询条件的,在Restrictions类中提供了大量的静态方法来创建查询条件。其常用的方法如表所示:
方法名 | 说明 |
Restrictions.eq | 等于 |
Restrictions.allEq | 使用Map,使用key/value进行多个等于的比较 |
Restrictions.gt | 大于> |
Restrictions.ge | 大于等于>= |
Restrictions.lt | 小于 |
Restrictions.le | 小于等于<= |
Restrictions.between | 对应SQL的between子句 |
Restrictions.like | 对应SQL的like子句 |
Restrictions.in | 对应SQL的IN子句 |
Restrictions.and | and关系 |
Restrictions.or | or关系 |
Restrictions.sqlRestriction | SQL限定查询 |
4.1 基本查询
/** * 简单的查询 */ @Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 创建criteria对象 Criteria criteria = session.createCriteria(Customer.class); // 执行查询,返回查询结果--查询所有的Customer对象 List<Customer> list = criteria.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
4.2 条件查询
/** * 条件查询 */ @Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = session.createCriteria(Customer.class); // 添加查询参数--查询cust_id为1的Customer对象 // criteria.add(Restrictions.eq("cust_id", 1l)); criteria.add(Restrictions.like("cust_name", "%王%")); // 执行查询获得结果 Customer customer = (Customer) criteria.uniqueResult(); System.out.println(customer); tx.commit(); session.close(); }
4.3 分页查询
/** * 分页查询 */ @Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 创建criteria对象 Criteria criteria = session.createCriteria(Customer.class); // 设置分页信息 limit ?,? criteria.setFirstResult(1); criteria.setMaxResults(2); // 执行查询 List<Customer> list = criteria.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
4.4 排序查询
/** * 排序查询 */ @Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = session.createCriteria(Customer.class); criteria.addOrder(Order.asc("cust_id")); List<Customer> list = criteria.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
4.5 统计查询
/** * 统计查询 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Criteria criteria = session.createCriteria(Customer.class); // 设置查询的聚合函数 => 总行数 criteria.setProjection(Projections.rowCount()); // 执行查询 Long count = (Long) criteria.uniqueResult(); System.out.println(count); tx.commit(); session.close(); }
4.6 离线条件查询
DetachedCriteria翻译为离线条件查询,因为它是可以脱离Session来使用的一种条件查询对象,我们都知道Criteria对象必须由Session对象来创建。那么也就是说必须先有Session才可以生成Criteria对象。而DetachedCriteria对象可以在其他层对条件进行封装。
这个对象也是比较有用的,尤其在SSH整合以后这个对象经常会使用。它的主要优点是做一些特别复杂的条件查询的时候,往往会在WEB层向业务层传递很多的参数,业务层又会将这些参数传递给DAO层。最后在DAO中拼接SQL完成查询。有了离线条件查询对象后,那么这些工作都可以不用关心了,我们可以在WEB层将数据封装好,传递到业务层,再由业务层传递给DAO完成查询。
我们可以先简单的测试一下离线条件查询对象,然后具体的使用我们会在后期整合中使用,到那时会更加体会出它的优势。
/** * 离线条件查询:DetachedCriteria(SSH整合经常使用) * 可以脱离session设置参数 */ @Test public void demo6() throws Exception { // Web/Service层 // 获得一个离线条件查询的对象 DetachedCriteria dc = DetachedCriteria.forClass(Customer.class); // 拼接调剂(全部与普通Criteria一致) dc.add(Restrictions.idEq(1l)); //------------------------------------------------------------ Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); List<Customer> list = dc.getExecutableCriteria(session).list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
五、原生SQL查询
采用HQL或QBC检索方式时,Hibernate生成标准的SQL查询语句,适用于所有的数据库平台,因此这两种检索方式都是跨平台的。但有的应用程序可能需要根据底层数据库的SQL方言,来生成一些特殊的查询语句。在这种情况下,可以利用Hibernate提供的SQL检索方式。使用SQL检索方式检索对象的示例代码,如下所示:
SQLQuery sqlQuery = session.createSQLQuery("select id,name,age,city from customer");
5.1 基本查询
/** * 基本查询 */ @Test public void demo1() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 创建sql查询对象 SQLQuery query = session.createSQLQuery("select * from cst_customer"); // 指定将结果集封装到哪个对象中 query.addEntity(Customer.class); // 调用方法查询结果 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
5.2 条件查询
/** * 条件查询 */ @Test public void demo2() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 创建sql查询对象 SQLQuery query = session.createSQLQuery("select * from cst_customer where cust_id = ?"); query.setParameter(0, 1l); // 指定将结果集封装到哪个对象中 query.addEntity(Customer.class); // 调用方法查询结果 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
5.3 分页查询
/** * 分页查询 */ @Test public void demo3() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); // 创建sql查询对象 SQLQuery query = session.createSQLQuery("select * from cst_customer limit ?,?"); query.setParameter(0, 0); query.setParameter(1, 1); // 指定将结果集 query.addEntity(Customer.class); // 调用方法查询结果 List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
六、Hibernate的多表查询
在做多表查询之前,我们需要先来回顾下使用SQL是如何完成多表的查询的。在学习SQL语句的时候进行多表联合查询一般都会采用连接查询,那么我们就来回顾一下SQL中的多表的联合查询。
6.1 SQL多表联合查询
【交叉连接】
交叉连接返回的结果是被连接的两个表中所有数据行的笛卡尔积,也就是返回第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数,例如department表中有四个部门,employee表中有四个员工,那么交叉连接的结果就是4*4=16条数据。需要注意的是,在实际开发中这种业务需求很少见的,一般不会使用交叉连接,而是使用具体的条件对数据进行有目的的查询。
【内连接】
内连接(INNER JOIN)又称简单连接或自然连接,是一种常见的连接查询。内连接使用比较运算符对两个表中的数据进行比较,并列出与连接条件匹配的数据行,组合成新的记录,也就是说在内连接查询中,只有满足条件的记录才能出现在查询结果中。内连接查询的语法格式如下所示:
select 查询字段 from 表1 [INNER] JOIN 表2 on 表1.关系字段=表2.关系字段
在上述语法格式中,INNER JOIN用于连接两个表,ON来指定连接条件,其中INNER可以省略。内连接其实还可以细分为如下两类:
- 隐式内连接:顾名思义,隐式的就是我们看不到inner join关键字,而是用where关键字替代。
select * from 表1, 表2 where 表1.关系字段 = 表2.关系字段;
- 显式内连接:显示的就是在语句中明显的调用了inner join的关键字。
select * from 表1 inner join 表2 on 表1.关系字段 = 表2.关系字段; select * from 表1 join 表2 on 表1.关系字段 = 表2.关系字段;
【外连接】
前面讲解的内连接查询中,返回的结果只包含符合查询条件和连接条件的数据,然而有时还需要包含没有关联的数据,即返回查询结果中不仅包含符合条件的数据,而且还包括左表(左连接或左外连接)、右表(右连接或右外连接)或两个表(全外连接)中的所有数据,此时就需要使用外连接查询,外连接分为左连接和右连接。外连接的语法格式如下:
select 所查字段 from 表1 left | rigth [outer] JOIN 表2 on 表1.关系字段 = 表2.关系字段 where 条件
外连接的语法格式和内连接类似,只不过使用的是LEFT JOIN、RIGHT JOIN关键字,其中关键字左边的表被称为左表,关键字右边的表被称为右表。
在使用左连接和右连接查询时,查询结果是不一致的,具体如下:
- LEFT JOIN(左连接): 返回包括左表中的所有记录和右表中符合连接条件的记录。
select * from 表1 left outer join 表2 on 表1.关系字段=表2.关系字段; select * from A left join 表2 on 表1.关系字段 = 表2.关系字段;
- RIGHT JOIN(右连接):返回包括右表中的所有记录和左表中符合连接条件的记录。
select * from 表1 right outer join 表2 on 表1.关系字段 = 表2.关系字段; select * from A right join 表2 on 表1.关系字段 = 表2.关系字段;
SQL语句的连接查询我们会写了,那么Hibernate中的HQL如何进行连接查询呢?
6.2 HQL连接查询
Hibernate进行多表查询与SQL其实是很相似的,但是HQL会在原来SQL分类的基础上又多出来一些操作。
HQL的多表连接查询的分类如下:
-
- 交叉连接
- 内连接
- 显式内连接
- 隐式内连接
- 迫切内连接
- 外连接
- 左外连接
- 迫切左外连接
- 右外连接
其实,这些连接查询语法大致都是一致的,就是HQL查询的是对象而SQL查询的是表。
【内连接】
- 显示内连接
SQL连接查询:
select * from cst_customer c inner join cst_linkman l on c.cust_id = l.lkm_cust_id;
HQL连接查询:
from Customer c inner join c.linkmans
在HQL中,我们不用写关联字段了,因为客户中的联系人集合其实对应的就是外键,所以我们在inner join的后面直接可以写c.linkMans。测试代码如下:
/** * HQL:内连接 */ @Test public void demo4() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c inner join c.linkMans"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(objects); } tx.commit(); session.close(); }
在控制台的输出的语句如下:
我们发现如果这样写HQL语句的化,生成的底层SQL语句就是使用inner join进行连接的,而连接的条件就是customer0_.cust_id=linkmans1_.lkm_cust_id。就是两个表关联的字段。所以HQL的连接不用写具体的on条件,直接写关联的属性即可。
- 迫切内连接
迫切内连接其实就是在内连接的inner join后添加一个fetch关键字。
/** * HQL的多表连接查询:迫切内连接 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c inner join fetch c.linkMans"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
控制台输出的语句如下:
我们会发现无论是内连接或是迫切内连接,发送的底层SQL都是一样的,而且在生成的SQL语句中也没有fetch关键字,当然fetch本身就不是SQL语句的关键字。所以一定要注意,fetch只能在HQL中使用,生成了SQL语句后,fetch就消失了。那么fetch到底有什么作用呢?
其实我们知道HQL内连接查询的和SQL的内连接查询到的结果集是一样的,都是两个表交集部分的数据。
然后在封装数据的时候,普通内连接会将客户的数据封装到Customer对象中,会将属于联系人的数据封装到LinkMan对象中,所以每条记录都会是装有两个对象的集合,所以封装以后的数据是List<Object[]>,在Object[]中有两个对象,一个是Customer,另一个是LinkMan。
那么加了fetch以后,虽然我们查询到的数据是一样的,但是Hibernate发现HQL中有fetch,就会将数据封装到一个对象中,把属于客户的数据封装到Customer对象中,将属于联系人的部分封装到Customer中的联系人集合中,这样最后封装完成以后是一个List<Customer>中。
内连接与迫切内连接的对比总结
其实内连接和迫切内连接的主要区别就在于封装数据,因为他们查询的结果集都是一样的,生成底层的SQL语句也是一样的。
- 内连接:发送就是内连接的语句,封装的时候将属于各自对象的数据封装到各自的对象中,最后得到List<Object[]>.
- 迫切内连接:发送的是内连接的语句,需要在编写HQL的时候在join后添加一个fetch关键字,Hibernate会发送HQL中的fecth关键字,从而将每条数据封装到对象中,最后得到一个List<Customer>。
但是迫切内连接封装以后会出现重复的数据,因为我们查询到目前有三条记录,就会被封装到三个对象中,其实我们真正的客户对象只有两个,所以往往自己在手动编写迫切内连接的时候使用distinct去掉重复值。
/** * HQL的多表连接查询:内连接 */ @Test public void demo5() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("select distinct c from Customer c inner join fetch c.linkMans"); List<Customer> list = query.list(); for (Customer customer : list) { System.out.println(customer); } tx.commit(); session.close(); }
【外连接】
- 左外连接
1、将连接的两端对象分别返回,放到数组中。
2、以左边表中数据为参照,显示左边表中所有数据,右边表只显示与左边表对应的数据,少了补null,多了删除。
/** * HQL:左外连接 */ @Test public void demo6() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c left join c.linkMans"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(objects); } tx.commit(); session.close(); }
- 右外连接
1、将连接的两端对象分别返回,放到数组中。
2、以右边为参照,左边少了补null,多了删除
/** * HQL:右外连接 */ @Test public void demo7() throws Exception { Session session = HibernateUtils.openSession(); Transaction tx = session.beginTransaction(); Query query = session.createQuery("from Customer c right join c.linkMans"); List<Object[]> list = query.list(); for (Object[] objects : list) { System.out.println(objects); } tx.commit(); session.close(); }