??xml version="1.0" encoding="utf-8" standalone="yes"?> Join fetching : Hibernate取关联数据或集合是通过OUTER JOIN的方式,通过同一条select 语句来实现?/p>
Select fetching:在没有指定了lazy = "false"Q既延迟加蝲有效Q的情况下,通过另一条select 语句来获得与已经获得的实体相关的实体或集合。当?br />q种情况发生在用L正要获得兌对象的时候?/p>
Subselect fetching:在没有指定了lazy = "false"Q既延迟加蝲有效Q的情况下,先通过一条查询语句获得了一个实体集Q?然后对这个实体集中的每一个对象通过另一条select 语句来获得与它相关的实体或集合?br />当然q种情况发生在用L正要获得兌对象的时候?/p>
Batch fetching Q它是ؓ查询数据提供的一U优化策略。通过指定主键或外键的列表的方式来实现一条select 语句获得一批实体或集合?/p>
从另一个角度来看,hibernate的fetching 分成以下几种?/p>
Immediate fetching: 如果实体已经被加载了Q他的关联对象,兌集合Q属性也要及时加栽?/p>
lazy collection fetching: 只有应用E序真正使用q个集合的时候,才加栽这个集合?/p>
"Extra-lazy" collection fetching : hibernate 不加载一个集合的所有对象到内存里,需要哪个个体,加蝲哪个?/p>
Proxy fetching :当前对象的单值相兛_象只有在调用它的主键外的其他属性的getҎ时才加蝲它?/p>
"NO-proxy"fetching :当前对象的单值相兛_象在它的实体变量被访问的时候就被加载。相对于Proxy fetching来说QProxy fetching更gq?br />Q因?NO-proxy"fetching即是访问关联对象的dQ关联对象都要被加蝲Q?NO-proxy"fetching对于应用来说更条理清晰。因为在应用 Lazy attribute fetching Q当前对象的某个属性或单值相兛_象只有在与它对应的实体变量被讉K的时候才加蝲?/p>
Working with lazy associations 如果你设|了hibernate.default_batch_fetch_size,Hibernate׃通过扚w获取来优化lazy fetching. lazy fetching 会引起一个问题。就是关闭了hibernate session以后加蝲延迟加蝲的对象。这样会引v异常。如下: s = sessions.openSession(); ׃在session被关闭之前,permissions 没有被初始化Q所以它的数据没有被加蝲。hibernate不支持已l被分离的对?br />的gq加载。修改的Ҏ是把相关代码Udtx.commit()之前?/p>
或者我们可以在配置文g里通过在关联对象那里指?lazy="false"来兌集合或对象不被gq加载。但是如果你定义太多?br />非gq加载对象,hibernate 在一ơ事务中可以需要把整个数据库加载到内存中?/p>
从另一个角度来_在一ơ事务中Q我们经怋用joint fetching q种方式Q它天生׃是gq加载)来代替select fetching q种方式?br />下边我们p看到怎么自定?取策略。在hibernate3?单值和集合兌对象的取{略的指定方式是一致的?/p>
Tuning fetch strategies 一般来_我们不是通过在映配|文件自定义取策略,而是通过在一个事务里Q通过在特定的HQL里?left join 来覆盖默认的?br />{略。对于Criteria 来说Q提供了setFetchMode(FetchMode.JOIN) API.如下Q?/p>
User user = (User) session.createCriteria(User.class) 另一U完全不同的避免N+1ơselects 的方式是使用second-level cache. Single-ended association proxies 默认的情况下,Hibernate3为所有的持久化类生成代理,通过q些代理来完?many-to-one ? 在映文件中可以为类声明一个接口做为它的代理接?通过proxy属性指定。实际上,hibernate真正代理的是 <class name="Cat" proxy="Cat"> 首先要注意的是,Cat的实例不能当作DomesticCat实例使用。即使Cat和DomesticCat对应的是同一条数据?br />Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db) 其次Q两者之间不能?= 实际情况q如我们看到的那么p糕。即使我们引用了两个不同的代理对象,实际的对象却是相同的?br />cat.setWeight(11.0); // hit the db to initialize the proxy q需注意的是如果一个类是final class,或者它有finalҎ。我们就不能使用CGLIB代理. 最后,如果你的持久化对象在实例化的q程中获得的M资源(例如 在initializers或者默认的构造函数里),q些 q些问题的根源是java不能多重l承.如果你想避免q些问题,你应该让每一个类(子类和父c?实现一个声明了业务Ҏ的接? Cat cat = (Cat) session.load(CatImpl.class, catid); 以下Ҏ不需要代理的初始倹{?br />equals() 此方法没有被覆盖的时候?br />hashCode() 此方法没有被覆盖的时候?br />主键对应的getҎ?/p>
Initializing collections and proxies 有时候我们需要在session关闭之前保一个代理或集合被初始化。当然我们可以通过cat.getSex()或cat.getKittents().size() 静态方法Hibernate.initialize() 和Hibernate.isInitialized()为应用提供了处理延迟加蝲集合或代理的一U便h式?br />Hibernate.initialize(Cat) 会强制加载代?cat. Hibernate.initialize(cat.getKittens())初始化kittens集合。当然这些方法要?br />session关闭之前执行?/p>
另一U方式是在所有需要的集合和代理对象都被加载之后再关闭session. 在一些应用中Q尤其是当应用用hibernate来获?br />数据Q却在其他的应用层处理这些数据。或是这些数据是在其他的处理q程中用。ؓ了确保这些集合在初始化的时候session 1 Z web 的应用可以通过filter 在一ơ请求的最后关闭session.当然q样做是Z你的应用可以正确处理异常。非帔R要的一Ҏ要确保把信息q回l用户之前把事务l束和把sessionxQ即使是在你的页面处理发生异常的情况下?spring 的OpenSessionInViewFilter是Z此开发出来的) 2 如果你的应用有一个单独的业务层。在业务逻辑q里要保证在q回lweb 层信息之前完成所有的集合初始化工作。这意味着你的 3 你也可以在访问没有初试化的集合(或代理)之前把先前加载的一个对象通过merge()或lock()攑ֈ新的Session里。但是hibernate ?br />会也不应该自动完成这L工作Q因样需要用特D的事务处理语法?/p>
有时候,你需要获得集合中数据的个敎ͼ或者集合数据的一部分׃需要初始话整个集合。你可以通过Collection filter来获得集合中数据 Using batch fetching 扚w获取c?实体Ҏ理解Q假设有如下情况Q?br /> 在你的session里加载了25个Cat实例。每一个Cat都有一个own的引用指向一个person.在这里这个关联的person是通过代理的方式gq加?br /> Q单值关联对象)。如果你现在要通过循环调用所有cat的getOwner()Ҏ。hibernate会默认的执行25个select 语句来获得被代理的owner对象?br />我们可以通过在Personq个表的映射文g中指定batch-size来实现批量取数据?br /><class name="Person" batch-size="10">...</class> 你也可以寚w合进行批量取操作.例如,每一个person都有一个被延迟加蝲的集合Cats.现在在session中已l加载了10?person实例.循环调用 Using subselect fetching Using lazy property fetching Z使某个属性被延迟加蝲Q只需要在q个属性的影射文g中加上lazy属性即可?/p>
<class name="Document"> 属性的延迟加蝲需要用运行时的字节设备来处理。如果你的持久化c还没有被这个设备处理。hibernate 会忽略这个设|?br />采用及时加蝲的方式?/p>
要想使用此字节设备处理持久化c,使用如下的Ant d?/p>
<target name="instrument" depends="compile"> 另一U避免加载不需要的列的方式Q至在只读事务中,是通过使用HQL或Criteria查询属性。这样可以避免用字?br />处理工具?/p>
你可以通过在HQL指定fetch all properties 来加载全部属性?/p>
Fetching stategies是指hibernate在需要关联数据的时候所采用的取兌数据的策略。这个策略既可以在O/R映射文g里配Q也可以通过Ҏ?br />HQL:或Criteria语句实现?br />Hibernate定义了以下取{略Q?/p>
中没有一个可见的proxy.
个h认ؓ可以q样理解上述情况Q假如在数据库中存在两张表 A,B.表A中有一个指向表Bd的外键。如果想知道A表中的某?br /> 数据对应B表中的那条记录的主键。完全不用访问B表,A表中的此条数据的外键值就是B表中对应数据的主键。所有只有访问B表中
对应数据的主键外其他属性时Q才需要加载B表中的这条数据?/p>
默认的情况下QHibernate3 在获取关联对象集合的时候用的是lazy{略Q获得单值关联对象的时候用的是lazy proxy{略。这L{略
几乎适用所有的应用?/p>
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
默认的select fetching q种取策略很容器DN+1ơselect 操作q样的问题。所以我们可以在配置文g里指定join fetching {略。如下:
Cat对应的配|文Ӟ
<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set>
Permission对应的配|文Ӟ
<many-to-one name="mother" class="Cat" fetch="join"/>
在映文仉定义的取{略会媄响如下操作:
?get() 或load()执行的取操作?br /> 操作兌对象而引发的取操作?br /> Criteria查询?br /> 如果使用了subselect q种取策略还会媄响HQLq种查询方式?/p>
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
集合的gq加载是通过Hibernate自己的持久化集合实现?但是对于单个相关对象的gq加?br />需要一个不同的机制.相关的对象必被代理.Hibernate Ҏ久化对象的代理的延迟加蝲是通过
对运行时字节的动态注入实现的(通过CGLIB实现).
one-to-one 兌对象的gq加?
q个cȝ子类。需要注意的是,被代理的cddC个默认的构造函敎ͼ此构造函数的范围臛_是包内可见的Q?br />推荐所有的持久化类使用q种构造函数。我们现在可以看到的是在cȝ多态的时候会采用q种方式Q?/p>
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy
DomesticCat dc = (DomesticCat) cat; // Error!
....
}
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc);
System.out.println( dc.getWeight() ); // 11.0
资源也将被proxy获得.实际上代理的是这个类的子cR?/p>
在你的映文件中指定q些接口,如下:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
CatImpl实现了接口Cat,DomesticCatImpl实现了接口DomesticCat.Cat和DomesticCat实例的代理可?br />被load()或iterator()Ҏq回.(list()Ҏ一般不q回代理).
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();
关系也被延迟加蝲.q意呌你必dCat中声明所有的属性,而不仅仅是CatImpl.
如果在session的外边访问一个没有初始化的集合或代理Q会抛出一个LazyInitializationException异常。例如在分离?br />状态下Qsession 已经close的情况下Q访问一个实体的延迟加蝲的集合或代理对象?/p>
q种方式来强q初试化。但是这样会使代码阅读者迷茫而且不是一U通用的方便的~码格式?/p>
q处于打开状态,可以通过以下两种方式Q?/p>
业务层需要加载所有的数据q且把这些包括gq加载的数据传给与一个特定的用户h的相兛_现部分。一般来说这是通过在session
关闭之前针对相关的集合调用Hibernate.initialize()Ҏ或者是采用Criteria 的FetchMode.JOIN 方式。采用命令模式往往比采用session
FacadeҎ一些?/p>
的个敎ͼ不需要初始化整个集合Q?br />( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()?br />当然Collection filter也可以获取集合的一部分数据
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
扚w获取数据可以提高Hibernate的效?扚w获取是gqselect fetching{略的一U优?我们可以对类或者集合两个角度采用批量取数据.
Hibernate 现在会执行三条查询语句来完成查询Q模式是10,10,5.
所有的person的getCats()Ҏ会?0条select 语句.如果你在person的映文件中定义了批量获取模?
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
通过讄batch-size设ؓ3QHibernate 会以3,3,3,1的模式通过四条select语句加蝲集合?/p>
如果要加载一个gq加载的集合或一个单值的代理QHibernate通过一个subselect q行原来的查询语句,q种情况和batch-fetching是异曲同工的?/p>
Hibernate支持对单个属性的延迟加蝲。这个优化技术也?UCؓfetch groups. 需要注意的是,q个技术还处于推销阶段。因为在实际中,对行的读?br />优化比对列的优化更重要。然而在一些特D情况下Q加载一个类的部分属性还是有必要的,比如一个承的表有几百列而且数据模型q不能改变?/p>
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>
~存是介于应用程序和物理数据源之_其作用是Z降低应用E序对物理数据源讉K的频ơ,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复Ӟ应用E序在运行时从缓存读写数据,在特定的时刻或事件会同步~存和物理数据源的数据?/font>
~存的介质一般是内存Q所以读写速度很快。但如果~存中存攄数据量非常大Ӟ也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,q要考虑到管理缓存的q发讉K和缓存数据的生命周期?/span>
Hibernate 的缓存包?/span> Session 的缓存和 SessionFactory 的缓存,其中 SessionFactory 的缓存又可以分ؓ两类Q内|缓存和外置~存?/span> Session 的缓存是内置的,不能被卸载,也被UCؓ Hibernate 的第一U缓存?/span> SessionFactory 的内|缓存和 Session 的缓存在实现方式上比较相|前者是 SessionFactory 对象的一些集合属性包含的数据Q后者是?/span> Session 的一些集合属性包含的数据?/span> SessionFactory 的内|缓存中存放了映元数据和预定义 SQL 语句Q映元数据是映文件中数据的拷贝,而预定义 SQL 语句是在 Hibernate 初始化阶D|据映元数据推导出来Q?/span> SessionFactory 的内|缓存是只读的,应用E序不能修改~存中的映射元数据和预定?/span> SQL 语句Q因?/span> SessionFactory 不需要进行内|缓存与映射文g的同步?/span> SessionFactory 的外|缓存是一个可配置的插件。在默认情况下, SessionFactory 不会启用q个插g。外|缓存的数据是数据库数据的拷贝,外置~存的介质可以是内存或者硬盘?/span> SessionFactory 的外|缓存也被称?/span> Hibernate 的第二~存?/span>
Hibernate 的这两~存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢Qؓ了理解二者的区别Q需要深入理解持久化层的~存的两个特性:~存的范围和~存的ƈ发访问策略?/span>
持久化层的缓存的范围
~存的范围决定了~存的生命周期以及可以被谁访问。缓存的范围分ؓ三类?/span>
1 事务范围Q缓存只能被当前事务讉K。缓存的生命周期依赖于事务的生命周期Q当事务l束Ӟ~存也就l束生命周期。在此范围下Q缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,~存内的数据通常采用怺兌的的对象形式?/span>
2 q程范围Q缓存被q程内的所有事务共享。这些事务有可能是ƈ发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进E的生命周期Q进E结束时Q缓存也q束了生命周期。进E范围的~存可能会存攑֤量的数据Q所以存攄介质可以是内存或盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据Ş式。松散的对象数据形式有点cM于对象的序列化数据,但是对象分解为松散的法比对象序列化的算法要求更快?/span>
3 集群范围Q在集群环境中,~存被一个机器或者多个机器的q程׃n。缓存中的数据被复制到集环境中的每个进E节点,q程间通过q程通信来保证缓存中的数据的一致性,~存中的数据通常采用对象的松散数据Ş式?/span>
对大多数应用来说Q应该慎重地考虑是否需要用集范围的~存Q因问的速度不一定会比直接访问数据库数据的速度快多?/span>
持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,q可以到q程范围或集范围的~存内查询,如果q是没有查到Q那么只有到数据库中查询。事务范围的~存是持久化层的W一U缓存,通常它是必需的;q程范围或集范围的~存是持久化层的W二U缓存,通常是可选的?/span>
持久化层的缓存的q发讉K{略
当多个ƈ发的事务同时讉K持久化层的缓存的相同数据Ӟ会引起ƈ发问题,必须采用必要的事务隔L施?/span>
在进E范围或集群范围的缓存,即第二~存Q会出现q发问题。因此可以设定以下四U类型的q发讉K{略Q每一U策略对应一U事务隔ȝ别?/span>
事务型:仅仅在受理环境中适用。它提供?/span> Repeatable Read 事务隔离U别。对于经常被M很少修改的数据,可以采用q种隔离cdQ因为它可以防止脏读和不可重复读q类的ƈ发问题?/span>
d型:提供?/span> Read Committed 事务隔离U别。仅仅在非集的环境中适用。对于经常被M很少修改的数据,可以采用q种隔离cdQ因为它可以防止脏读q类的ƈ发问题?/span>
非严D写型Q不保证~存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须数据配置一个很短的数据q期旉Q从而尽量避免脏诅R对于极被修改Qƈ且允许偶脏ȝ数据Q可以采用这Uƈ发访问策略?/span>
只读型:对于从来不会修改的数据,如参考数据,可以使用q种q发讉K{略?/span>
事务型ƈ发访问策略是事务隔离U别最高,只读型的隔离U别最低。事务隔ȝ别越高,q发性能p低?/span>
什么样的数据适合存放到第二~存中?
1 很少被修改的数据
2 不是很重要的数据Q允许出现偶ƈ发的数据
3 不会被ƈ发访问的数据
4 参考数?/span>
不适合存放到第二~存的数据?
1 l常被修改的数据
2 财务数据Q绝对不允许出现q发
3 与其他应用共享的数据?/span>
Hibernate 的二U缓?/span>
如前所qͼ Hibernate 提供了两U缓存,W一U是 Session 的缓存。由?/span> Session 对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的~存是事务范围的~存。第一U缓存是必需的,不允许而且事实上也无法比卸除。在W一U缓存中Q持久化cȝ每个实例都具有唯一?/span> OID ?/span>
W二U缓存是一个可插拔的的~存插gQ它是由 SessionFactory 负责理。由?/span> SessionFactory 对象的生命周期和应用E序的整个过E对应,因此W二U缓存是q程范围或者集范围的~存。这个缓存中存放的对象的松散数据。第二对象有可能出现ƈ发问题,因此需要采用适当的ƈ发访问策略,该策略ؓ被缓存的数据提供了事务隔ȝ别。缓存适配器用于把具体的缓存实现Y件与 Hibernate 集成。第二~存是可选的Q可以在每个cL每个集合的粒度上配置W二U缓存?/span>
Hibernate 的二U缓存策略的一般过E如下:
1) 条g查询的时候,L发出一?/span> select * from table_name where ? Q选择所有字D)q样?/span> SQL 语句查询数据库,一ơ获得所有的数据对象?/span>
2) 把获得的所有数据对象根?/span> ID 攑օ到第二~存中?/span>
3) ?/span> Hibernate Ҏ ID 讉K数据对象的时候,首先?/span> Session 一U缓存中查;查不刎ͼ如果配置了二U缓存,那么从二U缓存中查;查不刎ͼ再查询数据库Q把l果按照 ID 攑օ到缓存?/span>
4) 删除、更新、增加数据的时候,同时更新~存?/span>
Hibernate 的二U缓存策略,是针对于 ID 查询的缓存策略,对于条g查询则毫无作用。ؓ此, Hibernate 提供了针Ҏ件查询的 Query ~存?/span>
Hibernate ?/span> Query ~存{略的过E如下:
1) Hibernate 首先Ҏq些信息l成一?/span> Query Key Q?/span> Query Key 包括条g查询的请求一般信息: SQL, SQL 需要的参数Q记录范_起始位置 rowStart Q最大记录个?/span> maxRows) Q等?/span>
2) Hibernate Ҏq个 Query Key ?/span> Query ~存中查扑֯应的l果列表。如果存在,那么q回q个l果列表Q如果不存在Q查询数据库Q获取结果列表,把整个结果列表根?/span> Query Key 攑օ?/span> Query ~存中?/span>
3) Query Key 中的 SQL 涉及C些表名,如果q些表的M数据发生修改、删除、增加等操作Q这些相关的 Query Key 都要从缓存中清空?/span>