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。

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

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

      

      

  • 相关阅读:
    BZOJ 2034 【2009国家集训队】 最大收益
    vijos P1780 【NOIP2012】 开车旅行
    BZOJ 2115 【WC2011】 Xor
    BZOJ 3631 【JLOI2014】 松鼠的新家
    BZOJ 4717 改装
    BZOJ 2957 楼房重建
    BZOJ 4034 【HAOI2015】 T2
    BZOJ 1834 【ZJOI2010】 network 网络扩容
    BZOJ 2440 【中山市选2011】 完全平方数
    BZOJ 2733 【HNOI2012】 永无乡
  • 原文地址:https://www.cnblogs.com/chenkeyu/p/7881818.html
Copyright © 2011-2022 走看看