在实际开发中,我们不可能只是简简单单地去查询单表,绝大部分都是要对多表进行联合查询的。所以本文就来讲述Hibernate中的多表查询,讲之前,大家可以先复习一下使用SQL语句是如何进行多表查询的。这里我给大家提个醒,本文所有案例代码的编写都是建立在前一讲案例基础之上的!!!
SQL的多表查询可分为连接查询和子查询,子查询其实就是SQL嵌套(在这里,它并不是咱的重点,所以我就不展开讲解了),这里我会重点讲解连接查询。
连接查询又分为交叉连接(CROSS JOIN)、内连接(INNER JOIN ON)以及外连接。
交叉连接其实是没有实际意义的,它会产生迪卡尔积。例如:
SELECT * FROM cst_customer, cst_linkman;
使用内连接,它只能将有关联的数据得到,也就是说内连接查询到的是两张表的公共部分。内连接可分为两种,它们分别是隐式内连接和显示内连接。
内连接有一种隐式内连接,它使用"逗号"将表分开,使用WHERE来消除迪卡尔积。例如:
SELECT * FROM cst_customer c,cst_linkman l WHERE c.cust_id=l.lkm_cust_id;
显示内连接如果写全的话,就像下面这样,但是我们应知道INNER是可以省略的。
SELECT * FROM cst_customer c INNER JOIN cst_linkman l ON c.cust_id=l.lkm_cust_id;
外连接也可分为两种,它们分别是左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。
左外连接是以左表为基准关联数据,说的大白话一点就是它展示的数据只是在左表中有的,右表中没有的不管,也就是说它查询到的是左边表的全部数据以及两张表的公共部分。
左外连接如果写全的话,就像下面这样,但是我们应知道OUTER是可以省略的。
SELECT * FROM cst_customer c LEFT OUTER JOIN cst_linkman l ON c.cust_id=l.lkm_cust_id;
右外连接是以右表为基准关联数据,说的大白话一点就是它展示的数据只是在右表中有的,左表中没有的不管,也就是说它查询到的是右边表的全部数据以及两张表的公共部分。
右外连接如果写全的话,就像下面这样,但是我们应知道OUTER是可以省略的。
SELECT * FROM cst_customer c RIGHT OUTER JOIN cst_linkman l ON c.cust_id=l.lkm_cust_id;
HQL中的多表查询可分为下面几类。
温馨提示:在Hibernate框架中有迫切连接的这一概念,而在SQL中是没有的。
显示内连接使用的是inner join with。如果是在MySQL中使用显示内连接,SQL语句是咋写的,你还记得吗?
SELECT * FROM cst_customer c INNER JOIN cst_linkman l ON c.cust_id = l.lkm_cust_id;
但在Hibernate框架中,我们则要这样书写HQL语句,是不是大不同啊!
from Customer c inner join c.linkMans
为了便于测试,我们可以在com.meimeixia.hibernate.demo01包下编写一个HQLJoinTest单元测试类,并在该类中编写这样的一个测试方法:
package com.meimeixia.hibernate.demo01;
import java.util.Arrays;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.meimeixia.hibernate.utils.HibernateUtils;
/**
* HQL的多表查询
*/
public class HQLJoinTest {
// 测试显示内连接
@Test
public void demo01() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//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
List<Object[]> list = session.createQuery("from Customer c inner join c.linkMans").list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
tx.commit();
}
}
运行以上demo01方法,Eclipse控制台将打印如下内容:
于是,我们可得出结论:此时,list()方法返回的结果是一个List集合,集合中存放的是Object[],而Object[]中装入的无非是Customer和LinkMan对象。
当然了,我们又可书写这样的HQL语句,使用with再添加一个条件。
from Customer c inner join c.linkMans with c.cust_id=1
为了便于进行测试,将HQLJoinTest单元测试类中的demo01方法改为:
// 测试显示内连接
@Test
public void demo01() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
List<Object[]> list = session.createQuery("from Customer c inner join c.linkMans with c.cust_id=1").list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
tx.commit();
}
运行以上demo01方法,Eclipse控制台将打印如下内容:
隐式内连接使用频率并不高,它就是一鸡肋。相信在实际开发中,你压根就用不到这种隐式内连接,你都会显示内连接了,还用得着这玩意。
HQL迫切内连接很少自己手写,迫切内连接使用的是inner join fetch,其实就是在普通的内连接的inner join后面添加一个fetch关键字。迫切内连接是将得到的结果直接封装到PO类中,而内连接得到的是Object[]数组,数组中封装的是PO类对象。下面我们来验证这一点,在HQLJoinTest单元测试类中编写如下方法:
//测试迫切内连接
@Test
public void demo02() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//HQL迫切内连接(很少自己手写):其实就是在普通的内连接的inner join后面添加一个fetch关键字即可
//from Customer c inner join fetch c.linkMans
//fetch关键字的作用是通知Hibernate,将另一个对象的数据封装到该对象中。
List<Customer> list = session.createQuery("from Customer c inner join fetch c.linkMans").list();
for (Customer customer : list) {
System.out.println(customer);
}
tx.commit();
}
温馨提示:fetch关键字的作用是通知Hibernate,将另一个对象的数据封装到该对象中。在这种情境下,是指将查询出来的LinkMan对象封装到了Customer对象的linkMans集合属性中,你要是不信,可以重写Customer类中的toString()方法,如下图所示。
运行以上demo02方法,Eclipse控制台将打印如下内容:
从这里我们也能看出,HQL迫切内连接底层也是执行的inner join,只不过数据结果封装到了对象中了。但问题又来了,我们查询的是两张表的信息,那就会得到合并后的结果,如果是查LinkMan则没问题,但是你要查Customer,Customer就会出现很多重复的数据,这个时候,我们就需要使用关键字——distinct去消除重复了,所以应将demo02方法改为:
//测试迫切内连接
@Test
public void demo02() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//HQL迫切内连接(很少自己手写):其实就是在普通的内连接的inner join后面添加一个fetch关键字即可
//from Customer c inner join fetch c.linkMans
//fetch关键字的作用是通知Hibernate,将另一个对象的数据封装到该对象中。
//List list = session.createQuery("from Customer c inner join fetch c.linkMans").list();
List<Customer> list = session.createQuery("select distinct c from Customer c inner join fetch c.linkMans").list();
for (Customer customer : list) {
System.out.println(customer);
}
tx.commit();
}
再次运行以上demo02方法,Eclipse控制台会打印如下内容:
结论:使用迫切内连接,在结果有可能出现重复时,可以使用distinct关键字来去除重复。
左外连接使用的是left outer join。以码明示,在HQLJoinTest单元测试类中编写如下方法:
//测试左外连接
@Test
public void demo03() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
List<Object[]> list = session.createQuery("from Customer c left outer join c.linkMans").list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
tx.commit();
}
注意:此时,你得重写Customer类中的toString()方法,让其不要包含linkMans属性。接着,运行以上demo03方法,Eclipse控制台将打印如下内容。
右外连接使用的是right outer join。以码明示,在HQLJoinTest单元测试类中编写如下方法:
//测试右外连接
@Test
public void demo04() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
List<Object[]> list = session.createQuery("from Customer c right outer join c.linkMans").list();
for (Object[] objects : list) {
System.out.println(Arrays.toString(objects));
}
tx.commit();
}
注意:此时,你得重写Customer类中的toString()方法,让其不要包含linkMans属性。接着,运行以上demo04方法,Eclipse控制台将打印如下内容。
迫切左外连接使用的是left outer join fetch。以码明示,在HQLJoinTest单元测试类中编写如下方法:
//测试迫切左外连接
@Test
public void demo05() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
// 注意:fetch不可以与单独条件的with一起使用
String hql = "select distinct c from Customer c left outer join fetch c.linkMans with c.cust_id=1";
List<Customer> list = session.createQuery(hql).list();
for (Customer customer : list) {
System.out.println(customer);
}
tx.commit();
}
与HQL迫切内连接一样,fetch关键字的作用是通知Hibernate,将另一个对象的数据封装到该对象中。在这种情境下,是指将查询出来的LinkMan对象封装到了Customer对象的linkMans集合属性中,你要是不信,可以重写Customer类中的toString()方法,如下图所示。
接着你运行以上demo05方法,哎!妈啊!咋会报如下异常呢!
异常发生的原因:fetch与单独条件的with一起使用了。如果非要让fetch与单独的一个条件使用,那么就必须使用where关键字了,所以我们可以将HQLJoinTest单元测试类中的demo05方法改为:
//测试迫切左外连接
@Test
public void demo05() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
// 注意:fetch不可以与单独条件的with一起使用
String hql = "select distinct c from Customer c left outer join fetch c.linkMans where c.cust_id=1";
List<Customer> list = session.createQuery(hql).list();
for (Customer customer : list) {
System.out.println(customer);
}
tx.commit();
}