Hibernate第三天
1.多表映射
1.1.多表映射的总则
问题:我们为什么要学习多表映射?
答:
在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。试想一下,如果把我们web阶段的在线商城案例的持久层改为hibernate的实现,我们现在根本无法实现功能。究其原因是我们在线商城中表之间都是有关联关系的。
例如:商品和分类,用户和订单,订单和商品等等。
而通过第一天的Hibernate框架学习,我们知道hibernate实现了ORM的思想,可以让我们通过操作实体类就实现对数据库表的操作。
所以今天我们的学习重点是:掌握配置实体之间的关联关系。
要想实现多表映射,我们现阶段需要遵循的步骤:
第一步:首先确定两张表之间的关系。
如果关系确定错了,后面做的所有操作就都不可能正确。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射
配置的方式支持注解和XML,我们以注解为重点—需要确认。
思考:表之间的关系到底有几种呢?
1.2.表之间的关系划分
Hibernate框架实现了ORM的思想,将关系数据库中表的数据映射成对象,使开发人员把对数据库的操作转化为对对象的操作,Hibernate的关联关系映射主要包括多表的映射配置、数据的增加、删除等。
数据库中多表之间存在着三种关系,也就是系统设计中的三种实体关系。如图所示。
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。
注意:
一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
明确:
我们只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。
2.一对多关系映射
2.1.示例分析
我们采用的示例为CRM中的客户和联系人。
客户:通常情况下客户指的是一家公司。
联系人:一般都是指客户的员工。
在不考虑兼职的情况下,客户和联系人的关系即为一对多。
2.2.表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多数据库关系的建立,如下图所示:
2.3.实体类关系建立
客户实体类
/**
-
客户实体类
-
@author kevin
*/
public class Customer implements Serializable{private static final long serialVersionUID = 1L;
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_address;
private String cust_phone;//一对多关系映射:一个客户对应多个联系人
private Set linkMans = new HashSet();public Long getCust_id() {
return cust_id;
}
public void setCust_id(Long cust_id) {
this.cust_id = cust_id;
}
public String getCust_name() {
return cust_name;
}
public void setCust_name(String cust_name) {
this.cust_name = cust_name;
}
public String getCust_source() {
return cust_source;
}
public void setCust_source(String cust_source) {
this.cust_source = cust_source;
}
public String getCust_industry() {
return cust_industry;
}
public void setCust_industry(String cust_industry) {
this.cust_industry = cust_industry;
}
public String getCust_level() {
return cust_level;
}
public void setCust_level(String cust_level) {
this.cust_level = cust_level;
}
public String getCust_address() {
return cust_address;
}
public void setCust_address(String cust_address) {
this.cust_address = cust_address;
}
public String getCust_phone() {
return cust_phone;
}
public void setCust_phone(String cust_phone) {
this.cust_phone = cust_phone;
}public Set getLinkMans() {
return linkMans;
}
public void setLinkMans(Set linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return “Customer [cust_id=” + cust_id + “, cust_name=” + cust_name + “, cust_source=” + cust_source
+ “, cust_industry=” + cust_industry + “, cust_level=” + cust_level + “, cust_address=” + cust_address
+ “, cust_phone=” + cust_phone + “]”;
}
}
联系人实体类
/**
-
联系人实体类
-
@author kevin
*/
public class LinkMan implements Serializable{private static final long serialVersionUID = 1L;
private Long lkm_id;
private String lkm_name;
private String lkm_gender;
private String lkm_phone;
private String lkm_mobile;
private String lkm_email;
private String lkm_position;
private String lkm_memo;//多对一关系映射:多个联系人对应一个客户
private Customer customer;
public Long getLkm_id() {
return lkm_id;
}
public void setLkm_id(Long lkm_id) {
this.lkm_id = lkm_id;
}
public String getLkm_name() {
return lkm_name;
}
public void setLkm_name(String lkm_name) {
this.lkm_name = lkm_name;
}
public String getLkm_gender() {
return lkm_gender;
}
public void setLkm_gender(String lkm_gender) {
this.lkm_gender = lkm_gender;
}
public String getLkm_phone() {
return lkm_phone;
}
public void setLkm_phone(String lkm_phone) {
this.lkm_phone = lkm_phone;
}
public String getLkm_mobile() {
return lkm_mobile;
}
public void setLkm_mobile(String lkm_mobile) {
this.lkm_mobile = lkm_mobile;
}
public String getLkm_email() {
return lkm_email;
}
public void setLkm_email(String lkm_email) {
this.lkm_email = lkm_email;
}
public String getLkm_position() {
return lkm_position;
}
public void setLkm_position(String lkm_position) {
this.lkm_position = lkm_position;
}public String getLkm_memo() {
return lkm_memo;
}
public void setLkm_memo(String lkm_memo) {
this.lkm_memo = lkm_memo;
}public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return “LinkMan [lkm_id=” + lkm_id + “, lkm_name=” + lkm_name + “, lkm_gender=” + lkm_gender + “, lkm_phone=”
+ lkm_phone + “, lkm_mobile=” + lkm_mobile + “, lkm_email=” + lkm_email + “, lkm_position=”
+ lkm_position + “, lkm_memo=” + lkm_memo + “]”;
}
}
2.4.客户配置文件
2.5.联系人配置文件
?xml version=“1.0” encoding=“UTF-8”?>
3.2.表关系建立
多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:
3.3.实体类关系建立
一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:
/**
-
用户实体类
-
@author kevin
*/
public class SysUser implements Serializable{private static final long serialVersionUID = 1L;
private Long user_id;
private String user_code;
private String user_name;
private String user_password;
private String user_state;//多对多关系映射
private Set roles = new HashSet();public Long getUser_id() {
return user_id;
}
public void setUser_id(Long user_id) {
this.user_id = user_id;
}
public String getUser_code() {
return user_code;
}
public void setUser_code(String user_code) {
this.user_code = user_code;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public String getUser_password() {
return user_password;
}
public void setUser_password(String user_password) {
this.user_password = user_password;
}
public String getUser_state() {
return user_state;
}
public void setUser_state(String user_state) {
this.user_state = user_state;
}
public Set getRoles() {
return roles;
}
public void setRoles(Set roles) {
this.roles = roles;
}
@Override
public String toString() {
return “SysUser [user_id=” + user_id + “, user_code=” + user_code + “, user_name=” + user_name
+ “, user_password=” + user_password + “, user_state=” + user_state + “, roles=” + roles + “]”;
}
}
一个角色可以赋予多个用户,所以在角色实体类中应该包含多个用户的信息,代码如下:
/**
-
角色实体类
-
@author kevin
*/
public class SysRole implements Serializable {private static final long serialVersionUID = 1L;
private Long role_id;
private String role_name;
private String role_memo;//多对多关系映射
private Set users = new HashSet();
public Long getRole_id() {
return role_id;
}
public void setRole_id(Long role_id) {
this.role_id = role_id;
}
public String getRole_name() {
return role_name;
}
public void setRole_name(String role_name) {
this.role_name = role_name;
}
public String getRole_memo() {
return role_memo;
}
public void setRole_memo(String role_memo) {
this.role_memo = role_memo;
}
public Set getUsers() {
return users;
}
public void setUsers(Set users) {
this.users = users;
}
@Override
public String toString() {
return “SysRole [role_id=” + role_id + “, role_name=” + role_name + “, role_memo=” + role_memo + “, users=”
+ users + “]”;
}
}
3.4.用户配置文件
3.5.角色配置文件
<?xml version="1.0" encoding="UTF-8"?>4.多表增删改操作
4.1.一对多关系操作
4.1.1.普通保存操作
原则:先保存主表数据,再保存从表数据
设置联系人属于哪个客户,同时也设置客户下有哪些联系人,先保存客户,再保存联系人。
/**
* 一对多的普通保存
* 设置联系人属于哪个客户,同时也设置客户下有哪些联系人
*/
@Test
public void test1(){
//创建客户对象
Customer customer = new Customer();
customer.setCust_name(“阿里巴巴”);
//创建联系人对象
LinkMan linkMan = new LinkMan();
linkMan.setLkm_name("马云");
//设置联系人属于哪个客户
linkMan.setCustomer(customer);
//设置客户下有哪些联系人
customer.getLinkMans().add(linkMan);
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//先保存客户
session.save(customer);
//再保存联系人
session.save(linkMan);
tx.commit();
}
4.1.2.级联保存
级联:操作一个对象的时候,是否操作其关联对象。
级联保存是有方向性的:
1、保存客户,级联保存联系人。
2、保存联系人,级联保存客户。
级联保存需要在其中一方配置cascade=save-update。
保存联系人级联保存客户,需要在联系人的映射文件LinkMan.hbm.xml的many-to-one标签中设置cascade=save-update,save-update的意思就是可以级联保存,也可以级联更新。
联系人方设置cascade=save-update
/**
* 一对多的级联保存
* 需求:保存联系人,级联保存客户
* 在LinkMan.hbm.xml的many-to-one标签中配置cascade=save-update
* 设置联系人属于哪个客户,同时设置客户下有哪些联系人
* 保存联系人级联保存客户
*
*/
@Test
public void test4(){
//创建客户对象
Customer customer = new Customer();
customer.setCust_name(“阿里巴巴”);
//创建联系人对象
LinkMan linkMan = new LinkMan();
linkMan.setLkm_name(“马云”);
//设置联系人属于哪个客户
linkMan.setCustomer(customer);
//设置客户下有哪些联系人
customer.getLinkMans().add(linkMan);
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//保存联系人级联保存客户
session.save(linkMan);
tx.commit();
}
保存客户,级联保存联系人。需要在客户的映射文件Customer.hbm.xml的set标签中配置cascade=save-update
客户方设置
/**
* 一对多的级联保存
* 需求:保存客户,级联保存联系人
* 在Customer.hbm.xml的set标签中配置cascade=save-update
* 设置联系人属于哪个客户,同时设置客户下有哪些联系人
* 保存客户级联保存联系人
*
*/
@Test
public void test3(){
//创建客户对象
Customer customer = new Customer();
customer.setCust_name(“阿里巴巴11”);
//创建联系人对象
LinkMan linkMan = new LinkMan();
linkMan.setLkm_name("马云11");
//设置联系人属于哪个客户
linkMan.setCustomer(customer);
//设置客户下有哪些联系人(让客户知道联系人)
customer.getLinkMans().add(linkMan);
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//保存客户,级联保存联系人
session.save(customer);
tx.commit();
}
4.1.3.普通删除操作
直接删除联系人,客户不受影响
/**
* 直接删除联系人,客户不受影响
/
@Test
public void test1(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询id为1的联系人
LinkMan linkMan = session.get(LinkMan.class, 1L);
//删除id为1的联系人
session.delete(linkMan);
tx.commit();
}
直接删除客户,发送一条update语句和一条delete语句,先把外键置为null,再删除客户
/*
* 直接删除客户
* 结果:发送一条update语句和一条delete语句
* 先把外键置为null,再删除客户
*/
@Test
public void test2(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询id为1的客户
Customer customer = session.get(Customer.class, 1L);
//删除id为1的客户
session.delete(customer);
tx.commit();
}
4.1.4.级联删除
级联删除也是有方向性的:
1、删除客户时,级联删除联系人。
2、删除联系人时,级联删除客户。
删除客户时,级联把客户下的联系人也删除,发送一条update语句和两条delete语句,先把外键置为null,再删除联系人,最后删除客户。
在客户方设置cascade=delete
细节:如果想同时设置级联保存和级联删除,可以这么写:cascade=save-update,delete
/**
* 删除客户时,级联把客户下的联系人也删除
* 在客户的映射文件set标签中设置cascade=delete
* 结果:发送一条update语句和两条delete语句
* 先把外键置为null,再删除联系人,最后删除客户
*/
@Test
public void test3(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询id为1的客户
Customer customer = session.get(Customer.class, 1L);
//删除id为1的客户,
session.delete(customer);
tx.commit();
}
删除联系人,级联删除客户,发送一条update语句和两条delete语句,先把外键置为null,再删除联系人,最后删除客户。注意:一般不这么用。
/**
* 删除联系人,级联删除客户
* 在联系人的映射文件many-to-one标签中设置cascade=delete
* 结果:发送一条update语句和两条delete语句
* 先把外键置为null,再删除联系人,最后删除客户
*
* 注意:删除多方时,不需要级联
/
@Test
public void test4(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//查询id为1的联系人
LinkMan linkMan = session.get(LinkMan.class, 1L);
//删除id为1的联系人,级联删除客户
session.delete(linkMan);
tx.commit();
}
4.1.5.修改操作
/*
* 一对多的修改:把1号联系人移到2号客户下
*
*/
@Test
public void test6() {
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//再查询id为1的联系人
LinkMan linkMan = session.get(LinkMan.class, 1L);
//再先查询id为2的客户
Customer customer2 = session.get(Customer.class, 2L);
linkMan.setCustomer(customer2);
customer2.getLinkMans().add(linkMan);
tx.commit();
}
测试发现:能更新成功,但是多了一条更新外键的语句。如何解决?用inverse。
4.1.6. inverse的作用
作用:在一的一方(主表)设置inverse=true后,表示一的一方(主表)放弃维护外键的权利,由多的一方来维护。
默认情况下,一方,inverse的默认值为false,自己来维护外键。可以在一方设置invsese=true,放弃维护外键的权利。
注意:多方不能设置inverse。
那到底什么时候用inverse??
建立双向关系后,会产生多余的update语句,如果想解决的话,就要用inverse,让一方放弃外键的维护权。例如,4.1.5中就会出现多余的update语句。
如何解决:在一的一方(主表)的set标签中配置inverse=true.让一的一方放弃外键维护权。
4.2.多对多关系操作
4.2.1.普通保存操作
多对多保存,创建两个用户和三个角色,并设置用户拥有哪些角色以及角色下有哪些用户,分别保存两个用户和三个角色,会发送九条insert语句。
/**
* 多对多的保存
* 设置用户下有哪些角色以及角色下有哪些用户
* 测试结果:会报错,提示中间表中主键冲突
*/
@Test
public void test1(){
SysUser user1 = new SysUser();
user1.setUser_name(“用户1”);
SysUser user2 = new SysUser();
user2.setUser_name("用户2");
SysRole role1 = new SysRole();
role1.setRole_name("角色1");
SysRole role2 = new SysRole();
role2.setRole_name("角色2");
SysRole role3 = new SysRole();
role3.setRole_name("角色3");
//一个用户有多个角色
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
//一个角色下有多个用户
role1.getUsers().add(user1);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
role3.getUsers().add(user2);
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(user1);
session.save(user2);
session.save(role1);
session.save(role2);
session.save(role3);
tx.commit();
}
测试结果:报错,提示中间表主键冲突。原因:用户和角色都需要维护外键,都要向中间表插入数据,所以主键冲突。
解决:在任意一方的set标签中配置inverse=true,放弃外键维护权。
我们可以在角色方放弃外键维护:
4.2.2.级联保存
可以在用户方设置级联保存
/**
* 多对多的保存
* 设置用户下有哪些角色以及角色下有哪些用户
* 测试结果:会报错,提示中间表中主键冲突
*/
@Test
public void test1(){
SysUser user1 = new SysUser();
user1.setUser_name(“用户1”);
SysUser user2 = new SysUser();
user2.setUser_name("用户2");
SysRole role1 = new SysRole();
role1.setRole_name("角色1");
SysRole role2 = new SysRole();
role2.setRole_name("角色2");
SysRole role3 = new SysRole();
role3.setRole_name("角色3");
//一个用户有多个角色
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
//一个角色下有多个用户
role1.getUsers().add(user1);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
role3.getUsers().add(user2);
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
session.save(user1);
session.save(user2);
// session.save(role1);
// session.save(role2);
// session.save(role3);
tx.commit();
}
在用户方设置级联保存
也可以在角色方设置级联
@Test
public void test1(){
SysUser user1 = new SysUser();
user1.setUser_name(“用户1”);
SysUser user2 = new SysUser();
user2.setUser_name("用户2");
SysRole role1 = new SysRole();
role1.setRole_name("角色1");
SysRole role2 = new SysRole();
role2.setRole_name("角色2");
SysRole role3 = new SysRole();
role3.setRole_name("角色3");
//一个用户有多个角色
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
//一个角色下有多个用户
role1.getUsers().add(user1);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
role3.getUsers().add(user2);
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
// session.save(user1);
// session.save(user2);
session.save(role1);
session.save(role2);
session.save(role3);
tx.commit();
}
在角色的映射文件中配置cascade=save-update
4.2.3.删除操作
先查询出要删除的对象,再删除。也可以直接实例化出对象并设置id,再删除。
/**
* 多对多删除
* 先查询id为1的用户对象,再删除。
* 结果:会发两条delete语句,先发一条delete语句,删除中间表的数据,再发一条delete语句删除用户
*/
@Test
public void test9(){
Session session = HibernateUtils.getSession();
Transaction tx = session.beginTransaction();
SysUser user = session.get(SysUser.class, 1L);
session.delete(user);
tx.commit();
}
4.2.4.级联删除
多对多中最好不要设置级联删除。因为一旦设置级联删除后,用户级联角色,当删除用户时,就会删除把与之关联的其它角色删除,但是这些角色可能被其它用户用到。
在User.hbm.xml中设置cascase=”delete”
@Test
public void test9(){
Session session = HibernateUtils.getSession();
Transaction tx = session.beginTransaction();
SysUser user = session.get(SysUser.class, 1L);
session.delete(user);
tx.commit();
}
测试发现,会报错,因为删除用户会级联删除角色,但角色放弃了外键维护,不会删除中间表中与角色关联的数据,所以整个删除操作失败。
解决办法:把角色方的inverse去掉。但是,也没必要,应为在多对多中没有表级联删除。
4.2.5.修改操作
/**
* 多对多修改操作
* 需求:修改id为1的用户,把其中的1号角色改为3号角色
*/
@Test
public void test1(){
Session session = HibernateUtils.getCurrentSession();
Transaction tx = session.beginTransaction();
//先查询id为1的用户
SysUser user = session.get(SysUser.class, 1L);
//再查询id为1和3的角色
SysRole role1 = session.get(SysRole.class, 1L);
SysRole role3 = session.get(SysRole.class, 3L);
//再建立关系
user.getRoles().remove(role1);
user.getRoles().add(role3);
tx.commit();
}
5.Hibernate中的多表查询
5.1.对象导航查询
5.1.1.概述
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。
例如:我们通过OID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。
对象导航查询的使用要求是:两个对象之间必须存在关联关系。
5.1.2.对象导航检索示例
5.1.2.1.查询一个客户,获取该客户下所有联系人
/**
* 需求:
* 查询id为1的客户有多少联系人
/
@Test
public void test1(){
Session session = HibernateUtils.getSession();
Transaction tx = session.beginTransaction();
Customer customer = session.get(Customer.class, 1L);
Set linkMans = customer.getLinkMans();
for (LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
tx.commit();
}
5.1.2.2.查询一个联系人,获取该联系人所对应的客户
/*
* 需求:
* 查询一个联系人,获取该联系人所对应的客户
*/
@Test
public void test2(){
Session session = HibernateUtils.getSession();
Transaction tx = session.beginTransaction();
LinkMan linkMan = session.get(LinkMan.class, 1L);
System.out.println(linkMan.getCustomer().getCust_name());
tx.commit();
}
5.1.3.对象导航查询的问题分析
问题1:我们查询客户时,要不要把联系人查询出来?
分析:
如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。
如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:
采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置的方式:
在Customer.hbm.xml配置文件中的set标签上使用lazy属性。取值为true(默认值)|fasle
问题2:我们查询联系人时,要不要把客户查询出来?
分析:
如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。
如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。
例如:查询联系人详情时,肯定会看看该联系人的所属客户。
解决:
采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来。
配置的方式:
在LinkMan.hbm.xml配置文件中的many-to-one标签上使用lazy属性。取值为proxy|fasle
false:立即加载
proxy:看客户的映射文件class标签的lazy属性取值,如果客户的class标签lazy属性是true
那么proxy表示延迟加载,如果是false就表示立即加载。