Hibernate:

Hibernate不是用于提升软件性能,甚至你对jdbc不熟悉,或你对Hibernate控制不好,

你的应用的性能会更差。

Hibernate对应用的用户来讲,作用并不大。

Hibernate的主要作用是简化开发。

 

整个应用程序的上层,一直是面向对象编程的。

如果你不用ORM框架,那你到了底层的DAO层,就需要再次退回到JDBC、面向标量类型编程。

如果用ORM框架,ORM框架会负责把你面向对象的操作,转换为底层的JDBC操作。

  开发者就简化了,开发者就可以从上层、到底层的DAO,一直是面向对象的。

 

映射思想:

持久化类 - 数据表

持久化对象(PO Persistent Object) - 表的记录。

对象的属性 - 表的field。

 

JPA :对所有ORM框架进行统一。

DEMO       DEMO2

Hibernate的开发

1. 提供持久化类。

   标识属性 - 数据表的主键列对象。

2. 为持久化类提供映射文件。

   映射文件(XML文件)就负责定义 持久化类与数据表之间的映射关系。

   从JPA出来之后,映射文件可以不要,而是改为使用Annotation来定义映射。

 

  经过上面2步,我们知道持久化类和数据表之间对应关系了。

3. 为Hibernate提供配置文件。

   只要一次就够了。

 

Hibernate编程步骤

1. 创建Configuration

2. 创建SessionFactory

3. 创建Session

4. 打开事务

5. 持久化操作(面向对象)

6. 提交事务、回收资源

 

Configuration conf = new Configuration().configure() ;

       SessionFactory sf = conf.buildSessionFactory() ;

 

 

对象的状态:

瞬态:刚刚new出来的。

持久化:刚刚保存的、刚刚加载的。

        当程序修改持久化对象的属性时,Hibernate会自动修改该对象所对应的数据库记录。

脱管。

 

增加记录 : save()/persist()

 

修改记录 :PO的setter方法。

删除记录 : delete()

加载记录 : load()/get()



Hibernate 本质属于ORM框架

作用:允许开发者用面向对象的方式来进行持久化访问。

 

Hibernate的配置文件

hibernate.properties / hibernate.cfg.xml
//配置调试时数据库显示
<property name="show_sql">true</property>
        <property name="format_sql">true</property>

Configuration

SessionFactory 底层封装TransactionFactory和ConnectionProvider(Datasource)

Session 底层封装Connection

Transaction

 

持久化类 + 映射文件。

 

级联操作: 一匹马(N)和一个人(1)有关联关系,

           马的一端通过外键来记录与人的关联关系,

           当我们保存马记录时,如果它所参照的人的记录还没有保存,

                系统可以自动先保存人的记录——这就是所谓的级联保存。

 

lazy-load(延迟加载,代理模式的应用):比如有个老师想访问他关联的学生,

      如果没有延迟加载,系统会立即从底层数据库抓取该老师记录关联所有学生记录。

            有延迟加载,系统不会理解去抓取相应记录,它只是先取出这些学生记录对应的代理。

                        等到系统真正需要去访问某个学生属性、方法时,系统才会从数据库去抓取相应的记录。

 

hibernate-mapping 是根元素

class元素

id元素

property元素String、Date)的属性

 

可以考虑放弃Hibernate的映射文件,

改为使用JPA的Annotation来指定映射信息。  (见下面JPA的)

1. 需要复制Hibernate lib/jpa目录下的JAR包也复制到应用中。

2. 要使用AnnotationConfiguration来创建Configuration对象。

 

-------------------

映射集合属性:

某个属性的类型是Set 、List、Map、数组。

List - <list>

Set  - <set>

Map  - <map>

数组 - <array>

 

所有的集合属性,都会映射另一个表中。

这样,保存集合属性表都需要定义一个外键列,用于记录集合属性属于哪个主实体。

   用<key .../>来映射外键列。

   用<element.../>来映射集合属性。

   如果集合是有序集合(ListArrayMap),

    还需要使用<list-index../><map-key>映射顺序列。

-----------------

组件属性(component属性)

持久化类的属性类型可能是自定义的复合类型。

组件属性使用<component.../>元素

 

组件属性映射到当前表的多个数据列

所以需要在<component.../>元素里使用<property.../>映射组件所包含的属性。

注意:实体属性映射的列名和组件的属性所映射的列名要区分开。

------------------

集合元素元素是复合类型:



ORM 框架的作用:

对开发者来说,ORM框架可以解决不能用面向对象的方式进行持久化编程的问题。

 

ORM的映射方式:

表 - 类

列 - 属性

行 - 对象

 

Hibernate的操作方式:

1. 一个配置文件:hibernate.cfg.xml / hibernate.properties文件。

2. 持久化类(POJO + 映射文件(Annotation))

 

持久化访问的步骤:

1. 先创建Configuration , 负责加载配置文件、并管理持久化类。

2. 创建SessionFactory,底层封装了TransactionFactory和ConnectionProvider。

   实际应用中,ConnectionProvider往往就是DataSource。

3  创建Session。底层封装的是Connection。

   应用的持久化操作就是通过Session来完成:

4. 打开事务。

5. 进行持久化操作:

   保存 :persist和save

   加载 :load()和get()

   修改 :当你PO处于持久化状态时,调用PO的setter方法即可。

          当PO处于脱管状态时,调用PO的setter方法后,还需要手动地保存它的状态。

   删除: delete()

6. 提交事务、关闭资源。

 

集合映射:映射另一个表中。

组件映射:映射当前表的多个数据列中

集合元素又是组件的:

----------------------------

组件(component)作为复合主键(pk)

组件类的要求:

 1. 组件类要实现Serializable接口

 2. 应该正确重写hashCode()和equals()方法

 

此时的PK类型是复合类型,因此不再用<id .../>映射标识属性

                             而是用<composite-id.../>进行映射。

<composite-id name="son" type="org.crazyit.app.model.Son">

         <key-property name="name" column="son_name"/>

         <key-property name="grade" column="son_grade"/>                   

</composite-id>

 

----------------------------

直接用持久化类的多个属性来复合PK

要求:

 1. 持久化类本身要实现Serializable接口

 2. 应该正确重写hashCode()和equals()方法


 

 

persist()save()方法几乎完全类似,EJB的用法习惯,

两方法还有一个区别:      

使用save()方法保持持久化对象时,此方法返回此持久化对象的标识属性值(即对应记录的主键值);但使用persist()方法来保存持久化对象时,此方法没有任何反回值。

因为save()方法需要立即返回持久化对象的标识属性值,所以程序执行save方法会立即将持久化对象对应的数据插入数据库;而persist则保证当它在一个事务外部被调用时,并不立即转换成insert语句,这个功能是很有用的,龙其当我们封闭一个长期会话流程的时候,persist()方法就显得重要。

 

加载:

load()方法具有延迟加载功能,不会立即访问数据库,当试图加载的记录不存在进,load()方法可能返回一个未初始化的数据代理对象。

get()方法总是立即访问数据库,当试图加载的记录不存在时,get()方法将直接返回null.

 

 

集合属性的性能分析:

对于集合属性,通常推荐使用延迟加载策略。(等系统需要使用集合属性时才从数据库装载关联的数据)

对集合属性默认采用延迟加载(也可以用lazy="false"来取消之)

 

集合

有序集合(集合里的元素可以根据key或index访问)

无序集合(集合里的元素只能遍历)

有序集合都有一个由外键列和集合元素索引列组成部分的联合主键,在这种情况下,集合属性的更新是非常高效的--主键已经被有效地索引,因此当Hibernate试图更新或删除一行时,可以迅速找到此行数据;

无序集合例如Set的主键由<key>和其他字段构成,或者根本没有主键。如果集合中元素是组合元素或者大文本、大二进制字段,数据库可能无法有效地对复杂的主键进行索引。即使可以建立索引是,性能也是非常差的。

在这种情况下,<bag/>映射是最差的,因为<bag/>允许有重复的元素值,也没有索引字段...

Set集合更新时性能很差,

数组不能用自动缓存(固定的),

List > Map > Set 性能比较

注意:虽然数组也是有序集合,但数组无法使用延迟加载(因为数组的长度不可变),所以实际上用数组作为集合的性能并不高

 

在Hibernate中,Set应该是最通用的集合类型,这是因为"set集合"的语义最贴近关系模型的关系,因此Hibernate的关联映射都采用<set />元素(也可用<bag>)映射。

在设计良好的Hiberanate领域模型中,1-N关联的1一端通常带有inverse="true",对于这种关联,1的一端不再控制关联关系,所有更新操作都将会在N的一端,此情况就无需考虑其集合的更新性能。

(Set集合不同,需要保证集合元素不能重复,因此当程序试图向Set集合中添加元素时,Set集合需要先加载所有集合元素,再集资比较添加元素时性能较低。)

 

当我们试图删除集合的全部元素时,Hibernate是比较智能的。例如调用List集合的cleart()方法删除全部集合元素,Hibernate不会一个个地集合元素,而是使用一个delete 语句就稿定了。

但如果我们不是删除全部集合元素,而是删除绝大部分元素,只剩下一个集合元素,(先调用一条delete语句,删除全部集合元素,再调用insert添加一条记录,Hibenate不会这么智能)它只会一个个地删除,再剩下一条,这时候我们有一个小技巧: (先调用一条delete语句,删除全部集合元素,再调用insert添加希望剩下的记录)

List tmp = person.getSchools() ;

//强制Person的schools集合属性为null
//先删除关联的全部集合元素
person.setSchools(null) ;
//此处采用循环方式删除tmp集合中的9个元素
...
//两次将tmp集合设置成Person的Schools属性
//Hibenate将只需一张insert语句即可插入希望剩下的记录
person.setSchools(tmp) ;

 

 

 

 

DEMO

映射主键                   <idname="name" class="test.Name">

映射普通属性                   formula      generated    <propertyname="age" />

映射主要属性                   DEMO

映射集合属性                   (List集合属性,数组属性,Set集合,Map集合)  DEMO

List →Map→set (性能比较)            set性能最差,数组不能用自动缓存,固定长度

映射Set集合属性,如果element元素包括not-null="true"属性,则集合属性表以关联持久类的外键和元素作为联合主键,否则此表没有主键。但List集合属性不会,List集合属性的表总是以外键列和元素索引列作为联合主键

 

映射组件属性                   component

组件属性为集合            component-map

集合属性的元素为组件 map-component

组件作为Map的索引             map-key-component

组件作为复合主键                 component-key




如果Horse不是持久化类,称为component组件

如果Horse是持久化类,称为实体



关联映射:     DEMO    

单向:

1-1  

1-N

N-1

N-N

双向:     传智课堂标准版DEMO

1-1 (基于外键,基于主键,有连接表)

1-N

N-N




传播性持久性:



1.如果将从表记录岩层持久化类的组件,这些组件的生命周期就会依赖于父对象,Hibernate会默认启用级联操作,不需要额外的动作。当父对象被保存时,这些组件子对象也将被保存,删除时同理;2.如果从表记录映射成持久化实体,则从表也有自己的生命周期,从而应该允许共享对其的引用。(建议先保修父表记录,再保存从表记录)使用cascade=“all”     来指定所有操作都级联到关联实体。<one-to-one name="person" cascade="create,delete,lock" />        (这里不能用create ,不支持的,要用persist,官方文档有误) Hibenate有一个特殊伯级联策略:delete-orphan(),此级联策略只对one-to-many关联有效,表明delele()操作将被级联到所有从关联中删除的对象。 对于cascade="all" 解释,注意:如果没有主表实体删除,只是切断主表实体和从表实体之间的关联关系,则从表实体不会被删除。 单向关联来说: 1→1 、1→N 、N→1、N→N双向关联:1 - 1(1→1 、1←1)、1 - N(1→N 、N→1) 、 N - N(N→N、N←N) 双向 1-N 关联强制使用连接表:实际项目中。虽然可以用连接表来记录1-N关联,但这真没多大必要。在N的一端增加外键即可:传播持久化:①:通过cascade属性来指定。            ②:需要将关联的子实体添加到父实体的Set集合里。1-N关联的性能:           尽量用双向关联!避免使用1的一端来控制关联关系。           在<set.../>元素增加inverse="true"。 双向 1-1 关联  Hibernate支持:    基于主键的关联。    基于外键的关联    基于连接表的关联。 1-1的常用手段:可以在任意一方增加外键列即可。双向 N-N 关联   只能是使用连接表。   由于N - N的两个实体在底层数据库中的地位是绝对平等的。   所以Hibernate处理N-N反而最简单。  继承映射: DEMOsubclass          (性能最好,不用进行多表查询)需要在根父类增加额外一列,辨别者列 discriminator坏处:其子类中增加的属性映射的字段都不可为非空约束好处:当进行多态查询时,不用进行多表连接,性能好joined-subclass         (竖着切)无须使用辨别者列,子类增加的属性也可以用有非空约束,是一种比较理想的映射策略。只是在查询在实例的数据时中,可能需要跨越多个表来查询--不过这些底层数据库实现无须程序员关心。union-subclass       (横着切) 比上面的好一点映射文件是最简单的,既不需要辨别者列也不需要key不使用identity或sequence两种主键生成策略由于整个继承树的主键值不能重复,坏处:当进行多态查询时,尤其是查询最顶层的父类时,可能需要进行多次的union运算,子类可以增加非空约束  查询:  DEMOHQL查询        Query对象可以连续多次为HQL赋值...Query还包含两个方法,利于分页:setFirstResult()setMaxResults()from(类名)关联和连接:隐式           .                  从hiberanate3.5开始,隐式连接用的是sql99 的cross join显式inner join(内连接)                 可简写成joinleft outer join(左连接)           简写 left joinright outer join(右外连接)     简写 right joinfull join(全连接)         并不常用(mysql不支持)from Person pwhere p.myEvents.title = :eventTitle1.          如果myEvents是普通组件属性,或单个的关联实体,则会自动生成隐式内连接(SQL92,3.5后用SQL99),上面的语法有效;2.          如果myEvents是一个集合,那么系统会出现QueryException异常,要改为的显隐式两点区别:1.转换DQL92 992.返回结果不同select子句3.通常情况睛,查询的是集合,而集合元素就是select后的实例、属性等组成的数组4.在特殊情况睛,如果select后只有一项(包括持久化实例或属性),则查询得到的集合元素就是此持久化实例或属性5.如果sekect后有多个项,则每个集合元素就是选择同多项组成的数组,命名查询         named_HQL     相当于自定义了一 SQLInerface,降低耦合  与下面的命名SQL查询略有不同<query name="myNamedQuery">  <query>List  p1 = sess.getNamedQuery("myNamedQuery") ; 条件查询                   (完全面向对象) 了解DEMO          课堂标准DEMO  DEMO2批量         :     DEMO批量插入过多数据时会内存yi出       应定时将Session缓存的数据库  batchInsert由于它会在session级别缓存,所以通常需要控制每N条就刷新、并清空Sessin-------查询-------HQL查询:Hibernate提供的一种查询语言。条件查询:Hibernate提供了一套完全面向对象的查询体系。原生SQL查询:在某些情况下,Hibernate也允许开发者使用原生SQL进行查询。             因为可能开发者想自己对SQL语句进行优化,或者是对原来JDBC项目进行改写。---------------------HQL查询:a. HQL查询的编程步骤。b. 隐式连接和显式连接c. 使用select子句之后的查询结构。d. 命名查询 批量操作:当我们批量插入时、批量修改时,当我们插入、修改大量记录时,可能引发异常。因为Hibernate有两个级别的缓存:1. Session级别的一级缓存。默认总是开启的。2. SessionFactory级别的二级缓存、全局生效。   在SessionFactory级别缓存的数据,它将对整个应用都生效 —— 二级缓存默认是关闭的。==================批量插入:1. 调整hibernate.jdbc.batch_size属性值: N2. 每当保存了N条记录之后,程序就应该调用   session.flush();   session.clear()===================批量更新:1. 调整hibernate.jdbc.batch_size属性值: N2. 每当保存了N条记录之后,程序就应该调用   session.flush();   session.clear() 更新记录的方式: 1. 调用处于持久化状态的对象的setter方法。    该PO对应的数据库记录就会被修改了。   ——但这种方式有个很大弊端:每次只能修改一条数据库记录。 2. 用UPDATE语句(DML风格的HQL语句) 删除记录的方式: 1. session.delete(po) :它就会将该po对应的数据库记录删除。   ——但这种方式有个很大弊端:每次只能删除一条数据库记录。 2. 用DELETE语句(DML风格的HQL语句) ===============================条件查询Criteria : 代表一次查询。Criterion : 代表一个查询条件。Restrictions:它是产生Criterion 的工厂。编程步骤:1. 通过Session来创建Criteria2. 向Criteria中添加多个Criterion对象(查询条件)3. 执行查询即可。 原生查询         :     (自己对SQL语句进行,或对原有项目进行改进) DEMO原生SQL与HQL区别:查询结束标题类型,(Hiberanate提供一种额外的转换机制,允许将原生的SQL查询结果转换成对象,多出了addEntity(),addScalar()(尽量指出返回字段类型,系统性能))查询结果是实体命名SQL查询        :     DEMO                 与上面的 命名查询略有不同<return  />                  将查询结果转换成持久化实体<return-join  /> 预加载持久化实体的关联实体                  <return-scalar  />       将查询的数据列转换成标题值 数据过滤 (次要)   DEMO调用存储过程 (次要)DEMO nativeed_sql  Hibernate二级缓存与查询缓存配置教程.html    API     DEMOSession级别,总是存在,一级缓存只要Session访问过的对象,就在缓存在一级缓存里1.load语句,先会到Session缓存级别寻找,找到的就不会到数据库里找了。直接从内存找2.如果在一级缓存找不到,就检查是否开启了二级缓存,开启了的就找3.如果二级缓存也找不到,到数据库。只要Session曾经访问过的对象,这些持久化对象就会一直被缓存 一般不会清除Session里的缓存,通常只有等到Session关闭才清除,Session的生命周期好短 强制清除session里的缓存数据session.flush() ;session.clear() ;   //这两方法一般一起使用 二级缓存:二级缓存是SessionFactory级别的,默认关闭key 是实体的IDvalue 是实体对象本身开启二级缓存: (官方文档有误)通过属性---- cfg.xml配置文件夹 用了指定的二级缓存,还需要提供缓存相应的配置文件设置对哪些持久化类、哪些集合属性启用二级缓存,默认情况下,所有类、集合属性没被缓存的获取二级缓存的统计信息  从session获取  SessionFactory.getStatistics()查询缓存         作用不大,命中概率低 key 是查询语句value          如果查询目标是标量值,缓存数据就是本次查询得到的数据如果查询目标是实体,缓存数据就是本次查询得到的所有实体的ID< ...use_query_case> true </..>对于HQL查询来说。由于其查询缓存的作用很小,默认情况下,所有查询依然不会缓存。必须通过调用query对象的setCacheable()启用查询缓存应该和二级缓存结合使用 一级与二级缓存只能缓存实体对象,查询缓存可以缓存查询的结果集,即可以缓存普通的属性. 注意:只有经常使用相同的查询语句、并且相同的查询参数的才能通过查询缓存获得好处,查询缓存的生      命周期直到属性被修改了为止注意:查询缓存对查询实体对象的结果集只缓存id查询缓存只对query.list()起作用,对于使用query.iterator()获取查询结果的情况将不会缓存查询结果。需要指出的是:查询缓存应该与二级缓存配置使用。当查询实体对象时,查询缓存只缓存实体的标识属性值,并不会缓存整个实体(简单地说,查询缓存只会 缓存标量类型的结果集和实体标识属性值)  


事 务:

仅仅是对底层事务的包装

两种事务控制:

JDBC局部事务  不跨浏览器

通过Connection.setAutoCommit(false);   Connection.commit(); 

JTA全局事务       跨浏览器的

UserTransaction.begin();   UserTransaction.commit();

 JTA全局事务需要应用服务器支持——一般Web容器不支持。

SessionFactory会在应用程序启动时创建,一旦创建了SessionFactroy将不会轻易关闭,只有当应用程序关闭时才会...

Session的对象是线程不安全的,创建Session时,并不会立即打开数据库之间的连接,Session只在需要进行数据库操作时,才会获取JDBC连接。因此打开和Session,并不会对性能造成很大的影响。只要不进行数据库访问,Sessin就不会获取JDBC连接

数据库事务应该尽可能的短,从而降低数据锁定造成的资源争用。数据库长事务会导致应用程序无法承载高并发的负荷。

SessionFactory.getCurrentSession()

 

Hibernate禁止使用事务自动提交模式,或者让服务器禁止事务自动提交

通常我们建议每个请求对应于一个sessin

 

 

为了指定Hiberanate使用哪种session管理方式,可以在hibernate.cfg.xml文件中增加如下片段:

<!--指定根据当前线程来界定上下文相关session-->

<property name="hibernate.current_session_context_class">thread</property>

 

在JTA事务环境中,则应增加如睛片段

<!--指定根据当前线程来界定上下文相关session-->

<property name="hibernate.current_session_context_class" >jta</property>

 

 

 

 

解决Sessin的两个问题:

session的线程不安全问题     DEMO

session的延迟加载问题                  DEMO

 get() 性能差一些,但它不会延迟加载,因此更稳定。

   Hibernate在抓取关联实体、集合属性时,默认打开了延迟加载。

  为了解决这个问题:

  1. 关闭延迟加载!

      效果最差,因为这将导致抓取主实体时,与该实体关联的所有子实体都将被抓取出来。

  2. 调用Hibernate的静态方法initialize(Object proxy)强制初始化关联实体或集合属性。

     在关闭Session之前,你可以先访问关联实体、集合的任何属性、方法即可。

  3. open session in view。

     Spring提供了Filter,你配置这个Filter。

     Spring可以保证在JSP页面打开Session。

 

事件和拦截器:  DEMO  (次要)

通过事件机制和拦截器,

我们让Hibernate在执行持久化动作时,

回调我们自定义的代码。

1. 实现自己的拦截器。

   拦截器应该实现Interceptor接口,但是实现接口比较繁琐。

   所以一般都改为继承EmptyInterceptor实现类

2. 通过Configuration、Session启用拦截器。

   通过Configuration启用的拦截器将对整个应用生效。全局。

   通过Session启用的拦截器将只对当次Session会话生效。局部。