Hibernate3 关联关系映射
1 多表设计
l 在开发中,前期进行需求分析,需求分析提供E--R图,根据ER图编写表结构。
l 表之间关系存在3种:一对多、多对多、一对一。(回顾)
一对多:1表(主表)必须主键 和 多表(从表)必须外键,主表的主键 与 从表外键 形成主外键关系
多对多:提供中间表(从表),提供2个字段(外键)分别对应两个主表。
一对一:???
l 面单对象描述 对象与对象 之间关系?【掌握】
一对多:客户和订单
private class Customer{
//一对多:一个客户 拥有 多个订单
private Set<Order> orderSet;
}
private class Order{
//多对一:多个订单 属于 一个客户
private Customer customer;
}
多对多:Student学生 和 Course课程
private class Student{
//多对多:多个学生(当前)学习 【不同课程】
private Set<Course> courseSet ...;
}
private class Course{
//多对多:多个课程 可以被 【不同学生】学习
private Set<Student> student = ...;
}
一对一:公司company 和 地址address
private class Company{
private Address address;
}
private class Address{
private Company company;
}
2 关联关系映射
2.1 一对多实现【掌握】
2.1.1 实现类
public class Customer {
private Integer cid; private String cname;
//一对多:一个客户(当前客户) 拥有 【多个订单】 // * 需要容器存放多个值,一般建议Set (不重复、无序) // * 参考集合:List、Map、Array等 // ** 建议实例化--使用方便 private Set<Order> orderSet = new HashSet<Order>(); |
public class Order { private Integer xid; private String price;
//多对一:多个订单属于【一个客户】 private Customer customer; |
2.1.2 配置文件
l Customer.hbm.xml
<class name="com.hibernate.b_onetomany.Customer" table="t_customer"> <id name="cid"> <generator class="native"></generator> </id> <property name="cname"></property>
<!-- 一对多:一个客户(当前客户) 拥有 【多个订单】 1 确定容器 set <set> 2 name确定对象属性名 3 确定从表外键的名称 4 确定关系,及另一个对象的类型 注意: 在hibernate中可以只进行单向配置 每一个配置项都可以完整的描述彼此关系。 一般情况采用双向配置,双方都可以完成描述表与表之间关系。 --> <!-- 一对多:一个客户(当前客户) 拥有 【多个订单】 --> <set name="orderSet" cascade="delete-orphan"> <key column="customer_id"></key> <one-to-many class="com.it.b_onetomany.Order"/> </set> </class> |
l Order.hbm.xml
<class name="com.hibernate.b_onetomany.Order" table="t_order"> <id name="xid"> <generator class="native"></generator> </id> <property name="price"></property>
<!-- 多对一:多个订单属于【一个客户】 * name 确定属性名称 * class 确定自定义类型 * column 确定从表的外键名称 --> <many-to-one name="customer" class="com.hibernate.b_onetomany.Customer" column="customer_id"></many-to-one>
</class> |
2.2 一对多操作
2.2.1 保存客户
@Test public void demo01(){ // 1 创建客户,并保存客户--成功 Session session = factory.openSession(); session.beginTransaction();
Customer customer = new Customer(); customer.setCname("田志成");
session.save(customer);
session.getTransaction().commit(); session.close(); } |
2.2.2 保存订单
@Test public void demo02(){ // 2 创建订单,保存订单--成功,外键为null Session session = factory.openSession(); session.beginTransaction();
Order order = new Order(); order.setPrice("998");
session.save(order);
session.getTransaction().commit(); session.close(); } |
2.2.3 客户关联订单,只保存客户
@Test public void demo03(){ // 3 创建客户和订单,客户关联订单,保存客户? Session session = factory.openSession(); session.beginTransaction();
//1 客户和订单 Customer customer = new Customer(); customer.setCname("成成");
Order order = new Order(); order.setPrice("998");
//2 客户关联订单 customer.getOrderSet().add(order);
//3 保存客户 session.save(customer);
session.getTransaction().commit(); session.close(); }
|
2.2.4 双向关联,使用inverse映射
@Test public void demo04(){ // 4 创建客户和订单,客户关联订单,订单也关联客户,保存客户和订单? // * 开发中优化程序 , n + 1 问题? // ** 解决方案1:客户不关联订单 ,不建议 // ** 解决方案2:客户放弃对订单表外键值的维护。 // **** Customer.hbm.xml <set name="orderSet" inverse="true"> // ** inverse 将维护外键值的权利交予对象。相当于自己放弃。(反转) Session session = factory.openSession(); session.beginTransaction();
//1 客户和订单 Customer customer = new Customer(); customer.setCname("成成");
Order order = new Order(); order.setPrice("998");
//2 客户关联订单 customer.getOrderSet().add(order); //3 订单也关联客户 order.setCustomer(customer);
//4 保存客户 // * 1 save(order) -- insert --> 1,998 null // * 2 订单管理客户,此时null --预留update --> 更新所有(正常设置) // * 3 save(customer) -- insert --> 1,成成 // * 4 客户关联订单 --> 预留update --> 更新订单外键 (维护外键) // * 5 提交commit --> 执行2 和 4 session.save(order); session.save(customer);
session.getTransaction().commit(); session.close(); } |
l 在一对多开发中,一方一般都放弃对外键值的维护。及<set inverse="true
2.3 级联操作(读、理解)
2.3.1 save-update 级联保存或更新
@Test public void demo032(){ // 32 创建客户和订单,客户关联订单,保存客户? --抛异常 // ** 解决方案2:级联操作--级联保存或更新 // ** Customer.hbm.xml <set cascade="save-update"> // ** 在保存客户的同时,一并保存订单 Session session = factory.openSession(); session.beginTransaction();
//1 客户和订单 Customer customer = new Customer(); //瞬时态 customer.setCname("成成");
Order order = new Order(); //瞬时态 order.setPrice("998");
//2 客户关联订单 customer.getOrderSet().add(order);
//3 保存客户 session.save(customer); //持久态 // 关联操作都是持久态的,此时 持久态Customer 引用 一个 瞬时态的Order 抛异常
session.getTransaction().commit(); session.close(); } |
2.3.2 delete 级联删除
@Test public void demo05(){ // 5 查询客户,并删除(持久态) // 默认:当删除客户,默认将订单外键设置成null。 // 级联删除:删除客户时,并将客户的订单删除。 // ** Customer.hbm.xml <set name="orderSet" cascade="delete"> Session session = factory.openSession(); session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 10);
session.delete(customer);
session.getTransaction().commit(); session.close(); } |
2.3.3 孤儿删除
l 一对多关系,存在父子关系。1表(主表)可以成为父表,多表(从表)也可以子表。
总结:
主表不能删除,从表已经引用(关联)的数据
从表不能添加,主表不存在的数据。
@Test public void demo06(){ // 6 查询客户,并查询订单,解除客户和订单订单的关系 // * 默认:客户和订单解除关系后,外键被设置成null,此时订单就是孤儿。客户和订单都存在。 // * 孤儿删除(孤子删除),当订单称为孤儿,一并删除。客户仍存在。 Session session = factory.openSession(); session.beginTransaction();
//1 查询客户 Customer customer = (Customer) session.get(Customer.class, 9);
//2查询订单 Order order = (Order) session.get(Order.class, 8);
//3 解除关系 customer.getOrderSet().remove(order);
session.getTransaction().commit(); session.close(); } |
2.3.4 总结
save-update:A保存,同时保存B
delete:删除A,同时删除B,AB都不存在
delete-orphan:孤儿删除,解除关系,同时将B删除,A存在的。
如果需要配置多项,使用逗号分隔。<set cascade="save-update,delete">
all : save-update 和 delete 整合
all-delete-orphan : 三个整合
1.1.1 Hibernate的关联关系映射:(多对多)
1.1.1.1 多对多的配置:
:
Student:
public class Student {
private Integer sid;
private String sname;
// 学生选择多门课程.
private Set<Course> courses = new HashSet<Course>();
...
}
Course:
public class Course {
private Integer cid;
private String cname;
// 课程可以被多个学生选择:
private Set<Student> students = new HashSet<Student>();
...
}
Student.hbm.xml
<hibernate-mapping>
<class name="cn.itcast.demo3.Student" table="student">
<id name="sid" column="sid">
<generator class="native"/>
</id>
<property name="sname" column="sname"/>
<!-- 配置多对多关联关系 -->
<set name="courses" table="stu_cour">
<key column="sno"/>
<many-to-many class="cn.itcast. demo3.Course" column="cno"/>
</set>
</class>
</hibernate-mapping>
Course.hbm.xml
<hibernate-mapping>
<class name="cn.itcast. demo3.Course" table="course">
<id name="cid" column="cid">
<generator class="native"/>
</id>
<property name="cname" column="cname"/>
<!-- 配置多对多关联关系映射 -->
<set name="students" table="stu_cour">
<key column="cno"/>
<many-to-many class="cn.itcast. demo3.Student" column="sno"/>
</set>
</class>
</hibernate-mapping>
2 抓取策略(优化)
2.1 检索方式
l 立即检索:立即查询,在执行查询语句时,立即查询所有的数据。
l 延迟检索:延迟查询,在执行查询语句之后,在需要时在查询。(懒加载)
2.2 检查策略
l 类级别检索:当前的类的属性获取是否需要延迟。
l 关联级别的检索:当前类 关联 另一个类是否需要延迟。
2.3 类级别检索
l get:立即检索。get方法一执行,立即查询所有字段的数据。
l load:延迟检索。默认情况,load方法执行后,如果只使用OID的值不进行查询,如果要使用其他属性值将查询 。 Customer.hbm.xml <class lazy="true | false">
lazy 默认值true,表示延迟检索,如果设置false表示立即检索。
@Test public void demo02() { //类级别 Session session = factory.openSession(); session.beginTransaction();
//1立即 // Customer customer = (Customer) session.get(Customer.class, 1); //2延迟 Customer customer = (Customer) session.load(Customer.class, 1);
//打印 System.out.println(customer.getCid()); System.out.println(customer.getCname());
session.getTransaction().commit(); session.close(); } |
2.4 关联级别检索
2.4.1 一对多或多对多
2.4.1.1 介绍
l 容器<set> 提供两个属性:fetch、lazy
fetch:确定使用sql格式
lazy:关联对象是否延迟。
l fetch:join、select、subselect
join:底层使用迫切左外连接
select:使用多个select语句(默认值)
subselect:使用子查询
l lazy:false、true、extra
false:立即
true:延迟(默认值)
extra:极其懒惰
2.4.1.2 fetch="join"
l fetch="join" ,lazy无效。底层使用迫切左外连接,使用一条select将所有内容全部查询。
@Test public void demo03() { //关联级别:一对多, // * Customer.hbm.xml <set fetch="join"> // *** select语句使用左外连接,一次性查询所有 Session session = factory.openSession(); session.beginTransaction();
//1 查询客户 Customer customer = (Customer) session.get(Customer.class, 1); System.out.println(customer.getCname());
//2 查询客户订单数 Set<Order> orderSet = customer.getOrderSet(); System.out.println(orderSet.size());
//3 查询客户订单详情 for (Order order : orderSet) { System.out.println(order); }
session.getTransaction().commit(); session.close(); } |
2.4.1.3 fetch="select"
l 当前对象 和 关联对象 使用多条select语句查询。
l lazy="false" , 立即,先查询客户select,立即查询订单select
l lazy="true",延迟,先查询客户select,需要订单时,再查询订单select
l lazy="extra",极其懒惰(延迟),先查询客户select, 如果只需要订单数,使用聚合函数(不查询详情)
2.4.1.4 fetch="subselect"
l 将使用子查询。注意:必须使用Query否则看不到效果。
l lazy= 同上
@Test public void demo04() { //关联级别:一对多, // 演示3:* Customer.hbm.xml <set fetch="subselect"> Session session = factory.openSession(); session.beginTransaction();
//1 查询客户 List<Customer> allCustomer = session.createQuery("from Customer").list(); Customer customer = allCustomer.get(0); System.out.println(customer.getCname());
//2 查询客户订单数 Set<Order> orderSet = customer.getOrderSet(); System.out.println(orderSet.size());
//3 查询客户订单详情 for (Order order : orderSet) { System.out.println(order); }
session.getTransaction().commit(); session.close(); } |
2.4.2 多对一
2.4.2.1 介绍
l <many-to-one fetch="" lazy=""> (<one-to-one>)
l fetch取值:join、select
join:底层使用迫切左外连接
select:多条select语句
l lazy取值:false、proxy、no-proxy
false:立即
proxy:采用关联对象 类级别检索的策略。
订单 关联 客户 (多对一)
订单 立即获得 客户,需要在客户Customer.hbm.xml <class lazy="false">
订单 延迟获得 客户,需要在客户Customer.hbm.xml <class lazy="true">
no-proxy 不研究
2.4.2.2 fetch="join"
l fecth="join" select语句使用左外连接,此时lazy无效。
@Test public void demo05() { //关联级别:多对一, // 演示1:* Order.hbm.xml <set fetch="join"> lazy无效 // * 注意:检查Customer.hbm.xml 和 Order.hbm.xml 没有额外的配置 Session session = factory.openSession(); session.beginTransaction();
//1 查询订单 Order order = (Order) session.get(Order.class, 1); System.out.println(order.getPrice());
//2 查询订单客户信息 Customer customer = order.getCustomer(); System.out.println(customer.getCname());
session.getTransaction().commit(); session.close(); } |
2.4.2.3 fetch="select"
l 将采用多条select语句,lazy="proxy"是否延迟,取决关联对象 类级别检索策略。
l lazy="false"
l lazy="proxy"
2.5 批量查询
l 当客户 关联查询 订单,给每一个客户生产一个select语句查询订单。批量查询使用in语句减少查询订单语句个数。
默认:select * from t_order where customer_id = ?
批量:select * from t_order where customer_id in (?,?,?,?)
l <set batch-size="5"> 5表示括号中?个数。
@Test public void demo06() { //批量查询 Session session = factory.openSession(); session.beginTransaction();
//1 查询所有客户 List<Customer> allCustomer = session.createQuery("from Customer").list();
//2遍历 for (Customer customer : allCustomer) { System.out.println(customer.getCname()); System.out.println(customer.getOrderSet().size()); }
session.getTransaction().commit(); session.close(); } |
2.6 检索总结
检索策略 |
优点 |
缺点 |
优先考虑使用的场合 |
立即检索 |
对应用程序完全透明,不管对象处于持久化状态还是游离状态,应用程序都可以从一个对象导航到关联的对象 |
(1)select语句多 (2)可能会加载应用程序不需要访问的对象,浪费许多内存空间。 |
(1)类级别 (2)应用程序需要立即访问的对象 (3)使用了二级缓存 |
延迟检索 |
由应用程序决定需要加载哪些对象,可以避免执行多余的select语句,以及避免加载应用程序不需要访问的对象。因此能提高检索性能,并节省内存空间。 |
应用程序如果希望访问游离状态的代理类实例,必须保证她在持久化状态时已经被初始化。 |
(1)一对多或者多对多关联 (2)应用程序不需要立即访问或者根本不会访问的对象
|
表连接检索 |
(1)对应用程序完全透明,不管对象处于持久化状态还是游离状态,都可从一个对象导航到另一个对象。 (2)使用了外连接,select语句少 |
(1)可能会加载应用程序不需要访问的对象,浪费内存。 (2)复杂的数据库表连接也会影响检索性能。 |
(1)多对一或一对一关联 (2)需要立即访问的对象 (3)数据库有良好的表连接性能。 |
Customer Get(int id)
Return Session.load(Customer.class,id);
- layz=false
- 在Service层获得在页面要上要用到的属性=> 在Service层中确保数据已经
3 查询方式总结
1.通过OID检索(查询)
get()立即、如果没有数据返回null
load()延迟,如果没有数据抛异常。
2.导航对象图检索方式:关联查询
customer.getOrderSet()
user.getPost().getDepartment().getDepName();
3.原始sql语句
SQLQuery sqlQuery = session.createSQLQuery("sql 语句") --->表,表字段(列)
sqlQuery.list() 查询所有
sqlQuery.uniqueResult() 查询一个
4.HQL,hibernate query language hibernate 查询语言【1】
Query query = session.createQuery("hql语句") --> 对象,对象属性
5.QBC,query by criteria 纯面对对象查询语言【2】