美文网首页Java 杂谈程序员
三、Hibernate_多表关系&级联操作&外键维护

三、Hibernate_多表关系&级联操作&外键维护

作者: 明天你好向前奔跑 | 来源:发表于2017-07-04 17:55 被阅读0次

一、 一对多的ORM关系映射

ORM : Object Relational Mapping 。 对象 关系 映射。

  1. 首先完成Relational数据库表的一对多的关系
  2. 完成Object实体对象的一对多关系
  3. 完成Mapping映射文件中一对多关系的映射配置

一对多的实例:客户-联系人

一个客户(公司)---对应---多个联系人

步骤:

  1. 创建两个数据库表customer,linkman,在多的一方添加外键建立关系
  2. 创建两个实体类Customer和Linkman,在实体类中分别建立与对方的关系
  3. 创建两个实体类对应的配置文件,分别配置完成一对多的关系映射

1.导入jar包(省略)

导入Hibernate所需要的一些jar包。

2. 创建数据库表customer与linkman,并建立联系。R

    创建customer表与linkman表,在多的一方linkman的表中添加外键,指向一的一方的主键
    
    create database hibernate_day03;
    
    use hibernate_day03;
    
    客户表 :
    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;


    联系人数据库表 :
    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
    )

3. 创建Customer与Linkman实体类,并建立联系。O

分别在两个实体类中添加与另一个实体类的联系。

Customer的实体类:

img17.png

Linkman的实体类:

img18.png
4. 在对应的映射文件中添加一对多的映射关系

Customer.hbm.xml映射文件:


    <!-- 集合,一对多关系,在映射文件中配置 -->
    <!-- 
        name属性:集合属性名
        column属性: 外键列名(通过这个外键找到Linkman放入集合)
        class属性: 与我关联的对象完整类名(从哪里找)
     -->
    <set name="linkemans">
        <key column="lkm_cust_id"></key>
        <one-to-many class="com.itdream.domain.Linkeman"/>
    </set>

Linkman.hbm.xml映射文件:


    <!-- 多对一配置 -->
    <!-- 
        name属性:引用的属性名
        column属性: 外键列名(通过外键找到这个引用)
        class属性: 与我关联的对象完整类名(从哪找)
     -->
    <many-to-one name="customer" column="lkm_cust_id" class="com.itdream.domain.Customer"></many-to-one>
5.在核心配置文件中加载映射文件

    <?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的核心配置文件 --> 
    <hibernate-configuration>
        <session-factory>
            <!-- 1.Hibernate连接数据库的基本配置 -->
            <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_day03</property>
            <property name="hibernate.connection.username">root</property>
            <property name="hibernate.connection.password">root</property>
            
            <!-- 2.配置与当前线程绑定的Session对象 -->
            <property name="hibernate.current_session_context_class">thread</property>
            
            <!-- 3.Hibernate的普通配置 -->
            <!-- Hibernate的方言配置,根据配置生成对应SQL语句.这实现了Hibernate跨数据库.更换数据库只需要修改这里的配置即可-->
            <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
            
            <!-- 显示sql语句 -->
            <property name="hibernate.show_sql">true</property>
            
            <!-- 格式化sql语句 -->
            <property name="hibernate.format_sql">true</property>
            
            <!-- Hibernate自动创建数据库表的方式 hbm2ddl-->
            <property name="hibernate.hbm2ddl.auto">none</property>
            
            <!-- 设置隔离级别:4:Repeatable read可重复读 -->
            <property name="hibernate.isolation">4</property>
            
            <!-- c3p0连接池配置-->
            <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
            <!-- 最小连接 -->
            <property name="hibernate.c3p0.min_size">5</property>
            <!-- 最大连接数 -->
            <property name="hibernate.c3p0.max_size">20</property>
            <!-- 连接超时时长 -->
            <property name="hibernate.c3p0.timeout">120</property>
            <!-- 每120秒检查空闲连接 -->
            <property name="hibernate.c3p0.idle_test_period">120</property>
            <!-- 最大statments数量 -->
            <property name="hibernate.c3p0.max_statements">120</property>
            <!-- 连接用完后,每次增加的连接数 -->
            <property name="hibernate.c3p0.acquire_increment">2</property>
            <!-- 每次都验证连接是否可用 -->
            <property name="hibernate.c3p0.validate">false</property>
            
            <!-- 4.加载映射配置 -->
            <mapping resource="com/itdream/domain/Customer.hbm.xml"/>
            <mapping resource="com/itdream/domain/Linkman.hbm.xml"/>
        </session-factory>
    </hibernate-configuration>

6.Hibernate:1对多关系测试

    /**
     * 测试数据库表的1对多关系:
     */
    
    @Test
    //保存一个客户与两个联系人
    public void test() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 创建一个客户
        Customer customer = new Customer();
        customer.setCust_name("阿里巴巴");
        
        //2> 创建两个联系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("小马");
        
        Linkman linkeman2 = new Linkman();
        linkeman2.setLkm_name("小云");
        
        //3> 表达一对多,客户下有多个联系人
        customer.getLinkemans().add(linkeman);
        customer.getLinkemans().add(linkeman2);
        
        //4> 表达多对一,联系人属于哪个客户
        linkeman.setCustomer(customer);
        linkeman2.setCustomer(customer);
        
        //5> 保存.将瞬时态对象转换成持久态对象
        session.save(customer);
        session.save(linkeman);
        session.save(linkeman2);
        
        //------------------------------------------
        //提交事务
        transaction.commit();
    }
    
    
    @Test
    //为客户1添加一个联系人
    public void test2() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 获取客户1
        Customer customer = session.get(Customer.class, 1L);
        
        //2> 创建待添加的联系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("刘总");
        
        //3> 建立关系:表达一对多,客户下有多个联系人
        customer.getLinkemans().add(linkeman);
        
        //4> 建立关系:表达多对一,联系人属于哪个客户
        linkeman.setCustomer(customer);
        
        //5> 保存.将瞬时态对象转换成持久态对象
        //session.save(customer);(customer是持久态对象无需手动保存,会自动更新数据库)
        session.save(linkeman);
        
        //------------------------------------------
        //提交事务
        transaction.commit();
    }
    
    
    @Test
    //为客户1删除一个联系人刘总
    public void test3() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 获取客户1
        Customer customer = session.get(Customer.class, 1L);
        
        //2> 获取待删除的联系人
        Linkman linkeman = session.get(Linkman.class, 3L);
        
        //3> 建立关系:从客户的联系人集合中删除该联系人
        customer.getLinkemans().remove(linkeman);
        
        //4> 建立关系:从联系人的客户引用中删除该客户
        linkeman.setCustomer(null);
        
        //5> 保存.将瞬时态对象转换成持久态对象(customer与linkman都是持久态对象,无需保存会自动更新)
        //session.save(customer);
        //session.save(linkeman);
        
        //------------------------------------------
        //提交事务
        transaction.commit();
    }


上面在操作瞬时态对象时,每个瞬时态对象都要手动进行save保存,为了节省代码,Hibernate提供了级联操作。只要设置了级联的这一方添加了级联配置,与它产生关系的另一方就无需在进行save或者update等操作了。

级联测试:


    在客户的一端设置级联保存,那么只要客户是持久化状态,就无需对与其产生关系的联系人进行save或者update操作了.

     <!-- 
            级联操作:   cascade
                save-update: 级联保存更新
                delete:级联删除
                all:save-update+delete
            级联操作: 简化操作.目的就是为了少些两行代码.
     -->

    <set name="linkemans" cascade="save-update">
        <key column="lkm_cust_id"></key>
        <one-to-many class="com.itdream.domain.Linkman"/>
    </set>  


    
    @Test
    //保存一个客户与两个联系人(级联保存)
    //为了简化代码操作,在做session.save时,save客户就可以同时将客户中的ecustomer对象保存.
    //但是,如果直接省略,会报错:瞬时态与执行态关联的错误.因此在需要做级联保存的映射文件中配置,将cascad属性设为save-update即可
    public void test1() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 创建一个客户
        Customer customer = new Customer();
        customer.setCust_name("阿里巴巴");
        
        //2> 创建两个联系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("小马");
        
        Linkman linkeman2 = new Linkman();
        linkeman2.setLkm_name("小云");
        
        //3> 建立关系:将联系人添加到客户的集合中
        customer.getLinkemans().add(linkeman);
        customer.getLinkemans().add(linkeman2);
        
        //4> 建立关系:将客户添加到联系人中
        linkeman.setCustomer(customer);
        linkeman2.setCustomer(customer);
        
        //5> 保存.将瞬时态对象转换成持久态对象
        session.save(customer);
        //在customer的映射文件中配置级联保存,在保存customer的时候会自动保存与它产生关联的联系人
        // session.save(linkeman);
        // session.save(linkeman2);
        
        //------------------------------------------
        //提交事务
        transaction.commit();
    }
    
    
    @Test
    //为添加一个联系人(级联保存)
    //为了简化代码操作,在做session.save时,save客户就可以同时将客户中的ecustomer对象保存.
    //但是,如果直接省略,会报错:瞬时态与执行态关联的错误.因此在需要做级联保存的映射文件中配置,将cascad属性设为save-update即可
    public void test2() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 获取客户1
        Customer customer = session.get(Customer.class, 1L);
        //2> 创建一个联系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("小白");
        
        //3> 建立关系:将联系人添加到客户的集合中
        customer.getLinkemans().add(linkeman);
        
        //4> 建立关系:将客户添加到联系人中
        linkeman.setCustomer(customer);
        
        //5> 保存.customer通过get方法取出就是持久态了,无需手动保存
        //因为配置了级联保存,保存customer的时候Hibernate会自动将与这个customer产生关系的linkman设为持久态保存
        //session.save(customer);
        
        //------------------------------------------
        //提交事务
        transaction.commit();
    }
    
    
    @Test
    //为客户1删除联系人id为1的小马,增加一个联系人为小宋
    public void test4() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //-------------------------------
        //实现业务逻辑 : 为客户1删除联系人id为1的小马,增加一个联系人为小宋
        //1> 获取客户1
        Customer customer = session.get(Customer.class, 1L);
        
        //2> 获取要删除的联系人小马
        Linkman linkman = session.get(Linkman.class, 1L);
        
        //3> 创建一个联系人小宋
        Linkman linkman2 = new Linkman();
        linkman2.setLkm_name("小宋");
        
        //4> 建立关系: 在客户1方建立对联系人的关系
        //从联系人集合中删除要删除的联系人
        customer.getLinkemans().remove(linkman);
        //添加要添加的联系人到联系人集合
        customer.getLinkemans().add(linkman2);
        
        //5> 建立关系: 在联系人放建立对客户1的关系
        //解除客户在联系人的引用
        linkman.setCustomer(null);
        //在添加的联系人的客户引用中添加客户1
        linkman2.setCustomer(customer);
        
        //6> 保存
        //customer是持久态,删除的联系人也是持久态,待添加的联系人不是持久态,但是由于customer设置了级联保存因此也无需save保存
        //-------------------------------
        //提交事务
        transaction.commit();
        //关闭资源,getCurrentSession方法获得与线程绑定的Session对象无需手动关闭
    }
    
    
    @Test
    //级联删除 : 在实际开发中基本不用,这里测试使用
    //目的:在删除联系人的时候将它的客户也删除掉
    public void test5() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //-------------------------------
        //实现业务逻辑 : 删除联系人1的时候将它的客户也删除掉
        //1> 获取要删除的联系人
        Linkman linkman = session.get(Linkman.class, 1L);
        
        //2> 删除联系人
        session.delete(linkman);
        
        //3> 保存
        //linkman是持久态,无需手动保存,同时它设置了级联删除,因此删除联系人的同时,会将它对应的客户1也删除掉
        //如果这个时候在客户的一方也设置级联删除,会导致删除一个客户或者删除一个联系人,会将与它有关联的所有数据都删除掉
        //-------------------------------
        //提交事务
        transaction.commit();
        //关闭资源,getCurrentSession方法获得与线程绑定的Session对象无需手动关闭
    }

上面通过级联的配置,节省了一些多余代码的操作,但是我们通过查看Hibernate打印的sql语句发现(例如执行test1的代码),Hibernate会对外键lkm_cst_id进行多余的两次操作.Linkman自身在insert添加新联系人时已经维护了外键lkm_cst_id,那么下面update进行的维护很显然就是客户customer在进行维护,这是很冗余的操作。因此我们需要通过配置进行优化,Hibernate提供了inverse的属性来设置。

img21.png

      <!-- inverse属性: 配置关系是否维护. 
            true: customer不维护关系
            false(默认值): customer维护关系
            
        inverse属性: 性能优化.提高关系维护的性能.
        原则: 无论怎么放弃,总有一方必须要维护关系.
        一对多关系中: 一的一方放弃.也只能一的一方放弃.多的一方不能放弃.
      -->

    <set name="linkemans" cascade="save-update" inverse="true">
            <key column="lkm_cust_id"></key>
            <one-to-many class="com.itdream.domain.Linkman"/>
    </set>


    //操作进阶--关系维护属性
    public class Demo3 {
        @Test
        //保存客户 以及客户 下的联系人
        public void fun1(){
            //1 获得session
            Session session = HibernateUtils.openSession();
            //2 开启事务
            Transaction tx = session.beginTransaction();
            //-------------------------------------------------
            //3操作
            Customer c = new Customer();
            c.setCust_name("阿里巴巴");
            
            LinkMan lm1 = new LinkMan();
            lm1.setLkm_name("小马");
            
            LinkMan lm2 = new LinkMan();
            lm2.setLkm_name("小云");
            
            //表达一对多,客户下有多个联系人. 
            // 如果客户放弃维护与联系人的关系. 维护关系的代码可以省略
            //c.getLinkMens().add(lm1);
            //c.getLinkMens().add(lm2);
            
            //表达多对多,联系人属于哪个客户
            lm1.setCustomer(c);
            lm2.setCustomer(c);
            
            //如果Linkman的配置文件中配置了级联保存,则这条保存客户的语句可以省略
            //session.save(c);
            session.save(lm1);
            session.save(lm2);
            
            //-------------------------------------------------
            //4提交事务
            tx.commit();
            //5关闭资源
            session.close();
        }

上面的配置表示:customer放弃外键维护lkm_cst_id.那么同样再执行test1的代码时,下面update的冗余的sql语句就消失了,达到了优化的目的.

img22.png

二、 多对多的ORM关系映射

ORM : Object Relational Mapping 。 对象 关系 映射。

  1. 首先完成Relational数据库表的多对多的关系表达(外键)
  2. 完成Object实体对象的多对多关系表达(分别创建Set集合存储对方的对象)
  3. 完成Mapping映射文件中一对多关系的映射配置(配置)

多对多的实例

用户与角色(岗位) User & Role

    一个用户可以在公司中担任多个岗位
    一个岗位上也有多个员工用户在工作
1.jar包上面一对多的例子已经导入
2.创建多对多的两个数据库表,进行关系表达 Relational
    
    多对多的数据库多表关系表达,需要创建一个第三方表,这个第三方表至少有两个字段,分别指向两张表的主键.

    
    -- 用户表

    CREATE TABLE `sys_user` (
      `user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用户id',
      `user_code` varchar(32) NOT NULL COMMENT '用户账号',
      `user_name` varchar(64) NOT NULL COMMENT '用户名称',
      `user_password` varchar(32) NOT NULL COMMENT '用户密码',
      `user_state` char(1) NOT NULL COMMENT '1:正常,0:暂停',
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

    
    -- 用户角色表. 
    
    CREATE TABLE `sys_role` (
      `role_id` bigint(32) NOT NULL AUTO_INCREMENT,
      `role_name` varchar(32) NOT NULL COMMENT '角色名称',
      `role_memo` varchar(128) DEFAULT NULL COMMENT '备注',
      PRIMARY KEY (`role_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;


    -- 第三方表,负责维护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`),
      KEY `FK_user_role_user_id` (`user_id`),
      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 DEFAULT CHARSET=utf8;

3.创建多对多的两个实体类,进行关系表达 Object

    /**
     * 持久化类:用户实体类
     */
    public class User {
    
        // 普通属性
        private Long user_id;//外键
        private String user_code;
        private String user_name;
        private String user_password;
        private Character user_state;
    
        // 建立多对多关系:Set集合存储当前User对象持有的Role角色信息
        private Set<Role> roles = new HashSet<>();



    /**
     * 持久化类:用户角色的实体类
     */
    public class Role {
    
        // 普通属性
        private Long role_id;//外键
        private String role_name;
        private String role_memo;
    
        // 建立实体类多对多关系:Set集合存储属于该Role角色的所有User对象
        private Set<User> users = new HashSet<>();
4.配置两个映射文件,进行关系表达 Mapping

    User.hbm.xml映射文件配置多对多关系:

    <!-- 多对多的关系映射配置 --> 
    <!-- 
        name: 集合属性名
        table: 配置中间表名
        
        key
         |-column:外键,"我"的外键列名
         
        class: 我与哪个类是多对多关系
        column:外键.与我产生多对多关系的外键列名
     -->
    <set name="roles" table="sys_user_role">
        <key column="user_id"></key>
        <many-to-many class="com.itdream.domain.Role" column="role_id" ></many-to-many>
    </set>

    ------------------------------------------------------------

    Role.hbm.xml映射文件配置多对多关系:

    <!-- 多对多的关系映射配置 --> 
    <!-- 
        name: 集合属性名
        table: 配置中间表名
        
        key
         |-column:外键,"我"的外键列名
         
        class: 我与哪个类是多对多关系
        column:外键.与我产生多对多关系的外键列名
     -->
    <set name="roles" table="sys_user_role">
        <key column="user_id"></key>
        <many-to-many class="com.itdream.domain.Role" column="role_id" ></many-to-many>
    </set>

4.在hibernate.cfg.xml中加载映射文件:

    <mapping resource="com/itdream/domain/User.hbm.xml"/>
    <mapping resource="com/itdream/domain/Role.hbm.xml"/>
4.Hibernate多对多关系测试

    /**
     * Hibernate多对多关系测试
     */
    
    @Test
    //多对多关系:创建两个用户,创建三个角色由两个用户分配保存
    public void test1() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        
        //----------------------------
        //实现业务逻辑
        //1> 创建两个用户
        User user = new User();
        User user2 = new User();
        user.setUser_name("杨幂");
        user2.setUser_name("唐嫣");
        
        //2> 创建三个角色(岗位)
        Role role = new Role();
        Role role2 = new Role();
        Role role3 = new Role();
        role.setRole_name("明星");
        role2.setRole_name("女儿");
        role.setRole_name("妻子");
        
        //3> 用户表达关系:分别在用户中添加角色
        //杨幂持有明星,女儿,妻子三个角色
        user.getRoles().add(role);
        user.getRoles().add(role2);
        user.getRoles().add(role3);
        //唐嫣持有明星和女儿两个角色
        user2.getRoles().add(role);
        user2.getRoles().add(role2);
        
        //4> 角色表达关系:分别在角色中添加用户
        role.getUsers().add(user);
        role2.getUsers().add(user);
        role3.getUsers().add(user);
        
        role.getUsers().add(user2);
        role2.getUsers().add(user2);
        
        //5> 保存
        session.save(user);
        session.save(user2);
        session.save(role);
        session.save(role2);
        session.save(role3);
        
        //----------------------------
        //提交事务
        transaction.commit();
    }

    ==================================================

    我们按照非常正规的方式,分别建立了user和role对象然后逐个保存,但是就是这么看起来正确的操作却报错了。

    org.hibernate.exception.ConstraintViolationException: could not execute statement

    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: 
        Duplicate entry '1-1' for key 'PRIMARY'

    根据日志错误信息,我们知道产生了重复的主键:1-1.

    这是因为我们的第三方表只有两个字段,就是两张表的外键,这张表的主键就是两个外键组成的联合主键。
    那为什么会产生重复的主键1-1呢?

    我们在上面inverse外键维护关系的属性时讲了,inverse默认值是false,默认维护外键.
    我们在建立关系的时候,user一方建立了关系-getRoles.add(user).role一方也建立了关系-getUsers.add(role);
    这样它们在维护外键时会导致user维护了外键往第三方表插入了1 1. role在维护外键时也会往第三方表插入1 1.导致联合主键重复了。

    ======================================================

    要解决这一问题,有两种方式:

    方式1: 只让一方建立关系
        即: 注释掉一方的建立关系,例如注释掉role方建立与user的关系表达
        将 role.getUsers.add(role)的语句全都注释掉就可以了。
        但是这种方式,我们看起来不是很舒服,因为感觉不够符合逻辑。
        建立关系应该是两方都建立。因此就可以使用方式2.

    --------------------------------------------------------

    方式2: 让被动的一方放弃外键维护
    在这里因为user是主动选择的role角色,因此让role放弃外键维护

    
    <!-- 使用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>
    

设置级联保存

在上面的测试中,在保存瞬时态对象到一级缓存中时,需要将每一个瞬时态对象都一一save,为了节省代码,我们使用级联保存来解决。


    // 5> 保存
    //正常操作需要save保存每一个瞬时态对象,为了节省这些代码,我们使用级联保存cascade
    //分别在User.hbm.xml与Role.hbm.xml映射文件中设置级联配置

    <!-- 使用inverse属性
        true: 放弃维护外键关系
        false(默认值):维护关系
     -->    
    Role.hbm.xml :  
    <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>

    User.hbm.xml : 
    <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>

    ==================================================================

    //这样设置之后,只需要保存其中一个对象都能级联将其它瞬时对象全都保存.(要保证所有的对象都能被关联到)
    //例如,保存user,会级联保存与它建立关系的role,role2,role3.而role,role2,与user和uer2产生了关联,因此user2也成功保存
    //又如,保存role3,会级联保存与role3建立了关系的user2,user2会级联保存role和role2,role和role2又能级联保存user
    session.save(user);
    // session.save(user2);
    // session.save(role);
    // session.save(role2);
    // session.save(role3);

为用户添加角色:


    //为唐嫣增加一个角色 : 女神
    @Test
    public void test2() {
        //获得Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //----------------------------------
        //实现业务逻辑
        //1> 获取user对象唐嫣
        User user = session.get(User.class, 2L);
        
        //2> 创建一个新的角色:女神
        Role role = new Role();
        role.setRole_name("女神");
        
        //3> user表达关系: 将新建的角色添加到user对象的角色集合中
        user.getRoles().add(role);
        
        //4> role表达关系 : 将user对象添加到role对象的用户集合中
        role.getUsers().add(user);
        
        //5> 保存转化为持久态
        // user对象已经是持久态了,无需手动保存,又因为添加了级联保存,所以会级联保存role对象
        
        //----------------------------------
        //提交事务(事务提交时,数据更新到数据库,清空一级缓存区)
        transaction.commit();
    }

为唐嫣解除明星角色


    @Test
    //为唐嫣解除明星角色
    public void test3() {
        //获取Session对象
        Session session = HibernateUtils.getCurrentSession();
        //开启事务
        Transaction transaction = session.beginTransaction();
        //---------------------------------------------
        //处理业务逻辑
        //1> 获取User对象唐嫣:id为2
        User user = session.get(User.class, 2L);
        
        //2> 获取Role对象明星:id为2
        Role role = session.get(Role.class, 2L);
        
        //3> User表达关系:从role集合中移除明星角色
        user.getRoles().remove(role);
        
        //4> Role表达关系:从user集合中移除用户唐嫣
        role.getUsers().remove(user);
        
        //5> 保存,将瞬时态对象转换为持久态
        //user与role对象已经持久态了
        
        //---------------------------------------------
        //提交事务
        transaction.commit();
    }

cascade与inverse使用总结:

  • cascade : 实质上是为了节省代码操作

    cascade级联操作:
        save-update: 级联保存更新
        delete:级联删除
        all:级联保存更新+级联删除
        结论: cascade简化代码书写.该属性使不使用无所谓. 建议要用只用save-update.
        使用delete操作太过危险.尤其在多对多中.不建议使用.
  • inverse : 减少多余的sql语句,优化Hibernate的性能
    
    双向建立关系时,双方都进行外键维护产生了多余的sql语句,使用inverse属性使一方放弃外键维护。

    使用inverse属性
        true: 放弃维护外键关系
        false(默认值):维护关系
    
    结论: 
        在一对多关系中,通常选择一的一方放弃外键维护。

        多对多关系中.一定要选择一方放弃维护关系.
         一般谁来放弃要看业务方向(被动一方放弃维护). 例如录入员工时,需要为员工指定所属角色.
         那么业务方向就是由员工维护角色. 角色不需要维护与员工关系.角色放弃维护   

相关文章

  • 三、Hibernate_多表关系&级联操作&外键维护

    一、 一对多的ORM关系映射 ORM : Object Relational Mapping 。 对象 关系 映射...

  • MySQL操作-2 (笔记)

    1.多表关系维护 外键约束:forerign key 给product 添加一个外键约束 多表建表原则 一对一 一...

  • MySQL约束以及表之间的关系(主键and外键)

    一:约束 分类: 练习 二:为何要分表 三:外键约束 四:级联操作 五:多对多关系的处理

  • MySQL实践笔记

    关于外键 子表: 父表: 外键 支持外键只能使用 InnoDB 存储引擎。 级联操作问题 ON UPDATE CA...

  • MYSQL 多表操作<五>

    承上启下 承接上一个博客 技术分析 多表之间的关系如何维护外键约束(foreign key) 给product中的...

  • springBootJpa多表查询

    1、多表之间的关系和操作多表的操作步骤 表关系一对一一对多:一的一方:主表多的一方:从表外键:需要再从表上新建一列...

  • 草稿四

    ● 外键的删除操作---on_delete参数指定 <1> models.CASCADE---级联删除外键表的字段...

  • @OneToOne 入门

    几个问题 什么是级联操作? 级联保存操作 当两边只使用@OneToOne会创建表,并且会建外键,保存会报错,说明@...

  • java基础-day32-数据库3.0

    10.6 外键约束 10.7 级联操作 11. 数据表修改 alter 11.1 添加字段 11.2 修改字段 1...

  • MySQL级联操作

    MySQL级联操作 外键约束对子表的含义: 如果在父表中找不到候选键,则不允许在子表上进行insert/updat...

网友评论

    本文标题:三、Hibernate_多表关系&级联操作&外键维护

    本文链接:https://www.haomeiwen.com/subject/muuxhxtx.html