第三天,我们来使用Hibernate进行表之间一对多 多对多关系的操作:
这里我们先利用两个例子进行表关系的回顾:
一对多(重点): 例如分类和商品的关系,一个分类多个商品,一个商品属于一个分类
CRM 客户关系管理
客户和联系人:
客户:一般指的是有业务往来的公司(例如百度、腾讯)
联系人:公司里的员工(和联系人联系就联系上公司)
这里的客户和联系人就是一对多的关系(一个公司多个员工,一个员工从属与一个公司)
如何建表:通过外键建立关系
在多的那一方建立一个外键(这个外键指向客户的主键)(因为在一的那一方无法创建出合理的外键)
利用Hibernate建表无需我们手动建表,只需在 Hibernate中配置这个映射关系即可自动生成这个一对多的表
多对多(重点):例如订单和商品的关系,一个订单有多个商品,一个商品可以属于多个订单
用户和角色的关系:
一个用户可以是多个角色,一个角色可以对应多个用户
例如,小王是总经理,也可以是司机;小马可以是司机,也可以是秘书
如何建表:
需要建立第三张表维护两者的关系
至少要有两个外键指向两张表的主键
一对一(较少见):一夫一妻制(当然我们说的是中国的合法关系)
下面我们进行一对多的配置:
一对多的配置:
环境搭建完成后
一、创建实体类(客户和联系人)
二、让实体类之间先互相进行表示,客户实体类有多个联系人,一个联系人属于一个客户
//一个客户多个联系人,要求使用集合表示,但必须使用Set(不可重复)
三、配置映射关系
1.一般一个实体类对应一个映射文件,配置完各自的基本配置
2.配置一对多关系
在客户的映射文件中表示所有的联系人
在联系人表示联系人所属的客户
四、创建核心配置文件
引入映射配置文件(注意是两个)
先看大致的一对多的目录结构:
我们来看看实体类及其配置文件:
客户:
package cn.entity;
import java.util.HashSet;
import java.util.Set;
public class Customer {
//客户ID
private Integer cid;
//客户名称
private String custName;
//客户级别
private String custLevel;
//客户来源
private String custSource;
//客户电话
private String custPhone;
//客户手机
private String custMobile;
//一个客户多个联系人,要求使用集合表示,但必须使用Set
private Set<LinkMan> set_LinkMan = new HashSet<LinkMan>();
public Set<LinkMan> getSet_LinkMan() {
return set_LinkMan;
}
public void setSet_LinkMan(Set<LinkMan> set_LinkMan) {
this.set_LinkMan = set_LinkMan;
}
//空构造器
public Customer() {
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public String getCustMobile() {
return custMobile;
}
public void setCustMobile(String custMobile) {
this.custMobile = custMobile;
}
}
联系人:
package cn.entity;
public class LinkMan {
//联系人编号
private Integer lkm_id;
//联系人姓名
private String lkm_name;
//联系人性别
private String lkm_gender;
//联系人电话
private String lkm_phone;
//一个联系人对应一个客户
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
//空构造器
public LinkMan() {
}
public Integer getLkm_id() {
return lkm_id;
}
public void setLkm_id(Integer 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;
}
}
客户映射文件:
<?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>
<!-- 映射的类 name是类的全路径 table是表名 -->
<class name="cn.entity.Customer" table="t_customer">
<id name="cid" column="cid">
<!-- 设置id增长策略 -->
<generator class="native"></generator>
</id>
<!-- 配置其它属性 -->
<property name="custName" column="custName"></property>
<property name="custLevel" column="custLevel"></property>
<property name="custSource" column="custSource"></property>
<property name="custPhone" column="custPhone"></property>
<property name="custMobile" column="custMobile"></property>
<!-- 在客户的映射文件中表示所有的联系人 -->
<!-- 使用set集合表示所有联系人,name为客户实体类中表示联系人的set集合名称 -->
<set name="set_LinkMan" cascade="save-update,delete" inverse="true">
<!-- column属性值为外键的名字 -->
<key column="clid"></key>
<!-- 多的那一方的全路径 -->
<one-to-many class="cn.entity.LinkMan"/>
</set>
</class>
</hibernate-mapping>
联系人映射文件:
<?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>
<!-- 映射的类 name是类的全路径 table是表名 -->
<class name="cn.entity.LinkMan" table="t_linkman">
<id name="lkm_id" column="lkm_id">
<!-- 设置id增长策略 -->
<generator class="native"></generator>
</id>
<!-- 配置其它属性 -->
<property name="lkm_name" column="lkm_name"></property>
<property name="lkm_gender" column="lkm_gender"></property>
<property name="lkm_phone" column="lkm_phone"></property>
<!-- 在联系人表示联系人所属的客户 -->
<!-- name为表示客户的那个对象名称(不是对象类型) class为客户实体类的全路径 column为外键名称 -->
<many-to-one name="customer" class="cn.entity.Customer" column="clid"></many-to-one>
</class>
</hibernate-mapping>
在核心配置文件中引入资源(这里只写出核心的两句,其它相同的此处不再赘述):
<!-- 三、引入映射文件 -->
<mapping resource="cn/entity/Customer.hbm.xml"/>
<mapping resource="cn/entity/LinkMan.hbm.xml"/>
一对多级联操作 级联:cascade
级联保存:
添加了一个客户,为这个客户又添加了多个联系人,需要操作两张表
级联删除:
删除了一个客户,客户里的所有联系人也要删除
直接删除时无法直接删除,它有外键的关联(需要先删完联系人,或把联系人的外键设置为Null)
Hibernate进行了封装,可以方便的进行级联操作
级联保存两种写法(一种麻烦,一种简化)
第一种(底层写法)
//一对多的级联保存
@Test
public void testAdd(){
//请将声明放在try之外
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try{
//得到工厂
sessionFactory = HibernateUtils.getSessionFactory();
//得到session
session = sessionFactory.openSession();
//开启事务
tx = session.beginTransaction();
//级联保存操作(较麻烦的方法)
/*创建客户对象*/
Customer cust = new Customer();
cust.setCustName("传智播客");
cust.setCustLevel("VIP8");
cust.setCustSource("百度");
cust.setCustMobile("10086");
cust.setCustPhone("911");
/*创建联系人对象*/
LinkMan lkm = new LinkMan();
lkm.setLkm_name("黑马");
lkm.setLkm_gender("男");
lkm.setLkm_phone("10010");
//建立级联关系
/*联系人放客户set集合里面*/
cust.getSet_LinkMan().add(lkm);
/*客户放联系人里面*/
lkm.setCustomer(cust);
//保存到数据库中
session.save(cust);
session.save(lkm);
//提交事务
tx.commit();
}catch(Exception e){
e.printStackTrace();
//事务回滚
tx.rollback();
}finally{
//关闭资源
session.close();//使用openSession()需要手动关闭
sessionFactory.close();
}
}
第二种,一般都是根据客户加联系人
在客户映射文件里set标签上进行配置:
<set name="set_LinkMan" cascade="save-update">
设置级联保存,cascade就是级联的意思
在代码中创建两个对象后,把联系人放到客户里面就可以
底层实现就是复杂写法,配置帮助简化
//级联的简化写法,开发常用
@Test
public void testAdd2(){
//请将声明放在try之外
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try{
//得到工厂
sessionFactory = HibernateUtils.getSessionFactory();
//得到session
session = sessionFactory.openSession();
//开启事务
tx = session.beginTransaction();
//级联保存操作(较麻烦的方法)
/*创建客户对象*/
Customer cust = new Customer();
cust.setCustName("百度");
cust.setCustLevel("VIP6");
cust.setCustSource("搜狗");
cust.setCustMobile("10000");
cust.setCustPhone("911");
/*创建联系人对象*/
LinkMan lkm = new LinkMan();
lkm.setLkm_name("白马");
lkm.setLkm_gender("男");
lkm.setLkm_phone("10010");
//联系人放客户即可
cust.getSet_LinkMan().add(lkm);
//再保存客户即可
session.save(cust);
//提交事务
tx.commit();
}catch(Exception e){
e.printStackTrace();
//事务回滚
tx.rollback();
}finally{
//关闭资源
session.close();//使用openSession()需要手动关闭
sessionFactory.close();
}
}
推荐的是第二种,第一种作为原理理解
级联删除写法:
(需要先删完联系人,或把联系人的外键设置为Null)
根据客户去删除联系人,在客户的映射文件中进行配置
也是set标签上配置
<set name="set_LinkMan" cascade="save-update,delete">
/**
* 级联删除的操作
*/
@Test
public void testDelete(){
//请将声明放在try之外
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try{
//得到工厂
sessionFactory = HibernateUtils.getSessionFactory();
//得到session
session = sessionFactory.openSession();
//开启事务
tx = session.beginTransaction();
//先查后删
Customer cust = session.get(Customer.class, 2);
session.delete(cust);
//提交事务
tx.commit();
}catch(Exception e){
e.printStackTrace();
//事务回滚
tx.rollback();
}finally{
//关闭资源
session.close();//使用openSession()需要手动关闭
sessionFactory.close();
}
}
一对多的修改操作:
先查后改:通过公司增加员工与设置员工的所属公司来操作
但是,Hibernate中有双向维护外键,所以会修改两次外键,这是影响性能的
解决方案是让其中的一方放弃维护外键(让一对多中的一这一方放弃维护,这里就是客户这一方放弃维护)
所有人都认识习主席,而习主席不一定认识所有人
具体操作是通过配置解决 inverse true表示放弃维护,false表示放弃(也就是说inverse表示是否放弃维护)
<set name="set_LinkMan" cascade="save-update,delete" inverse="true">
/**
* 级联修改的操作
*/
@Test
public void testUpdate(){
//请将声明放在try之外
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try{
//得到工厂
sessionFactory = HibernateUtils.getSessionFactory();
//得到session
session = sessionFactory.openSession();
//开启事务
tx = session.beginTransaction();
/*先查后改*/
Customer cust = session.get(Customer.class, 2);
LinkMan lkm = session.get(LinkMan.class, 1);
cust.getSet_LinkMan().add(lkm);
lkm.setCustomer(cust);
//提交事务
tx.commit();
}catch(Exception e){
e.printStackTrace();
//事务回滚
tx.rollback();
}finally{
//关闭资源
session.close();//使用openSession()需要手动关闭
sessionFactory.close();
}
}
接下来是多对多的操作,有了上面的基础,接下来的就简单一些了
多对多的配置:
一般使用的是多对多的级联保存,而比较少用级联删除
配置过程和很像
一、创建实体类:用户和角色
二、两个实体类互相表示
都是使用Set互相表示
三、配置映射关系
包括基本的映射配置和关系映射
四、在核心配置文件中引入映射文件
完成后测试:
运行一下工具类,表出来即正常
多对多级联保存:
和一对多很相似
第一步是配置级联
第二步是代码实现
配置用户的级联保存:<set name="set_LinkMan" cascade="save-update">
把角色放到用户里,保存用户即可完成
步骤都是创建用户、角色对象,再将角色放入用户,保存用户,详细代码和一对多极其相似,略
用户:
package cn.entity02;
import java.util.HashSet;
import java.util.Set;
public class User {
private Integer user_id;//用户ID
private String user_name;//用户名
private String user_password;//密码
//一个用户可以有多个角色
private Set<Role> set_Role = new HashSet<Role>();
public Set<Role> getSet_Role() {
return set_Role;
}
public void setSet_Role(Set<Role> set_Role) {
this.set_Role = set_Role;
}
public User() {
}
public Integer getUser_id() {
return user_id;
}
public void setUser_id(Integer user_id) {
this.user_id = user_id;
}
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;
}
}
用户的映射配置文件:
<?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>
<!-- 映射的类 name是类的全路径 table是表名 -->
<class name="cn.entity02.User" table="t_user03">
<id name="user_id" column="user_id">
<!-- 设置id增长策略 -->
<generator class="native"></generator>
</id>
<!-- 配置其它属性 -->
<property name="user_name" column="user_name"></property>
<property name="user_password" column="user_password"></property>
<!-- 多对多的配置
name属性是角色色集合的名称
table是关系表的名字
-->
<set name="set_Role" table="user_role" cascade="save-update">
<!-- 当前表在第三张表中外键的名字 -->
<key column="user_id"></key>
<!-- class为角色的全路径,column为角色在第三张表中的外键 -->
<many-to-many class="cn.entity02.Role" column="role_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
角色:
package cn.entity02;
import java.util.HashSet;
import java.util.Set;
public class Role {
private Integer role_id;//角色ID
private String role_name;//角色名
private String role_memo;//角色描述
//一个角色可以有多个用户
private Set<User> set_User = new HashSet<User>();
public Set<User> getSet_User() {
return set_User;
}
public void setSet_User(Set<User> set_User) {
this.set_User = set_User;
}
public Role() {
}
public Integer getRole_id() {
return role_id;
}
public void setRole_id(Integer 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;
}
}
角色的映射文件:
<?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>
<!-- 映射的类 name是类的全路径 table是表名 -->
<class name="cn.entity02.Role" table="t_role">
<!-- 当前表在第三张表中外键的名字 -->
<id name="role_id" column="role_id">
<!-- 设置id增长策略 -->
<generator class="native"></generator>
</id>
<!-- 配置其它属性 -->
<property name="role_name" column="role_name"></property>
<property name="role_memo" column="role_memo"></property>
<!-- 配置和User同理 ,都是主要配置外键信息-->
<set name="set_User" table="user_role">
<key column="role_id"></key>
<many-to-many class="cn.entity02.Role" column="user_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
这里配置完一定记得去核心配置文件引入资源!
级联删除也是先配置后删除,但是用的较少,而且代码与一对多极其相似
就注意一点,删除是需要传入对象的,故一定是先查后删
多对多时注意维护第三张表:
一般思路都是通过维护第三张表来维护关系
例如让某个用户有某个角色
根据id查询到用户和角色
把角色放到用户里(getSet().add())
移除则是(getSet.remove())
这里还是得记得:持久态会更新到数据库