一、hibernate一对多
1.1 一对多映射配置
以客户和联系人为例:客户是一,联系人是多。
第一步:创建两个实体类,客户和联系人
第二步:让两个实体类之间互相表示。
- 让客户实体类里面表示多个联系人。一个客户可以对应多个联系人,这里装载联系人,装载的容器用的是Set集合,而不是LinkedList,因为Set集合的特点是,值不能够重复。
- 在联系人实体类里面表示所属客户。一个联系人只能属于一个客户。
public class Customer {
private Integer cid;//客户id
private String custName;//客户姓名
private String custLevel;//客户级别
private String custSource;//客户来源
private String custPhone;//客户电话
private String custMobile;//手机
//在客户实体类里表示多个联系人,一个客户有多个联系人
//hibernate要求使用集合表示多的数据,使用set集合
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 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 Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public LinkMan() {
super();
}
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 + "]";
}
}
第三步:配置映射关系
- 一般一个实体类对应一个映射文件
- 把映射最基本配置完成
- 在映射文件中,配置一对多关系(重点)
在客户的映射文件(Customer.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>
<!-- 1. 配置类和表 -->
<class name="xxx.entity.Customer" table="Customer">
<!-- 2.配置实体类id和表id对应 -->
<id name="cid" 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="linkMans" >
<!-- 一对多建表,有外键。
hibernate机制:双向维护外键,在一和多两方都配置外键。
column属性值:外键名称,这里的外键名称可以随便写。
-->
<key column="clid"></key>
<!-- 客户所有的联系人,class里面写联系人实体类全路径 -->
<one-to-many class="xxx.entity.LinkMan"/>
</set>
</class>
</hibernate-mapping>
在联系人映射文件(LinkMan.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>
<!-- 1. 配置类和表 -->
<class name="xxx.entity.LinkMan" table="LinkMan">
<!-- 2.配置实体类id和表id对应 -->
<id name="lid" column="lid">
<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="xxx.entity.Customer" column="clid"></many-to-one>
</class>
</hibernate-mapping>
第四步:把映射文件引入到核心配置文件中。
<mapping resource="xxx/entity/Customer.hbm.xml"/>
<mapping resource="xxx/entity/LinkMan.hbm.xml"/>
第五步:写测试类。
最后:运行项目,让表被创建。
可以看到结果,客户表和联系人表进行了关联,联系人引入客户表的id作为外键。
1.2 一对多级联操作
1.2.1 级联保存
添加一个客户,为这个客户添加了多个联系人。
@Test
public void testAddDemo1() {
SessionFactory factory = null;
Session session = null;
Transaction ts = null;
try {
factory = HibernateUtils.getSessionFactory();
session = factory.openSession();
ts = session.beginTransaction();
// 添加一个客户为这个客户添加联系人
// 1 创建客户和联系人对象
Customer customer = new Customer("网易", "普通", "网络", "110", "120");
LinkMan linkMan = new LinkMan("小马", "男", "123");
// 2 建立客户和联系人对象的联系
// 2.1 将联系人对象放到客户对象set集合中
customer.getLinkMans().add(linkMan);
// 2.2 将客户对象放到联系人对象中
linkMan.setCustomer(customer);
// 3 保存到数据库
session.save(customer);
session.save(linkMan);
// 提交事务
ts.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
ts.rollback();
} finally {
// 关闭资源
session.close();
factory.close();
}
}
简化操作:
在客户映射文件中进行配置,在se属性中t进行配置:
<set name="linkMans" cascade="save-update">
简化后代码:
// 添加一个客户为这个客户添加联系人
// 1 创建客户和联系人对象
Customer customer = new Customer("网易", "普通", "网络", "110", "120");
LinkMan linkMan = new LinkMan("小马", "男", "123");
// 2 建立客户和联系人对象的联系:将联系人对象放到客户对象set集合中
customer.getLinkMans().add(linkMan);
// 3 保存到数据库
session.save(customer);
注:在这里就只需要把联系人(多的那方)放入到客户方(一的那方),就可以了。
最后保存的时候就只需要保存客户方(一的那方)
1.2.2 级联删除
删除某一个客户,这个客户里面的所有的联系人也删除。
映射文件配置
<set name="linkMans" cascade="save-update,delete">
<key column="clid"></key>
<one-to-many class="xxx.entity.LinkMan"/>
</set>
测试代码
@Test
public void testDeleteDemo() {
SessionFactory factory = null;
Session session = null;
Transaction ts = null;
try {
factory = HibernateUtils.getSessionFactory();
session = factory.openSession();
ts = session.beginTransaction();
// 1 根据id查询客户对象
Customer customer = session.get(Customer.class, 2);
// 2 调用方法删除
session.delete(customer);
// 提交事务
ts.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
ts.rollback();
} finally {
// 关闭资源
session.close();
factory.close();
}
}
SQL语句分析
1 根据id查询客户
Hibernate:
select
customer0_.cid as cid1_0_0_,
customer0_.custName as custName2_0_0_,
customer0_.custLevel as custLeve3_0_0_,
customer0_.custSource as custSour4_0_0_,
customer0_.custPhone as custPhon5_0_0_,
customer0_.custMobile as custMobi6_0_0_
from
Customer customer0_
where
customer0_.cid=?
2 根据id查询联系人
Hibernate:
select
linkmans0_.clid as clid5_1_0_,
linkmans0_.lid as lid1_1_0_,
linkmans0_.lid as lid1_1_1_,
linkmans0_.lname as lname2_1_1_,
linkmans0_.lgender as lgender3_1_1_,
linkmans0_.lphone as lphone4_1_1_,
linkmans0_.clid as clid5_1_1_
from
LinkMan linkmans0_
where
linkmans0_.clid=?
3 把联系人外键设为null
Hibernate:
update
LinkMan
set
clid=null
where
clid=?
4 删除联系人
Hibernate:
delete
from
LinkMan
where
lid=?
5 删除客户
Hibernate:
delete
from
Customer
where
cid=?
1.3.3 级联修改
现在是让一个联系人的客户变成另外一个。我们需要修改的就是某一个员工的外键值。
普通更新操作
@Test
public void testUpdateDemo() {
SessionFactory factory = null;
Session session = null;
Transaction ts = null;
try {
factory = HibernateUtils.getSessionFactory();
session = factory.openSession();
ts = session.beginTransaction();
// 1 根据id查询联系人,根据id查询客户
Customer customer = session.get(Customer.class, 2);
LinkMan linkMan = session.get(LinkMan.class, 1);
// 2 设置持久态的值
customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);
// 提交事务
ts.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
ts.rollback();
} finally {
// 关闭资源
session.close();
factory.close();
}
}
sql代码分析
Hibernate:
select
customer0_.cid as cid1_0_0_,
customer0_.custName as custName2_0_0_,
customer0_.custLevel as custLeve3_0_0_,
customer0_.custSource as custSour4_0_0_,
customer0_.custPhone as custPhon5_0_0_,
customer0_.custMobile as custMobi6_0_0_
from
Customer customer0_
where
customer0_.cid=?
Hibernate:
select
linkman0_.lid as lid1_1_0_,
linkman0_.lname as lname2_1_0_,
linkman0_.lgender as lgender3_1_0_,
linkman0_.lphone as lphone4_1_0_,
linkman0_.clid as clid5_1_0_
from
LinkMan linkman0_
where
linkman0_.lid=?
Hibernate:
select
linkmans0_.clid as clid5_1_0_,
linkmans0_.lid as lid1_1_0_,
linkmans0_.lid as lid1_1_1_,
linkmans0_.lname as lname2_1_1_,
linkmans0_.lgender as lgender3_1_1_,
linkmans0_.lphone as lphone4_1_1_,
linkmans0_.clid as clid5_1_1_
from
LinkMan linkmans0_
where
linkmans0_.clid=?
Hibernate:
update
LinkMan
set
lname=?,
lgender=?,
lphone=?,
clid=?
where
lid=?
Hibernate:
update
LinkMan
set
clid=?
where
lid=?
由上可见:我们修改了两次外键--两次Update。
原因:因为hibernate双向维护外键,在客户和联系人里面都需要维护外键,修改客户时候修改一次外键,修改联系人时有修改一次外键,造成效率问题
解决方式:让其中的一方不维护外键。
- 一对多里面,让其中一方放弃外键维护。
- 一个国家有总统,国家有很多人,总统不可能认识国家所有人,国家所有人可以认识总统。
- 即让一的那方不维护外键。
具体实现:
在放弃关系维护映射文件中,进行配置,在set标签上使用inverse属性。(inverse:反向)
inverse:
- 默认值为false;
- true表示放弃关系维护
- false表示不放弃关系维护
- 目的:提高性能问题。
<set name="linkMans" cascade="save-update,delete" inverse="true">
<!-- 一对多建表,有外键。
hibernate机制:双向维护外键,在一和多两方都配置外键。
column属性值:外键名称,这里的外键名称可以随便写。
-->
<key column="clid"></key>
<!-- 客户所有的联系人,class里面写联系人实体类全路径 -->
<one-to-many class="xxx.entity.LinkMan"/>
</set>
再次运行,查看SQL结果:
Hibernate:
select
customer0_.cid as cid1_0_0_,
customer0_.custName as custName2_0_0_,
customer0_.custLevel as custLeve3_0_0_,
customer0_.custSource as custSour4_0_0_,
customer0_.custPhone as custPhon5_0_0_,
customer0_.custMobile as custMobi6_0_0_
from
Customer customer0_
where
customer0_.cid=?
Hibernate:
select
linkman0_.lid as lid1_1_0_,
linkman0_.lname as lname2_1_0_,
linkman0_.lgender as lgender3_1_0_,
linkman0_.lphone as lphone4_1_0_,
linkman0_.clid as clid5_1_0_
from
LinkMan linkman0_
where
linkman0_.lid=?
Hibernate:
select
linkmans0_.clid as clid5_1_0_,
linkmans0_.lid as lid1_1_0_,
linkmans0_.lid as lid1_1_1_,
linkmans0_.lname as lname2_1_1_,
linkmans0_.lgender as lgender3_1_1_,
linkmans0_.lphone as lphone4_1_1_,
linkmans0_.clid as clid5_1_1_
from
LinkMan linkmans0_
where
linkmans0_.clid=?
Hibernate:
update
LinkMan
set
lname=?,
lgender=?,
lphone=?,
clid=?
where
lid=?
可以看到这里update只更新了一次;设置inverse=true,只是解决对更新所属关系时带来的效率问题。
二、hibernate多对多
2.1 多对多的映射配置:以用户和角色为例
第一步:创建实体类,用户和角色。
第二步:让两个实体类之间互相表示。
- 一个用户里面表示多个角色,使用set集合
- 一个角色里面表示多个用户,使用set集合
第三步:配置映射关系
(1)基本配置
(2)配置多对多的关系
第四步:在核心配置文件中引入映射文件
第五步:测试:使用工具类测试
1. user实体类
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;
}
}
2. Role实体类
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;
}
}
3. User.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>
<!-- 1. 配置类和表 -->
<class name="xxx.manytomany.User" table="t_user">
<!-- 2.配置实体类id和表id对应 -->
<id name="uid" column="uid">
<generator class="native"></generator>
</id>
<!-- 设置其他属性和表字段类型 -->
<property name="uname"/>
<property name="pword"/>
<!--在用户里面表示角色,使用set标签
name属性:角色set集合名称
table属性:第三张表名称
-->
<set name="roleSet" table="user_role">
<!-- key标签里面配置:
配置当前映射文件在第三张表外键名称
-->
<key column="userid"></key>
<!--
class:角色实体类全路径
column:角色在第三张表外键名称
-->
<many-to-many class="xxx.manytomany.Role" column="roleid"></many-to-many>
</set>
</class>
</hibernate-mapping>
4. Role.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>
<!-- 1. 配置类和表 -->
<class name="xxx.manytomany.Role" table="t_role">
<!-- 2.配置实体类id和表id对应 -->
<id name="rid" column="rid">
<generator class="native"></generator>
</id>
<!-- 设置其他属性和表字段类型 -->
<property name="rname"/>
<property name="rmemo"/>
<!--在用户里面表示角色,使用set标签
name属性:角色set集合名称
table属性:第三张表名称
-->
<set name="userSet" table="user_role">
<!-- key标签里面配置
配置当前映射文件在第三张表外键名称
-->
<key column="roleid"></key>
<!-- class:用户实体类全路径
column:用户在第三张表外键名称 -->
<many-to-many class="xxx.manytomany.User" column="userid"></many-to-many>
</set>
</class>
</hibernate-mapping>
5. 在核心配置文件中引入配置文件
<mapping resource="xxx/manytomany/User.hbm.xml"/>
<mapping resource="xxx/manytomany/Role.hbm.xml"/>
2.2 级联保存
第一步:在用户配置文件中set标签进行配置,cascade值save-update,
<set name="roleSet" table="user_role" cascade="save-update">
第二步:写代码实现
- 创建用户和角色对象,把角色放到用户里面,最终保存用户就可以了。
//多对多保存实现
@Test
public void testAddDemo() {
SessionFactory factory = null;
Session session = null;
Transaction ts = null;
try {
factory = HibernateUtils.getSessionFactory();
session = factory.openSession();
ts = session.beginTransaction();
// 添加两个用户,为这两个用户各添加两个角色
// 1 创建对象
User user1 = new User();
user1.setUname("小明");
user1.setPword("123");
User user2 = new User();
user2.setUname("小马");
user2.setPword("123");
Role role1 = new Role();
role1.setRname("经理");
role1.setRmemo("经理");
Role role2 = new Role();
role2.setRname("秘书");
role2.setRmemo("秘书");
Role role3 = new Role();
role3.setRname("客服");
role3.setRmemo("客服");
// 2 建立联系,将角色分配给用户
// user1 --- r1/r2
user1.getRoleSet().add(role1);
user1.getRoleSet().add(role2);
// user2 --- r2/r3
user2.getRoleSet().add(role2);
user2.getRoleSet().add(role3);
// 3 保存到数据库
session.save(user1);
session.save(user2);
// 提交事务
ts.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
ts.rollback();
} finally {
// 关闭资源
session.close();
factory.close();
}
}
2.3 级联删除
第一步:在set标签中配置cascade属性,值为delete;如果有多个值记得用”,”号隔开,比如cascade=”save-update,delete”;
第二步:直接进行删除。
注:
对于级联删除,在我们一对多的时候还可以用,也挺方便的。但是在我们的多对多的里面就显得有些鸡肋了,因为,假如我们删除用户表里面的某个用户,那么就会把该用户和角色表里面属于该用户角色属性的角色以及第三张表涉及到该用户的都会删掉。而我们一般角色是不能够删除的,因为有可能其它用户还有该角色,如果你把别人的角色删掉了,那么其它用户和该角色在第三张表中的关系也会被删掉的。
所以我们不推荐使用级联删除来操作多对多关系。
我们应该做的是维护第三张表,也就是用户和角色的主键组成的一个外建表。
2.4 维护第三张表关系
用户和角色多对多关系,维护关系通过第三张表维护
2.4.1 让某个用户有某个角色
第一步:根据id查询用户和角色
第二步:把角色放到用户里面:放到set集合
@Test
public void testTable1() {
SessionFactory factory = null;
Session session = null;
Transaction ts = null;
try {
factory = HibernateUtils.getSessionFactory();
session = factory.openSession();
ts = session.beginTransaction();
// 让1拥有3的角色
// 1 查找对象
User user1 = session.get(User.class, 1);
Role role3 = session.get(Role.class, 3);
// 2 建立联系,将角色分配给用户
user1.getRoleSet().add(role3);
// 提交事务
ts.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
ts.rollback();
} finally {
// 关闭资源
session.close();
factory.close();
}
}
2.4.2 让某个用户没有某个角色
@Test
public void testTable2() {
SessionFactory factory = null;
Session session = null;
Transaction ts = null;
try {
factory = HibernateUtils.getSessionFactory();
session = factory.openSession();
ts = session.beginTransaction();
// 让2没有3的角色
// 1 查找对象
User user2 = session.get(User.class, 2);
Role role3 = session.get(Role.class, 3);
// 2 建立联系,用户移除角色
user2.getRoleSet().remove(role3);
// 提交事务
ts.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
ts.rollback();
} finally {
// 关闭资源
session.close();
factory.close();
}
}