Hibernate 关联关系映射
Hibernate 一对多映射
以 客户关系管理系统(CRM)
为例:
Java WEB中一对多的设计及其建表原则
多方表建一个字段作为外键,指向一方表的主键。
先导入SQL的建表语句
客户表:
1 2 3 4 5 6 7 8 9 10 11 12 13
| CREATE TABLE `cst_customer` ( `cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)', `cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)', `cust_user_id` bigint(32) DEFAULT NULL COMMENT '负责人id', `cust_create_id` bigint(32) DEFAULT NULL COMMENT '创建人id', `cust_source` varchar(32) DEFAULT NULL COMMENT '客户信息来源', `cust_industry` varchar(32) DEFAULT NULL COMMENT '客户所属行业', `cust_level` varchar(32) DEFAULT NULL COMMENT '客户级别', `cust_linkman` varchar(64) DEFAULT NULL COMMENT '联系人', `cust_phone` varchar(64) DEFAULT NULL COMMENT '固定电话', `cust_mobile` varchar(16) DEFAULT NULL COMMENT '移动电话', PRIMARY KEY (`cust_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
|
联系人表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| CREATE TABLE `cst_linkman` ( `lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)', `lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名', `lkm_cust_id` bigint(32) NOT NULL COMMENT '客户id', `lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别', `lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话', `lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机', `lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱', `lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq', `lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位', `lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注', PRIMARY KEY (`lkm_id`), KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`), CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
|
编写客户和联系人的 JavaBean 程序
(注意一对多的编写规则)
一方 Set 集合,必须自己初始化;多方编写一个对象,不要自己 new !
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Customer { private Long cust_id; private String cust_name; private Long cust_user_id; private Long cust_create_id; private String cust_source; private String cust_industry; private String cust_level; private String cust_linkman; private String cust_phone; private String cust_mobile; private Set<Linkman> linkmans = new HashSet<Linkman>(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Linkman { 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_qq; private String lkm_position; private String lkm_memo; private Customer customer; }
|
编写客户和联系人的映射配置文件
注意一对多的配置编写:
1
| <many-to-one name="customer" class="com.renkaigis.domain.Customer" column="lkm_cust_id"/>
|
其中:
name
:当前 JavaBean 中的属性
class
:属性的全路径
column
:外键的字段
1 2 3 4 5 6 7
| <set name="linkmans"> <key column="lkm_cust_id"/> <one-to-many class="com.renkaigis.domain.Linkman"/> </set>
|
客户的映射配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <class name="com.renkaigis.domain.Customer" table="cst_customer"> <id name="cust_id" column="cust_id"> <generator class="native"/> </id> <property name="cust_name" column="cust_name"/> <property name="cust_user_id" column="cust_user_id"/> <property name="cust_create_id" column="cust_create_id"/> <property name="cust_source" column="cust_source"/> <property name="cust_industry" column="cust_industry"/> <property name="cust_level" column="cust_level"/> <property name="cust_linkman" column="cust_linkman"/> <property name="cust_phone" column="cust_phone"/> <property name="cust_mobile" column="cust_mobile"/> <set name="linkmans"> <key column="lkm_cust_id"/> <one-to-many class="com.renkaigis.domain.Linkman"/> </set> </class>
|
联系人的映射配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <class name="com.renkaigis.domain.Linkman" table="cst_linkman"> <id name="lkm_id" column="lkm_id"> <generator class="native"/> </id> <property name="lkm_name" column="lkm_name"/> <property name="lkm_gender" column="lkm_gender"/> <property name="lkm_phone" column="lkm_phone"/> <property name="lkm_mobile" column="lkm_mobile"/> <property name="lkm_email" column="lkm_email"/> <property name="lkm_qq" column="lkm_qq"/> <property name="lkm_position" column="lkm_position"/> <property name="lkm_memo" column="lkm_memo"/>
<many-to-one name="customer" class="com.renkaigis.domain.Customer" column="lkm_cust_id"/> </class>
|
保存客户和联系人的数据
双向关联数据保存(麻烦)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
@Test public void run1() { Session session = HibernateUtils.getCurrentSession(); Transaction tr = session.beginTransaction();
Customer c1 = new Customer(); c1.setCust_name("小虞"); Linkman l1 = new Linkman(); l1.setLkm_name("小项"); Linkman l2 = new Linkman(); l2.setLkm_name("小羽");
c1.getLinkmans().add(l1); c1.getLinkmans().add(l2);
l1.setCustome1r(c1); l2.setCustomer(c1);
session.save(c1); session.save(l1); session.save(l2);
tr.commit(); }
|
级联保存
测试:
如果现在代码只插入其中的一方的数据
级联保存效果
注意:级联保存有方向性
1 2 3 4 5
| <set name="linkmans" cascade="save-update"> <key column="lkm_cust_id"/> <one-to-many class="com.renkaigis.domain.Linkman"/> </set>
|
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Test public void run2() { Session session = HibernateUtils.getCurrentSession(); Transaction tr = session.beginTransaction();
Customer c1 = new Customer(); c1.setCust_name("小虞"); Linkman l1 = new Linkman(); l1.setLkm_name("小项"); Linkman l2 = new Linkman(); l2.setLkm_name("小羽");
c1.getLinkmans().add(l1); c1.getLinkmans().add(l2);
session.save(c1);
tr.commit(); }
|
- 相反的,要使用联系人去关联客户,那么需要在联系人的映射里面配置
cascade="save_update"
,这是只要保存了联系人,与其对应的客户也会保存或更新。
级联删除
1.数据库中删除含有外键的客户时,SQL 语句会报出错误的:delete from customers where cid = 1;
2.如果使用 Hibernate 框架直接删除客户的时候,测试发现是可以删除的;
- Hibernate 框架删除有外键的信息是,会将外键先查出来置为 null,然后执行删除操作;
3.上述的删除是普通的删除,那么也可以使用级联删除,注意:级联删除也是有方向性的!!(轻易不要使用)
1
| <many-to-one cascade="delete" />
|
级联的取值(cascade的取值)
none
– 不使用级联
save-update
– 级联保存或更新
delete
– 级联删除
delete-orphan
– 孤儿删除(注意:只能应用在一对多关系)
all
– 除了 delete-orphan 的所有情况(包含save-update delete)
all-delete-orphan
– 包含了 delete-orphan 的所有情况(包含save-update delete delete-orphan)
孤儿删除(孤子删除)
只有在一对多的环境下才有孤儿删除
- 在一对多的关系中,可以将一的一方认为是父方。将多的一方认为是子方。孤儿删除:在解除了父子关系的时候,将子方记录就直接删除。
1
| <many-to-one cascade="delete-orphan" />
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Test public void run3() { Session session = HibernateUtils.getCurrentSession(); Transaction tr = session.beginTransaction();
Customer c1 = session.get(Customer.class, 1L); Linkman l1 = session.get(Linkman.class, 1L);
c1.getLinkmans().remove(l1);
tr.commit(); }
|
放弃外键的维护
双方都维护外键的时候,会产生多余的 SQL 语句
放弃外键维护
如果不想产生多余的 SQL 语句,那么需要一方来放弃外键的维护!
只有 一方
可以放弃,多方
没有哪个属性
- 在
<set>
标签上配置一个 inverse="true"
,true:放弃,false:不放弃,默认值是false
1 2 3 4 5
| <set name="linkmans" inverse="true"> <key column="lkm_cust_id"/> <one-to-many class="com.renkaigis.domain.Linkman"/> </set>
|
cascade 和 inverse 的区别
一般情况下,在 一方
配置 inverse
放弃外键维护,在 多方
配置 cascade
来进行级联保存操作。
Hibernate 多对多映射
多对多的建表原则
多对多 JavaBean 的编写
以用户和角色为例,一个用户可以有多个角色,一个角色可以被多个用户扮演。
编写用户和角色的 JavaBean
多对多都要用 Set
集合:
1 2 3 4 5 6 7 8 9 10
| public class User { private Long user_id; private String user_code; private String user_name; private String user_password; private String user_state;
private Set<Role> roles = new HashSet<Role>(); }
|
1 2 3 4 5 6 7 8
| public class Role { private Long role_id; private String role_name; private String role_memo; private Set<User> users = new HashSet<User>(); }
|
用户和角色的映射配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <class name="com.renkaigis.domain.User" table="sys_user"> <id name="user_id" column="user_id"> <generator class="native"/> </id> <property name="user_code" column="user_code"/> <property name="user_name" column="user_name"/> <property name="user_password" column="user_password"/> <property name="user_state" column="user_state"/>
<set name="roles" table="sys_user_role"> <key column="user_id"/>
<many-to-many class="com.renkaigis.domain.Role" column="role_id"/> </set> </class>
|
1 2 3 4 5 6 7 8 9 10 11 12
| <class name="com.renkaigis.domain.Role" table="sys_role"> <id name="role_id" column="role_id"> <generator class="native"/> </id> <property name="role_name" column="role_name"/> <property name="role_memo" column="role_memo"/> <set name="users" table="sys_user_role"> <key column="role_id"/> <many-to-many class="com.renkaigis.domain.User" column="user_id"/> </set> </class>
|
关联
多对多进行双向关联的时候:必须有一方去放弃外键维护权。
否则会发生异常!
级联保存
级联保存
1
| <set cascade="save-update">
|
级联删除
(在多对多中是很少使用的)
开发中不用!
操作中间表
操作集合,就是操作中间表。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public void run(){ Session session = HibernateUtils.getCurrentSession(); Transaction tr = session.beginTransaction(); User u1 = session.get(User.class, 1L); Role r2 = session.get(Role.class, 2L); u1.getRoles().remove(r2); tr.commit(); }
|