外连接与预先抓取的区别
首先讲述一个重点,它将贯穿本小节的始末
假设有一个类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()方法
发表评论
-
好文好博链接
2010-07-17 12:02 669好文关于反射 http://www.blogjava.net ... -
设计模式
2010-04-21 19:58 711适配器模式(Adapter): ... -
spring笔记(读spring技术手册)
2009-07-19 22:15 891Bean、消息、事件 Spring的核心是一个容器(Cont ... -
Log4j笔记(摘自javaEye)
2009-07-18 20:00 854Log4j由3个重要的组件构 ... -
Hibernate事务与Cache管理笔记
2009-07-18 14:26 943事务(Transaction)是工作中的基本逻辑单位,可以用于 ... -
读重构笔记
2009-07-04 15:00 698什么是重构? 所谓重构是这样一个过程:在不改变代码外在行为的 ... -
实现DAO工厂类 笔记
2009-06-29 15:50 2344所谓DAO工厂类,它的作用简单地说就是得到DAO类的实例,也可 ... -
java基础摘记
2009-06-27 19:12 748继承的原始设计动机是抽象,即通过继承的做法,我们可以“ ... -
Hibernate数据查询笔记
2009-06-22 13:47 817使用Hibernate取得对象有以下几种方法: 1. ... -
接口的作用笔记
2009-06-22 12:56 948接口的引进是为了实现多继承,同时免除C++中的多继承那样的复杂 ... -
在web应用中使用FreeMarker
2009-04-07 21:16 889在web应用中使用freeMarker与在java应用中使用F ...
相关推荐
在深入探讨《hibernate笔记.txt》所提及的关键知识点前,我们先来解析一下标题和描述中的核心概念。“自上而下的依赖,单向依赖,层与层之间最好依赖抽象”,这一描述实际上触及了软件架构设计中的关键原则,特别是...
《韩顺平.2011版.hibernate笔记》是一份针对Hibernate框架的详细学习资料,由知名IT讲师韩顺平在2011年编撰而成。Hibernate是Java开发领域中广泛使用的对象关系映射(ORM)框架,它极大地简化了数据库操作,使开发者...
【传智播客 Hibernate 笔记】是一份深入学习 Hibernate ORM 框架的资源集合,包括了 word 笔记、pdf 讲义、源代码以及相关分析图表和 jar 包,旨在帮助开发者全面理解并掌握 Hibernate 的核心概念与实际应用。...
《Hibernate笔记完整版》 在Web开发领域,Hibernate作为三大框架之一,扮演着至关重要的角色。本文将全面介绍Hibernate,包括其数据持久化的概念、优缺点对比,以及Hibernate的核心概念和编程步骤。 对象持久化是...
《韩顺平Hibernate笔记》是一份详尽记录了著名IT讲师韩顺平关于Hibernate框架讲解内容的资料集合。Hibernate作为Java领域中广泛使用的对象关系映射(ORM)框架,极大地简化了数据库操作,使得开发者可以更加专注于...
《韩顺平Hibernate笔记》是一份详尽的关于Hibernate框架的学习资料,由知名IT教育专家韩顺平编撰。Hibernate是Java开发中的一个强大的对象关系映射(ORM)框架,它简化了数据库与Java对象之间的交互,使得开发者可以...
Hibernate 是一个开源的对象关系映射(ORM)框架,它允许Java开发者使用面向对象的方式来操作数据库。这个框架将数据库操作转化为对Java对象的操作,简化了数据持久化的复杂度。以下是对Hibernate的一些关键知识点的...
### 尚学堂Hibernate笔记知识点详解 #### 一、项目初始化与环境搭建 1. **新建项目**:在IDE中创建一个新的Java项目。 2. **构建用户库并添加所需的JAR包**: - 右键点击项目 -> `Build Path` -> `Configure ...
培训期间的hibernate笔记 hibernate笔记 达内培训
hibernate笔记.pdf
总结起来,"韩顺平hibernate笔记及图解"涵盖了Hibernate的基础概念、核心组件、对象关系映射、查询机制、事务管理以及实体间的关系等内容。通过学习这份笔记,开发者能深入理解Hibernate的工作原理和使用技巧,提升...
【Spring+Hibernate笔记】这篇文档主要涵盖了在Web开发中使用Spring和Hibernate的基础知识。首先,我们从Spring的Hello World开始,了解如何配置JSP运行环境。 1.1. 配置JSP运行环境是开发Web应用的第一步。这包括...
【马士兵Hibernate笔记】是一份面向初学者的教程,旨在帮助读者深入了解Hibernate这一持久化框架。Hibernate是一个基于Java的ORM(对象关系映射)工具,它允许开发者将数据库操作转换为面向对象的方式,从而简化了...