zoukankan      html  css  js  c++  java
  • Hibernate级联之一对多和inverse解析

    hibernate的级联可以说是hibernate最重要的部分,只有深入了解了级联的特性与用法,才能运用自如。

      这次讨论一对多的情况,所以就使用博客项目的用户表和博客表作为示例,来一起学习hibernate的级联

    基本准备

    文件结构:

    hibernate核心配置文件hibernate.cfg.xml:

    <?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-configuration>
        <!-- 先配置SessionFactory标签,一个数据库对应一个SessionFactory标签 -->
        <session-factory>
            <!-- 必须的配置的参数5个,4个连接参数,1个数据库方言 -->
            <!--
            #hibernate.connection.driver_class com.mysql.jdbc.Driver
            #hibernate.connection.url jdbc:mysql:///test
            #hibernate.connection.username gavin
            #hibernate.connection.password 
            数据库方言
            #hibernate.dialect org.hibernate.dialect.MySQLDialect
             -->
            <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="hibernate.connection.url">jdbc:mysql:///blog</property>
            <property name="hibernate.connection.username">root</property>
            <property name="hibernate.connection.password">123456</property>
             <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
            <!-- 可选配置 -->
            <!-- 显示sql语句 -->
            <property name="hibernate.show_sql">true</property>
            <!-- 格式化sql语句 -->
            <property name="hibernate.format_sql">false</property>
            <!-- 生成数据库的表结构 
            (hbm2dd全称hibernate mapping to db define language auto create)
            update 没表会自动创建,有表添加数据。
                如果开发中间需要添加字段,可以在实体类添加属性。update会自动在数据库添加字段,并且不改变原来数据库值
            validate 校验实体属性和数据库是否一致
            -->
            <property name="hibernate.hbm2ddl.auto">update</property>
            
            <!-- 映射配置文件,可以在map配置文件右键copy qualified name-->
            <mapping resource="com/cky/domain/User.hbm.xml"/>
            <mapping resource="com/cky/domain/Blog.hbm.xml"/>
        </session-factory>
    </hibernate-configuration>
    View Code

    如果对hibernate的配置还不是很清楚,可以看看这里

    实体类的创建

      Hibernate中,可以直接将表的关系用对象表示。

      如本例中,一个博客只能有一个作者,所以Blog就可以添加一个User对象。

      一个用户有多个博客,所以可以在User中添加一个BlogSet集合。

      这里需要注意的是如果关联的是一个对象,那么不能在类中进行初始化new操作。

      如果关联的是一个集合,那么必须用HashSet在类中进行初始化new操作

    实体类Blog.java

    package com.cky.domain;
    
    import java.sql.Timestamp;
    
    public class Blog {
        private int bId;
        private String bSubject;
        private String bContent;
        private Timestamp createtime;
        private Timestamp updatetime;
        //hibernate中关联对象不能初始化
        private User user;
        
        //...getter setter 方法省略
        public int getbId() {
            return bId;
        }
        public void setbId(int bId) {
            this.bId = bId;
        }
        public String getbSubject() {
            return bSubject;
        }
        public void setbSubject(String bSubject) {
            this.bSubject = bSubject;
        }
        public String getbContent() {
            return bContent;
        }
        public void setbContent(String bContent) {
            this.bContent = bContent;
        }
        public Timestamp getCreatetime() {
            return createtime;
        }
        public void setCreatetime(Timestamp createtime) {
            this.createtime = createtime;
        }
        public Timestamp getUpdatetime() {
            return updatetime;
        }
        public void setUpdatetime(Timestamp updatetime) {
            this.updatetime = updatetime;
        }
        public User getUser() {
            return user;
        }
        public void setUser(User user) {
            this.user = user;
        }
        
        
    }
    View Code

    实体类User.java

    package com.cky.domain;
    
    import java.sql.Timestamp;
    import java.util.HashSet;
    import java.util.Set;
    
    public class User {
        private Integer uId;
        private String uEmail;
        private String uName;
        private String uUsername;
        private String uPassword;
        private String uAge;
        private String uDetail;
        private String uAvatar;
        private String isAdmin;
        private Timestamp createtime;
        private Timestamp updatetime;
        //hibernate的集合必须初始化
        private Set<Blog> blogs=new HashSet<Blog>();
        
        //...getter setter 方法省略
        public Integer getuId() {
            return uId;
        }
        public void setuId(Integer uId) {
            this.uId = uId;
        }
        public String getuEmail() {
            return uEmail;
        }
        public void setuEmail(String uEmail) {
            this.uEmail = uEmail;
        }
        public String getuName() {
            return uName;
        }
        public void setuName(String uName) {
            this.uName = uName;
        }
        public String getuUsername() {
            return uUsername;
        }
        public void setuUsername(String uUsername) {
            this.uUsername = uUsername;
        }
        public String getuPassword() {
            return uPassword;
        }
        public void setuPassword(String uPassword) {
            this.uPassword = uPassword;
        }
        public String getuAge() {
            return uAge;
        }
        public void setuAge(String uAge) {
            this.uAge = uAge;
        }
        public String getuDetail() {
            return uDetail;
        }
        public void setuDetail(String uDetail) {
            this.uDetail = uDetail;
        }
        public String getuAvatar() {
            return uAvatar;
        }
        public void setuAvatar(String uAvatar) {
            this.uAvatar = uAvatar;
        }
        public String getIsAdmin() {
            return isAdmin;
        }
        public void setIsAdmin(String isAdmin) {
            this.isAdmin = isAdmin;
        }
        public Timestamp getCreatetime() {
            return createtime;
        }
        public void setCreatetime(Timestamp createtime) {
            this.createtime = createtime;
        }
        public Timestamp getUpdatetime() {
            return updatetime;
        }
        public void setUpdatetime(Timestamp updatetime) {
            this.updatetime = updatetime;
        }
        public Set<Blog> getBlogs() {
            return blogs;
        }
        public void setBlogs(Set<Blog> blogs) {
            this.blogs = blogs;
        }
    
        
    }
    View Code

    编写基础映射文件

      多对一情况映射文件的编写

      多对一时,使用<many-to-one>标签,只需要指定三个属性:

      name:指定此标签所映射的属性名

      class:关联的表所对应的实体类的全限定类名

      column:关联表的外键名

      Blog.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.cky.domain.Blog"  table="blog">
            <id name="bId" column="b_id">
                <generator class="native"></generator>
            </id>
            <!-- 普通属性 -->
            <property name="bSubject" column="b_subject"></property>
            <property name="bContent" column="b_content"></property>
            <property name="createtime" column="createtime"></property>
            <property name="updatetime" column="updatetime"></property>
            <!-- 
            private User user; 
            多对一 配置-->
            <many-to-one name="user" class="com.cky.domain.User" column="u_id" ></many-to-one>
        </class>
    </hibernate-mapping>

      一对多情况映射文件的编写

      与多对一情况不同的是,一对多时关联对象是一个set集合。

      配置文件需要使用<set>标签来和集合对象建立联系,其中的name指定对应的属性名

      在<set>中,需要指定查询关联对象所需要的表(实体类)和比较字段(外键)

      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>
    <class name="com.cky.domain.User" table="user">
    <!-- 配置id
        name实体类属性,column表字段,如果一样,column可以省略。-->
        <id name="uId" column="u_id">
        <!-- 主键生成策略 -->
            <generator class="native"></generator>
        </id>
        <!-- 普通属性-->
        <property name="uEmail" column="u_email"></property>
        <property name="uName" column="u_name"></property>
        <property name="uUsername" column="u_username"></property>
        <property name="uPassword" column="u_password"></property>
        <property name="uAge" column="u_age"></property>
        <property name="uDetail" column="u_detail"></property>
        <property name="uAvatar" column="u_avatar"></property>
        <property name="isAdmin" column="is_admin"></property>
        <property name="createtime" column="createtime"></property>
        <property name="updatetime" column="updatetime"></property>
        <!-- private Set<Blog> blogs=new HashSet<Blog>();
            集合的配置
            name:这个类中对应的属性名
         -->
         <set name="blogs">
             <!--column: 外键,hibernate会根据这个字段来查询与这个对象对应的多端的所有对象 -->
             <key column="u_id"></key>
             <!--class:集合代表的实体类,同时也代表要查询的表。
                     与上面的条件结合,就可以查询出表中所有外键字段为指定值的所有结果的集合。
                 -->
             <one-to-many class="com.cky.domain.Blog"/>
         </set>
    </class>
    </hibernate-mapping>

     为了方便使用,还需要一个工具类HibernateUtils.java,很简单就不介绍了,下面是代码:

    package com.cky.utils;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.cfg.Configuration;
    
    public class HibernateUtils {
        //ctrl+shift+x
        private static final Configuration CONFIG;
        private static final SessionFactory FACTORY;
        
        //编写静态代码块
        static {
            //加载XML的配置文件
            CONFIG =new Configuration().configure();
            //构造工作
            FACTORY=CONFIG.buildSessionFactory();
        }
        /**
         * 从工厂获取session对象
         */
        public static Session getSession() {
            return FACTORY.openSession();
        }
    }
    View Code

    测试基础配置(不使用级联)

    到这里,基本的配置都设置完了,接下来测试配置的怎么样

    package com.cky.Demo;
    
    import org.hibernate.Session;
    import org.hibernate.Transaction;
    import org.junit.Test;
    
    import com.cky.domain.Blog;
    import com.cky.domain.User;
    import com.cky.utils.HibernateUtils;
    
    public class CascadeTest {
        @Test
        public void testMTO2() {
            Session session = HibernateUtils.getSession();
            Transaction tr = session.beginTransaction();
            //保存用户和博客
            User user=new User();
            user.setuName("王五");
            
            Blog blog1=new Blog();
            blog1.setbSubject("王五日常一");
            blog1.setbContent("看电视");
            
            Blog blog2=new Blog();
            blog2.setbSubject("王五日常二");
            blog2.setbContent("玩游戏");
            //为用户添加博客
            user.getBlogs().add(blog1);
            user.getBlogs().add(blog2);
            //保存用户
            session.save(user);
            
            tr.commit();
            session.close();
        }       
    }

    什么,居然报错了:TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing

    翻译一下,大致意思就是user对象引用了一个瞬时对象,因为当save(user)时,user已经被保存到缓存成为持久态对象,而给他添加的blog1和blog2,因为没有设置级联,所以不会被自动添加到缓存中,依然是瞬时态对象。

    解决方法就是把两个blog1和blog2也进行save(),保存到session中:

    @Test
        public void testMTO2() {
           //.....上面省略
            //保存用户
            session.save(user);
            session.save(blog1);
            session.save(blog2);
            tr.commit();
            session.close();
        }       

     关于级联

      hibernate中用cascade属性设置级联。

      在基础的配置中,因为没有设置级联,默认是none,也就是不进行级联操作。

      就如上面的代码一样,我们需要手动的保证对象和他级联的对象都在同一状态,才能正确运行,这显然是很麻烦的,下面就看看如何通过设置级联属性来让代码更简单。

      cascade取值共有5个:

        none     默认值,不级联

        save-update  在保存、更新操作时自动级联保存更新关联对象

        delete    在删除时自动级联删除关联对象

        all      类似save-update-delete,即所以的操作都会级联

        all-delete-orphan 解除某一节点的关系时删除该节点(默认只是清除外键关系)

      接下来就在上面的基础配置上添加上面的属性看看有什么区别:

      save-update:

      为user配置文件的添加cascade属性

    <set name="blogs" cascade="save-update">
        <key column="u_id"></key>
        <one-to-many class="com.cky.domain.Blog" />
    </set>

    此时我们运行上次报错的那段代码:

    @Test
        public void testMTO() {
            Session session = HibernateUtils.getSession();
            Transaction tr = session.beginTransaction();
            //保存用户和博客
            User user=new User();
            user.setuName("王五");
            
            Blog blog1=new Blog();
            blog1.setbSubject("王五日常一");
            blog1.setbContent("看电视");
            
            Blog blog2=new Blog();
            blog2.setbSubject("王五日常二");
            blog2.setbContent("玩游戏");
            
            user.getBlogs().add(blog1);
            user.getBlogs().add(blog2);
            
            blog1.setUser(user);
            blog2.setUser(user);
            //自动关联
            session.save(user);
                    //删除掉保存blog的代码
            
            tr.commit();
            session.close();
        }
    View Code

    发现可以正确执行,因为保存user时,会自动级联保存两个blog,所以他们就全是持久态。

      我们同时为blog配置文件添加cascade属性

    <many-to-one name="user" 
      class
    ="com.cky.domain.User"
      column
    ="u_id"
      
    cascade="save-update"></many-to-one>

    然后保存一个blog看看会发生什么

    @Test
        public void testMTO() {
            Session session = HibernateUtils.getSession();
            Transaction tr = session.beginTransaction();
    
            User user=new User();
            user.setuName("王五");
            
            Blog blog1=new Blog();
            blog1.setbSubject("王五日常一");
            blog1.setbContent("看电视");
            
            Blog blog2=new Blog();
            blog2.setbSubject("王五日常二");
            blog2.setbContent("玩游戏");
    
            user.getBlogs().add(blog1);
            user.getBlogs().add(blog2);
            
            blog1.setUser(user);
            blog2.setUser(user);
            
            /*session.save(user);
            session.save(blog1);*/
            //只保存blog2
            session.save(blog2);
            
            tr.commit();
            session.close();
        }

    运行成功,不过更有意思的是他保存了三条信息,而不是两条。

    因为当保存 blog2 时,会级联保存 user ,而user又会级联把 blog1 保存

    删除也是同样的道理,就不演示了,下面再研究一个all-delete-orphan,传说的孤儿删除

      关于all-delete-orphan

    all-delete-orphan上面已经简单介绍过,就是解除关系时会把节点删除而不只是删除外键。

    我们把使用和不使用孤儿删除分别用代码实现,并做一次比较:

    正常情况下的解除关系:

      原来的blog表中两条数据都和user id=1产生关系

    现在我们把user和其中一个blog id=1解除关系

    //普通解除关系
        @Test
        public void testMTO4() {
            Session session = HibernateUtils.getSession();
            Transaction tr = session.beginTransaction();
            
            User user=(User) session.get(User.class, 1);
            Blog blog=(Blog) session.get(Blog.class, 1);
            //解除关系只需要把user集合中的blog移除即可
            user.getBlogs().remove(blog);
            
            tr.commit();
            session.close();
        }

    运行sql:

    再看看表情况:

    正常情况,解除关系只是删除外键。

    使用all-delete-orphan时解除关系:

    为user配置文件添加all-delete-orphan

     <set name="blogs" cascade="all-delete-orphan">
         <key column="u_id"></key>
         <one-to-many class="com.cky.domain.Blog" />
    </set>

    执行同样的代码解除关系:

    //孤儿删除
        @Test
        public void testMTO4() {
            Session session = HibernateUtils.getSession();
            Transaction tr = session.beginTransaction();
            
            User user=(User) session.get(User.class, 1);
            Blog blog=(Blog) session.get(Blog.class, 1);
            //解除关系只需要把user集合中的blog移除即可
            user.getBlogs().remove(blog);
            
            tr.commit();
            session.close();
        }

    sql的执行情况

    数据表变化:

    关于inverse(外键维护)

     什么是外键维护呢?

      就是在两个关联对象中,如果关系发生改变需要修改外键。这么一说感觉这个功能肯定是必备的,要不然这么保证对象之间的关系呢?

      在hibernate是根据对象关系来判断是否要维护外键

      这里有两个关键字,对象关系外键

      什么是对象关系?在hibernate中就是你这个对象A存的有对象B的引用,那么对象A就有对象B的的对象关系。有趣的是,对象关系可以是单向的,即A有B的对象关系,B不一定有A的对象关系。Hibernate是根据对象的对象关系来进行外键处理的。如果两边的对象关系都改变,那么默认hibernate都会进行外键处理(处理两次)。

      举个例子

      user1有blog1和blog2俩对象关系  、user2有blog3和blog4俩对象关系

      1.现在我们把blog3添加到user1中(对象关系改变)

      2.因为这时blog3中的user还是user2,还要把blog3的user换成user1(对象关系改变)

      上面两个操作都改变了对象关系,如之前说的,session的缓存和快照不一致了,对于User对象,需要更新外键,对于Blog对象,也需要更新外键。

      但是,他们更新的是同一外键,也就是说对同一外键更新了两次,多了一个无意义的操作无疑增加了数据库的压力。

      

      也许有人可能会说,我不执行步骤2不就行了,结果还是正确的,还减少了sql。

      但是按照人的思维定式,在不知道的情况还是会按上面两个步骤走,感觉更合理。

      所以解决方法就在一方放弃外键维护。并且在多对多的情况下必须有一方需要放弃外键,否者程序无法运行。

      

      

  • 相关阅读:
    HA 高可用集群概述及其原理解析
    iOS 平台上常见的安装包有三种,deb、ipa 和 pxl
    爪洼人的第五天
    Java基础数组篇
    猛男学Java的第四天
    猛男学习JAVA的第三个日子
    猛男学习Java的第二天
    【笔记】ubuntu12.04 添加启动器图标的办法(解决启动器图标消失的问题)
    【笔记】Ubuntu12.04键盘图标(输入法图标)不见后重新显示方法
    【笔记】ubuntu下手动更新firefox浏览器
  • 原文地址:https://www.cnblogs.com/chenkeyu/p/7881818.html
Copyright © 2011-2022 走看看