hibernate学习笔记(四)hibernate关系映射:一对多、多对多(案例)

作者:叁念


一、 一对多关系映射

数据库环境准备:

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>  

猜你喜欢

转载自blog.csdn.net/qq_36868342/article/details/80652733