Hibernate使用详解(一)

一、前言

  这些天都在为公司框架重构做准备,浏览了一下代码,挑了几个不熟或者没接触过的知识点进行攻坚,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 <?xml version="1.0"?>  
 2 <!DOCTYPE hibernate-mapping PUBLIC
 3     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
 4     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
 5 <hibernate-mapping package="com.hibernate.beans">  
 6     <class name="Student">  
 7         <id name="id">  
 8             <!-- 生成器,自动生成主键 ,试用于Mysql -->  
 9             <generator class="identity"/>  
10         </id>  
11         <property name="name" column="name"/>
12         <many-to-one name="cls" class="Classes" column="cid" cascade="all"></many-to-one>
13     </class>  
14 </hibernate-mapping>

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

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

  hibernate.cfg.xml:

 1 <?xml version="1.0" ?>
 2 <!DOCTYPE hibernate-configuration PUBLIC
 3     "-//Hibernate/Hibernate Configuration DTD 3.0//EN"  
 4     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
 5 <hibernate-configuration>  
 6     <session-factory>  
 7         <!-- mysql数据库连接驱动 -->  
 8         <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>  
 9         <!-- 数据库连接地址 -->  
10         <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>  
11         <!-- 连接数据库用户名和密码 -->  
12         <property name="hibernate.connection.username">root</property>  
13         <property name="hibernate.connection.password">root.123</property>  
14         <!-- <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>-->
15         <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
16         <!-- Echo all executed SQL to stdout 在控制台打印后台sql语句-->
17         <property name="show_sql">true</property>
18         <!-- 格式化语句 -->
19        <property name="format_sql">true</property>
20         
21         <mapping resource="com/hibernate/beans/Classes.hbm.xml" />
22         <mapping resource="com/hibernate/beans/Student.hbm.xml" />
23     </session-factory>  
24 </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类:

 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 stub
15         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 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类:

 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<Student> students = new HashSet<Student>();
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<Student> getStudents() {
24         return students;
25     }
26     public void setStudents(Set<Student> students) {
27         this.students = students;
28     }
29     
30     
31 }

  Classes.hbm.xml:

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

  然后执行的代码改为:

 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 stub
15         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、http://www.cnblogs.com/amboyna/archive/2008/02/18/1072260.html

四、联系本人

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值