Hibernate级联之一对多和inverse解析

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

  此次讨论一对多的状况,因此就使用博客项目的用户表和博客表做为示例,来一块儿学习hibernate的级联java

基本准备

文件结构:mysql

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

<?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对象。session

  一个用户有多个博客,因此能够在User中添加一个BlogSet集合。app

  这里须要注意的是若是关联的是一个对象,那么不能在类中进行初始化new操做。框架

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

实体类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。

  可是按照人的思惟定式,在不知道的状况仍是会按上面两个步骤走,感受更合理。

  因此解决方法就在一方放弃外键维护。而且在多对多的状况下必须有一方须要放弃外键,否者程序没法运行。

相关文章
相关标签/搜索