一、 一对多关系映射
数据库环境准备:
CREATE TABLE `cst_customer` (
`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
`cust_name` varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
`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=5 DEFAULT CHARSET=utf8;
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;
1.1 一对多关系映射
首先观察以上表格,一个客户表,一个联系人表。
其中客户表中有一个id是1的客户,但是一个客户中对应着有很多联系人保存在联系人表中(其中cid为外键)
——这样就形成了这样一个关系:一个客户有多个联系人,一条客户表记录对应着多条联系人表记录
1.2 一对多关系映射的hibernate实现
在hibernate框架中,我们如何用实体类来对应上面的一对多关系呢?
Customer(客户表:少) —— Linkman(联系人表:多)
1.2.1 书写相关文件
LinkMan.java
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;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
/**
* 省略众多get/set方法
*/
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 >
<class name="com.sannian.bean.LinkMan" table="cst_linkman">
<id name="lkm_id">
<generator class="native"></generator>
</id>
<property name="lkm_name"></property>
<property name="lkm_gender"></property>
<property name="lkm_phone"></property>
<property name="lkm_mobile"></property>
<property name="lkm_email"></property>
<property name="lkm_qq"></property>
<property name="lkm_position"></property>
<property name="lkm_memo"></property>
<!-- 配置多对一映射
name 集合属性名 column外键列名 class与之相关的完整类名-->
<many-to-one name="customer" column="lkm_cust_id" class="com.sannian.bean.Customer"></many-to-one>
</class>
</hibernate-mapping>
Customer.java
public class Customer {
private long cust_id;
private String cust_name;
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> linkmens = new HashSet<>();
public Set<LinkMan> getLinkmens() {
return linkmens;
}
public void setLinkmens(Set<LinkMan> linkmens) {
this.linkmens = linkmens;
}
/**
* 省略众多get/set方法
*/
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 package="com.sannian.bean">
<class name="com.sannian.bean.Customer" table="cst_customer">
<id name="cust_id">
<generator class="native"></generator>
</id>
<property name="cust_name"></property>
<property name="cust_source"></property>
<property name="cust_industry"></property>
<property name="cust_level"></property>
<property name="cust_linkman"></property>
<property name="cust_phone"></property>
<property name="cust_mobile"></property>
<!-- 配置一对多映射 -->
<set name="linkmens"><!--name 集合属性名-->
<key column="lkm_cust_id"></key> <!--column外键列名-->
<one-to-many class="LinkMan" /><!-- com.sannian.bean.LinkMan 与之相关的完整类名-->
</set>
</class>
</hibernate-mapping>
1.2.2 测试
新建客户新建联系人并添加
/**1.保存客户以及客户以下的两个联系人*/
Customer c = new Customer();
c.setCust_name("youKu");
LinkMan lm1 = new LinkMan();
lm1.setLkm_name("z1");
LinkMan lm2 = new LinkMan();
lm2.setLkm_name("z2");
/** 2.表达一对多关系*/
c.getLinkmens().add(lm1);
c.getLinkmens().add(lm2);
/** 3.表达多对一关系*/
lm1.setCustomer(c);
lm2.setCustomer(c);
/** 4.save(这里请读者注意,想想看为什么要save三次?保存一次你会发现有错误)*/
session.save(c);
session.save(lm1);
session.save(lm2);//当然这里有简单的方法——级联操作,后文会讲到,开发时也是一般用的是级联操作方式,可以减少代码的书写量和错误率等等
已有客户新建联系人并添加
Customer customer = session.get(Customer.class, 6l);// 为youKU添加一个新的联系人,查得数据库中id为6
LinkMan lm = new LinkMan();
lm.setLkm_name("王五");
customer.getLinkmens().add(lm);
lm.setCustomer(customer);
session.save(lm);// 将王五转化为持久态
获取指定用户的所有联系人信息(最好理解的例子)
Customer customer = session.get(Customer.class, 5l);// 获取要操作的客户对象
/**获取联系人列表(注意:因为配置了一对多的表关系,所以可以获取出所有的联系人信息哦!*/
/**表与表之前是有关联的,而以前我们要获取这样的信息就必须收到到另一张表之前去查询)*/
System.out.println(customer.getLinkmens());
1.3 一对多关系操作进阶——级联操作与关系维护
1.3.1 什么是级联操作呢?
下面是摘自百度百科的一段描述:
重复性的操作十分烦琐,尤其是在处理多个彼此关联对象情况下,此时我们可以使用级联(Cascade)操作。级联 在关联映射中是个重要的概念,指当主动方对象执行操作时,被关联对象(被动方)是否同步执行同一操作。
级联还指用来设计一对多关系。例如一个表存放老师的信息:表A(姓名,性别,年龄),姓名为主键。还有一张表存放老师所教的班级信息:表B(姓名,班级)。他们通过姓名来级联。级联的操作有级联更新,级联删除。 在启用一个级联更新选项后,就可在存在相匹配的外键值的前提下更改一个主键值。系统会相应地更新所有匹配的外键值。如果在表A中将姓名为张三的记录改为李四,那么表B中的姓名为张三的所有记录也会随着改为李四。级联删除与更新相类似。如果在表A中将姓名为张三的记录删除,那么表B中的姓名为张三的所有记录也将删除。
1.3.2 级联操作的使用
引入上文提到的例子,我们只需要在我们之前书写的Customer.hbm.xml文件中修改如下:(cascade=”save-update”,后文会解释什么意思)
<set name="linkmens" cascade="save-update">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan" /><!-- com.sannian.bean.LinkMan -->
</set>
接着我们使用的例子我们这里只需sava一次,其他的都会自动级联保存下来
/** 4.save(这里请读者注意,想想看为什么要save三次?保存一次你会发现有错误)*/
session.save(c);
//session.save(lm1);这两句就不需要了
//session.save(lm2);
当然除了级联保存之外还有级联删除的概念,两者概念相同,下文有详细配置方法
1.3.3 一对多、级联相关的配置详解(懂得小伙伴可以直接看这里)
<!-- 配置一对多映射 -->
<!--
name属性:集合属性名
column属性:外键列名
class属性:与我关联的对象的完整类名
-->
<!--
级联操作:cascade
save-update:级联保存更新
delete:级联删除
all:save-update + delete
目的:简化操作,少写两行代码
-->
<!--
inverse属性:配置关系是否维护(拓展)
true:customer不维护关系
false(默认值):customer维护关系
目的:性能优化,提高关系维护的性能
原则:无论怎么放弃维护,总有一方要维护关系
在一对多关系中,一的一方放弃,也只能一的一方放弃,多的一方不能放弃
-->
<set name="linkmens" cascade="save-update" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="Linkman"/>
</set>
二、 多对多关系映射
有了一对多关系的理解基础上,多对多就容易理解很多
2.1 多对多关系映射
我们在来看一下两张表,员工表和角色表
- 举个例子来说明多对多的关系是怎么样的:
如:
id为1的张三,他可能担任的角色是保安,但是他同时也可能担任的角色是总裁
而反过来说,保安中有人名字叫李四,当然保安中也有可能有人叫张三,
两张表相互关联,这样就简单的形成了多对多的关系
一开始我觉得这样的关系用外键就能解决,但是发现这种方式无法实现,后来查了资料发现只有使用中间表的方式才能解决这种问题
2.2 多对多关系映射的hibernate实现
思路很简单,如图
在实现之前我们先来创建为我们需要的一些数据表,脚本如下:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
`role_memo` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `sys_role` VALUES (1, '员工', NULL);
INSERT INTO `sys_role` VALUES (2, '部门经理', NULL);
INSERT INTO `sys_role` VALUES (3, '部门经理', NULL);
INSERT INTO `sys_role` VALUES (4, '部门经理', NULL);
INSERT INTO `sys_role` VALUES (5, '员工', NULL);
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_code` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户账号',
`user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
`user_password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户密码',
`user_state` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '1:正常,0:暂停',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
INSERT INTO `sys_user` VALUES (5, 'm0003', '小军', '123', '1');
INSERT INTO `sys_user` VALUES (6, 'm0001', '小红', '123', '1');
INSERT INTO `sys_user` VALUES (7, 'm0001', '小明', '123', '1');
INSERT INTO `sys_user` VALUES (8, 'm0001', '小红', '123', '1');
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`role_id` bigint(32) NOT NULL COMMENT '角色id',
`user_id` bigint(32) NOT NULL COMMENT '用户id',
PRIMARY KEY (`role_id`, `user_id`) USING BTREE,
INDEX `FK_user_role_user_id`(`user_id`) USING BTREE,
CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
为了更加直观,贴上ER图:
2.2.1 书写相关文件
Role.java
public class Role {
private Long role_id;
private String role_name;
private String role_memo;
// 表达多对多关系
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
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">
<!-- 配置表与实体的关系 -->
<!-- package属性:填写一个包名,在元素内部凡是需要书写完整类名的属性,就可以直接写简单类名 -->
<hibernate-mapping package="com.sannian.bean">
<class name="Role" table="sys_role">
<id name="role_id">
<generator class="native"></generator>
</id>
<property name="role_name"></property>
<property name="role_memo"></property>
<!-- 表达多对多关系 -->
<!-- name:集合的属性名 table:配置的中间表名 key column:外键,别人引用我的外键列名 many-to-many class:与哪个类是多对多关系
column:外键,我引用别人的外键列名 -->
<!-- inverse属性 true 放弃维护外键关系 false(默认值) 维护外键关系 结论:将来开发中如果遇到多对多关系,必须要有一方放弃维护关系
一般谁来放弃要看业务方向,例如输入员工时,要为员工指定所属角色 那么业务方向就是员工来维护角色,角色就不需要维护员工的关系,角色就应该放弃维护外键 -->
<set name="users" table="sys_user_role" inverse="true">
<key column="role_id"></key>
<many-to-many class="User" column="user_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
User.java
public class User {
private Long user_id;
private String user_code;
private String user_name;
private String user_password;
private Character user_state;
// 表达多对多关系
private Set<Role> roles = new HashSet<>();
public Set<Role> getRoles() {
return roles;
}
<?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">
<!-- 配置表与实体的关系 -->
<!-- package属性:填写一个包名,在元素内部凡是需要书写完整类名的属性,就可以直接写简单类名 -->
<hibernate-mapping package="com.sannian.bean">
<class name="User" table="sys_user">
<id name="user_id">
<generator class="native"></generator>
</id>
<property name="user_code"></property>
<property name="user_name"></property>
<property name="user_password"></property>
<property name="user_state"></property>
<!-- 表达多对多关系 -->
<!-- name:集合的属性名 table:配置的中间表名 key column:外键,别人引用我的外键列名 many-to-many class:与哪个类是多对多关系
column:外键,我引用别人的外键列名 -->
<!-- cascade属性:级联操作 save-update:级别保存-更新 delete:级联删除 all:级联保存更新+删除 -->
<set name="roles" table="sys_user_role" cascade="save-update">
<key column="user_id"></key>
<many-to-many class="Role" column="role_id"></many-to-many>
</set>
</class>
</hibernate-mapping>