day31_Hibernate学习笔记_03
一、Hibernate的关联关系映射(多对多)
在数据库表中如何表达多对多关系:
使用中间表,分别引用两方的ID。
在对象中如何表达多对多关系:
两方都使用集合表达。
在配置文件中如何表达一对多关系:
<!-- 配置多对多关联关系 -->
<set name="courses" table="t_stu_cour">
<key column="stu_id"/>
<many-to-many class="Course" column="cour_id"/>
</set>
<!-- 配置多对多关联关系 -->
<set name="students" table="t_stu_cour">
<key column="cour_id"/>
<many-to-many class="Student" column="stu_id"/>
</set>
操作:
inverse:本方是否要放弃维护外键关系
cascade:是否需要级联操作(有5个值)
注意:配置级联删除时,要小心!当双方都配置级联删除时,任意删除一条记录,整个关系链数据都会被删除。
1.1、多对多实现【掌握】
1.1.1、实现类
Student.java
package com.itheima.domain;
import java.util.HashSet;
import java.util.Set;
public class Student {
private Integer sid;
private String sname;
// 学生可以选择多门课程
private Set<Course> courses = new HashSet<Course>();
// getter和setter方法
}
Course.java
package com.itheima.domain;
import java.util.HashSet;
import java.util.Set;
public class Course {
private Integer cid;
private String cname;
// 课程可以被多个学生选择
private Set<Student> students = new HashSet<Student>();
// getter和setter方法
}
1.1.2、配置文件
Student.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 package="com.itheima.domain">
<class name="Student" table="t_student">
<id name="sid" column="sid">
<generator class="native"></generator>
</id>
<property name="sname" column="sname"></property>
<!-- 配置多对多关联关系 -->
<set name="courses" table="t_stu_cour">
<key column="stu_id"/>
<many-to-many class="Course" column="cour_id"/>
</set>
</class>
</hibernate-mapping>
Course.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 package="com.itheima.domain">
<class name="Course" table="t_course">
<id name="cid" column="cid">
<generator class="native"></generator>
</id>
<property name="cname" column="cname"></property>
<!-- 配置多对多关联关系 -->
<set name="students" table="t_stu_cour">
<key column="cour_id"/>
<many-to-many class="Student" column="stu_id"/>
</set>
</class>
</hibernate-mapping>
hibernate.cfg.xml
......
<!-- 添加ORM映射文件 ,填写src之后的路径 -->
<mapping resource="com/itheima/domain/Student.hbm.xml"/>
<mapping resource="com/itheima/domain/Course.hbm.xml"/>
......
1.1.3、测试代码
架构图如下所示:
测试多对多代码:
package com.itheima.a_many2many;
import org.hibernate.Session;
import org.junit.Test;
import com.itheima.domain.Course;
import com.itheima.domain.Student;
import com.itheima.utils.HibernateUtils;
public class Demo1 {
@Test
// 保存学生 => 通过学生保存课程,由学生来维护外键
// Student 的
// inverse => false
// cascade => save-update
// Course 的
// inverse => true
// 注意:实际开发中,在配置多对多关系时,一般需要有一方放弃维护外键关联关系
// 5条 select 语句 + 6条 update 语句 (因为1个学生关联了3个课程)
public void fun1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Student stu1 = new Student();
stu1.setSname("李晓艺");
Student stu2 = new Student();
stu2.setSname("陈明军");
Course c1 = new Course();
c1.setCname("Struts2");
Course c2 = new Course();
c2.setCname("Hibernate");
Course c3 = new Course();
c3.setCname("Spring");
stu1.getCourses().add(c1); // 维护关系+级联保存
stu1.getCourses().add(c2);
stu1.getCourses().add(c3);
stu2.getCourses().add(c1);
stu2.getCourses().add(c2);
stu2.getCourses().add(c3);
session.save(stu1);
session.save(stu2);
session.getTransaction().commit();
session.close();
}
}
二、加载(抓取)策略(优化查询)
1、加载策略种类:
延迟加载:等到使用的时候才会加载数据。
立即加载:不管使用不使用,都会立刻将数据加载。
2、加载策略的应用:
类级别的加载策略。
关联级别的加载策略。
3、类级别加载策略:
get/load
get:立即查询数据库,将数据初始化。
load:hbm文件中,class元素的lazy属性决定该方法的类级别加载策略,默认值是true。
true:先返回一个代理对象,使用代理对象的属性时,才去查询数据库。
false:load方法一执行就会发送sql语句,与get一致,会立即加载数据。
4、关联级别加载策略:
在查询有关联关系的数据时,加载一方的数据是否需要将另一方立即查询出。
默认是:与我关联的数据,在使用时才会被加载。
注意:下面所用的例子是:客户(一)和订单(多)
------------------------------------------------------------------------------------------------------------------------------
一对多(集合的检索策略):根据客户去找订单
<set
lazy:是否对set数据使用懒加载
true (默认值)对集合使用赖加载
false 集合将会被立即加载
extra 极其懒惰,在使用集合时,若调用size方法查询数量,则Hibernate会发送count语句,只查询数量,不加载集合内的数据
fetch:决定加载集合使用的sql语句种类
select (默认值)普通select语句查询
join 使用 表连接语句 查询集合数据,即使用 多表查询语句 集合数据
subselect 使用子查询语句,在一次加载多个客户的订单数据的情况下才有效
fetch lazy 结果
------------------------
select true 都是默认值,会在使用集合(订单)时才加载,使用普通select语句查询集合数据
select false 立刻使用select语句加载集合数据
select extra 会在使用集合(订单)时才加载,普通select语句,如果你使用集合只是为了获得集合的长度,则Hibernate只会发送count语句查询集合长度
join true 因为查询集合(订单)时使用表连接语句查询,所以会立刻加载集合数据(与lazy属性无关了,lazy属性失效)
join false 因为查询集合(订单)时使用表连接语句查询,所以会立刻加载集合数据(与lazy属性无关了,lazy属性失效)
join extra 因为查询集合(订单)时使用表连接语句查询,所以会立刻加载集合数据(与lazy属性无关了,lazy属性失效)
subselect true 会在使用集合(订单)时才加载,使用子查询语句查询集合(订单)数据
subselect false 会在查询客户时,立即使用子查询语句加载客户的订单数据
subselect extra 会在使用集合(订单)时才加载,子查询语句,如果你使用集合只是为了获取集合的长度,则Hibernate只会发送count语句查询集合长度
------------------------------------------------------------------------------------------------------------------------------
多对一:根据订单去找客户
<many-to-one
lazy
false 加载订单时,会立即加载客户
proxy 看客户对象的类加载策略来决定
no-proxy 不做研究
fetch
select (默认值)使用普通select加载
join 使用表连接加载数据
fetch lazy 结果
------------------------
select false 加载订单时,立即加载客户数据,普通select语句加载客户
select proxy 客户的类加载策略为:lazy="false" 时, 同上
lazy="true" 时, 加载订单时,先不加载客户数据,使用客户数据时才加载
join false 使用表连接查询订单以及对应客户信息,lazy属性无效
join proxy 使用表连接查询订单以及对应客户信息,lazy属性无效
5、批量加载
set
batch-size 决定一次加载几个对象的集合数据,in 条件加载多个用户的订单
2.1、类级别加载策略
示例代码如下:演示类级别加载策略--懒加载
package com.itheima.b_lazy;
import org.hibernate.Session;
import org.junit.Test;
import com.itheima.domain.Customer;
import com.itheima.utils.HibernateUtils;
public class Demo1 {
@Test
// 演示类级别加载策略--懒加载
// load方法
// class的lazy属性
// 默认值是:true load获得时,会返回一个代理对象,当使用代理对象的属性时,才去查询数据库。
public void fun1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.load(Customer.class, 1);
System.out.println(c.getCname());
session.getTransaction().commit();
session.close();
}
@Test
// 演示类级别加载策略--懒加载
// load方法
// class的lazy属性
// 属性值改为:false load方法一执行就会发送sql语句,与get方法一致。
public void fun2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.load(Customer.class, 1);
System.out.println(c.getCname());
session.getTransaction().commit();
session.close();
}
}
2.2、关联级别加载策略
2.2.1、一对多:根据客户去查找订单
一对多加载策略,也叫集合的检索策略。
示例代码如下:
package com.itheima.b_lazy;
import java.util.List;
import org.hibernate.Session;
import org.junit.Test;
import com.itheima.domain.Customer;
import com.itheima.domain.Order;
import com.itheima.utils.HibernateUtils;
// 一对多(集合的检索策略):根据客户去查找订单
// 特别注意:本示例代码中,为了简化演示,共有1个客户:表示客户数 >= 1 才可以演示; 共有2个客户:表示客户数 >= 2 才可以演示。
public class Demo2 {
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 true 时,
// 结果:(默认值为true)即对集合使用赖加载,即与客户关联的数据,在使用时才会被加载。
// 演示前提:一对多,1个客户有2个订单,共有1个客户
public void fun1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.get(Customer.class, 1); // 1条 select 语句,查询出1个客户
for (Order o : c.getOrders()) {
System.out.println(o.getOname()); // 1条 select 语句,查询出对应客户的订单
}
session.getTransaction().commit();
session.close();
}
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 false 时,
// 结果:集合将会被立即加载
// 演示环境:一对多,1个客户有2个订单,共有1个客户
public void fun2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.get(Customer.class, 1); // 2条 select 语句,查询出客户以及查询出客户对应的订单
for (Order o : c.getOrders()) {
System.out.println(o.getOname());
}
session.getTransaction().commit();
session.close();
}
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 false/true/extra 时
// fetch 的值为 join 时
// 结果:因为查询集合时使用表连接语句查询,所以会立刻加载集合数据,lazy的属性失效
// 演示环境:一对多,1个客户有2个订单,共有1个客户
public void fun3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Customer c = (Customer) session.get(Customer.class, 1); // 1条 表连接查询 语句(左外连接查询)
for (Order o : c.getOrders()) {
System.out.println(o.getOname());
}
session.getTransaction().commit();
session.close();
}
@SuppressWarnings("unchecked")
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 true 时
// fetch 的值为 subselect 时,注意:该属性的值,在一次加载多个客户的订单数据的情况下才有效
// 结果:会在使用集合(订单)时才加载,使用子查询语句查询集合(订单)数据
// 演示环境:一对多,1个客户有2个订单,共有2个客户
public void fun4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list(); // 1条 select 语句,查询出2个客户
for (Customer c : list) {
System.out.println(c.getCname() + "下单数量:" + c.getOrders().size()); // 1条 子查询 语句,查询出2个客户各自所有的订单
}
session.getTransaction().commit();
session.close();
}
@SuppressWarnings("unchecked")
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 false 时
// fetch 的值为 subselect 时,注意:该属性的值,在一次加载多个客户的订单数据的情况下才有效
// 结果:会在查询客户时,立即使用子查询语句加载客户的订单数据
// 演示前提:一对多,1个客户有2个订单,共有2个客户
public void fun5() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list(); // 1条 select 语句,查询出2个客户 + 1条 子查询 语句,查询出2个客户各自所有的订单
for (Customer c : list) {
System.out.println(c.getCname() + "下单数量:" + c.getOrders().size());
}
session.getTransaction().commit();
session.close();
}
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 extra 时
// fetch 的值为 select 时
// 结果:会在使用集合(订单)时才加载,普通select语句,如果你使用集合只是为了获得集合的长度,则Hibernate只会发送count语句查询集合长度
// 演示环境:一对多,1个顾客有2个订单,共有1个顾客
public void fun6() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
// 查询Customer
Customer c = (Customer) session.get(Customer.class, 1); // 1条 select 语句,查询出1个客户
// 查询Customer下的订单数量
System.out.println(c.getOrders().size()); // 1条 select count() 语句,查询出集合的长度
// 真正使用订单中的数据
for (Order o : c.getOrders()) {
System.out.println(o.getOname()); // 1条 select 语句,查询出对应客户的订单数据
}
session.getTransaction().commit();
session.close();
}
@SuppressWarnings("unchecked")
@Test
// 演示关联级别加载策略
// <set
// lazy 的值为 extra 时
// fetch 的值为 subselect 时,注意:该属性的值,在一次加载多个客户的订单数据的情况下才有效
// 结果:会在使用集合(订单)时才加载,子查询语句,如果你使用集合只是为了获取集合的长度,则Hibernate只会发送count语句查询集合长度
// 演示环境:一对多,1个客户有2个订单,共有2个客户
public void fun7() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list(); // 1条 select 语句,查询出2个客户
for (Customer c : list) { // 遍历Customer,获取每个Customer的订单数量
System.out.println(c.getCname() + "下单数量:" + c.getOrders().size()); // 1条 select count 语句,查询出对应客户的订单的长度
} // 1条 select count 语句,查询出对应客户的订单的长度
for (Customer c : list) {
for (Order o : c.getOrders()) { // 遍历每个订单的具体数据
System.out.println(c.getCname() + "下单名称:" + o.getOname()); // 1条 子查询语句,查询出订单的详细信息
}
}
session.getTransaction().commit();
session.close();
}
}
2.2.2、多对一:根据订单去查找客户
示例代码如下:
package com.itheima.b_lazy;
import org.hibernate.Session;
import org.junit.Test;
import com.itheima.domain.Order;
import com.itheima.utils.HibernateUtils;
// 多对一 检索策略:根据订单去查找客户
public class Demo3 {
@Test
// Order <many-to-one
// fetch: select
// lazy: false
// 结果:加载订单时,会立即加载客户数据,使用普通select语句加载客户数据
public void fun1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Order o = (Order) session.get(Order.class, 2); // 2条 select 语句
System.out.println(o.getCustomer().getCname());
session.getTransaction().commit();
session.close();
}
@Test
// Order <many-to-one
// fetch: select
// lazy: proxy
// Customer <class
// lazy: false
// 结果:结果同fun1()
public void fun2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Order o = (Order) session.get(Order.class, 2); // 2条 select 语句
System.out.println(o.getCustomer().getCname());
session.getTransaction().commit();
session.close();
}
@Test
// Order <many-to-one
// fetch: select
// lazy: proxy
// Customer <class
// lazy: true
// 结果:加载订单时,先不加载客户数据,使用客户数据时才加载客户数据
public void fun3() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Order o = (Order) session.get(Order.class, 2); // 1条 select 语句
System.out.println(o.getCustomer().getCname()); // 1条 select 语句
session.getTransaction().commit();
session.close();
}
@Test
// Order <many-to-one
// fetch: join
// lazy: proxy|false 该属性失效
// 结果:使用表连接查询订单以及对应客户信息,lazy属性无效
public void fun4() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
Order o = (Order) session.get(Order.class, 2); // 1条 表连接查询 语句(左外连接查询)
System.out.println(o.getCustomer().getCname());
session.getTransaction().commit();
session.close();
}
}
2.2.3、批量加载策略
示例代码如下:
package com.itheima.b_lazy;
import java.util.List;
import org.hibernate.Session;
import org.junit.Test;
import com.itheima.domain.Customer;
import com.itheima.utils.HibernateUtils;
// 演示:批量加载策略
public class Demo4 {
@SuppressWarnings("unchecked")
@Test
// 查询所有客户
// 遍历客户,打印客户下的订单信息
// 演示环境:一对多,1个客户有2个订单,共有2个客户
// <set
// batch-size="1"
// 结果:一次加载1个对象(客户)的集合数据
public void fun1() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list(); // 1条 select 语句,查询出2个客户
for (Customer c : list) {
System.out.println(c.getOrders().size()); // 1条 select 语句,查询出第一个客户的所有的订单信息
} // 1条 select 语句,查询出第二个客户的所有的订单信息
session.getTransaction().commit();
session.close();
}
@SuppressWarnings("unchecked")
@Test
// 批量加载策略
// <set
// batch-size="2"
// 结果:一次加载2个对象(客户)的集合数据
public void fun2() {
Session session = HibernateUtils.openSession();
session.beginTransaction();
List<Customer> list = session.createQuery("from Customer").list(); // 1条 select 语句,查询出2个客户
for (Customer c : list) {
System.out.println(c.getOrders().size()); // 1条 select ... in ... 语句,就查询出2个客户的所有订单信息
}
session.getTransaction().commit();
session.close();
}
}
2.2.4、检索总结
如下表所示:
检索策略 | 优点 | 缺点 | 优先考虑使用的场合 |
---|---|---|---|
立即检索 | 对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象。即使用关联对象的属性很方便。 | (1)select语句多,检索效率低。 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 |
(1)类级别检索中使用。 (2)应用程序需要立即访问的对象中使用。 (3)使用了二级缓存的情况下。 |
延迟检索 (懒加载/检索) |
(1)由应用程序决定需要加载哪些对象,可以避免执行多余的select语句。 (2)避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 |
应用程序如果希望访问游离状态的代理类实例,必须保证它在持久化状态时已经被初始化。 | (1)一对多或者多对多关联检索中使用。 (2)应用程序不需要立即访问或者根本不会访问的对象延时检索使用。 要特别注意代理对象的问题。 开发中常见这种问题!
|
表连接检索 (用的比较少,不灵活) |
(1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少。 |
(1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 |
(1)多对一或一对一关联检索中使用 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。 |
类级别加载策略:
get/load
get:立即查询数据库,将数据初始化。
load:hbm文件中,class元素的lazy属性决定该方法的类级别加载策略,默认值是true。
true:先返回一个代理对象,使用代理对象的属性时,才去查询数据库。
false:load方法一执行就会发送sql语句,与get一致,会立即加载数据。
由以上的类级别加载策略可知,代理对象在Session关闭之后可能会取不到值,该如何解决呢?
- 法一:配置layz="false" ,集合将会被立即加载,此法不好,当数据量级很大时,比如:银行项目、三大运营商项目,服务器就要疯了。
- 法二:因为是在Service层调用的Dao层的方法,那么我们在Service层就知道以后我们在页面上要用的数据,所以我们在Seesion关闭之前,在Service层先getXxx()在页面要上要用到的属性(数据),即在Service层中确保数据已经加载进来了。
三、查询方式总结
Hibernate查询分类:
1、get/load 根据OID检索
2、对象导航图检索 c.getOrders();
3、Sql语句 createSqlQuery
4、Hql语句 createQuery
5、Criteria查询 createCriteria
--------------------------------------------------
1.通过OID检索(查询)
get(int); 立即、如果没有数据返回null。
load(Class, int); 延迟,如果没有数据抛异常。
2.导航对象图检索方式(关联查询)
customer.getOrders();
3.Sql: Structured Query Language 结构化查询语言,原始Sql语句查询
SQLQuery sqlQuery = session.createSQLQuery("sql语句"); // 查询的是:表,表字段(列)
sqlQuery.list(); 查询所有
sqlQuery.uniqueResult(); 查询一个
4.HQL: Hibernate Query Language Hibernate查询语言
Query query = session.createQuery("hql语句"); // 查询的是:对象,对象属性
5.QBC: Query By Criteria 条件查询,纯面向对象查询语言
Criteria criteria = session.createCriteria(Class);
四、HQL【掌握】
4.1、HQL介绍
- HQL(Hibernate Query Language:Hibernate查询语言)是描述对象操作的查询语言,是Hibernate所特有。
- 与SQL语法基本一致,不同的是HQL是面向对象的查询,查询的是对象和对象中的属性。
HQL的关键字不区分大小写,但是类名和属性名区分大小写。
HQL语法示例:
上一篇: 去总公司开会
发表评论