`

hibernate笔记

    博客分类:
  • java
阅读更多

外连接与预先抓取的区别

首先讲述一个重点,它将贯穿本小节的始末

假设有一个类A,它有两个属性property1和property2,则HQL语句“from A as a left outer join a.property1”有一个原则----HQL语句忽略配置文件中的预先抓取策略。这句话有两个意思:

1.不管A类对property1在配置文件里是什么策略(可能是预先抓取、立即或延迟检索,他们都失效),这时都采用HQL指定的左外连接;左外连接必定会初始化property1属性(或对象),但是如果配置文件里对property1的检索策略是延迟加载,A类得不到对property1的引用。为了得到这个引用,需要再次发送一条SQL语句来确立这种引用关系。这种情况在property1为集合时经常出现。

2.A类在配置文件中设置的对property2的预先抓取策略将被忽略(不管这个策略师fetch或是select),对property2有影响的设置是立即和延迟加载,Hibernate只看得到这两种策略。所以在使用语句“from A as a left outer join a.property1”时,property2的加载策略将仅由lazy=“true”或则是lazy="false"来决定。

 

 

1.外连接

 

 

编写如下代码:

...//打开Session,开启事务

Query q=session.createQuery("from Student as s left join s.team");

list=q.list();

....//提交事务,关闭Session

//list中包括两个长度为2的数组,每一个数组包括一个学生对象和一个班级对象

Object stuAndTeam1[]=(Object[])list.get(0);//取得第一个数组

Student stu=(Student)stuAndTeam1[0];//stuAndTeam1【1】为班级对象

System.out.println(stu.getName());

System.out.println(stu.getTeam().getTeamName());

System.out.println(stu.getTeam().getStudents().size());

 

 

运行代码,执行结果如下:

Hibernate:select s.*,t.*from studnet s left outer join team t on s.team_id=t_.id

Hibernate:select c.*from certificate c where c.id=?

Hibernate:select c.*from certificate c where c.id=?

tomclus

team1

ERROR [main]...........................

 

上述第一条语句忽略配置文件中的所有策略,通过HQL指定的左外连接取得学生和班级对象,第二和第三语句立即加载和学生对应的身份证对象(之所以是立即加载而不是配置文件中指定的预先抓取,是因为通过HQL指定学生对象左外连接班级,从而忽略了学生对象所有的预先抓取策略,取而代之的是立即加载),第四条语句打印学生名,第五条语句打印班级名称。比较奇怪的是当打印stu.getTeam().getStudents().size()时弹出了第6条语句的例外。

 

 

这里有个重点:在配置文件中设置的检索策略只能影响到session.get()或load()方法,对于直接使用Query q=session.createQuery("from Student as s left join s.team")这种指明HQL方式,将忽略配置文件的预先抓取检索策略(学生对班级的预先抓取被忽略,实际上是Hibernate根本不查看在配置文件中学生对班级检索策略的符号“<outer-join>”,或是“<fetch>”,直接以HQL中指定的左外连接为检索策略)。“from Student as  s left join s.team”这条语句指明了学生和班级之间使用左外连接策略,因此控制台输出的第一条语句,即使用左外连接得到了3个对象:2个学生和1个班级。

    得到了学生对象之后,学生对身份证的预先抓取策略失效,所以才会有第2和第3条语句,即使用立即加载得到身份证。

    得到班级对象后,由于班级对学生采用了延迟加载(这个策略不会被忽略),于是班级的学生集合并未得到初始化(没有对两个学生进行引用。)

 

正因为如此,语句System。out。println(stu。getTeam()。getTeamName())可以正常打印班级名称,而System。out。println(stu。getTeam()。getStudents()。size())试图打印班级中学生数量时,由于班级集合set并没有初始化,所以会弹出例外。

 

 

修改本例子的检索策略,其中只是班级对学生的检索策略改成了立即检索。

 

代码不修改仍然如下

...//打开Session,开启事务

Query q=session.createQuery("from Student as s left join s.team");

list=q.list();

....//提交事务,关闭Session

//list中包括两个长度为2的数组,每一个数组包括一个学生对象和一个班级对象

Object stuAndTeam1[]=(Object[])list.get(0);//取得第一个数组

Student stu=(Student)stuAndTeam1[0];//stuAndTeam1【1】为班级对象

System.out.println(stu.getName());

System.out.println(stu.getTeam().getTeamName());

System.out.println(stu.getTeam().getStudents().size());

运行后得到的控制台信息如下:

Hibernate:select s.*,t.* from student s left outer join team t on s.team_id=t_id

Hibernate:select c.* from certificate c where c.id=?

Hibernate:select c.*from certificate c where c.id=?

Hibernate:select c.*s.*,from student s inner join certificate c on s.id=c.id where s.team_id=?

tomclus

team1

2

 

 

由于学生集合得到了初始化,所以System.out.println(stu.getTeam().getStudents().size())这条语句可以正确打印出学生的数量2

 

这里有个重点,就是上述控制台信息的第四条信息 ,读者可能会有疑问,为何在第2和第3条语句已经得到了身份证,第4条SQL语句还要把身份证信息再从数据库中取一遍?这是因为第一次发送SQL语句从数据库取身份证对象时,学生对身份证的预先抓取被忽略,所以采用了立即加载的策略。

而班级对学生采用HQL指定的左外连接取得对象。

虽然班级和学生对象都取得了,但是由于集合代理的特殊性,还需要取得集合对学生对象的引用,这就通过第4句

select c.* s.* from student s inner join certificate c on s.id=c.id where s.team=?

 来取得班级对学生的引用,原本只是要取得学生对象,如果学生与身份证之间是立即或延迟加载的策略,则第4句会是

select s.*from student s where s.team_id=?

但是学生与身份证之间是预先抓取(有点绑定的意思,取得学生的同时就要取得身份证),因此在实际的第4句加入了身份证的信息,使用预先抓取(此时的表现是内连接),可以说,身份证信息室搭了顺风车被引入的,实际上身份证对象已经在内存中生成了,因此就算有第四句,也不会在内存中生成新的身份证对象。

 

 

2.预先抓取

将此程序的HQL语句由左外连接改为预先抓取,其他配置文件不变,如下代码所示:

 

......//打开Session,开启事务

Query q=session.createQuery("from Student as s left join fetch s.team");

list=q.list();

Object stuAndTeam1[]=(Object[])list.get(0);

Student stu=(Student)stuAndTeam1[0];

System.out.println(stu.getName);

System.out.println(stu.getTeam().getTeamName());

System.out.println(stu.getTeam().getTeamName());

.....//提交事务,关闭Session

 

运行以上程序,在控制台显示如下:

Hibernate:select s.*,t.*from student s left outer join team t on s.team_id=t_.id

Hibernate:select c.*from certificate c where c.id=?

Hibernate:select c.*from certificate c where c.id=?

Exception in thread "main" java.lang.ClassCastException:model.student

 

在控制台输出语句的第4条弹出了例外,说明Student类的转型错误。跟踪这个例外,引起它的源代码如下:

Object stuAndTeam1[]=(Object[])list.get(0);

Student stu=(Student)stuAndTeam1[0];

弹出这种例外的原因是在HQL中使用关键字“fetch”,让学生对象以预先抓取的方式得到班级,虽然生成的SQL语句是一样的,但是在内存中生成的对象表却发生了改变。

 

此时检索出来的list对象有2个学生元素,而不是4个元素(两个学生和1个重复了2次的班级)。这时使用数组明显是不正确的,将代码改为如下所示:

...//打开Session,开启事务

 Query q=session.createQuery("from Student as  s left join fetch s.team");

list=q.list();

Student stu=(Student)list.get(0);

System.out.println(stu.getName());

System.out.println(stu.getTeam().getTeamName());

System.out.println(stu.getTeam().getTeamName());

........//提交事务,关闭Session

运行上述代码,得到控制台的信息如下:

Hibernate:select s.*,t.*from student s left outer join team t on s.team_id=t_.id

Hibernate:select c.*from certificate c where c.id=?

Hibernate:select c.*from certificate c where c.id=?

Hibernate:select s.*,c.* from student s inner join certificate c on s.id=c.id where s.team_id=?

tomclus

team1

2

上述信息和本小节中“左外连接”所得到的控制台信息相同

 

 

 

 

3.HQL忽略配置文件预先抓取策略的深度

如果在HQL语句中设置了左外连接,那么对于所有关联到的类来说,是不是都要遵循HQL语句忽略配置文件中的预先抓取策略呢?

 

如果使用HQL语句“from Team t left outer join t.students”,则班级对学生的预先抓取策略将被HQL指定的左外连接所覆盖,那么学生对身份证的预先抓取会不会被忽略,而以立即检索来代替?

 

.....//打开Session,开启事务

Query q=session.createQuery("from Team t left outer join t.student");

list=q.list();

......//提交事务,关闭Session

 

 

运行上述代码后控制台的输出语句如下:

Hibernate:select t.*,s.*from team t left outer join students s on t.id=t_.team_id

Hibernate:select c.*from certificate c where c.id=?

Hibernate :select c.*from certificate c where c.id=?

 

可以看到第2和第3语句使用的是立即检索策略,这说明学生对身份证的预先抓取被忽略了。在“from Team t left outer join t.students” 语句中,Team和Student对象配置文件中的预先抓取都将被忽略。因此,HQL忽略配置文件预先抓取策略深度就是在HQL语句中指定的对象,Hibernate对这些对象直接的属性配置忽略预先抓取,而采取立即或是延迟检索的策略。假设身份证还关联其它对象,则这些对象的抓取策略按照配置文件的设定,预先抓取策略不会被忽略。

 

 

Hibernate仅在第一层关联时忽略配置文件的预先抓取,在更深的层次,按照配置文件设置的策略取得对象。 

 

 

 总结:

(1)仅从使用的角度来看,预先抓取和立即检索的效果一样,只不过预先抓取可以减少SQL语句的条数。

(2)预先抓取的关键字是join和fetch,而外连接的关键字只有join

(3)预先抓取将初始化代理对象的引用,把对象的数据填充完毕,但是外连接仅把对象组装好,而不会初始化对象之间的引用关系(在集合引用时最为突出)。

(4)HQL将忽略配置文件的预先抓取策略,有两种情况:

在第一层(HQL指定的类)使用HQL指定的策略:比如“from A as a left  join a.property1",不管A类对property1在配置文件里是什么策略(可能是预先抓取、立即或延迟检索,它们都失效),这时都采用HQL指定的左外连接;左外连接必定会初始化property1属性(或对象),但是如果配置文件中对property1的检索策略师延迟加载,A类得不到对property1的引用。为了得到这个引用,需要再次发送一条SQL语句来确定这种引用关系。

对第一层类的属性使用配置文件指定的立即或延迟加载:A类很可能不只是有property1这一属性,在HQL“from A as a left join a.property1中指定了A类取得property1是采用左外连接,那么对于A类的property2、property3采用什么策略呢?Hibernate将会忽略property2中的预先抓取策略,也就是对<outer-join>或<fetch>标识符视而不见,直接查看lazy标识符来确定使用立即还是延迟策略。对附属层采取配置文件指定的策略,不忽略预先加载。

 

 

 

 

Hibernate的主键策略分为3类:

1. Hibernate对主键id赋值。

2.应用程序自己对id赋值

3.由数据库对id赋值

 

 

Hibernate对主键id赋值

<id>标签下的可选<generator>子元素是一个Java类的名字,用来为该持久化类的实例生成唯一的标识,所有的生成器都实现net.sf.hibernate.id.IdentifierGenerator接口。这是一个非常简单的接口,某些应用程序可以选择提供他们自己特定的实现。当然,Hibernate提供了很多内置的实现。下面是一些内置主键生成器的意义。

     1.assigned:主键由外部程序负责生成,无须Hibernate参与。

     2.hilo:通过hi/lo算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态。

     3.identity:采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL中的主键生成机制。

     4.

     5.

 

 

 

 

操纵实体对象

 

   编写持久化类

     分析已知的XXX.hbm.xml文件

对于一个已知的XXX.hbm.xml文件的持久化类,只需要根据XML文件中指定的属性域编写一个java类,就可以通过操纵这个java类,间接地操纵数据库中的字段值。编码人员(相对于设计人员而言)需要知道的只是XXX.hbm.xml文件。

 

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping

       PUBLIC"-//Hibernate/Hibernate Mapping DTD//EN"

       "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

       <!--把类和数据表关联-->

       <class name="model.Student" table="student" lazy="true">

              <!--id的产生方式是uuid.hex-->

              <id name="id" unsave-value="null">

                     <generator class="uuid.hex"/>

              </id>

              <property name="cardid" type="string"/><!--映射号-->

              <property name="name" type="string"/><!--映射学生名-->

              <property name="age" type="int"/><!--映射学生岁数-->

              <one-to-one name="cer" class="model.Centificate" fetch="join" cascade="all"/><!--映射对应的身份证对象-->

              <many-to-one name="team" column="team-id" class="model.Team" fetch="join"/><!--映射班级-->

        </class>

</hibernate-mapping>

 

 

配置信息的解释如下:

1.model.Student类对应的数据表是student表。当使用多对一、一对一方式加载Student类时,将采用延迟加载(前提是设置预先抓取为false)。

2.Student类有一个主键id,id由Hibernate自动生成,生成的算法是uuid.hex.在内存中有很多对象,可以通过unsaved-value来判定对象是持久化的,还是临时状态。当对象的id值为unsaved-value指定的"null"时,认为此对象是未持久化的,否则认为此对象是持久化状态或托管状态。

3.一个一对一关联的对象属性,名为cer,在得到Student对象时,采用预先抓取得到cer对象。并且级联为all,说明Student的增加、删除及修改操作都会影响到cer对象。

4.一个多对一关联的对象属性,名为team,在student表中通过team-id与team对象发生关联,team的实体类是model.Team。在取得Student实例时,使用预先抓取得到team对象。

 

.....

 

编写持久化类

每一个XXX.hbm.xml文件都必须对应一个实体类,对于XXX.hbm.xml文件,只需注意其中名为name的标签

 

如果目的只是根据配置文件写出对应的java类,很简单,只需注意两点:

name标签

type标签(如果不写的话,则是string)

 

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

Session的保存、删除及更新方法

 

save()方法

 

Session的save()方法将一个对象的属性取出放入PreparedStatement语句中,然后向数据库中插入一条记录(或多条,如果有级联)。

 

当Session保存一个对象时,按照以下的步骤进行:

(1)根据配置文件为主键id设置的生成算法,为stu指定一个id

(2)将stu对象纳入session的内部缓存内(一个Map)。

(3)事务提交时,清理缓存,将新的对象通过insert语句持久化到数据库中。

如果要为新对象强制指定一个id,可以调用Session的重载方法sava(Object obj,Serializable id),这种方法在使用代理主键时不推荐使用,除非在特殊场合确实需要指定特别的id才使用。

 

在调用save()方法时,并不立即执行SQL语句,而是等到清理缓存时才执行。如果调用save()方法后又修改了stu的属性,则Hibernate将会发送一条insert语句和一条update语句来完成持久化操作。

 

因此,最好是在对象状态稳定(也即属性不会再变化)时再调用save()方法,可以少执行一条update语句。

 

注意: 在应用程序中,使用代理主键时最好不要修改或设定主键id的值,因此在编写持久化类时,可以将setId()方法设为private,禁止外部程序的访问,而Hibernate则可以访问它。

 

 

update()方法

 

在实际应用中,update()方法一般有两种用途:

一.重新关联托管对象为持久化对象。

二.显式调用update()以更新对象。

 

1.update()方法不发送SQL语句

     调用update()方法时,并不立即发送SQL语句,对对象的更新操作将累积起来,在事务提交时由flush()清理缓存,然后发送一条SQL语句完成全部的更新操作。其实在大部分时候,调用update()只为了关联一个托管对象到持久状态,当对象已经是持久状态时,调用update()就没有多大意义了,因为对持久对象的更改在事务提交时总是会同步到数据库,而不管是否调用update()方法。

2.Hibernate总是执行update语句

     在使用update()方法进行脱管对象关联时,不管这个脱管对象在离开Session之后有没有更改过,在清理缓存时Hibernate总是发送一条update语句,以确保脱管对象和数据库记录的数据一致。

3.先执行select语句来判断,然后才执行update语句

     如果希望只有脱管对象改变了,Hibernate才生成update语句,可以把映射文件中<class>标签的select-before-update设为true,这样在执行这一个类的update语句之前,Hibernate都会发送一条select语句取得数据库中的值,和当前对象的值进行对比,如果值相同则不必发送update语句,只有不相同才发送update语句。

     select-before-update并不是设为true就好,对于经常更改的类,在每次update()语句之前总是要发送一条多于的select语句,反而影响性能;只有对于偶尔更改的类,设置select-before-update为true才是有效的,它可以用一条select语句防止一条update语句的生成,因为select语句总是比update语句执行效率要高。

4.批量更新

    在进行批量操作时(包括更新、删除),最好的方法是调用JDBC的API进行操作。

 

saveOrUpdate()方法

    saveOrUpdate()方法如何判断对象是脱管状态还是临时状态呢?当满足以下情况之一时,Hibernate就认为它是临时对象。

1.在映射文件中为<id>标签设置了unsaved-value属性,并且实体对象的id取值和unsaved-value匹配(默认为null)

2.在映射文件中位<vision>标签设置了unsaved-value属性,并且实体对象的version取值和unsaved-value匹配

 

注意:int、long型的主键id的默认unsaved-value值为0.

 

 

delete()方法。

    delete()方法负责删除一个对象(包括持久和脱管对象)。

 

    需要注意的是,在调用delete()方法时并不发送SQL语句,而是在提交事务时,清理缓存才发送SQL语句。

 

 

 

 

 

通过主键id取得数据对象

 

get()方法

经常使用的get()方法如下

public Object get(Class entityClass,Serializable id){...}

entityClass表明类的类型,id也就是对象的主键值。

get()方法的执行顺序如下:

1.首先通过id在Session缓存中查找对象,如果存在此id主键值的对象,直接将其返回

2.在二级缓存中查找,找到后将其返回。

3.如果在Session缓存和二级缓存中都找不到此对象,则从数据库加载拥有此id的对象。

 

get()方法具有以下特点

1.首先在缓存中查找对象,如果没有才到数据库中查找

2.对象存在时,返回立即检索策略得到的对象,永远不返回代理对象。

3.对象不存在时返回null

 

load()方法

load()方法和get()方法都能通过主键id值从数据库中加载一个实体对象,它们之间的不同点可以分为两个方面来讨论

1.立即加载对象

    在立即加载对象时,如果对象存在,load()和get()方法没有区别,都是取得已经初始化的对象,如果对象不存在并且是立即加载,get()方法返回null,但load()方法则弹出了例外。

2.延迟加载对象

    get()方法依然使用立即加载的方式发送SQL语句,并得到已经初始化的对象,而load()方法则根本不发送SQL语句,它返回一个代理对象,直到这个对象被访问使用时,此代理对象才会被初始化。

 

 

 

 

 

 

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

Query接口

 

 

 

绑定参数

1.使用“?”指定参数

通过Query接口可以先设定查询参数,然后通过setXXX()等方法,将指定的参数值填入,而不是每次都编写完整的HQL

2.使用“:”后跟变量的方法设置参数

可以使用命名参数来取代使用“?”设置参数的方法,这可以不依照特定的顺序来设定参数值。

3.常用绑定参数的方法

在绑定参数时,需要参数和型态相对应。

4.setEntity()方法

setEntity()方法把参数与一个持久化类的实例绑定。

5.setParameter()方法

6.setPropertyies()方法

 

 

使用命名查询

 (未看。。。。) 

 

 

list()方法

 

uniqueResult()方法

 

iterator()方法

 

查询缓存

 

 

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

清除缓存对象

在批量更新和查询中,缓存(包括Session缓存和二级缓存)占用率过高是不能忽视的问题,在适当时可以使用clear()和evict()方法来释放一定的缓存空间将有助于改善系统性能。

clear()方法

 

分享到:
评论

相关推荐

    hibernate笔记.txt

    在深入探讨《hibernate笔记.txt》所提及的关键知识点前,我们先来解析一下标题和描述中的核心概念。“自上而下的依赖,单向依赖,层与层之间最好依赖抽象”,这一描述实际上触及了软件架构设计中的关键原则,特别是...

    韩顺平.2011版.hibernate笔记.zip

    《韩顺平.2011版.hibernate笔记》是一份针对Hibernate框架的详细学习资料,由知名IT讲师韩顺平在2011年编撰而成。Hibernate是Java开发领域中广泛使用的对象关系映射(ORM)框架,它极大地简化了数据库操作,使开发者...

    传智播客 hibernate笔记(word+pdf+源码)

    【传智播客 Hibernate 笔记】是一份深入学习 Hibernate ORM 框架的资源集合,包括了 word 笔记、pdf 讲义、源代码以及相关分析图表和 jar 包,旨在帮助开发者全面理解并掌握 Hibernate 的核心概念与实际应用。...

    Hibernate笔记完整版

    《Hibernate笔记完整版》 在Web开发领域,Hibernate作为三大框架之一,扮演着至关重要的角色。本文将全面介绍Hibernate,包括其数据持久化的概念、优缺点对比,以及Hibernate的核心概念和编程步骤。 对象持久化是...

    韩顺平hibernate笔记

    《韩顺平Hibernate笔记》是一份详尽记录了著名IT讲师韩顺平关于Hibernate框架讲解内容的资料集合。Hibernate作为Java领域中广泛使用的对象关系映射(ORM)框架,极大地简化了数据库操作,使得开发者可以更加专注于...

    韩顺平Hibernate笔记

    《韩顺平Hibernate笔记》是一份详尽的关于Hibernate框架的学习资料,由知名IT教育专家韩顺平编撰。Hibernate是Java开发中的一个强大的对象关系映射(ORM)框架,它简化了数据库与Java对象之间的交互,使得开发者可以...

    Hibernate,hibernate笔记

    Hibernate 是一个开源的对象关系映射(ORM)框架,它允许Java开发者使用面向对象的方式来操作数据库。这个框架将数据库操作转化为对Java对象的操作,简化了数据持久化的复杂度。以下是对Hibernate的一些关键知识点的...

    尚学堂hibernate笔记

    ### 尚学堂Hibernate笔记知识点详解 #### 一、项目初始化与环境搭建 1. **新建项目**:在IDE中创建一个新的Java项目。 2. **构建用户库并添加所需的JAR包**: - 右键点击项目 -&gt; `Build Path` -&gt; `Configure ...

    Hibernate笔记

    培训期间的hibernate笔记 hibernate笔记 达内培训

    hibernate笔记.pdf

    hibernate笔记.pdf

    韩顺平hibernate笔记及图解

    总结起来,"韩顺平hibernate笔记及图解"涵盖了Hibernate的基础概念、核心组件、对象关系映射、查询机制、事务管理以及实体间的关系等内容。通过学习这份笔记,开发者能深入理解Hibernate的工作原理和使用技巧,提升...

    Spring+Hibernate笔记

    【Spring+Hibernate笔记】这篇文档主要涵盖了在Web开发中使用Spring和Hibernate的基础知识。首先,我们从Spring的Hello World开始,了解如何配置JSP运行环境。 1.1. 配置JSP运行环境是开发Web应用的第一步。这包括...

    马士兵hibernate笔记

    【马士兵Hibernate笔记】是一份面向初学者的教程,旨在帮助读者深入了解Hibernate这一持久化框架。Hibernate是一个基于Java的ORM(对象关系映射)工具,它允许开发者将数据库操作转换为面向对象的方式,从而简化了...

Global site tag (gtag.js) - Google Analytics