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>

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

实体类的创建

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

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

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

  这里需要注意的是如果关联的是一个对象,那么不能在类中进行初始化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;
} }

实体类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;
} }

编写基础映射文件

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

  多对一时,使用<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();
}
}

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

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

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();
}

发现可以正确执行,因为保存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。

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

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

  

  

Hibernate级联之一对多和inverse解析的更多相关文章

  1. Hibernate级联及控制反转的增删改查

    在JavaHibernate中,双向多对一的操作一直是一个重点难点,本篇文章就是来探讨这个问题. 双向多对一:一个班级对应多个学生,多个学生同属于一个班级,通过班级信息可以查到班级内的学生,通过学生可 ...

  2. hibernate 级联删除报更新失败的问题(org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update)

    首先hibernate级联删除的前提是,首先需要在映射文件中配置,配置多表之间的关联关系: 下面以部门表(Dept)和员工表(Emp)为例: 1.在Emp.hbm.xml映射文件中配置many-to- ...

  3. Hibernate级联操作解密(inverse和cascade)

    总结: Cascade:对级联操作进行限制,有如下几个参数: all : 所有情况下均进行关联操作.  none:所有情况下均不进行关联操作.这是默认值.  save-update:在执行save/u ...

  4. 【转】Hibernate级联注解CascadeType参数详解

    cascade(级联) 级联在编写触发器时经常用到,触发器的作用是当 主控表信息改变时,用来保证其关联表中数据同步更新.若对触发器来修改或删除关联表相记录,必须要删除对应的关联表信息,否则,会存有脏数 ...

  5. hibernate级联与反向

    cascade:设置本表与关联表之间的级联操作,如:设置为save-update,则插入或更新对象时同时保存或更新另一端的表,但不会产生关联关系数据,除非inverse为false. inverse: ...

  6. Hibernate级联操作 注解

    EJB3 支持的操作类型 /** * Cascade types (can override default EJB3 cascades */ public enum CascadeType { AL ...

  7. hibernate中一对多 多对多 inverse cascade

    ----------------------------一对多------------------------------------------- inverse属性:是在维护关联关系的时候起作用的 ...

  8. Hibernate级联删除

    如果cascade属性是默认值"none",当hibernate删除一个持久化对象的时候,不会自动删除与它关联的其他持久化对象.如果希望自动删除它关联的其他持久化对象,可以把cas ...

  9. hibernate级联保存,更新个人遇到的问题

    在级联更新的时候,数据库中的数据是增加的,只是外键不存在,导致这样的问题产生的原因是,字表主键ID没有添加到集合中,导致Hibernate找不到子项而执行更新.

随机推荐

  1. Loadrunner Http接口Get/Post方法性能测试脚本解析

    最近使用LoadRunner 11进行了一次完整的Http WEB接口性能测试,下面介绍下Http接口Get/Post方法性能测试脚本通用编写方法. 1. Http接口性能测试基本流程 首先定义了一个 ...

  2. spacemacs 初始安装报错

    尝试使用emcas的配置文件spacemacs,第一次安装启动时,界面为纯白色,而且在输入完几个配置选项后,报了一个错误 Symbol's value as variable is void 根据官网 ...

  3. Myeclipse常见快捷键及配置

    0. 快捷键 ================================================================================ 编辑: Ctrl+Shi ...

  4. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  5. CentOS7安装GitLab、汉化及使用

    同步首发:http://www.yuanrengu.com/index.php/20171112.html 一.GitLab简介 GitLab是利用Ruby On Rails开发的一个开源版本管理系统 ...

  6. ASP.NET没有魔法——ASP.NET Identity的加密与解密

    前面文章介绍了如何使用Identity在ASP.NET MVC中实现用户的注册.登录以及身份验证.这些功能都是与用户信息安全相关的功能,数据安全的重要性永远放在第一位.那么对于注册和登录功能来说要把密 ...

  7. Muduo阅读笔记---入门(一)

    第一步:下载源码和文档 下载muduo项目的源码.<muduo-manual.pdf>文档,以及<Linux多线程服务端编程:使用muduo C++网络库.pdf>,这些是前期 ...

  8. Jacobi symbol(裸雅可比符号)

    Jacobi symbol Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tot ...

  9. 楼梯T-SQL:超越基础6级:使用CASE表达式和IIF函数

     从他的楼梯到T-SQL DML,Gregory Larsen涵盖了更多的高级方面的T-SQL语言,如子查询. 有时您需要编写一个可以根据另一个表达式的评估返回不同的TSQL表达式的单个TSQL语句. ...

  10. 一款超好用轻量级JS框架——Zepto.js(上)

       前  言 絮叨絮叨 之前我们介绍过JQuery怎么自定义一个插件,但没有详细介绍过JQuery,那么今天呢....我们还是不说JQuery,哈哈哈哈 但是今天我们介绍一款和JQuery超级像的一 ...