一、前言

  这些天都在为公司框架重构做准备,浏览了一下代码,挑了几个不熟或者没接触过的知识点进行攻坚,hibernate是其中之一。其实接触hibernate是在大学期间,应该是在2012年,已经2017-2012=5年时间了,当初给我的印象就是hibernate难学(特别是关联关系的配置这块内容),没学好,很多概念当时理解不了,于是我经手的项目基本都是使用mybatis,不再去碰这个“麻烦”(所以,给人的第一印象很重要,平时要注意一下形象了)。但是呢,我发现,编程的世界就是这么小,兜兜转转最后还是需要照面,于是乎,我决定,啃下这块骨头~我翻出了2012年的大学课件,也在网上搜索了一大堆的博文,算是理清了hibernate关联关系配置这块内容,果真是“会当凌绝顶,一览众山小”,现在回头想想,hibernate并没有第一印象那么难,只是有些细节需要注意~

  本篇博文持续更新,主要是记录一些hibernate使用细节、难点,知识点顺序不分难易,想到哪记到哪儿,有需要自行全文搜索,如有错误之处,还望斧正~

  本文运行环境:

  jdk1.8.0_131

  Eclipse Mars.2 Release (4.5.2)

  Hibernate-release-5.2.11.final

  Mysql 5.6.14

二、正文

  写这篇博文的起因是研究hibernate的关联关系配置过程中,发现很多细节问题,对于新手或者一知半解的人来说,理解起来很困难,作为“过来人”,我希望能用通俗一点的描述加上自己写的实例代码解决同行的疑惑,所以这边就先记录一下“关联关系”配置过程中的问题~

  数据库中表与表之间的关系分为三种:一对一,一对多,多对多。数据表是如何体现这种关系的呢?对于一对一和一对多,会在其中一张表增加一个外键字段(有可能和这张表的主键同一字段),关联另外一张表的主键,多对多则会建立一张中间表,存储了两张关系表的主键,hibernate中的关联关系是建立在数据库中表的这层关系之上。至于hibernate中单向、双向问题,完全是业务需求决定的,因为从数据库层面来讲,A表和B表有关联关系,那么必定可以通过连接查询,从A表查询出B表的信息,或者从B表查询出A表的信息,所以,从数据库的层面来说,就是双向的。而到了程序里面,有些时候我们只需要从A表对应(映射)的ClassA查询出B表对应(映射)的ClassB,而不需要从ClassB查询出ClassA,这时我们用单向就行,如果需要双向查询,这样的情况,就需要双向的关联关系。所以希望初学者不要迷惑hibernate中单双向配置问题,这个完全是业务需求决定,要单向就配置单向,要双向就配置双向。

1)cascade和inverse之间的区别

  cascade定义的是级联,也就是说对某个对象A做CRUD(增删改查)操作是否同样对其所关联的另外一个对象B做同样的操作。而inverse定义的是维护关联关系的责任,这个怎么理解呢?现有一个数据表Student如下,其中cid表示的是Classes表的id:

  Classes表:

  表Student中的cid是外键,关联Classes的主键id,这两张表的关联关系就体现在cid字段上,如果某条记录cid为空,那么当条记录就与Classes中的任何记录无关联关系,假如整个表这个字段都为空,那么这张表就和Classes无关联关系。inverse定义的就是谁去维护这个cid字段的责任!就是由谁去设置这个值!这样说可能也不太确切,应该这样表述:哪个类对应的映射配置了inverse="false"(默认都是false,并且只有集合标记“set/map/list/array/bag”才有inverse属性”),那么就是对这个类进行CRUD的时候,触发hibernate去维护这个字段!如果还是不太清楚,那么请看下面代码~

  假设现在有一个班级类(Classes),学生类(Student),他们之间是“一对多”的关系,在学生类(Student)中包含一个队Classes类的引用,Classes不包含对学生类的引用,两个类以及对应的映射文件分别如下:

Student类:

 package com.hibernate.beans;

 public class Student {
private int id;
private String name;
private Classes cls;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Classes getCls() {
return cls;
}
public void setCls(Classes cls) {
this.cls = cls;
} }

  Student.hbm.xml:

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernate.beans">
<class name="Student">
<id name="id">
<!-- 生成器,自动生成主键 ,试用于Mysql -->
<generator class="identity"/>
</id>
<property name="name" column="name"/>
<many-to-one name="cls" class="Classes" column="cid" cascade="all"></many-to-one>
</class>
</hibernate-mapping>

  Classes类:

 package com.hibernate.beans;

 import java.util.HashSet;
import java.util.Set; public class Classes {
private int id;
private String clsName; public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getClsName() {
return clsName;
}
public void setClsName(String clsName) {
this.clsName = clsName;
} }

  Classes.hbm.xml:

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernate.beans">
<class name="Classes" table="class">
<!-- 给表提供一个标识,也就是主键,同样需要提供生成策略,是数据库自增,还是手动增 -->
<id name="id">
<!-- 生成器,自动生成主键 ,适用于mysql-->
<generator class="identity"/>
</id>
<property name="clsName" column="name"/>
</class>
</hibernate-mapping>

再附加一个hibernate.cfg.xml的配置吧~

  hibernate.cfg.xml:

 <?xml version="1.0" ?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- mysql数据库连接驱动 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 数据库连接地址 -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
<!-- 连接数据库用户名和密码 -->
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root.123</property>
<!-- <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- Echo all executed SQL to stdout 在控制台打印后台sql语句-->
<property name="show_sql">true</property>
<!-- 格式化语句 -->
<property name="format_sql">true</property> <mapping resource="com/hibernate/beans/Classes.hbm.xml" />
<mapping resource="com/hibernate/beans/Student.hbm.xml" />
</session-factory>
</hibernate-configuration>

  由Student.hbm.xml中配置可知,配置的是“多对一”关系中的单向关联。注意:在<many-to-one />关联关系中,没有inverse属性,但是默认就是由配置<many-to-one />这端去维护关联关系(也就是设置外键字段的值),相当于默认inverse="false",在<many-to-one />节点有个cascade属性,其取值有如下几个(多个cascade属性之间可以用英文逗号隔开,比如:cascade="save-update,delete"):

1.none :默认值,Session操作当前对象时,忽略其他关联的对象

2.delete:当通过Session的delete()方法删除当前的对象时,会级联删除所有关联的对象

3.delete-orphan:接触所有和当前对象解除关联关系的对象

   例如:customer.getOrders().clear();

   执行后,数据库中的先前与该customer相关联的order都被删除。

4.save-update:当通过Session的save()、update()及saveOrUpdate()方法更新或保存当前对象

   时,级联保存所有关联的新建的临时对象,并且级联更新所有关联的游离对象

5.persist:当通过Session的persist()方法来保存当前对象时,会级联保存所关联的

   新建的临时对象

6.merge:当通过Session的merge()方法来保存当前对象时,会级联融合所有关联的游离对象

7.lock:当通过Session的lock()方法把当前游离对象加入到Session()缓存中时,会把所有关联的游离对象也加入到

   Session缓存中。

8.replicate:当通过Session的replicate()方法赋值当前对象时,会级联赋值所有关联的对象

9.evict:当通过Session的evict()方法从Session缓存中清除当前对象时,会级联清除所有关联的对象

10.refresh:当通过Session的refresh()方法刷新当前对象时,会级联刷新所有关联的对象,所为刷新是指读取数据库中相应的数据

    然后根据数据库中的最新的数据去同步更新Session缓存中的数据

11.all:包含save-update、persist、merge、delete、lock、replicate、evict及refresh的行为

12.all-delete-orphan:包含all和delete-orphan的行为

  这边配置了cascade="all"属性之后,如果Student中cls有值,那么在保存Student对象的时候,也会保存cls引用的Classess对象到表Classes中,默认cascade="none",此时保存Student对象时,就算cls有值,也不会保存到表Classes中,这就是级联的作用:

  HibernateMain类:

 package com.hibernate.main;

 import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; import com.hibernate.beans.Classes;
import com.hibernate.beans.Student; public class HibernateMain { public static void main(String[] args) {
// TODO Auto-generated method stub
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction ts = session.getTransaction();
ts.begin(); Student st1 = new Student();
st1.setName("学生甲"); Student st2 = new Student();
st2.setName("学生乙"); Classes cls = new Classes();
cls.setClsName("班级2"); st1.setCls(cls);
st2.setCls(cls); session.save(st1);
session.save(st2); ts.commit();
System.exit(0);
} }

  控制台输出的sql语句执行顺序(一对多关联关系,save时,先save“一”的一方,然后才是“多”的一方,删除的时候,先删除“多”的一方,然后才是“一”的一方):

Hibernate:
insert
into
classes
(name)
values
(?)
Hibernate:
insert
into
Student
(name, cid)
values
(?, ?)
Hibernate:
insert
into
Student
(name, cid)
values
(?, ?)

  数据库中的数据:

  classes:                                                                                                                           student:

                                                                      

  现在将两张表数据删除,并且将文件Student.hbm.xml中<many-to-one />节点的cascade属性删除(默认cascade=“none”),然后再执行上面的代码,这个时候你会发现如下报错,这是什么原因呢?前面我有说过,<many-to-one />节点虽然没有inverse属性,但是hibernate默认赋予配置<many-to-one />的一端,在对这个类进行CRUD的时候,触发hibernate去维护体现关联关系的字段(也就是设置“外键”cid的值),在执行的代码里面,Student类实例st1和st2都设置了cls属性,这就向Hibernate表明,需要维护体现关联关系那个字段(因为<many-to-one />默认本端维护,无法修改),但是cascade属性并没有设置(默认为cascade="none"),也就是在保存st1和st2的时候,并不会先保存cls引用的Classes对象,而要维护cid这个“外键”字段时,又必须要先保存Class对象才能获取到这个cid,这边就出现冲突(这边是个人理解,仅供参考,我觉得这边应该还涉及到hibernate中持久化对象状态问题,但是现象上来说可以这儿解释)。如果我们不去设置st1和st2的cls属性,那么我们是能够保存成功的(这边就不贴执行结果了)

十月 11, 2017 3:50:28 下午 org.hibernate.internal.ExceptionMapperStandardImpl mapManagedFlushFailure
ERROR: HHH000346: Error during managed flush [org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hibernate.beans.Classes]
Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hibernate.beans.Classes
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:146)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1443)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:493)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3207)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2413)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:467)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:156)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
at com.hibernate.main.HibernateMain.main(HibernateMain.java:36)
Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hibernate.beans.Classes
at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:279)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:462)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:315)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:326)
at org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:325)
at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:4218)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:528)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:215)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:142)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1437)
... 9 more

  

  接下来,我们再测试一下“一对多”双向关联关系,Student类和student.hbm.xml都不需要改变,我们将Classes类和classes.hbm.xml修改如下:

  Classes类:

 package com.hibernate.beans;

 import java.util.HashSet;
import java.util.Set; public class Classes {
private int id;
private String clsName;
private Set<Student> students = new HashSet<Student>(); public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getClsName() {
return clsName;
}
public void setClsName(String clsName) {
this.clsName = clsName;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
} }

  Classes.hbm.xml:

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.hibernate.beans">
<class name="Classes" table="classes">
<!-- 给表提供一个标识,也就是主键,同样需要提供生成策略,是数据库自增,还是手动增 -->
<id name="id">
<!-- 生成器,自动生成主键 ,适用于mysql-->
<generator class="identity"/>
</id>
<property name="clsName" column="name"/>
<set name="students" cascade="all">
<key column="cid"></key>
<one-to-many class="Student"/>
</set> </class>
</hibernate-mapping>

  然后执行的代码改为:

 package com.hibernate.main;

 import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; import com.hibernate.beans.Classes;
import com.hibernate.beans.Student; public class HibernateMain { public static void main(String[] args) {
// TODO Auto-generated method stub
Configuration cfg = new Configuration().configure();
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();
Transaction ts = session.getTransaction();
ts.begin(); Student st1 = new Student();
st1.setName("学生甲"); Student st2 = new Student();
st2.setName("学生乙"); Classes cls = new Classes();
cls.setClsName("班级2"); cls.getStudents().add(st1);
cls.getStudents().add(st2);
session.save(cls);
ts.commit();
System.exit(0);
} }

  运行之后,控制台的sql语句执行顺序如下:

Hibernate:
insert
into
classes
(name)
values
(?)
Hibernate:
insert
into
Student
(name, cid)
values
(?, ?)
Hibernate:
insert
into
Student
(name, cid)
values
(?, ?)
Hibernate:
update
Student
set
cid=?
where
id=?
Hibernate:
update
Student
set
cid=?
where
id=?

  这个时候你会发现,本来在insert student的时候已经设置了cid,为什么,最后还会有个update操作?这是因为两边的配置默认都要维护表示关联关系的字段cid!之前我提过(往前翻),凡是可以设置inverse属性的地方(只有集合标记“set/map/list/array/bag”才有inverse属性”),如果没有设置,那么默认都是inverse="false",也就是说在操作本端对象的CRUD时,会触发维护体现关联关系字段的操作。在文件Classes.hbm.xml中有配置set节点,但是没有设置inverse属性,默认就是inverse="false",也就是本端负责维护关联关系的那个字段,又因为对端配置的是<many-to-one />默认就赋予它inverse="false"的效果,所以变成两端都维护这个字段。

  如果我们此时在文件Classes.hbm.xml中的set节点,配置inverse="true",也就是明确表示自己不参与维护体现关联关系的字段,这时候,我们再执行程序,控制台的sql执行顺序如下:

Hibernate:
insert
into
classes
(name)
values
(?)
Hibernate:
insert
into
Student
(name, cid)
values
(?, ?)
Hibernate:
insert
into
Student
(name, cid)
values
(?, ?)

  这时并没有update的语句!至此,cascade和inverse的使用和区别,我想我已经在上面讲清楚了,如果有错误或者不能理解的地方,请加我建立的群进行探讨~

三、链接

1、http://www.cnblogs.com/amboyna/archive/2008/02/18/1072260.html

四、联系本人

  为方便没有博客园账号的读者交流,特意建立一个企鹅群(纯公益,非利益相关),读者如果有对博文不明之处,欢迎加群交流:261746360,小杜比亚-博客园

Hibernate使用详解(一)的更多相关文章

  1. hibernate Expression详解

    关键字: hibernate expression hibernate Expression详解Expression.gt:对应SQL条件中的"field > value " ...

  2. Java程序员从笨鸟到菜鸟之(五十一)细谈Hibernate(二)开发第一个hibernate基本详解

    在上篇博客中,我们介绍了<hibernate基本概念和体系结构>,也对hibernate框架有了一个初步的了解,本文我将向大家简单介绍Hibernate的核心API调用库,并讲解一下它的基 ...

  3. Hibernate配置文件详解

    Hibernate配置方式 Hibernate给人的感受是灵活的,要达到同一个目的,我们可以使用几种不同的办法.就拿Hibernate配置来说,常用的有如下三种方式,任选其一. 在 hibernate ...

  4. (转)spring hibernate properties详解

    转载地址:http://blog.sina.com.cn/s/blog_692d0a650100xyqx.html Hibernate配置属性 hibernate.dialect:一个Hibernat ...

  5. Maven搭建SpringMVC+Hibernate项目详解 【转】

    前言 今天复习一下SpringMVC+Hibernate的搭建,本来想着将Spring-Security权限控制框架也映入其中的,但是发现内容太多了,Spring-Security的就留在下一篇吧,这 ...

  6. Hibernate入门详解

    学习Hibernate ,我们首先要知道为什么要学习它?它有什么好处?也就是我们为什么要学习框架技术? 还要知道    什么是Hibernate?    为什么要使用Hibernate?    Hib ...

  7. Maven搭建SpringMVC+Hibernate项目详解

    前言 今天复习一下SpringMVC+Hibernate的搭建,本来想着将Spring-Security权限控制框架也映入其中的,但是发现内容太多了,Spring-Security的就留在下一篇吧,这 ...

  8. Hibernate 配置详解(3)

    7) hibernate.max_fetch_depth: 该属性用于设置left out join单对象查询关系(one-to-one/many-to-one)中最大的关联深度.默认值为0,即默认情 ...

  9. Hibernate 配置详解(2)

    6) hibernate.session_factory_name: 配置一个JNDI名称,通过Configuration对象创建的SessionFactory会绑定到JNDI下该名称中.一般名字格式 ...

  10. Hibernate 配置详解(5)

    9) hibernate.batch_fetch_style: 该配置是hibernate4.2.0新添加的,使用这个设置可以配置hibernate在做batch-fetch的时候,生成SQL的策略. ...

随机推荐

  1. C语言实现 "谁是凶手?"

    日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个嫌疑犯的一个.以下为4个嫌疑犯的供词.A说:不是我.   a=0B说:是C.   c=1 C说:是D.      d=1D说:C在胡说    ...

  2. linux mint系统 cinnamon桌面 发大镜功能

    让我来告诉迷途中的你cinnamon桌面一个好用的功能. 选择设置 选择窗口 -> 选择行为 看那个窗口移动和调整大小的特殊键 Alt 好了按住alt在滑动滑轮 世界不一样了 对于小屏幕高分辨率 ...

  3. vim 配色方案

    1. 自己电脑上的vim 注释很难看清,又不想取消高亮.原来显示: 在 if has("syntax") syntax onendif 语句下面追加一句: colorscheme ...

  4. mRNA翻译成蛋白

    dna = "ATGCACGTGCGCTCACTGCGAGCTGCGGCGCCGCACAGCTTCGTGGCGCTCTGGGCACCCCTGTTCCTGCTGCGCTCCGCCCTGGCCG ...

  5. 使用MATLAB设计FIR滤波器

    1.      采用fir1函数设计,fir1函数可以设计低通.带通.高通.带阻等多种类型的具有严格线性相位特性的FIR滤波器.语法形式: b = fir1(n, wn) b = fir1(n, wn ...

  6. 结合《需求征集系统》谈MVC框架

    结合<需求征集系统>分析MVC框架. 六个质量属性: 可用性:在系统压力过大时,会提示系统繁忙. 可修改性:使用配置文件,修改配置文件即可.对于一些公共的方法,进行封装,修改时,只需修改封 ...

  7. 洛谷P4136 谁能赢呢?

    题目描述 小明和小红经常玩一个博弈游戏.给定一个n×n的棋盘,一个石头被放在棋盘的左上角.他们轮流移动石头.每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问 ...

  8. 成都Uber优步司机奖励政策(2月2日)

    滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...

  9. VINS(八)初始化

    首先通过imu预积分陀螺仪和视觉特征匹配分解Fundamental矩阵获取rotationMatrix之间的约束关系,联立方程组可以求得外参旋转矩阵: 接下来会检测当前frame_count是否达到W ...

  10. springBoot cache操作2

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zxd1435513775/article/details/85091793一.基本项目搭建测试项目是 ...