zoukankan      html  css  js  c++  java
  • 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,小杜比亚-博客园

  • 相关阅读:
    Qt全局宏和变量
    QT_begin_namespace和QT_end_namespace的作用
    Qt 打开文件的默认路径 QFileDialog::getOpenFileName()
    QT的安装及环境配置
    C/C++文件操作1
    C/C++文件操作2
    AnsiString和String的区别、使用
    字符转换
    C++Builder中MessageBox的基本用法
    Windows 编程中恼人的各种字符以及字符指针类型
  • 原文地址:https://www.cnblogs.com/xdouby/p/7649988.html
Copyright © 2011-2022 走看看