Hibernate实践

http://35java.com/zhibo/forum.php?mod=viewthread&tid=358&extra=page%3D4

一.

在实际项目中使用Hibernate有两年多了,在两年多的实践过程中既体验到了Hibernate带来的N多好处,同时也碰到不少的问题,特写此篇文章做个总结,记录自己在Hibernate实践中的一些经验,希望对于新使用Hibernate的朋友能有个帮助,避免走过多的弯路。

阅读本文前建议至少拥有Hibernate的一些基本知识,因为本文不会去详细介绍相关的基本知识,最好就是先用Hibernate开发了一个HelloWorld,^_^。

根据自己所经历的项目中使用Hibernate所涉及的范围,本文从开发环境、开发、设计、性能、测试以及推荐的相关书籍方面进行讲述,本篇文档不会讲的非常细致,只是根据自己在实践时的经验提出一些建议,关于细致以及具体的部分请参阅《HibernateReference》或推荐的相关书籍章节。

此文档的PDF版本请到此下载:

http://www.blogjava.net/Files/BlueDavy/Hibernate实践.rar

本文允许转载,但转载时请注明作者以及来源。

作者:BlueDavy

来源:www.blogjava.net/BlueDavy

二.

开发环境

Hibernate开发环境的搭建非常的简单,不过为了提高基于Hibernate开发的效率,通常都需要使用一些辅助工具,如xdoclet、middlegen等。

尽管Hibernate已经封装提供了很简单的进行持久的方法,但在实际项目的使用中基本还是要提供一些通用的代码,以便在进行持久的相关操作的时候能够更加的方便。

2.1.lib

2.1.1.

Hibernatelib

Hibernate相关的lib自然是开发环境中首要的问题,这部分可以从Hibernate的官方网站进行下载,在其官方网站中同时提供了对于Hibernate所必须依赖的lib以及其他可选lib的介绍。

2.2.xdoclet

Hibernate作为ORM工具,从名字上就能看出它需要一个从OàR的Mapping的描述,而这个描述就是在使用Hibernate时常见的hbm.xml,在没有工具支持的情况下,需要在编写持久层对象的同时手写这个文件,甚为不便。

在jdk5.0未推出之前,xdoclet支持的在javadoc中编写注释生成相关配置文件的方式大受欢迎,减少了编写hibernate映射文件的复杂性,手写一个完整的hibernate映射文件出错几率比较的高,再加上手写容易造成编写出来的风格相差很大,因此,基于xdoclet来生成hbm.xml的方式被大量的采用,基于xdoclet来编写能够基于我们在持久层对象上编写的javadoc来生成hbm.xml文件,非常的方便。

2.2.1.

Hibernatetemplate

如果没记错的话,大概在04年的时候javaeye上有位同仁整理了一个这样的template文件,^_^,非常感谢,我一直都在用着,呵呵。

这个文件的方便就是把它导入eclipse后,在javadoc中我们可以直接写hibid,然后按eclipse的代码辅助键(alt+/)来生成整个hibernate.id的相关的格式,呵呵,免得在写hibernate.id这些东西的时候过于麻烦,^_^,这个template文件我稍微做了修改,可在这里下载:

http://www.blogjava.net/Files/BlueDavy/templates-eclipse-tags.rar

当然,你也可以选择直接用xdoclet提供的template文件,不过xdoclet官方网站上好像只提供了可直接导入idea的模板文件。

关于注释上的hibernate.id这些东西具体请参见xdoclet官方网站的说明。

如果你的项目采用的是jdk5,那么就可以直接使用hibernateannotation了,那就更为方便。

2.2.2.

Anttaskbuild

Eclipse里没有集成xdoclet的插件,你也可以去安装一个jbosside的插件,里面有xdoclet的插件,反正我是觉得太麻烦了。

在项目中我仍然采用anttask的方式来生成hbm.xml,target如下所示:

<pathid="app.classpath">

<pathelementpath="${java.class.path}"/>

<filesetdir="${xdoclib.dir}">

<includename="*.jar"/>

</fileset>

</path>

<targetname="hbm"description="生成映射文件">

<tstamp>

<formatproperty="TODAY"pattern="yy-MM-dd"/>

</tstamp>

<taskdefname="hibernatedoclet"classname="xdoclet.modules.hibernate.HibernateDocletTask"classpathref="app.classpath"/>

<hibernatedocletdestdir="src/java"force="true"verbose="true"excludedtags="@version,@author,@todo">

<filesetdir="src/java">

<includename="**/po/**/*.java"/>

</fileset>

<hibernateversion="3.0"/>

</hibernatedoclet>

</target>

这个文件请根据项目情况以及环境稍做修改,^_^,其中需要通过properties文件指明xdocletlib.dir,类似xdocletlib.dir=c:\xdocletlib,里面放置xdoclet的相关jar文件。

在搭建好了这样的环境后,就可以在直接在eclipse中运行ant文件中的这个target来生成hbm.xml。

2.3.Hibernate3Tools

如果采用Hibernate3,则可以直接下载Hibernate3Tools的EclipsePlugin,那就可以类似在PL/SQL里执行sql一样在eclipse里执行hql,^_^

2.4.HibernateUtil

为了方便项目中Hibernate的使用,一般来说都会提供HibernateUtil这样的类,这个类的作用主要是创建sessionFactory和管理session,在Hibernate3以前采用的是在这里建立ThreadLocal来存放session,在Hibernate3以后则可以直接使用SessionFactory.getCurrentSession来获取session,而session的获取方式则可通过在hibernate.cfg.xml中执行current_session_context_class的属性来决定是采用thread或jta或自定义的方式来产生session。

2.5.CommonDao

在持久层部分目前采用的较多的仍然是dao模式,Hibernate作为ORM工具已经提供了CRUD的封装,类如可以使用session.save();session.persist()这样简单的方式来完成CRUD的操作,但在实际的项目中还是需要提供一个通用的Dao,来简化对于事务、异常处理以及session的操作,同时提供一些项目中需要的相关操作。

三.

开发

在完成了Hibernate的开发环境的搭建后,就可以基于Hibernate进行持久层的开发了,对于持久层开发来说,会涉及到实体的编写、实体的维护以及实体的查询三个部分。

3.1.实体的编写

Hibernate的一个明显的优点就是在于可透明化的对对象进行持久,这也就意味着持久对象根本就不需要依赖任何的东西,可以采用POJO的方式来编写,在Hibernate3以上版本还提供了对于Map、XML的方式的持久的支持,就更方便了,在项目中,更多采用的仍然是POJO的方式。

在实体的编写上应该说不会有什么问题,只要仔细查看xdoclet关于hibernatedoclet部分的说明即可完成。

这块需要学习的主要是普通的值类型注释的编写、id字段注释的编写、关联注释的编写,这些部分xdoclet均提供了较详细的说明。

3.2.实体的维护

3.2.1.

新增/编辑/删除

新增/编辑/删除是持久操作中最常使用的维护性操作,基于Hibernate做这样的维护就比采用sql的方式简单多了,通过上面CommonDao,就可以直接完成dao.save、dao.update、dao.delete的操作,而且在Hibernate3也支持了批量的insert、update和delete。

这个部分中需要注意的是Hibernate对于对象的三种状态的定义:

u

Transient

很容易理解,就是从未与session发生过关系的对象,^_^,例如在代码中直接Useruser=newUser();这样形成的user对象,就称为Transient对象了。

u

Detached

同样很容易理解,就是与session发生过关系的对象,但session已经关闭了的情况下存在的对象,例如:

Useruser=newUser();

user.setName(“bluedavy”);

session.save(user);

session.close();

在session.close()后这个时候的user对象就处于Detached状态之中了,如果想将这个对象变为Persistent状态,可以通过session.merge或session.saveOrUpdate()等方式来实现。

Detached状态的对象在实际的应用中最常采用,从概念上我们可以这么理解,处于Detached状态的对象可以看做是一个DTO,而不是PO,这从很大程度上就方便了PO在实际项目中的使用了。

u

Persistent

Persistent状态就是指和Session发生了关系的对象,并且此时session未关闭,举例如下:

Useruser=newUser();

user.setName(“bluedavy”);

session.save(user);

user.getName();

在session.save后user就处于Persistent状态,此时如果通过session根据user的id去获取user对象,则可发现获取的对象和之前的user是同一个对象,这是session一级缓存所起的作用了,当然,也可以强制的刷新session的一级缓存,让session从数据库中重新获取,只需要在获取前执行session.evict(user)或session.clear()。

3.2.2.

关联维护

关联维护在Hibernate中表现出来可能会让熟悉使用sql的人有些的不熟,但其实以对象的观点去看是会觉得很正常的。

在Hibernate的关联维护中,最重要的是inverse和cascade两个概念。

u

inverse

inverse从词义上看过去可能不是那么容易理解,其实它的意思就是由谁来控制关联关系的自动维护,当inverse=true就意味着当前对象是不能自动维护关联关系,当inverse=false就意味着当前对象可自动维护关联关系,还是举例来说:

假设Org和User一对多关联,

当org中getUsers的inverse=false的情况:

org.getUsers().add(user);

dao.save(org);

这样执行后将会看到数据库中user这条记录中的orgId已经被设置上去了。

当inverse=true的情况下,执行上面的代码,会发现在数据库中user这条记录中的orgId没有被设置上去。

^_^,inverse的作用这样可能看的不是很明显,在下面的一对多中会加以描述。

u

cascade

cascade的概念和数据库的cascade概念是基本一致的,cascade的意思形象的来说就是当当前对象执行某操作的情况下,其关联的对象也执行cascade设置的同样的操作。

例如当org.getUsers的cascade设置为delete时,当删除org时,相应的users也同样被删除了,但这个时候要注意,org.getUsers这个集合是被删除的user的集合,也就是说如果这个时候数据库中新增加了一个user给org,那么这个user是不会被删除的。

cascade的属性值详细见《Hibernatereference》。

3.2.2.1.

一对一

一对一的关联维护在实际项目中使用不多,一对一在Hibernate中可采用两种方式来构成,一种是主键关联,一种是外键关联。

一对一的使用推荐使用主键关联,具体配置方法请参见《HibernateReference》。

3.2.2.2.

一对多/多对一

一对多/多对一的关联维护在实际项目中使用是比较多的,在Hibernate中可采用多种方式来配置一对多的关联,如采用Set、List、Bag、Map等,具体在《HibernateReference》中都有详细说明。

在这里我想说的一点就是关于inverse的设置,在一对多的情况下建议将一端的inverse设为true,而由多端去自动维护关联关系,为什么这样做其实挺容易理解的,假设org和user为一对多的关联,org.getUsers的inverse设置为false,org.getUsers().add(user);dao.update(org);当update的时候org所关联的所有user的orgId都会更新一次,可想而知这个效率,而如果改为在多端维护(多端设置为inverse=false),则是这样:user.setOrg(org);dao.update(user);当update的时候就仅仅是更新user这一条记录而已。

另外一点就是合理的设置cascade,这个要根据需求来实际决定。

3.2.2.3.

多对多

多对多的关联维护在实际项目中其实也是比较多的,尽管在《HibernateReference》中认为多对多的情况其实很多时候都是设计造成的。

多对多的关联也同样可以采用Set、List等多种方式来配置,具体在《HibernateReference》中也有详细的说明。

多对多的关联维护上没有什么需要多说的,在实践过程中来看这块不会出什么太多问题,唯一需要注意的是合理设置cascade,这个要根据项目的实际情况而定。

3.3.实体的查询

Hibernate提供了多种方式来支持实体的查询,如对于原有熟悉sql的人可以继续使用sql,符合对象语言的对象查询语句(HQL)以及条件查询API(Criteria)。

在熟练使用hql或criteria的情况下,我相信你会觉得Hibernate的查询方式会比采用sql的方式更加简便。

3.3.1.

符合对象语言的查询语句

Hibernate提供了一种符合对象语言的查询语句,称为HQL,这种语句的好处是能够避免使用sql的情况下依赖数据库特征的情况出现,同时它带来的最大的好处就是我们能够根据OO的习惯去进行实体的查询。

对于HQL没有什么多讲的,如果熟悉sql的人应该也是能够很快就学会HQL,而如果不熟悉sql的人那也没关系,HQL的上手是非常容易的,具体请参考《HibernateReference》。

3.3.2.

占位符式的查询

占位符式的查询(就是采用?替换查询语句中的变量)是在采用sql的情况下经常使用的一种查询方式,也是查询时推荐使用的一种方式。

Hibernate中的查询参数主要有两种类型:值类型和实体类型,值类型就是指一个切实的值(如String、int、List这些),实体类型就是一个具体的实体,如编写的User、Organization等,值类型的查询和普通sql几乎一样,而实体类型的查询就体现了Hibernate的强项,^_^,可以起到简化sql的作用,并且使得查询语句更加容易理解。

3.3.2.1.

值类型

3.3.2.1.1.

简单值

举例如下:

fromUseruwhereu.name=:usernameandu.yearold=:yearold

这就是一个常见的简单值的占位符式的查询,通过这样的方式就可以把值注入到参数中:

query.setParameter(“username”,”bluedavy”);

query.setParameter(“yearold”,25);

同样,hibernate也支持和sql完全相同的?的方式,那么上面的语句以及注入参数的方式就变为了:

fromUseruwhereu.name=?andu.yearold=?

query.setParameter(0,”bluedavy”);

query.setParameter(1,25);

推荐使用第一种,那样参数的意义更容易被理解。

3.3.2.1.2.

in查询

in查询也是经常被使用到的一种查询,在Hibernate中表现出来会稍有不同,不过如果按照对象观点去看就很容易理解了,例如下面这句:

fromUseruwhereu.namein(:usernameList)

在Hibernate中通过这样的方式将值注入到这个参数中:

Listlist=newArrayList();

list.add(“jerry”);

list.add(“bluedavy”);

query.setParameterList(“usernameList”,list);

在sql中通常是组装一个由,连接的值来构成in中的参数值,而在Hibernate中则依照对象转化为采用list了,^_^,是不是更方便些。

3.3.2.2.

实体类型

在Hibernate中关联采用的都是对象形式,表现对外就是隐藏了数据库的外键的部分,这也就对习惯使用sql查询的人带来一个问题,因为无法再操作外键字段,那么在涉及到关联的实体的查询时应该怎么做呢,我把它分为单实体和实体集合两种情况来说说。

3.3.2.2.1.

单实体

单实体的查询对应到sql情况通常是在一对多的情况下通过多端查询同时结合一端的一些过滤条件,在sql中通常采用join的方式来实现这个,而在Hibernate中要实现这点就更容易了,举例如下:

User和Organization是一对多,现在要查询属于组织机构名称为”Blogjava”以及用户年龄大于20的用户:

fromUseruwhereu.org.name=rgnameandu.yearold>:yearold

query.setParameter(“orgname”,”Blogjava”);

query.setParameter(“yearold”,20);

可以看到这样的查询语句比sql更简单多了,同时也更容易理解多了。

3.3.2.2.2.

实体集合

实体集合过滤形式的查询在实际的项目中也经常会碰到,仍然用上面的例子,但改为通过Organization去查询:

fromOrganizationorgwhereorg.name=rgnameandorg.users.yearold>:yearold

是不是比sql简单多了,而且更容易理解呢,^_^

这个时候对象化查询语句的优势就体现出来了,而不用陷入sql的那种关系型的通过外键进行查询的方式。

3.3.3.

NamedQuery

NamedQuery的意思就是指在PO的映射文件中定义关于PO的查询语句,而在应用中指需要直接调用此查询语句的别名即可,这个好处非常明显,使得所有的查询语句可以统一的进行管理,同样,我们可以在PO中通过javadoc的方式进行定义,这就更方便了,^_^

操作NamedQuery的方法和普通hql的方法基本一样:

session.getNamedQuery(queryname);

其中的queryname就是我们定义的查询语句的别名,一个namedQuery的语句的示例如下:

<queryname="validate"><![CDATA[

fromUseruwhereu.loginname=:loginnameandu.password=:password

]]></query>

3.3.4.

Criteria

条件查询的API使得我们可以采用完全对象化的方式进行实体的查询,而不是通过hql的方式,在实际项目中,使用hql的方式更为居多,毕竟写起来更方便。

关于Criteria的具体介绍请参阅《HibernateReference》。

3.3.5.

原生SQL

原生SQL不推荐使用,但在某些确实需要用sql的情况下那么Hibernate还是支持的,具体见《HibernateReference》。

四.

设计

独立的编写这个章节的原因是希望在采用Hibernate的情况下充分的去发挥Hibernate的优势,改变我们以关系形式去做持久层的设计的惯性思维,形成以OO的思想去设计持久层,所以我非常推荐通过写PO去生成数据表的方式,而不是设计表反向导成PO的形式,当然,对于原有的系统那就没办法了。

OO思想中的核心三要素:封装、继承和多态,在Hibernate的支持下同样可以充分发挥OO的三要素来优化持久层的设计。

4.1.封装

4.1.1.

Component

Hibernate中有一个Component的概念,这就允许在进行持久层设计的时候采用细粒度级的领域模型进行设计,例如在User对象中需要记录User的firstname、lastname这些信息,而在其他的表中也有这种需求,那么在Hibernate中我们就可以把firstname、lastname组装为一个UserName对象,作为Component放入User中,在user中就可以变为采用user.getUserName.getFristName的方式来获取。

Component对于我们采用对象的封装概念进行持久层设计提供了很好的支持,同时在Hibernate中还有Elements、Properties这些元素,具体请参见《HibernateReference》。

4.2.继承

继承使得我们可以对持久层中的对象进行抽象,类如我们可以形成Person这个对象,而User、Employee都继承自这个对象。

继承在数据库形式的设计中固然也可以实现,但通常不能以对象的观点去发挥的淋漓尽致,当然不是说以对象的方式去设计一定是最好的。

在Hibernate中对于继承映射到数据表有几种不同的策略,各有适用的不同场合,具体的解释和说明见《HibernateReference》

4.2.1.

单表策略

单表策略很容易理解,就是将类、子类中所有的属性都放至一张表中,这对于子类属性不多的情况非常有效。

在Hibernate中通常将子类定义为@hibernate.subclass的方式来实现这个策略。

4.2.2.

每个子类一张表

每个子类一张表在Hibernate中有几种实现方式,@hibernate.join-subclass、@hibernate.join-subclass-key的组合方式以及@hibernate.join-subclass、@hibernate.discriminator的组合方式是较常用的两种方式,第一种方式采用的是主键关联方式,第二种方式采用的是discriminator字段的关联方式,个人比较推崇第一种方式。

这种策略适合在子类属性和父类有较大不同的情况下采用。

4.2.3.

每个具体类一张表

这种策略适合在类层次结构上有一定数量的抽象类的情况下使用,同样有两种方式,一种是采用显式多态的方式,另一种是采用隐式多态的方式,显式多态采用的为@hibernate.union-subclass的方式,隐式多态则采用每个具体类的PO独立建表的策略,在它的映射文件中将看不出任何的和接口、抽象类的关系,同时对于抽象类,需要指明其abstract=”true”。

4.3.多态

4.3.1.

查询

在查询中很容易体现Hibernate对于多态的支持,如系统有Person对象、User和Employee分别继承自Person,同时Person和Organization对象关联,这个时候我们通过Organization获取其关联的Person时得到的既有可能是User,也有可能是Employee,^_^…

五.

性能

Hibernate作为ORM工具,从性能上来讲带给了很多人忧虑,但我觉得Hibernate在性能上也许会带来少许的降低,但如果对于不能合理设计数据库和使用SQL的人来说,我觉得Hibernate反倒能提高性能,除非是在一些特殊的场合,如报表式的那种查询推荐继续采用JDBC的方式。

Hibernate在性能提升上其实有很多种做法,在《HibernateReference》中也有专门的提升性能的章节,在这里我提几点在项目中通常采用的方法。

相关推荐