博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Hibernate使用详解(一)
阅读量:7173 次
发布时间:2019-06-29

本文共 14277 字,大约阅读时间需要 47 分钟。

一、前言

  这些天都在为公司框架重构做准备,浏览了一下代码,挑了几个不熟或者没接触过的知识点进行攻坚,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类:

1 package com.hibernate.beans; 2  3 public class Student { 4     private int id; 5     private String name; 6     private Classes cls; 7     public int getId() { 8         return id; 9     }10     public void setId(int id) {11         this.id = id;12     }13     public String getName() {14         return name;15     }16     public void setName(String name) {17         this.name = name;18     }19     public Classes getCls() {20         return cls;21     }22     public void setCls(Classes cls) {23         this.cls = cls;24     }25     26 }

  Student.hbm.xml:

1 
2 5
6
7
8
9
10
11
12
13
14

  Classes类:

1 package com.hibernate.beans; 2  3 import java.util.HashSet; 4 import java.util.Set; 5  6 public class Classes { 7     private int id; 8     private String clsName; 9     10     public int getId() {11         return id;12     }13     public void setId(int id) {14         this.id = id;15     }16     public String getClsName() {17         return clsName;18     }19     public void setClsName(String clsName) {20         this.clsName = clsName;21     }22     23     24 }

  Classes.hbm.xml:

1 
2 5
6
7
8
9
10
11
12
13
14

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

  hibernate.cfg.xml:

1 
2 5
6
7
8
com.mysql.jdbc.Driver
9
10
jdbc:mysql://localhost:3306/test
11
12
root
13
root.123
14
15
org.hibernate.dialect.MySQL5InnoDBDialect
16
17
true
18
19
true
20 21
22
23
24

   由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类:

1 package com.hibernate.main; 2  3 import org.hibernate.Session; 4 import org.hibernate.SessionFactory; 5 import org.hibernate.Transaction; 6 import org.hibernate.cfg.Configuration; 7  8 import com.hibernate.beans.Classes; 9 import com.hibernate.beans.Student;10 11 public class HibernateMain {12 13     public static void main(String[] args) {14         // TODO Auto-generated method stub15         Configuration cfg = new Configuration().configure();16         SessionFactory factory = cfg.buildSessionFactory();17         Session session = factory.openSession();18         Transaction ts = session.getTransaction();19         ts.begin();20         21         Student st1 = new Student();22         st1.setName("学生甲");23         24         Student st2 = new Student();25         st2.setName("学生乙");26         27         Classes cls = new Classes();28         cls.setClsName("班级2");29         30         st1.setCls(cls);31         st2.setCls(cls);32         33         session.save(st1);34         session.save(st2);35         36         ts.commit();37         System.exit(0);38     }39 40 }

  控制台输出的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 mapManagedFlushFailureERROR: 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类:

1 package com.hibernate.beans; 2  3 import java.util.HashSet; 4 import java.util.Set; 5  6 public class Classes { 7     private int id; 8     private String clsName; 9     private Set
students = new HashSet
();10 11 public int getId() {12 return id;13 }14 public void setId(int id) {15 this.id = id;16 }17 public String getClsName() {18 return clsName;19 }20 public void setClsName(String clsName) {21 this.clsName = clsName;22 }23 public Set
getStudents() {24 return students;25 }26 public void setStudents(Set
students) {27 this.students = students;28 }29 30 31 }

  Classes.hbm.xml:

1 
2 5
6
7
8
9
10
11
12
13
14
15
16
17 18
19

  然后执行的代码改为:

1 package com.hibernate.main; 2  3 import org.hibernate.Session; 4 import org.hibernate.SessionFactory; 5 import org.hibernate.Transaction; 6 import org.hibernate.cfg.Configuration; 7  8 import com.hibernate.beans.Classes; 9 import com.hibernate.beans.Student;10 11 public class HibernateMain {12 13     public static void main(String[] args) {14         // TODO Auto-generated method stub15         Configuration cfg = new Configuration().configure();16         SessionFactory factory = cfg.buildSessionFactory();17         Session session = factory.openSession();18         Transaction ts = session.getTransaction();19         ts.begin();20         21         Student st1 = new Student();22         st1.setName("学生甲");23         24         Student st2 = new Student();25         st2.setName("学生乙");26         27         Classes cls = new Classes();28         cls.setClsName("班级2");29         30         cls.getStudents().add(st1);31         cls.getStudents().add(st2);32         session.save(cls);33         ts.commit();34         System.exit(0);35     }36 37 }

  运行之后,控制台的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、

四、联系本人

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

转载于:https://www.cnblogs.com/xdouby/p/7649988.html

你可能感兴趣的文章
Redis入门系列之队列和发布订阅模式
查看>>
Ceph学习笔记
查看>>
unity自带的水
查看>>
LVS搭建过程中需要用到的命令-- ipvsadm
查看>>
【No.9 内存泄漏了么】
查看>>
想成为一名DBA 至少要具备哪些技术
查看>>
CentOS 编译安装php5.5, 并配制支持apach,nignx核心代码
查看>>
第3章 初探HTML
查看>>
基于S/MIME V2标准的加密和解密的控件software IP*Works! S/MIME
查看>>
mysql 备份数据库脚本
查看>>
Linux文件系统上的特殊权限
查看>>
IBM携手红帽将助力企业加快虚拟化步伐
查看>>
8.C++引用
查看>>
利用imgateaselect插件实现前端页面图片截取功能
查看>>
Java super()
查看>>
xinetd服务介绍及配置
查看>>
在Redis-Sentinel的client-reconfig-script脚本中设置VIP
查看>>
服务器资源使用情况统计--脚本
查看>>
Oracle查询数据库的索引字段以及查询用索引
查看>>
第二讲、实例变量可见度、方法
查看>>