Hibernate一对多,多对多操作

时间:2021-12-13 11:58:22

表与表之间的关系

一对多:

  • 分类和商品关系,一个分类里面有多个商品,一个商品只能属于一个分类。

客户和联系人是一对多关系:

联系人在人际交流中担任着重要的角色,在销售过程中,我们通常不是在最开始就联系到有决策权的高管,而有可能是接电话的前台A、营销人员B、客服C、技术人员D等等。这些人都是我们与企业保持交流的联系人。他们对产品的支持态度决定了产品是否能够顺利推送到高管面前。

通常销售人员在跟进一个客户时,会有一个或者多个联系人,这些联系人所处的职位决定了他们的角色。

  • 客户:与公司有业务往来,比如淘宝商家与淘宝就有业务往来,淘宝商家和淘宝谈判想要让自己的商品在淘宝上架,其中淘宝就是客户。
  • 联系人:为了和淘宝联系,淘宝公司里面的员工等就是联系人。

也就是说客户和联系人的关系是:
公司和公司员工的关系。

  • 一个公司可以有多个员工,即一个客户可以有多个联系人。
  • 一个联系人只能有一个客户,即一个员工只能有一个公司。

Hibernate一对多,多对多操作

多对多:

  • 订单和商品关系,一个订单里面有多个商品,一个商品属于多个订单。
  • 老师和学生,一个老师可以教多个学生,一个学生可以被多个老师教。

Hibernate一对多,多对多操作

一对一:

Hibernate中的一对多操作(重点)

一对多映射配置:

以客户和联系人为例:客户是一,联系人是多。

第一步:创建两个实体类,客户和联系人:

第二步:让两个实体类之间互相表示。

  • 让客户实体类里面表示多个联系人。

一个客户可以对应多个联系人。

这里装载联系人,装载的容器用的是Set集合,而不是LinkedList,因为Set集合的特点是,值不能够重复。

  • 在联系人实体类里面表示所属客户。

一个联系人只能属于一个客户。

//客户的实体类:
public class Customer {

private Integer cid;
private String custName;
private String custLevel;
private String custSource;
private String custPhone;
private String custMobile;
private Set<LinkMan> linkMans=new HashSet<LinkMan>();


public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
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;
}
public Customer(String custName, String custLevel, String custSource, String custPhone,
String custMobile) {
super();
this.custName = custName;
this.custLevel = custLevel;
this.custSource = custSource;
this.custPhone = custPhone;
this.custMobile = custMobile;
}
public Customer() {
super();
}
@Override
public String toString() {
return "Customer [cid=" + cid + ", custName=" + custName + ", custLevel=" + custLevel + ", custSource="
+ custSource + ", custPhone=" + custPhone + ", custMobile=" + custMobile + "]";
}
}
//联系人实体类
public class LinkMan {
private Integer lid;
private String lname;
private String lgender;
private String lphone;
private Customer customer;


public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public Integer getLid() {
return lid;
}
public void setLid(Integer lid) {
this.lid = lid;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
public String getLgender() {
return lgender;
}
public void setLgender(String lgender) {
this.lgender = lgender;
}
public String getLphone() {
return lphone;
}
public void setLphone(String lphone) {
this.lphone = lphone;
}
public LinkMan() {
super();
// TODO Auto-generated constructor stub
}
public LinkMan(String lname, String lgender, String lphone) {
super();
this.lname = lname;
this.lgender = lgender;
this.lphone = lphone;
}
@Override
public String toString() {
return "LinkMan [lid=" + lid + ", lname=" + lname + ", lgender=" + lgender + ", lphone=" + lphone + "]";
}
}

第三步:配置映射关系

  • 一般一个实体类对应一个映射文件
  • 把映射最基本配置完成
  • 在映射文件中,配置一对多关系(重点)

在客户的映射文件中,表示所有的联系人

<class name="类的全路径" table="表名">

<id name="id" column="cid">//实体类属性名以及表字段名
<generator class="native"></generator>
</id>
<property name="custName"></property>
<property name="custLevel"></property>
<property name="custSource"></property>
<property name="custPhone"></property>
<property name="custMobile"></property>


<!--
在客户映射文件中,表示所有联系人
使用set标签表示所有联系人
set标签里面有name属性:属性值写,在客户实体类里面表示联系人的set集合名称
-->


<set name="setLinkMan">
<!--
一对多建表,有外键
hibernate机制:双向维护外键,在一和多两方都配置外键。
column属性值:外键名称。这里的外键名称可以随便写。
-->

<key column="clid"></key>

<!-- 客户所有的联系人,class里面写联系人实体类全路径 -->
<one-to-many class="联系人实体类全路径" />
</set>
</class>

在联系人映射文件中,表示所属客户

<class name="实体类的全路径" table="表名">
<id name="lid"><!-- 当column不写时,名称和name的名称一样 -->
<generator class="native"></generator>
</id>
<property name="lname"></property>
<property name="lgender"></property>
<property name="lphone"></property>

<!--
表示联系人所属客户
name属性:联系人实体类被装载到Customer中,这里写customer名称
class属性:customer全路径
column属性:外键名称
-->

<many-to-one name="customer" class="customer全路径" column="clid"></many-to-one>
</class>

第四步:创建核心配置文件,把映射文件引入到核心配置文件中。

<mapping resource="cn/domarvel/entity/Customer.hbm.xml"/>
<mapping resource="cn/domarvel/entity/LinkMan.hbm.xml"/>

第五步:写测试类:

最后:运行项目,让表被创建。

可以看到结果,客户表和联系人表进行了关联,联系人引入客户表的id作为外键。

一对多级联操作

这些操作Hibernate都帮你封装好了。

级联操作

级联保存:

  • 添加一个客户,为这个客户添加了多个联系人。
//在没有进行简化前,需要将联系人和客户的关系在代码行中进行说明。
@Test
public void showCreate(){
Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

Customer customer=new Customer("百度", "vip", "网络", "110", "120");
LinkMan linkMan=new LinkMan("侯松", "男", "123");

customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);//注意在没有进行配置简化配置的时候还需要让联系人实体类和客户实体类关联。
session.save(customer);
session.save(linkMan);

transaction.commit();
} catch (Exception e) {
transaction.rollback();
}
}

简化操作:

在映射文件的客户方,也就是一对多的一这方进行配置:

<set name="linkMans" cascade="save-update"><!-- 这里配置了一个属性cascade,值为save-update,表示对保存和更新操作,做了简化,不用对多的那方

设置和一的那方的关系。 -->

<key column="clid"></key>
<one-to-many class="cn.domarvel.entity.LinkMan"/>
</set>
//简化后代码

@Test
public void showCreate02(){
Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

Customer customer=new Customer("腾讯", "svip", "网络", "110", "120");
LinkMan linkMan=new LinkMan("FireLang", "女", "123");

customer.getLinkMans().add(linkMan);//注意在这里就只需要把联系人(多的那方)放入到客户方(一的那方),就可以了。
session.save(customer);//最后保存的时候就只需要保存客户方(一的那方)

transaction.commit();
} catch (Exception e) {
transaction.rollback();
}
}

级联删除:

  • 删除某一个客户,这个客户里面的所有的联系人也删除。

这里就直接上简化后的代码了。

<!-- 配置映射文件 -->

<!-- 在一的那方配置删除的简化,当要想直接删除一的那方,Hibernate自动帮你删除相关联的多的那方 -->

<set name="linkMans" cascade="save-update,delete"><!-- 在cascade里面配置delete,多个值用","分割 -->
<key column="clid"></key>
<one-to-many class="cn.domarvel.entity.LinkMan"/>
</set>
//测试代码
@Test
public void showDelete(){
//在以前我们要删除一的那方,是先根据一的那方删除多的那方,再删除一的那方。
//现在Hibernate已经帮我们封装好了,我们只需要配置好,再直接删除一的那方就行了。

Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

Customer customer=session.get(Customer.class, 2);
session.delete(customer);

transaction.commit();
} catch (Exception e) {
transaction.rollback();
}

}

级联修改:

操作:现在是让一个联系人的客户变成另外一个。我们需要修改的就是某一个员工的外键值。

我们的普通更新操作:

    @Test
public void showUpdate(){
//下面通过一个例子引入Hibernate中作更新操作时能够使用的一个关键字,目的是能够简化更新操作

Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

Customer customer=session.get(Customer.class, 1);
LinkMan linkMan=session.get(LinkMan.class, 3);

linkMan.setCustomer(customer);//因为这里的数据是持久态的所以我们直接设置值后,Hibernate会帮我们自动更新数据。
customer.getLinkMans().add(linkMan);

transaction.commit();
} catch (Exception e) {
transaction.rollback();
}
}
//通过输出底层sql代码可以知道

Hibernate:
select
customer0_.cid as cid1_1_0_,
customer0_.custName as custName2_1_0_,
customer0_.custLevel as custLeve3_1_0_,
customer0_.custSource as custSour4_1_0_,
customer0_.custPhone as custPhon5_1_0_,
customer0_.custMobile as custMobi6_1_0_
from
customer customer0_
where
customer0_.cid=?
Hibernate:
select
linkman0_.lid as lid1_2_0_,
linkman0_.lname as lname2_2_0_,
linkman0_.lgender as lgender3_2_0_,
linkman0_.lphone as lphone4_2_0_,
linkman0_.clid as clid5_2_0_
from
linkman linkman0_
where
linkman0_.lid=?
Hibernate:
select
linkmans0_.clid as clid5_2_0_,
linkmans0_.lid as lid1_2_0_,
linkmans0_.lid as lid1_2_1_,
linkmans0_.lname as lname2_2_1_,
linkmans0_.lgender as lgender3_2_1_,
linkmans0_.lphone as lphone4_2_1_,
linkmans0_.clid as clid5_2_1_
from
linkman linkmans0_
where
linkmans0_.clid=?
Hibernate:
update
linkman
set
lname=?,
lgender=?,
lphone=?,
clid=?
where
lid=?
Hibernate:
update
linkman
set
clid=?
where
lid=?

//在这里进行了重复的修改外键的操作,修改两次的原因是Hibernate双向维护外键。

inverse属性

  • 在一对多里面,当我们更新一对多的关系的时候,更新联系人所属的客户的时候。也就是上面的代码。我们设置linkMan.setCustomer(customer);。达到了更新联系人所属关系。但是从上面的sql语句输出的情况可以看出。有两次的update操作。这两次是怎么来的呢???一次是我们自己写的。linkMan.setCustomer(customer);另外一次是Hibernate帮我们修改外键关系的。所以这里就重复了update语句。使得修改关系时效率降低了。(注意:当我们修改多的那方的时候,不是修改的所属关系,而是其它的数据,那么就不会有两次更新。)那么我们该怎么解决更新两次的问题呢???那这里就得靠inverse属性了。

解决方式:让其中的一方不维护外键。

  • 一对多里面,让其中一方放弃外键维护。
  • 一个国家有总统,国家有很多人,总统不可能认识国家所有人,国家所有人可以认识总统。
  • 即让一的一方不维护外键。

具体实现:

在放弃关系维护映射文件中,进行配置,在set标签上使用inverse属性。(inverse:反向)

inverse:

  • 默认值为false;
  • true表示放弃关系维护
  • false表示不放弃关系维护
  • 目的:提高性能问题。

//在这里不需要对java代码进行修改,只需要进行配置就可以了。这里还是用的是上面的问题代码。

//在一的一方进行配置,目的,不让一的一方反向维护
<set name="linkMans" cascade="save-update,delete" inverse="true">
<key column="clid"></key>
<one-to-many class="cn.domarvel.entity.LinkMan"/>
</set>

再次作关系更新操作,看一下运行结果:

Hibernate: 
select
customer0_.cid as cid1_1_0_,
customer0_.custName as custName2_1_0_,
customer0_.custLevel as custLeve3_1_0_,
customer0_.custSource as custSour4_1_0_,
customer0_.custPhone as custPhon5_1_0_,
customer0_.custMobile as custMobi6_1_0_
from
customer customer0_
where
customer0_.cid=?
Hibernate:
select
linkman0_.lid as lid1_2_0_,
linkman0_.lname as lname2_2_0_,
linkman0_.lgender as lgender3_2_0_,
linkman0_.lphone as lphone4_2_0_,
linkman0_.clid as clid5_2_0_
from
linkman linkman0_
where
linkman0_.lid=?
Hibernate:
select
linkmans0_.clid as clid5_2_0_,
linkmans0_.lid as lid1_2_0_,
linkmans0_.lid as lid1_2_1_,
linkmans0_.lname as lname2_2_1_,
linkmans0_.lgender as lgender3_2_1_,
linkmans0_.lphone as lphone4_2_1_,
linkmans0_.clid as clid5_2_1_
from
linkman linkmans0_
where
linkmans0_.clid=?
Hibernate:
update
linkman
set
lname=?,
lgender=?,
lphone=?,
clid=?
where
lid=?

//可以看到这里update只更新了一次
//这里要注意:我们设置inverse=true,只是解决对更新所属关系时带来的效率问题。

这里有篇文章大家一定要看看(inverse和cascade的区别,以及一对多添加操作时外键为Null的问题)

Hibernate多对多操作

多对多映射配置

  • 以用户和角色为例

第一步:创建实体类,用户和角色。
第二步:让两个实体类之间互相表示。

  • 一个用户里面表示所有角色,使用set集合
public class User {
private Integer uid;
private String uname;
private String pword;
private Set<Role> roleSet=new HashSet<Role>();

public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getPword() {
return pword;
}
public void setPword(String pword) {
this.pword = pword;
}
public Set<Role> getRoleSet() {
return roleSet;
}
public void setRoleSet(Set<Role> roleSet) {
this.roleSet = roleSet;
}
@Override
public String toString() {
return "User [uid=" + uid + ", uname=" + uname + ", pword=" + pword + ", roleSet=" + roleSet + "]";
}
public User() {
super();
// TODO Auto-generated constructor stub
}
public User( String uname, String pword, Role role) {
super();
this.uname = uname;
this.pword = pword;
roleSet.add(role);
}
}
  • 一个角色有多个用户,使用set集合
public class Role {
private Integer rid;
private String rname;
private String rmemo;
private Set<Role> userSet=new HashSet<Role>();


public Set<Role> getUserSet() {
return userSet;
}
public void setUserSet(Set<Role> userSet) {
this.userSet = userSet;
}
public Integer getRid() {
return rid;
}
public void setRid(Integer rid) {
this.rid = rid;
}
public String getRname() {
return rname;
}
public void setRname(String rname) {
this.rname = rname;
}
public String getRmemo() {
return rmemo;
}
public void setRmemo(String rmemo) {
this.rmemo = rmemo;
}
@Override
public String toString() {
return "Role [rid=" + rid + ", rname=" + rname + ", rmemo=" + rmemo + "]";
}
public Role(String rname, String rmemo) {
super();
this.rname = rname;
this.rmemo = rmemo;
}
public Role() {
super();
// TODO Auto-generated constructor stub
}
}

第三步:配置映射关系

  • 基本配置
  • 配置多对多关系

  • 在用户里面表示所有角色,使用set标签

<?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>
<class name="cn.domarvel.manytomany.Userb" table="userb">
<!-- table是要生成的表名 -->
<id name="uid">
<generator class="native"></generator>
</id>
<property name="uname"/>
<property name="pword"/>
<!-- property里面的column属性可以不用写,不写就默认和property里面的name属性值相同 -->

<set name="roleSet" table="user_role" cascade="save-update">
<!--
在用户里面表示角色,使用set标签
name属性:角色set集合名称
table属性:第三张表名称
-->

<!--
注意如果还没有在其它映射文件中配置表名,那么第一次配置就可以乱写,
但是如果有一张表已经声明说表名叫什么了,那就不能够再乱写
-->

<key column="fuid"/>
<!--
key标签里面配置:
配置当前映射文件在第三张表外键名称
-->


<many-to-many class="cn.domarvel.manytomany.Role" column="frid"></many-to-many>
<!--
class:角色实体类全路径
column:角色在第三张表外键名称
-->

</set>
</class>
</hibernate-mapping>
  • 在角色里面表示所有用户,使用set标签
<?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>
<class name="cn.domarvel.entity.Role" table="role">
<id name="rid">
<generator class="native"></generator>
</id>
<property name="rname"/>
<property name="rmemo"/>

<set name="userSet" table="user_role">
<key column="frid"></key>
<many-to-many class="cn.domarvel.entity.User" column="fuid"></many-to-many>
</set>
</class>
</hibernate-mapping>

Hibernate一对多,多对多操作

第四步:在核心配置文件中引入映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>

<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>

<property name="hibernate.hbm2ddl.auto">update</property>


<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.current_session_context_class">thread</property>

<mapping resource="cn/domarvel/entity/Role.hbm.xml"/>
<mapping resource="cn/domarvel/entity/User.hbm.xml"/><!-- 引入映射配置文件到核心配置文件 -->
</session-factory>
</hibernate-configuration>

多对多级联保存

根据用户保存角色:

第一步:在用户配置文件中set标签进行配置,cascade值save-update,意思是对级联操作进行了简化,比如说:当我一对多的时候,我们以前是先删除多的那方再删除一的那方,或者先保存多的那方再保存一的那方。但是现在不用了如果我们设置了save-update或者delete,那我们只需要删除一的那方或者保存一的那方就行了,但前提是必须要先把多的那方保存到一的那方里面才可以使用。这也就是简化操作。

<set name="roleSet" table="user_role" cascade="save-update"><!-- 注意cascade是配置到哪儿里后,在保存的时候就保存哪儿一个被配置的实体。并且

另外一个实体就保存到被配置的实体里面。这里配置的是User所以就是根据User保存角色 -->

第二步:写代码实现

  • 创建用户和角色对象,把角色放到用户里面,最终保存用户就可以了。
    @Test
public void showAddManyToM(){
Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

User user=new User("LangSheng", "123456",new Role("开发工程师", "专注于网页前后台开发,和图像算法!!"));
user.getRoleSet().add(new Role("架构师", "进行程序开发架构!"));
User user2=new User("FireLang", "456123", new Role("狼神总部副总裁", "国际知名企业!!"));
user2.getRoleSet().add(new Role("图像算法师", "通过代码对程序的算法处理!以及识别!!"));

session.save(user);
session.save(user2);
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
}

多对多级联删除

对于级联删除

在完成上面级联删除的操作后,对于级联删除就变得很简单了。

第一步:在set标签中配置cascade属性,值为delete;如果有多个值记得用”,”号隔开,比如cascade=”save-update,delete”;

第二步:直接进行删除。

对于级联删除,在我们一对多的时候还可以用,也挺方便的。但是在我们的多对多的里面就显得有些鸡肋了,因为,假如我们删除用户表里面的某个用户,那么就会把该用户和角色表里面属于该用户角色属性的角色以及第三张表涉及到该用户的都会删掉。而我们一般角色是不能够删除的,因为有可能其它用户还有该角色,如果你把别人的角色删掉了,那么其它用户和该角色在第三张表中的关系也会被删掉的。

所以我们不推荐使用级联删除来操作多对多关系。

我们应该做的是维护第三张表,也就是用户和角色的主键组成的一个外建表。

维护第三张表关系

用户和角色多对多关系,维护关系通过第三张表维护

让某个用户有某个角色

第一步:根据id查询用户和角色

第二步:把角色放到用户里面

  • 把角色对象放到用户set集合
    @Test
public void showCreateRelation(){
Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

User user=session.get(User.class, 4);
Role role=session.get(Role.class, 6);
user.getRoleSet().add(role);

transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
}

让某个用户没有某个角色

    @Test
public void showRemoveReation(){
Transaction transaction=null;
try {
Session session=HibernateUtils.getCurrentSession();
transaction=session.beginTransaction();

User user=session.get(User.class, 4);
Role role=session.get(Role.class, 6);
user.getRoleSet().remove(role);

transaction.commit();
} catch (Exception e) {
e.printStackTrace();
transaction.rollback();
}
}