【Java中级】13.0 SSH之Hibernate框架(十)—
本篇内容源代码:
Github —— HibernateQueryDemo
1.0 QBC在多表查询上有局限。
2.0 SQL的多表查询
- 连接查询
- 交叉连接:最不经常用,获得两个表的笛卡尔积(两个表的乘积)
- 内连接:inner join(inner可以省略),查到的是两个表公共的部分(两个表的交集),相当于只查两个表有关联的数据。
- 隐式外连接:在SQL语句中不能看到inner join两个关键字,但是最后查到的结果和inner join查到的是一样的。
- 显式外连接:在SQL语句中能够看到inner join这两个关键字
- 外连接:outer join
- 左外连接:left outer join(outer可以省略)查询左边表的全部数据和两个表的公共部分。
- 右外连接:right outer join(outer可以省略)查询右边表的全部数据和两个表的公共部分。
- 子查询:SQL语句的嵌套
相应的SQL语句为:
//交叉连接
SELECT * FROM cst_customer,cst_linkman;
//显式内连接
SELECT * FROM cst_customer JOIN cst_linkman ON cst_customer.cust_id = cst_linkman.lkm_cust_id;
SELECT * FROM cst_customer INNER JOIN cst_linkman ON cst_customer.cust_id = cst_linkman.lkm_cust_id;
//可以起别名,上面语句等同于:
SELECT * FROM cst_customer c INNER JOIN cst_linkman l ON c.cust_id = l.lkm_cust_id;
//隐式内连接
SELECT * FROM cst_customer,cst_linkman WHERE cst_customer.cust_id = cst_linkman.lkm_cust_id;
//左外连接
SELECT * FROM cst_customer LEFT JOIN cst_linkman ON cst_customer.cust_id = cst_linkman.lkm_cust_id;
SELECT * FROM cst_customer LEFT OUTER JOIN cst_linkman ON cst_customer.cust_id = cst_linkman.lkm_cust_id;
//右外连接
SELECT * FROM cst_customer RIGHT JOIN cst_linkman ON cst_customer.cust_id = cst_linkman.lkm_cust_id;
SELECT * FROM cst_customer RIGHT OUTER JOIN cst_linkman ON cst_customer.cust_id = cst_linkman.lkm_cust_id;
3.0 HQL进行多表的查询
- 连接查询
- 交叉连接
- 内连接:
- 隐式外连接
- 显式外连接
- 迫切内连接:
- 外连接:
- 左外连接
- 右外连接
- 迫切左外连接:没有迫切右外连接
- 子查询
@Test
/**
*
* @Title: demo9
* @Description: HQL的多表查询
* @param
* @return void
* @throws
*
*/
public void demo9() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 显示内连接,根据配置,自动关联
// SELECT * FROM cst_customer JOIN cst_linkman ON cst_customer.cust_id =
// cst_linkman.lkm_cust_id;
// List<Object[]> list = session.createQuery("from Customer c inner join
// c.linkMans").list();
// for (Object[] objects : list) {
// System.out.println(Arrays.toString(objects));
// }
// 迫切内连接,在普通的内连接inner join后添加一个关键字fetch
// fetch:通知Hibernate将另一个对象的数据封装到对象中
//// 一条数据,数据里面有1个联系人
// List<Customer> list = session.createQuery("from Customer c inner join fetch
// c.linkMans").list();
// 三条数据,数据里面有10条联系人
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);
}
transaction.commit();
}
4.0 SQL检索
@Test
/**
*
* @Title: demo1
* @Description: SQL查询
* @param
* @return void
* @throws
*
*/
public void demo1() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// NativeQuery nativeQuery = session
// .createSQLQuery("SELECT * FROM cst_customer c INNER JOIN cst_linkman l ON c.cust_id = l.lkm_cust_id;");
//
// List<Object[]> list2 = nativeQuery.list();
// for (Object[] objects : list2) {
// System.out.println(Arrays.toString(objects));
// }
//封装到对象中
NativeQuery nativeQuery = session
.createSQLQuery("SELECT * FROM cst_customer;");
nativeQuery.addEntity(Customer.class);
List<Customer> list = nativeQuery.list();
for (Customer customer : list) {
System.out.println(customer);
}
transaction.commit();
}
5.0 Hibernate的抓取策略(优化)
抓取策略往往不单独使用,和延迟加载同时使用。(往往和其中的关联级别的延迟加载一起使用)来优化语句。
5.1 延迟加载
延迟加载:lazy(懒加载),执行带该行代码时,不会发送语句去进行查询,在真正使用这个对象的属性的时候才会发送SQL语句进行查询。
5.2 延迟加载的分类
1. 类级别的延迟加载
指的是通过load方法查询某个对象的时候,是否采用延迟。session.load(Customer.class,1l);
通过<class>上的lazy进行配置,如果想要lazy失效,可以:
image.png
- 将lazy设置为false,但是只对自己的普通属性有效,对关联对象无效。
- 将持久化类使用final修饰。
- 调用
Hibernate.initialize();
方法。
@Test
/**
*
* @Title: demo1
* @Description: 类级别的延迟加载
* 在<class>的标签上配置lazy =true
* @param
* @return void
* @throws
*
*/
public void demo1() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
Customer customer = session.load(Customer.class, 1l);
//该语句使延迟加载失效
// Hibernate.initialize(customer);
System.out.println(customer);
transaction.commit();
}
2. 关联级别的延迟加载
指的是在查询到某个对象的时候,在查询其关联的对象的时候,是否采用延迟加载。
Cuteomer customer = session.get(Customer.class,1l);
customer.getLinkMans();//通过客户获得联系人的时候,联系人对象是否采用了延迟加载,称为是关联级别的延迟。
设置Customer.hbm.xml中(一的一方的表中),<set>标签,或者LinkMan.hbm.xml表中(多的一方的表)<many-to-one>标签的lazy属性。
image.png
5.3 抓取策略的概述
通过一个对象抓取到关联对象,需要发送SQL语句,SQL语句如何发送,发送成什么样格式通过策略进行配置。
- 设置Customer.hbm.xml中(一的一方的表中),<set>标签,或者LinkMan.hbm.xml表中(多的一方的表)<many-to-one>标签的fetch属性进行配置。
fetch和这些标签上的lazy如何设置优化发送SQL语句
image.png
5.4 <set>上的fetch和lazy
- fetch:抓取策略,控制SQL语句格式。
- select(默认值):发送普通的select语句,查询关联对象。
- join:发送一条迫切左外连接查询关联对象。
- subselect:发送一条子查询查询其关联对象。
- lazy: 延迟加载,控制查询关联对象的时候是否采用延迟
- true(默认值):查询关联对象时,采用延迟加载
- false:查询关联对象时,不采用延迟加载
- extra:极其懒惰,用什么语句发什么样的语句。
1. 默认情况:
@Test
/**
*
* @Title: demo1
* @Description:默认情况
* @param
* @return void
* @throws
*
*/
public void demo1() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 查询1号客户
Customer customer = session.get(Customer.class, 1l);//发送一条查询客户的SQL
System.out.println(customer.getCust_name());
// 查看1号客户的每个联系人的信息
for (LinkMan linkMan : customer.getLinkMans()) {//发送一条根据客户ID查询联系人的SQL
System.out.println(linkMan.getLkm_name());
}
System.out.println(customer);
transaction.commit();
}
运行:
image.png
等同于:<set name="linkMans" lazy="true" fetch="select">
设置Customer.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!-- hibernate-mapping是根标签 -->
<!-- 建立类与表的映射 catalog="hibernate0207" -->
<class name="com.edp.hibernate.domain.Customer" table="cst_customer" lazy="true">
<!-- 配置一对多的关系:放置的是多的一方的集合 -->
<!-- 联系人是多,客户是一 -->
<!-- set标签
name:对的一方的对象集合的属性名称
cascade:级联,用逗号隔开,就同时拥有保存或更新、删除功能
inverse:放弃外键维护权,true是确定放弃外键维护权。默认的false是不放弃。
-->
<set name="linkMans" lazy="true" fetch="select">
<!-- key标签
column:多的一方的外键的名称
-->
<key column="lkm_cust_id"></key>
<!-- one-to-many标签
class:多的一方的类的全路径
-->
<one-to-many class="com.edp.hibernate.domain.LinkMan"/>
</set>
</class>
</hibernate-mapping>
2. 其他配置
fetch配置成join,lazy配置没有意义,发送迫切左外连接,两个表的数据全查了,只发送一次语句。
fetch配置成select,lazy配置true,相当于默认情况。
fetch配置成select,lazy配置false,在第一次运行时就会发送2条sql语句。
fetch配置成select,lazy配置extra,如果调用个数,就会发送一条select count() from……
fetch配置成subselect,lazy配置true,子查询加懒查询。
fetch配置成subselect,lazy配置false,子查询和本体查询一起完成,没有懒。
fetch配置成subselect,lazy配置extra,子查询,更加懒。
在实际开发中,一般都采用默认值。如果有特殊的需求,可能需要配置join。
5.5 <many-to-one>上的fetch和lazy
- fetch:抓取策略,控制SQL语句格式。
- select(默认值):发送普通的select语句,查询关联对象。
- join:发送一条迫切左外连接查询关联对象。
- lazy: 延迟加载,控制查询关联对象的时候是否采用延迟
- proxy(默认值):查询关联对象时,采用延迟加载
- false:查询关联对象时,不采用延迟加载
- no-proxy:(不会使用)
此时这样的设计,用于联系人查客户(多的一方查一的一方)
- fetch配置成select,lazy配置proxy,默认情况,发送两次sql语句。取决于客户一端(一的一方)的lazy是true还是false,如果是true, 则<many-to-one>上proxy属性等于true。
- fetch配置成select,lazy配置false,一次发送2次SQL语句。
- fetch配置成join,lazy配置无效,迫切左外连接查询,只需要一条查询语句。
在实际开发中,一般都采用默认值。如果有特殊的需求,可能需要配置join。
6.0 批量抓取
一批关联对象一起抓取,batch-size默认值为1。
测试批量抓取
@Test
/**
*
* @Title: demo1
* @Description: 获取客户的时候,批量抓取联系人
* @param
* @return void
* @throws
*
*/
public void demo1() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 以下代码会发送5条SQL语句
List<Customer> list = session.createQuery("from Customer").list();
for (Customer customer : list) {
System.out.println(customer.getCust_name());
for (LinkMan linkMan : customer.getLinkMans()) {
System.out.println(linkMan.getLkm_name());
}
}
// 使用批量抓取
// 在Customer.hbm.xml中设置<set name="linkMans" batch-size="4">
//batch-size默认是1
// 只发送2条sql语句
transaction.commit();
}
@Test
/**
*
* @Title: demo2
* @Description: 获取联系人的时候,批量抓取客户
* @param
* @return void
* @throws
*
*/
public void demo2() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 以下代码会发送4条SQL语句
List<LinkMan> list = session.createQuery("from LinkMan").list();
for (LinkMan linkMan : list) {
System.out.println(linkMan.getLkm_name());
System.out.println(linkMan.getCustomer().getCust_name());
}
// 使用批量抓取
// 在Customer.hbm.xml中设置<class name="com.edp.hibernate.domain.Customer" table="cst_customer" batch-size="4" >
// 只发送2条sql语句
transaction.commit();
}
END