数据库查询性能的提高也是涉及到开发中的各个阶段,在开发中选用正确的查询方法无疑是最基础也最简单的。java
使用正确的SQL语句能够在很大程度上提升系统的查询性能。得到一样数据而采用不一样方式的SQL语句在性能上的差距多是十分巨大的。mysql
因为Hibernate是对JDBC的封装,SQL语句的产生都是动态由Hibernate自动完成的。Hibernate产生SQL语句的方式有两种:一种是经过开发人员编写的HQL语句来生成,另外一种是依据开发人员对关联对象的访问来自动生成相应的SQL语句。程序员
至于使用什么样的SQL语句能够得到更好的性能要依据数据库的结构以及所要获取数据的具体状况来进行处理。在肯定了所要执行的SQL语句后,能够经过如下三个方面来影响Hibernate所生成的SQL语句:web
● HQL语句的书写方法。正则表达式
● 查询时所使用的查询方法。spring
● 对象关联时所使用的抓取策略。sql
在前面已经介绍过,执行数据查询功能的基本方法有两种:一种是获得单个持久化对象的get()方法和load()方法,另外一种是Query对象的list()方法和iterator()方法。在开发中应该依据不一样的状况选用正确的方法。数据库
get()方法和load()方法的区别在于对二级缓存的使用上。load()方法会使用二级缓存,而get()方法在一级缓存没有找到的状况下会直接查询数据库,不会去二级缓存中查找。在使用中,对使用了二级缓存的对象进行查询时最好使用load()方法,以充分利用二级缓存来提升检索的效率。express
list()方法和iterator()方法之间的区别能够从如下几个方面来进行比较。
● 执行的查询不一样
list()方法在执行时,是直接运行查询结果所须要的查询语句,而iterator()方法则是先执行获得对象ID的查询,而后再根据每一个ID值去取得所要查询的对象。所以,对于list()方式的查询一般只会执行一个SQL语句,而对于iterator()方法的查询则可能须要执行N+1条SQL语句(N为结果集中的记录数)。
iterator()方法只是可能执行N+1条数据,具体执行SQL语句的数量取决于缓存的状况以及对结果集的访问状况。
● 缓存的使用
list()方法只能使用二级缓存中的查询缓存,而没法使用二级缓存对单个对象的缓存(可是会把查询出的对象放入二级缓存中)。因此,除非重复执行相同的查询操做,不然没法利用缓存的机制来提升查询的效率。
iterator()方法则能够充分利用二级缓存,在根据ID检索对象的时候会首先到缓存中查找,只有在找不到的状况下才会执行相应的查询语句。因此,缓存中对象的存在与否会影响到SQL语句的执行数量。
● 对于结果集的处理方法不一样
list()方法会一次得到全部的结果集对象,并且它会依据查询的结果初始化全部的结果集对象。这在结果集很是大的时候必然会占据很是多的内存,甚至会形成内存溢出状况的发生。
iterator()方法在执行时不会一次初始化全部的对象,而是根据对结果集的访问状况来初始化对象。所以在访问中能够控制缓存中对象的数量,以免占用过多缓存,致使内存溢出状况的发生。使用iterator()方法的另一个好处是,若是只须要结果集中的部分记录,那么没有被用到的结果对象根本不会被初始化。因此,对结果集的访问状况也是调用iterator()方法时执行数据库SQL语句多少的一个因素。
因此,在使用Query对象执行数据查询时应该从以上几个方面去考虑使用何种方法来执行数据库的查询操做。
所谓抓取策略(fetching strategy)是指当应用程序须要利用关联关系进行对象获取的时候,Hibernate获取关联对象的策略。抓取策略能够在O/R映射的元数据中声明,也能够在特定的HQL或条件查询中声明。
Hibernate 3定义了如下几种抓取策略。
● 链接抓取(Join fetching)
链接抓取是指Hibernate在得到关联对象时会在SELECT语句中使用外链接的方式来得到关联对象。
● 查询抓取(Select fetching)
查询抓取是指Hibernate经过另一条SELECT语句来抓取当前对象的关联对象的方式。这也是经过外键的方式来执行数据库的查询。与链接抓取的区别在于,一般状况下这个SELECT语句不是当即执行的,而是在访问到关联对象的时候才会执行。
● 子查询抓取(Subselect fetching)
子查询抓取也是指Hibernate经过另一条SELECT语句来抓取当前对象的关联对象的方式。与查询抓取的区别在于它所采用的SELECT语句的方式为子查询,而不是经过外链接。
● 批量抓取(Batch fetching)
批量抓取是对查询抓取的优化,它会依据主键或者外键的列表来经过单条SELECT语句实现管理对象的批量抓取。
以上介绍的是Hibernate 3所提供的抓取策略,也就是抓取关联对象的手段。为了提高系统的性能,在抓取关联对象的时机上,还有如下一些选择。
● 当即抓取(Immediate fetching)
当即抓取是指宿主对象被加载时,它所关联的对象也会被当即加载。
● 延迟集合抓取(Lazy collection fetching)
延迟集合抓取是指在加载宿主对象时,并不当即加载它所关联的对象,而是到应用程序访问关联对象的时候才抓取关联对象。这是集合关联对象的默认行为。
● 延迟代理抓取(Lazy proxy fetching)
延迟代理抓取是指在返回单值关联对象的状况下,并不在对其进行get操做时抓取,而是直到调用其某个方法的时候才会抓取这个对象。
● 延迟属性加载(Lazy attribute fetching)
延迟属性加载是指在关联对象被访问的时候才进行关联对象的抓取。
介绍了Hibernate所提供的关联对象的抓取方法和抓取时机,这两个方面的因素都会影响Hibernate的抓取行为,最重要的是要清楚这两方面的影响是不一样的,不要将这两个因素混淆,在开发中要结合实际状况选用正确的抓取策略和合适的抓取时机。
抓取时机的选择
在Hibernate 3中,对于集合类型的关联在默认状况下会使用延迟集合加载的抓取时机,而对于返回单值类型的关联在默认状况下会使用延迟代理抓取的抓取时机。
对于当即抓取在开发中不多被用到,由于这极可能会形成没必要要的数据库操做,从而影响系统的性能。当宿主对象和关联对象老是被同时访问的时候才有可能会用到这种抓取时机。另外,使用当即链接抓取能够经过外链接来减小查询SQL语句的数量,因此,也会在某些特殊的状况下使用。
然而,延迟加载又会面临另一个问题,若是在Session关闭前关联对象没有被实例化,那么在访问关联对象的时候就会抛出异常。处理的方法就是在事务提交以前就完成对关联对象的访问。
因此,在一般状况下都会使用延迟的方式来抓取关联的对象。由于每一个当即抓取都会致使关联对象的当即实例化,太多的当即抓取关联会致使大量的对象被实例化,从而占用过多的内存资源。
抓取策略的选取
对于抓取策略的选取将影响到抓取关联对象的方式,也就是抓取关联对象时所执行的SQL语句。这就要根据实际的业务需求、数据的数量以及数据库的结构来进行选择了。
在这里须要注意的是,一般状况下都会在执行查询的时候针对每一个查询来指定对其合适的抓取策略。指定抓取策略的方法以下所示:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
本文介绍了查询性能提高的方法,关键是如何经过优化SQL语句来提高系统的查询性能。查询方法和抓取策略的影响也是经过执行查询方式和SQL语句的多少来改变系统的性能的。这些都属于开发人员所应该掌握的基本技能,避免因为开发不当而致使系统性能的低下。
在性能调整中,除了前面介绍的执行SQL语句的因素外,对于缓存的使用也会影响系统的性能。一般来讲,缓存的使用会增长系统查询的性能,而下降系统增长、修改和删除操做的性能(由于要进行缓存的同步处理)。因此,开发人员应该可以正确地使用有效的缓存来提升数据查询的性能,而要避免滥用缓存而致使的系统性能变低。在采用缓存的时候也应该注意调整本身的检索策略和查询方法,这三者配合起来才能够达到最优的性能。
另外,事务的使用策略也会影响到系统的性能。选取正确的事务隔离级别以及使用正确的锁机制来控制数据的并发访问都会影响到系统的性能。
posted @ 2009-07-19 21:36 jadmin 阅读(1) 评论(0) 编辑
Hibernate是对JDBC的轻量级封装,所以在不少状况下Hibernate的性能比直接使用JDBC存取数据库要低。然而,经过正确的方法和策略,在使用Hibernate的时候仍是能够很是接近直接使用JDBC时的效率的,而且,在有些状况下还有可能高于使用JDBC时的执行效率。
在进行Hibernate性能优化时,须要从如下几个方面进行考虑:
● 数据库设计调整。
● HQL优化。
● API的正确使用(如根据不一样的业务类型选用不一样的集合及查询API)。
● 主配置参数(日志、查询缓存、fetch_size、batch_size等)。
● 映射文件优化(ID生成策略、二级缓存、延迟加载、关联优化)。
● 一级缓存的管理。
● 针对二级缓存,还有许多特有的策略。
● 事务控制策略。
数据的查询性能每每是影响一个应用系统性能的主要因素。对查询性能的影响会涉及到系统软件开发的各个阶段,例如,良好的设计、正确的查询方法、适当的缓存都有利于系统性能的提高。
系统性能的提高设计到系统中的各个方面,是一个相互平衡的过程,须要在应用的各个阶段都要考虑。而且在开发、运行的过程当中要不断地调整和优化才能逐步提高系统的性能。
posted @ 2009-07-19 21:30 jadmin 阅读(1) 评论(0) 编辑
在前面介绍了Hibernate的缓存技术以及基本的用法,在这里就具体的Hibernate所提供的查询方法与Hibernate缓存之间的关系作一个简单的总结。
在开发中,一般是经过两种方式来执行对数据库的查询操做的。一种方式是经过ID来得到单独的Java对象,另外一种方式是经过HQL语句来执行对数据库的查询操做。下面就分别结合这两种查询方式来讲明一下缓存的做用。
经过ID来得到Java对象能够直接使用Session对象的load()或者get()方法,这两种方式的区别就在于对缓存的使用上。
● load()方法
在使用了二级缓存的状况下,使用load()方法会在二级缓存中查找指定的对象是否存在。
在执行load()方法时,Hibernate首先从当前Session的一级缓存中获取ID对应的值,在获取不到的状况下,将根据该对象是否配置了二级缓存来作相应的处理。
如配置了二级缓存,则从二级缓存中获取ID对应的值,如仍然获取不到则还须要根据是否配置了延迟加载来决定如何执行,如未配置延迟加载则从数据库中直接获取。在从数据库获取到数据的状况下,Hibernate会相应地填充一级缓存和二级缓存,如配置了延迟加载则直接返回一个代理类,只有在触发代理类的调用时才进行数据库的查询操做。
在Session一直打开的状况下,并在该对象具备单向关联维护的时候,须要使用相似Session.clear(),Session.evict()的方法来强制刷新一级缓存。
● get()方法
get()方法与load()方法的区别就在于不会查找二级缓存。在当前Session的一级缓存中获取不到指定的对象时,会直接执行查询语句从数据库中得到所须要的数据。
在Hibernate中,能够经过HQL来执行对数据库的查询操做。具体的查询是由Query对象的list()和iterator()方法来执行的。这两个方法在执行查询时的处理方法存在着必定的差异,在开发中应该依据具体的状况来选择合适的方法。
● list()方法
在执行Query的list()方法时,Hibernate的作法是首先检查是否配置了查询缓存,如配置了则从查询缓存中寻找是否已经对该查询进行了缓存,如获取不到则从数据库中进行获取。从数据库中获取到后,Hibernate将会相应地填充一级、二级和查询缓存。如获取到的为直接的结果集,则直接返回,如获取到的为一些ID的值,则再根据ID获取相应的值(Session.load()),最后造成结果集返回。能够看到,在这样的状况下,list()方法也是有可能形成N次查询的。
查询缓存在数据发生任何变化的状况下都会被自动清空。
● iterator()方法
Query的iterator()方法处理查询的方式与list()方法是不一样的,它首先会使用查询语句获得ID值的列表,而后再使用Session的load()方法获得所须要的对象的值。
在获取数据的时候,应该依据这4种获取数据方式的特色来选择合适的方法。在开发中能够经过设置show_sql选项来输出Hibernate所执行的SQL语句,以此来了解Hibernate是如何操做数据库的。
posted @ 2009-07-19 21:29 jadmin 阅读(2) 评论(0) 编辑
查询缓存是专门针对各类查询操做进行缓存。查询缓存会在整个SessionFactory的生命周期中起做用,存储的方式也是采用key-value的形式来进行存储的。
查询缓存中的key是根据查询的语句、查询的条件、查询的参数和查询的页数等信息组成的。而数据的存储则会使用两种方式,使用SELECT语句只查询实体对象的某些列或者某些实体对象列的组合时,会直接缓存整个结果集。而对于查询结果为某个实体对象集合的状况则只会缓存实体对象的ID值,以达到缓存空间能够共用,节省空间的目的。
在使用查询缓存时,除了须要设置hibernate.cache.provider_class参数来启动二级缓存外,还须要经过hibernate.cache.use_query_cache参数来启动对查询缓存的支持。
另外须要注意的是,查询缓存是在执行查询语句的时候指定缓存的方式以及是否须要对查询的结果进行缓存。
下面就来了解一下查询缓存的使用方法及做用。
修改Hibernate配置文件
首先须要修改Hibernate的配置文件,增长hibernate.cache.use_query_cache参数的配置。配置方法以下:
<property name="hibernate.cache.use_query_cache">true</property>
Hibernate配置文件的详细内容请参考配套光盘中的hibernate\src\cn\hxex\ hibernate\cache\hibernate.cfg.xml文件。
编写主测试程序
因为这是在前面二级缓存例子的基础上来开发的,因此,对于EHCache的配置以及视图对象的开发和映射文件的配置工做就都不须要再从新进行了。下面就来看一下主测试程序的实现方法,如清单14.11所示。
清单14.11 主程序的实现
……
public void run() {
SessionFactory sf = QueryCacheMain.getSessionFactory();
Session session = sf.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery( "from User" );
Iterator it = query.setCacheable( true ).list().iterator();
while( it.hasNext() ) {
System.out.println( it.next() );
}
User user = (User)session.get( User.class, "1" );
System.out.println( user );
session.getTransaction().commit();
}
public static void main(String[] args) {
QueryCacheMain main1 = new QueryCacheMain();
main1.start();
try {
Thread.sleep( 2000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
QueryCacheMain main2 = new QueryCacheMain();
main2.start();
}
}
主程序在实现的时候采用了多线程的方式来运行。首先将“from User”查询结果进行缓存,而后再经过ID取得对象来检查是否对对象进行了缓存。另外,多个线程的执行能够看出对于进行了缓存的查询是不会执行第二次的。
运行测试主程序
接着就来运行测试主程序,其输出结果应该以下所示:
Hibernate: select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_ from USERINFO user0_
ID: 1
Namge: galaxy
Age: 32
ID: 1
Namge: galaxy
Age: 32
ID: 1
Namge: galaxy
Age: 32
ID: 1
Namge: galaxy
Age: 32
经过上面的执行结果能够看到,在两个线程执行中,只执行了一个SQL查询语句。这是由于根据ID所要获取的对象在前面的查询中已经获得了,并进行了缓存,因此没有再次执行查询语句。
posted @ 2009-07-19 21:25 jadmin 阅读(1) 评论(0) 编辑
与Session相对的是,SessionFactory也提供了相应的缓存机制。SessionFactory缓存能够依据功能和目的的不一样而划分为内置缓存和外置缓存。
SessionFactory的内置缓存中存放了映射元数据和预约义SQL语句,映射元数据是映射文件中数据的副本,而预约义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来的。SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预约义SQL语句,所以SessionFactory不须要进行内置缓存与映射文件的同步。
SessionFactory的外置缓存是一个可配置的插件。在默认状况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的副本,外置缓存的介质能够是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的二级缓存。
Hibernate的二级缓存的实现原理与一级缓存是同样的,也是经过以ID为key的Map来实现对对象的缓存。
因为Hibernate的二级缓存是做用在SessionFactory范围内的,于是它比一级缓存的范围更广,能够被全部的Session对象所共享。
Hibernate的二级缓存同一级缓存同样,也是针对对象ID来进行缓存。因此说,二级缓存的做用范围是针对根据ID得到对象的查询。
二级缓存的工做能够归纳为如下几个部分:
● 在执行各类条件查询时,若是所得到的结果集为实体对象的集合,那么就会把全部的数据对象根据ID放入到二级缓存中。
● 当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,若是查不到而且配置了二级缓存,那么会从二级缓存中查找,若是还查不到,就会查询数据库,把结果按照ID放入到缓存中。
● 删除、更新、增长数据的时候,同时更新缓存。
Hibernate的二级缓存做为一个可插入的组件在使用的时候也是能够进行配置的,但并非全部的对象都适合放在二级缓存中。
在一般状况下会将具备如下特征的数据放入到二级缓存中:
● 不多被修改的数据。
● 不是很重要的数据,容许出现偶尔并发的数据。
● 不会被并发访问的数据。
● 参考数据。
而对于具备如下特征的数据则不适合放在二级缓存中:
● 常常被修改的数据。
● 财务数据,绝对不容许出现并发。
● 与其余应用共享的数据。
在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在本身程序中使用其余方式进行数据的修改,例如,JDBC),由于那样Hibernate将不会知道数据已经被修改,也就没法保证缓存中的数据与数据库中数据的一致性。
在默认状况下,Hibernate会使用EHCache做为二级缓存组件。可是,能够经过设置hibernate.cache.provider_class属性,指定其余的缓存策略,该缓存策略必须实现org.hibernate.cache.CacheProvider接口。
经过实现org.hibernate.cache.CacheProvider接口能够提供对不一样二级缓存组件的支持。
Hibernate内置支持的二级缓存组件如表14.1所示。
表14.1 Hibernate所支持的二级缓存组件
posted @ 2009-07-19 21:23 jadmin 阅读(0) 评论(0) 编辑
你们都知道,Hibernate是以JDBC为基础实现的持久层组件,于是其性能确定会低于直接使用JDBC来访问数据库。所以,为了提升Hibernate的性能,在Hibernate组件中提供了完善的缓存机制来提升数据库访问的性能。
什么是缓存
缓存是介于应用程序和物理数据之间的,其做用是为了下降应用程序对物理数据访问的频次从而提升应用系统的性能。缓存思想的提出主要是由于对物理数据的访问效率要远远低于对内存的访问速度,于是采用了将部分物理数据存放于内存当中,这样能够有效地减小对物理数据的访问次数,从而提升系统的性能。
缓存普遍地存在于咱们所接触的各类应用系统中,例如数据库系统、Windows操做系统等,在进行物理数据的访问时无一例外地都使用了缓存机制来提升操做的性能。
缓存内的数据是对物理数据的复制,所以一个缓存系统所应该包括的最基本的功能是数据的缓存和读取,同时在使用缓存的时候还要考虑缓存中的数据与物理数据的同步,也就是要保持二者是一致的。
缓存要求对数据的读写速度很高,所以,通常状况下会选用内存做为存储的介质。但若是内存有限,而且缓存中存放的数据量很是大时,也会用硬盘做为缓存介质。缓存的实现不只仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。
为了提升系统的性能,Hibernate也使用了缓存的机制。在Hibernate框架中,主要包括如下两个方面的缓存:一级缓存和二级缓存(包含查询缓存)。Hibernate中缓存的做用主要表如今如下两个方面:
● 经过主键(ID)加载数据的时候
● 延迟加载中
一级缓存
Hibernate的一级缓存是由Session提供的,所以它只存在于Session的生命周期中,也就是当Session关闭的时候该Session所管理的一级缓存也会当即被清除。
Hibernate的一级缓存是Session所内置的,不能被卸载,也不能进行任何配置。
一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。因此说,一级缓存是以实体对象为单位进行存储的,在访问的时候使用的是主关键字ID。
虽然,Hibernate对一级缓存使用的是自动维护的功能,没有提供任何配置功能,可是能够经过Session中所提供的方法来对一级缓存的管理进行手工干预。Session中所提供的干预方法包括如下两种。
● evict() :用于将某个对象从Session的一级缓存中清除。
● clear() :用于将一级缓存中的对象所有清除。
在进行大批量数据一次性更新的时候,会占用很是多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以免产生内存溢出的状况。具体的实现方法如清单14.8所示。
清单14.8 大批量更新时缓存的处理方法
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(……);
session.save(customer);
if ( i % 20 == 0 ) {
//将本批插入的对象当即写入数据库并释放内存
session.flush();
session.clear();
}
}
tx.commit();
session.close();
posted @ 2009-07-19 21:18 jadmin 阅读(0) 评论(0) 编辑
并发控制
当数据库系统采用Read Committed隔离级别时,会致使不可重复读取和两次更新丢失的并发问题,能够在应用程序中采用锁机制来避免这类问题的产生。
从应用程序的角度上看,锁能够分为乐观锁和悲观锁两大类。
悲观锁
在多个客户端可能读取同一笔数据或同时更新一笔数据的状况下,必需要有访问控制的手段,防止同一个数据被修改而形成混乱,最简单的手段就是对数据进行锁定。在本身进行数据读取或更新等动做时,锁定其余客户端不能对同一笔数据进行任何的动做。
悲观锁(Pessimistic Locking),如其名称所示,悲观地认定每次资料存取时,其余的客户端也会存取同一笔数据,所以将会锁住该笔数据,直到本身操做完成后再解除锁。
悲观锁假定任什么时候刻存取数据时,均可能有另外一个客户也正在存取同一笔数据,于是对数据采起了数据库层次的锁定状态,在锁定的时间内其余的客户不能对数据进行存取。对于单机或小系统而言,这并不成问题,然而若是是在网络上的系统,同时间会有许多访问的机器,若是每一次读取数据都形成锁定,其后继的存取就必须等待,这将形成效能上的问题,形成后继使用者的长时间等待。
悲观锁一般透过系统或数据库自己的功能来实现,依赖系统或数据库自己提供的锁机制。Hibernate便是如此,能够利用Query或Criteria的setLockMode()方法来设定要锁定的表或列及其锁模式,可设定的锁模式有如下几个。
LockMode.UPGRADE:利用数据库的for update子句进行锁定。
LockMode.UPGRADE_NOWAIT:使用for update nowait子句进行锁定,在Oracle数据库中使用。
下面来实现一个简单的例子,测试一下采用悲观锁时数据库是如何进行操做的。
首先来完成一个实体对象——User,该对象包含了id,name和age三个属性,实现的方法如清单14.1所示。
清单14.1 User对象的实现
package cn.hxex.hibernate.lock;
public class User {
private String id;
private String name;
private Integer age;
// 省略了getter和setter方法
……
}
接下来就是映射文件的配置,因为该映射文件没有涉及到任何与其余对象的关联配置,因此实现的方法也很是简单,代码如清单14.2所示。
清单14.2 User映射文件的实现
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.hxex.hibernate.lock">
<class name="User" table="USERINFO">
<id name="id" column="userId">
<generator class="uuid.hex"/>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
</class>
</hibernate-mapping>
另一件重要的工做就是Hibernate的配置文件了,在这个配置文件中包含了链接数据库的参数以及其余一些重要的参数,实现的方法如清单14.3所示。
清单14.3 Hibernate配置文件的实现
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 数据库的URL -->
<!-- property name="hibernate.connection.url">
jdbc:oracle:thin:@192.168.10.121:1521:HiFinance</property-->
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/lockdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&autoReconnectForPools=true
</property>
<!-- 数据库的驱动程序 -->
<!-- property name="hibernate.connection.driver_class">
oracle.jdbc.driver.OracleDriver</property-->
<property name="hibernate.connection.driver_class">
com.mysql.jdbc.Driver
</property>
<!-- 数据库的用户名 -->
<property name="hibernate.connection.username">lockdb</property>
<!-- 数据库的密码 -->
<property name="hibernate.connection.password">lockdb</property>
<!-- 数据库的Dialect -->
<!-- property name="hibernate.dialect">
org.hibernate.dialect.Oracle9Dialect</property -->
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLDialect</property>
<!-- 输出执行的SQL语句 -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- HBM文件列表 -->
<mapping resource="cn/hxex/hibernate/lock/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
最后要实现的就是测试主程序了,在测试主程序中包含了Hibernate的初始化代码以及悲观锁的测试方法。测试主程序的实现方法如清单14.4所示。
清单14.4 测试主程序的实现
package cn.hxex.hibernate.lock;
import java.net.URL;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class LockMain {
private static Log log = LogFactory.getLog( LockMain.class );
// 静态Configuration和SessionFactory对象的实例(全局惟一的)
private static Configuration configuration;
private static SessionFactory sessionFactory;
static
{
// 从默认的配置文件建立SessionFactory
try
{
URL configURL = ClassLoader.getSystemResource(
"cn/hxex/hibernate/lock/hibernate.cfg.xml" );
// 建立默认的Configuration对象的实例
configuration = new Configuration();
// 读取hibernate.properties或者hibernate.cfg.xml文件
configuration.configure( configURL );
// 使用静态变量来保持SessioFactory对象的实例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
// 输出异常信息
log.error("Building SessionFactory failed.", ex);
ex.printStackTrace();
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
public void testPessimisticLock() {
SessionFactory sf = LockMain.getSessionFactory();
Session session = sf.getCurrentSession();
session.beginTransaction();
Query query = session.createQuery("from User user");
query.setLockMode("user", LockMode.UPGRADE);
List users = query.list();
for( int i=0; i<users.size(); i++ ) {
System.out.println( users.get( i ) );
}
session.getTransaction().commit();
}
public static void main(String[] args) {
LockMain main = new LockMain();
main.testPessimisticLock();
}
}
在上面的清单中,testPessimisticLock()方法就是测试悲观锁的方法,该方法在执行查询以前经过Query对象的setLockMode()方法设置了访问User对象的模式,这样,这个程序在执行的时候就会使用如下的SQL语句:
select user0_.userId as userId0_, user0_.name as name0_, user0_.age as age0_
from USERINFO user0_ for update
除了Query对象外,也能够在使用Session的load()或是lock()时指定锁模式。
除了前面所说起的两种锁模式外,还有三种Hibernate内部自动对数据进行加锁的模式,但它的处理是与数据库无关的。
LockMode.WRITE:在insert或update时进行锁定,Hibernate会在调用save()方法时自动得到锁。
LockMode.READ:在读取记录时Hibernate会自动得到锁。
LockMode.NONE:没有锁。
若是数据库不支持所指定的锁模式,Hibernate会选择一个合适的锁替换,而不是抛出一个异常。
乐观锁
乐观锁(Optimistic Locking)认为资料的存取不多发生同时存取的问题,于是不作数据库层次上的锁定。为了维护正确的数据,乐观锁是使用应用程序上的逻辑来实现版本控制的。
在使用乐观锁策略的状况下,数据不一致的状况一旦发生,有几个解决方法,一种是先更新为主,一种是后更新为主,比较复杂的就是检查发生变更的数据来实现,或是检查全部属性来实现乐观锁。
Hibernate中经过检查版本号来判断数据是否已经被其余人所改动,这也是Hibernate所推荐的方式。在数据库中加入一个version字段记录,在读取数据时连同版本号一同读取,并在更新数据时比较版本号与数据库中的版本号,若是等于数据库中的版本号则予以更新,并递增版本号,若是小于数据库中的版本号就抛出异常。
下面就来在前面例子的基础上进行Hibernate乐观锁的测试。
首先须要修改前面所实现的业务对象,在其中增长一个version属性,用来记录该对象所包含数据的版本信息,修改后的User对象如清单14.5所示。
清单14.5 修改后的User对象
package cn.hxex.hibernate.lock;
public class User {
private String id;
private Integer version; // 增长版本属性
private String name;
private Integer age;
// 省略了getter和setter方法
……
}
而后是修改映射文件,增长version属性的配置。在这里须要注意的是,这里的version属性应该使用专门的<version>元素来进行配置,这样才能使其发挥乐观锁的做用。若是还使用<property>元素来进行配置,那么Hibernate只会将其做为一个普通的属性来进行处理。
修改后的映射文件如清单14.6所示。
清单14.6 修改后的映射文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.hxex.hibernate.lock">
<class name="User" table="USERINFO" optimistic-lock="version">
<id name="id" column="userId">
<generator class="uuid.hex"/>
</id>
<version name="version" column="version" type="java.lang.Integer"/>
<property name="name" column="name" type="java.lang.String"/>
<property name="age" column="age" type="java.lang.Integer"/>
</class>
</hibernate-mapping>
接下来还要进行测试主程序的修改。因为须要模拟两我的同时修改同一个记录的状况,因此在这里须要将主程序修改成是能够多线程执行的,而后在run()方法中,调用对User对象的修改程序。
实现后的主测试程序如清单14.7所示。
清单14.7 修改后的测试主程序
package cn.hxex.hibernate.lock;
import java.net.URL;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class LockMain extends Thread{
……
public void testOptimisticLock() {
SessionFactory sf = LockMain.getSessionFactory();
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
User userV1 = (User)session.load( User.class, "1" );
// 等第二个进程执行
try {
sleep( 3000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
userV1.setAge(new Integer(32));
tx.commit();
session.close();
}
public void run() {
testOptimisticLock();
}
public static void main(String[] args) {
// LockMain main = new LockMain();
// main.testPessimisticLock();
LockMain main1 = new LockMain();
main1.start();
LockMain main2 = new LockMain();
main2.start();
}
}
最后,执行测试主程序,在控制台中应该看到相似下面的输出:
Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?
Hibernate: select user0_.userId as userId0_0_, user0_.version as version0_0_, user0_.name as name0_0_, user0_.age as age0_0_ from USERINFO user0_ where user0_.userId=?
Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?
Hibernate: update USERINFO set version=?, name=?, age=? where userId=? and version=?
2006-10-3 21:27:20 org.hibernate.event.def.AbstractFlushingEventListener performExecutions
严重: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [cn.hxex.hibernate.lock.User#1]
……
在Hibernate所执行的UPDATE语句中能够看到,version字段是做为更新的条件来执行的。对于第二个进程来讲,因为数据库中的记录已经被第一个进程更新(更新的同时会致使version自动增长),就必然会致使第二个进程操做的失败。Hibernate正是利用这种机制来避免两次更新问题的出现。
posted @ 2009-07-19 21:11 jadmin 阅读(6) 评论(0) 编辑
在如今的B/S体系结构的软件开发中,对于数据库事务处理中最常使用的方式是每一个用户请求一个事务。也就是说,当服务器端接收到一个用户请求后,会开始一个新的事务,直到对用户请求的全部处理都进行完毕而且完成了响应用户请求的全部输出以后才会关闭这个事务。
对于使用Hibernate实现持久化功能的系统来讲,事务的处理是这样的:服务器端在接收到用户的请求后,会建立一个新的Hibernate Session对象,而后经过该Session对象开始一个新的事务而且以后全部对数据库的操做都经过该Session对象来进行。最后,完成将响应页面发送到客户端的工做后再提交事务而且关闭Session。
Session的对象是轻型的,非线程安全的,因此在每次用户请求时建立,请求处理完毕后丢弃。
那么,该如何实现这种方式的事务处理呢?处理的难点在于如何在业务处理以前建立Session并开始事务以及在业务处理以后提交事务并关闭Session。对于如今的Web应用来讲,一般状况下是经过ServletFilter来完成事务处理的操做。这样,就能够轻松地实如今用户请求到达服务器端的时候建立Session并开始事务,而服务器端响应处理结束以前提交事务并关闭Session。
另一个问题是,在ServletFilter中建立的Session是如何传递给业务处理方法中的呢?处理的方法是经过一个ThreadLocal变量来把建立的Session对象绑定处处理用户请求的线程上去,这样就能够使任何的业务处理方法能够轻松获得Session对象。
Hibernate中事务处理的具体方法能够参照前面的网络博客的实例。
可是这种事务处理的方式仍是会遇到一些问题,其中最突出的就是更新冲突的问题。例如,某个操做人员进入了用户信息的修改页面,在通过一段时间的对用户信息的修改后,进行提交操做,而与此同时可能会有另一个操做人员也进行了相同的操做,这样在处理提交的时候就会产生冲突。
产生这个冲突的缘由在于在开发中须要使用多个数据库事务来实现一个应用事务。也就是说,在应用程序层,应该将读取用户信息、显示修改页面以及用户提交工做来做为一个事务进行处理,在处理的过程当中应该避免其余操做人员进行相似的操做。
回想前面的介绍,咱们对于数据库事务所采起的策略是每一个用户请求一个事务,而上面的业务处理则至少须要两个请求才能完成。这样,二者之间就存在着必定的矛盾,这也就致使了不可重复读取和两次更新问题的发生。
为了解决并发中数据访问的问题,一般会采用锁的机制来实现数据访问的排他性,从而避免两次更新问题的发生。
posted @ 2009-07-19 21:07 jadmin 阅读(0) 评论(0) 编辑
为了不上面出现的几种状况,在标准SQL规范中,定义了4个事务隔离级别,不一样的隔离级别对事务的处理不一样。
● 未受权读取(Read Uncommitted):容许脏读取,但不容许更新丢失。若是一个事务已经开始写数据,则另一个数据则不容许同时进行写操做,但容许其余事务读此行数据。该隔离级别能够经过“排他写锁”实现。
● 受权读取(Read Committed):容许不可重复读取,但不容许脏读取。这能够经过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务容许其余事务继续访问该行数据,可是未提交的写事务将会禁止其余事务访问该行。
● 可重复读取(Repeatable Read):禁止不可重复读取和脏读取,可是有时可能出现幻影数据。这能够经过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但容许读事务),写事务则禁止任何其余事务。
● 序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。若是仅仅经过“行级锁”是没法实现事务序列化的,必须经过其余机制保证新插入的数据不会被刚执行查询操做的事务访问到。
隔离级别越高,越能保证数据的完整性和一致性,可是对并发性能的影响也越大。对于多数应用程序,能够优先考虑把数据库系统的隔离级别设为Read Committed,它可以避免脏读取,并且具备较好的并发性能。尽管它会致使不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,能够由应用程序采用悲观锁或乐观锁来控制。
经过前面的介绍已经知道,经过选用不一样的隔离等级就能够在不一样程度上避免前面所说起的在事务处理中所面临的各类问题。因此,数据库隔离级别的选取就显得尤其重要,在选取数据库的隔离级别时,应该注意如下几个处理的原则:
首先,必须排除“未受权读取”,由于在多个事务之间使用它将会是很是危险的。事务的回滚操做或失败将会影响到其余并发事务。第一个事务的回滚将会彻底将其余事务的操做清除,甚至使数据库处在一个不一致的状态。极可能一个已回滚为结束的事务对数据的修改最后却修改提交了,由于“未受权读取”容许其余事务读取数据,最后整个错误状态在其余事务之间传播开来。
其次,绝大部分应用都无须使用“序列化”隔离(通常来讲,读取幻影数据并非一个问题),此隔离级别也难以测量。目前使用序列化隔离的应用中,通常都使用悲观锁,这样强行使全部事务都序列化执行。
剩下的也就是在“受权读取”和“可重复读取”之间选择了。咱们先考虑可重复读取。若是全部的数据访问都是在统一的原子数据库事务中,此隔离级别将消除一个事务在另一个并发事务过程当中覆盖数据的可能性(第二个事务更新丢失问题)。这是一个很是重要的问题,可是使用可重复读取并非解决问题的惟一途径。
假设使用了“版本数据”,Hibernate会自动使用版本数据。Hibernate的一级Session缓存和版本数据已经为你提供了“可重复读取隔离”绝大部分的特性。特别是,版本数据能够防止二次更新丢失的问题,一级Session缓存能够保证持久载入数据的状态与其余事务对数据的修改隔离开来,所以若是使用对全部的数据库事务采用受权读取隔离和版本数据是行得通的。
“可重复读取”为数据库查询提供了更好的效率(仅对那些长时间的数据库事务),可是因为幻影读取依然存在,所以不必使用它(对于Web应用来讲,通常也不多在一个数据库事务中对同一个表查询两次)。
也能够同时考虑选择使用Hibernate的二级缓存,它能够如同底层的数据库事务同样提供相同的事务隔离,可是它可能弱化隔离。假如在二级缓存大量使用缓存并发策略,它并不提供重复读取语义(例如,后面章节中将要讨论的读写,特别是非严格读写),很容易能够选择默认的隔离级别:由于不管如何都没法实现“可重复读取”,所以就更没有必要拖慢数据库了。另外一方面,可能对关键类不采用二级缓存,或者采用一个彻底的事务缓存,提供“可重复读取隔离”。那么在业务中须要使用到“可重复读取”吗?若是你喜欢,固然能够那样作,但更多的时候并无必要花费这个代价。
posted @ 2009-07-19 21:04 jadmin 阅读(0) 评论(0) 编辑
数据库的事务处理是在进行数据库应用开发中必须进行处理的一个问题。那么对于选择Hibernate做为持久层组件,了解Hibernate的事务处理机制就显得尤其重要了。
事务的基本概念
事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操做序列,这些操做要么都执行,要么都不执行,它是一个不可分割的工做单位。例如,银行转帐工做:从一个帐号扣款并使另外一个帐号增款,这两个操做要么都执行,要么都不执行。因此,应该把它们当作一个事务。事务是数据库维护数据一致性的单位,在每一个事务结束时,都能保持数据一致性。
针对上面的描述能够看出,事务的提出主要是为了解决并发状况下保持数据一致性的问题。
事务具备如下4个基本特征。
● Atomic(原子性):事务中包含的操做被看作一个逻辑单元,这个逻辑单元中的操做要么所有成功,要么所有失败。
● Consistency(一致性):只有合法的数据能够被写入数据库,不然事务应该将其回滚到最初状态。
● Isolation(隔离性):事务容许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其余并行事务的修改相互独立。
● Durability(持久性):事务结束后,事务处理的结果必须可以获得固化。
数据库确定是要被广大客户所共享访问的,那么在数据库操做过程当中极可能出现如下几种不肯定状况。
● 更新丢失(Lost update):两个事务都同时更新一行数据,可是第二个事务却中途失败退出,致使对数据的两个修改都失效了。这是由于系统没有执行任何的锁操做,所以并发事务并无被隔离开来。
● 脏读取(Dirty Reads):一个事务开始读取了某行数据,可是另一个事务已经更新了此数据但没有可以及时提交。这是至关危险的,由于极可能全部的操做都被回滚。
● 不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次,可是却获得了不一样的结果。例如,在两次读取的中途,有另一个事务对该行数据进行了修改,并提交。
● 两次更新问题(Second lost updates problem):没法重复读取的特例。有两个并发事务同时读取同一行数据,而后其中一个对它进行修改提交,而另外一个也进行了修改提交。这就会形成第一次写操做失效。
● 虚读(Phantom Reads):事务在操做过程当中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是由于在两次查询过程当中有另一个事务插入数据形成的。
posted @ 2009-07-19 21:04 jadmin 阅读(2) 评论(0) 编辑
HibernateTemplate还提供了一种更加灵活的方式来操做数据库,经过这种方式能够彻底使用Hibernate的操做方式。HibernateTemplate的灵活访问方式可经过以下两个方法完成:
● Object execute(HibernateCallback action)。
● List execute(HibernateCallback action)。
这两个方法都须要一个HibernateCallback的实例,HibernateCallback实例可在任何有效的Hibernate数据访问中使用。程序开发者经过HibernateCallback,能够彻底使用Hibernate灵活的方式来访问数据库,解决Spring封装Hibernate后灵活性不足的缺陷。
HibernateCallback是一个接口,该接口包含一个方法doInHibernate(org.hibernate. Session session),该方法只有一个参数Session。在开发中提供HibernateCallback实现类时,必须实现接口里包含的doInHibernate方法,在该方法体内便可得到Hibernate Session的引用,一旦得到了Hibernate Session的引用,就能够彻底以Hibernate的方式进行数据库访问。
注意:doInHibernate方法内能够访问Session,该Session对象是绑定在该线程的Session实例。该方法内的持久层操做,与不使用Spring时的持久层操做彻底相同。这保证了对于复杂的持久层访问,依然能够使用Hibernate的访问方式。
下面的代码对HibernateDaoSupport类进行扩展(虽然Spring 2.0的HibernateTemplate提供了一个分页方法setMaxResults,但仅此一个方法依然不能实现分页查询),这种扩展主要是为该类增长了3个分页查询的方法,分页查询时必须直接调用Hibernate的Session完成,所以,必须借助于HibernateCallBack的帮助。
public class YeekuHibernateDaoSupport extends HibernateDaoSupport
{
/**
* 使用hql 语句进行分页查询操做
* @param hql 须要查询的hql语句
* @param offset 第一条记录索引
* @param pageSize 每页须要显示的记录数
* @return 当前页的全部记录
*/
public List findByPage(final String hql,
final int offset, final int pageSize)
{
//HibernateDaoSupport已经包含了getHibernateTemplate()方法
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
//该方法体内以Hibernate方法进行持久层访问
{
List result = session.createQuery(hql)
.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
/**
* 使用hql 语句进行分页查询操做
* @param hql 须要查询的hql语句
* @param value 若是hql有一个参数须要传入,value就是传入的参数
* @param offset 第一条记录索引
* @param pageSize 每页须要显示的记录数
* @return 当前页的全部记录
*/
public List findByPage(final String hql , final Object value ,
final int offset, final int pageSize)
{
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
{
//下面查询的是最简单的Hiberante HQL查询
List result = session.createQuery(hql)
.setParameter(0, value)
.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
/**
* 使用hql 语句进行分页查询操做
* @param hql 须要查询的hql语句
* @param values 若是hql有多个参数须要传入,values就是传入的参数数组
* @param offset 第一条记录索引
* @param pageSize 每页须要显示的记录数
* @return 当前页的全部记录
*/
public List findByPage(final String hql, final Object[] values,
final int offset, final int pageSize)
{
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
{
Query query = session.createQuery(hql);
for (int i = 0 ; i < values.length ; i++)
{
query.setParameter( i, values[i]);
}
List result = query.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
}
在上面的代码实现中,直接使用了getHibernateTemplate()方法,这个方法由Hibernate- DaoSupport提供。而YeekuHibernateDaoSupport是HibernateDaoSupport的子类,所以,能够直接使用该方法。
当实现doInHibernate(Session session)方法时,彻底以Hibernate的方式进行数据库访问,这样保证了Hibernate进行数据库访问的灵活性。
注意:Spring提供的XxxTemplate和XxxCallBack互为补充,两者体现了Spring框架设计的用心良苦:XxxTemplate对通用操做进行封装,而XxxCallBack解决了封装后灵活性不足的缺陷。
为了实现DAO组件,Spring提供了大量的XxxDaoSupport类,这些DAO支持类对于实现DAO组件大有帮助,由于这些DAO支持类已经完成了大量基础性工做。
Spring为Hibernate的DAO提供了工具类HibernateDaoSupport。该类主要提供以下两个方法以方便DAO的实现:
● public final HibernateTemplate getHibernateTemplate()。
● public final void setSessionFactory(SessionFactory sessionFactory)。
其中,setSessionFactory方法可用于接收Spring的ApplicationContext的依赖注入,可接收配置在Spring的SessionFactory实例,getHibernateTemplate方法用于返回经过SessionFactory产生的HibernateTemplate实例,持久层访问依然经过HibernateTemplate实例完成。
下面实现的DAO组件继承了Spring提供的HibernateDaoSupport类,依然实现了PersonDao接口,其功能与前面提供的PersonDao实现类彻底相同。其代码以下:
public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao
{
/**
* 加载人实例
* @param id 须要加载的Person实例的主键值
* @return 返回加载的Person实例
*/
public Person get(int id)
{
return (Person)getHibernateTemplate().get(Person.class, new
Integer(id));
}
/**
* 保存人实例
* @param person 须要保存的Person实例
*/
public void save(Person person)
{
getHibernateTemplate().save(person);
}
/**
* 修改Person实例
* @param person 须要修改的Person实例
*/
public void update(Person person)
{
getHibernateTemplate().update(person);
}
/**
* 删除Person实例
* @param id 须要删除的Person的id
*/
public void delete(int id)
{
getHibernateTemplate().delete(getHibernateTemplate().
get(Person.class, new Integer(id)));
}
/**
* 删除Person实例
* @param person 须要删除的Person实例
*/
public void delete(Person person)
{
getHibernateTemplate().delete(person);
}
/**
* 根据用户名查找Person
* @param name 用户名
* @return 用户名对应的所有用户
*/
public List findByPerson(String name)
{
return getHibernateTemplate().find("from Person p where p.name
like ?" , name);
}
/**
* 返回所有的Person实例
* @return 所有的Person实例
*/
public List findAllPerson()
{
return getHibernateTemplate().find("from Person ");
}
}
上面的代码与前面的PersonDAOImpl对比会发现,代码量大大减小。事实上,DAO的实现依然借助于HibernateTemplate的模板访问方式,只是HibernateDaoSupport将依赖注入SessionFactory的工做已经完成,获取HibernateTemplate的工做也已完成。该DAO的配置必须依赖于SessionFactory,配置文件与前面部署DAO组件的方式彻底相同,此处再也不赘述。
在继承HibernateDaoSupport的DAO实现里,Hibernate Session的管理彻底不须要打开代码,而由Spring来管理。Spring会根据实际的操做,采用“每次事务打开一次session”的策略,自动提升数据库访问的性能。
至此为止,J2EE应用所须要的各类组件都已经出现了,从MVC层的控制器组件,到业务逻辑组件,以及持久层的DAO组件,已经所有成功实现。应用程序代码并未将这些组件耦合在一块儿,代码中都是面向接口编程,所以必须利用Spring的IoC容器将他们组合在一块儿。
从用户角度来看,用户发出HTTP请求,当MVC框架的控制器组件拦截到用户请求时,将调用系统的业务逻辑组件,而业务逻辑组件则调用系统的DAO组件,而DAO组件则依赖于SessionFactory和DataSource等底层组件实现数据库访问。
从系统实现角度来看,IoC容器先建立SessionFactory和DataSource等底层组件,而后将这些底层组件注入给DAO组件,提供一个完整的DAO组件,并将此DAO组件注入给业务逻辑组件,从而提供一个完整的业务逻辑组件,而业务逻辑组件又被注入给控制器组件,控制器组件负责拦截用户请求,并将处理结果呈现给用户——这一系列的衔接都由Spring的IoC容器提供实现。
下面给出关于如何在容器中配置J2EE组件的大体模板,其模板代码以下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入数据源,注入的正是上文中定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用来列出全部的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
<!-- 此处还可列出更多的PO映射文件 -->
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的链接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 指定启动应用时,是否根据Hibernate映射文件建立数据表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Person持久化类的DAO Bean -->
<bean id="personDao" class="lee.PersonDaoImpl">
<!-- 采用依赖注入来传入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 下面能以相同的方式配置更多的持久化Bean -->
...
<bean id="myService" class="lee.MyServiceImp">
<!-- 注入业务逻辑组件所必需的DAO组件 -->
<property name="peronDdao" ref=" personDao "/>
<!-- 此处可采用依赖注入更多的DAO组件 -->
...
</bean>
<!-- 配置控制器Bean,设置起做用域为Request -->
<bean name="/login" class="lee.LoginAction" scope="request">
<!-- 依赖注入控制器所必需的业务逻辑组件 -->
<property name="myService" ref=" myService "/>
</bean>
</beans>
在上面的配置文件中,同时配置了控制器Bean、业务逻辑组件Bean、DAO组件Bean以及一些基础资源Bean。各组件的组织被解耦到配置文件中,而不是在代码层次的低级耦合。
当客户端的HTTP请求向/login.do发送请求时,将被容器中的lee.LoginAction拦截,LoginAction调用myService Bean,myService Bean则调用personDao等系列DAO组件,整个流程将系统中的各组件有机地组织在一块儿。
注意:在实际应用中,不多会将DAO组件、业务逻辑组件以及控制组件都配置在同一个文件中。而是在不一样配置文件中,配置相同一组J2EE应用组件。
在上面的配置文件中,部署了控制器组件、业务逻辑组件、DAO组件,几乎能够造成一个完整的J2EE应用。但有一个小小的问题:事务控制。系统没有任何事务逻辑,没有事务逻辑的应用是不可想象的。
Spring提供了很是简洁的声明式事务控制,只须要在配置文件中增长事务控制片断,业务逻辑代码无须任何改变。Spring的声明式事务逻辑,甚至支持在不一样事务策略之间切换。
配置Spring声明式事务时,一般推荐使用BeanNameAutoProxyCreator自动建立事务代理。经过这种自动事务代理的配置策略,增长业务逻辑组件,只须要在BeanNameAutoProxyCreator Bean配置中增长一行便可,从而避免了增量式配置。
在上面的配置模板文件中增长以下配置片断,系统的myService业务逻辑组件将变成事务代理Bean,从而为业务逻辑方法增长事务逻辑。
<!-- 配置Hibernate的局部事务管理器 -->
<!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager
接口,针对采用Hibernate持久化链接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean须要依赖注入一个SessionFactory
bean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置事务拦截器Bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事务拦截器bean须要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对知足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是全部须要自动建立事务代理的Bean -->
<list>
<value>myService</value>
<!-- 下面还可增长须要增长事务逻辑的业务逻辑Bean -->
...
</list>
<!-- 此处可增长其余须要自动建立事务代理的Bean -->
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增长其余新的Interceptor -->
</list>
</property>
</bean>
一旦增长了如上的配置片断,系统中的业务逻辑方法就有了事务逻辑。这种声明式事务配置方式能够在不一样的事务策略之间自由切换。
提示:尽可能使用声明式事务配置方式,而不要在代码中完成事务逻辑。
posted @ 2009-07-19 10:24 jadmin 阅读(1) 评论(0) 编辑
时至今日,可能极少有J2EE应用会直接以JDBC方式进行持久层访问。毕竟,用面向对象的程序设计语言来访问关系型数据库,是一件让人沮丧的事情。大部分时候,J2EE应用都会以ORM框架来进行持久层访问,在全部的ORM框架中,Hibernate以其灵巧、轻便的封装赢得了众多开发者的青睐。
Spring具备良好的开放性,能与大部分ORM框架良好整合。下面将详细介绍Spring与Hibernate的整合。
DAO模式是一种标准的J2EE设计模式,DAO模式的核心思想是,全部的数据库访 问,都经过DAO组件完成,DAO组件封装了数据库的增、删、改等原子操做。而业务逻辑组件则依赖于DAO组件提供的数据库原子操做,完成系统业务逻辑的实现。
对于J2EE应用的架构,有很是多的选择,但无论细节如何变换,J2EE应用都大体可分为以下3层:
● 表现层。
● 业务逻辑层。
● 数据持久层。
轻量级J2EE架构以Spring IoC容器为核心,承上启下。其向上管理来自表现层的Action,向下管理业务逻辑层组件,同时负责管理业务逻辑层所需的DAO对象。各层之间负责传值的是值对象,也就是JavaBean实例。
图6.5精确地描绘了轻量级J2EE架构的大体情形。
DAO组件是整个J2EE应用的持久层访问的重要组件,每一个J2EE应用的底层实现都难以离开DAO组件的支持。Spring对实现DAO组件提供了许多工具类,系统的DAO组件可经过继承这些工具类完成,从而能够更加简便地实现DAO组件。
Spring的DAO支持,容许使用相同的方式、不一样的数据访问技术,如JDBC、Hibernate或JDO。Spring的DAO在不一样的持久层访问技术上提供抽象,应用的持久层访问基于Spring的DAO抽象。所以,应用程序能够在不一样的持久层技术之间切换。
Spring提供了一系列的抽象类,这些抽象将被做为应用中DAO实现类的父类。经过继承这些抽象类,Spring简化了DAO的开发步骤,能以一致的方式使用数据库访问技术。无论底层采用JDBC、JDO或Hibernate,应用中均可采用一致的编程模型。
图6.5 轻量级J2EE应用架构
应用的DAO类继承这些抽象类,会大大简化应用的开发。最大的好处是,继承这些抽象类的DAO能以一致的方式访问数据库,意味着应用程序能够在不一样的持久层访问技术中切换。
除此以外,Spring提供了一致的异常抽象,将原有的Checked异常转换包装成Runtime异常,于是,编码时无须捕获各类技术中特定的异常。Spring DAO体系中的异常,都继承DataAccessException,而DataAccessException异常是Runtime的,无须显式捕捉。经过DataAccessException的子类包装原始异常信息,从而保证应用程序依然能够捕捉到原始异常信息。
Spring提供了多种数据库访问技术的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring能够使用相同的访问模式、不一样的数据库访问技术。就Hibernate的持久层访问技术而言,Spring提供了以下3个工具类(或接口)来支持DAO组件的实现:
● HibernateDaoSupport。
● HibernateTemplate。
● HibernateCallBack。
前面介绍Hibernate时已经知道,在经过Hibernate进行持久层访问时,Hibernate的SessionFactory是一个很是重要的对象,它是单个数据库映射关系编译后的内存镜像。大部分状况下,一个J2EE应用对应一个数据库,也即对应一个SessionFactory对象。
在纯粹的Hibernate访问中,应用程序须要手动建立SessionFactory实例,可想而知,这不是一个优秀的策略。在实际开发中,但愿以一种声明式的方式管理SessionFactory实例,直接以配置文件来管理SessionFactory实例,在示范Struts的PlugIn扩展点时,大体示范了这种方式(请参阅2.12.1节的内容)。
Spring的IoC容器则提供了更好的管理方式,它不只能以声明式的方式配置Session- Factory实例,也可充分利用IoC容器的做用,为SessionFactory注入数据源引用。
下面是Spring配置文件中配置Hibernate SessionFactory的示范代码:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入数据源,正是上文定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用来列出全部的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的链接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 配置启动应用时,是否根据Hibernate映射自动建立数据表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
</beans>
一旦在Spring的IoC容器中配置了SessionFactory Bean,它将随应用的启动而加载,并能够充分利用IoC容器的功能,将SessionFactory Bean注入任何Bean,好比DAO组件。一旦DAO组件得到了SessionFactory Bean的引用,就能够完成实际的数据库访问。
固然,Spring也支持访问容器数据源。若是须要使用容器数据源,可将数据源Bean修改为以下配置:
<!-- 此处配置JNDI数据源 -->
<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<!-- 指定数据源的JNDI -->
<value>java:comp/env/jdbc/myds</value>
</property>
</bean>
可见,以声明式的方式管理SessionFactory实例,可让应用在不一样数据源之间切换。若是应用更换数据库等持久层资源,只需对配置文件进行简单修改便可。
提示:以声明式的方式管理SessionFactory,很是相似于早期将数据库服务的相关信息放在web.xml文件中进行配置。这种方式是为了提供更好的适应性,当持久层服务须要更改时,应用代码无须任何改变。
HibernateTemplate提供持久层访问模板,使用HibernateTemplate无须实现特定接口,它只须要提供一个SessionFactory的引用就可执行持久化操做。SessionFactory对象既可经过构造参数传入,也可经过设值方式传入。HibernateTemplate提供以下3个构造函数:
● HibernateTemplate()。
● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。
● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。
第一个构造函数,构造一个默认的HibernateTemplate实例。所以,使用Hibernate- Template实例以前,还必须使用方法setSessionFactory(SessionFactory sessionFactory)来为HibernateTemplate传入SessionFactory的引用。
第二个构造函数,在构造时已经传入SessionFactory引用。
第三个构造函数,其boolean型参数代表,若是当前线程已经存在一个非事务性的Session,是否直接返回此非事务性的Session。
在Web应用中,一般启动时自动加载ApplicationContext,SessionFactory和DAO对象都处在Spring上下文管理下,所以无须在代码中显式设置,可采用依赖注入完成Session- Factory和DAO的解耦,依赖关系经过配置文件来设置,以下所示:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入数据源,注入的正是上文中定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用来列出全部的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的链接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 指定启动应用时,是否根据Hibernate映射文件建立数据表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Person持久化类的DAO bean -->
<bean id="personDao" class="lee.PersonDaoImpl">
<!-- 采用依赖注入来传入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
在PersonDao组件中,全部的持久化操做都经过HibernateTemplate实例完成,而HibernateTemplate操做数据库很是简洁,大部分CRUD操做均可经过一行代码解决问题。下面介绍如何经过HibernateTemplate进行持久层访问。
HibernateTemplate提供了很是多的经常使用方法来完成基本的操做,好比一般的增长、删除、修改、查询等操做,Spring 2.0更增长了对命名SQL查询的支持,也增长了对分页的支持。大部分状况下,使用Hibernate的常规用法,就可完成大多数DAO对象的CRUD操做。下面是HibernateTemplate的经常使用方法简介:
● void delete(Object entity),删除指定持久化实例。
● deleteAll(Collection entities),删除集合内所有持久化类实例。
● find(String queryString),根据HQL查询字符串来返回实例集合。
● findByNamedQuery(String queryName),根据命名查询返回实例集合。
● get(Class entityClass, Serializable id),根据主键加载特定持久化类的实例。
● save(Object entity),保存新的实例。
● saveOrUpdate(Object entity),根据实例状态,选择保存或者更新。
● update(Object entity),更新实例的状态,要求entity是持久状态。
● setMaxResults(int maxResults),设置分页的大小。
下面是一个完整DAO类的源代码:
public class PersonDaoImpl implements PersonDao
{
//执行持久化操做的HibernateTemplate实例
private HibernateTemplate ht = null;
private SessionFactory sessionFactory;
//该DAO组件持久化操做所需的SessionFactory对象
public void setSessionFactory(SessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
//用于根据SessionFactory实例返回HibernateTemplate实例的方法
private HibernateTemplate getHibernateTemplate()
{
if (ht == null)
{
ht = new HibernateTemplate(sessionFactory);
}
return ht;
}
/**
* 加载人实例
* @param id 须要加载的Person实例的主键值
* @return 返回加载的Person实例
*/
public Person get(int id)
{
return (Person)getHibernateTemplate().get(Person.class, new
Integer(id));
}
/**
* 保存人实例
* @param person 须要保存的Person实例
*/
public void save(Person person)
{
getHibernateTemplate().save(person);
}
/**
* 修改Person实例
* @param person 须要修改的Person实例
*/
public void update(Person person)
{
getHibernateTemplate().update(person);
}
/**
* 删除Person实例
* @param id 须要删除的Person的id
*/
public void delete(int id)
{
getHibernateTemplate().delete(getHibernateTemplate().get(Person.
class,new Integer(id)));
}
/**
* 删除Person实例
* @param person 须要删除的Person实例
*/
public void delete(Person person)
{
getHibernateTemplate().delete(person);
}
/**
* 根据用户名查找Person
* @param name 用户名
* @return 用户名对应的所有用户
*/
public List findByName(String name)
{
return getHibernateTemplate().find("from Person p where p.name
like ?" , name);
}
/**
* 返回所有的Person实例
* @return 所有的Person实例
*/
public List findAllPerson()
{
return getHibernateTemplate().find("from Person ");
}
}
经过上面实现DAO组件的代码能够看出,经过HibernateTemplate进行持久层访问的代码如此清晰,大部分CRUD操做一行代码便可完成,彻底无须Hibernate访问那些繁琐的步骤。并且,一旦DAO组件得到了SessionFactory的引用,便可很轻易地建立HibernateTemplate实例。
提示:HibernateTemplate是Spring众多模板工具类之一,Spring正是经过这种简便地封装,完成了开发中大量须要重复执行的工做。
posted @ 2009-07-19 10:24 jadmin 阅读(4) 评论(0) 编辑
Struts的plug-in配置部分明确指出,Spring的配置文件有两个:applicationContext.xml和action-servlet.xml。其实,彻底能够使用一个配置文件。一般,习惯将Action Bean配置在控制器的context内。action-servlet.xml用于配置表现层上下文,其详细配置信息以下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring配置文件的根元素,以及对应的Schame信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 每一个request请求产生一个新实例,将全部该请求的做用域配置成request -->
<bean name="/login" class="lee.LoginAction" scope="request">
<property name="vb" ref="vb"/>
</bean>
</beans>
由于每次请求都应该启动新的Action处理用户请求,所以,应将Action的做用域配置成Request。
注意:ActionServlet转发请求时,是根据Bean的name属性,而不是id属性。所以,此处肯定的name属性与Struts的action属性相同。
applicationContext.xml只有一个bean配置,即vb bean。其详细配置以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring 配置文件的根元素,以及对应的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置ValidBean实例 -->
<bean id="vb" class="lee.ValidBeanImpl"/>
</beans>
ValidBeanImpl是一个业务逻辑bean,本示例程序中仅做简单的判断,ValidBeanImpl的源代码以下:
//面向接口编程,实现ValidBean接口
public class ValidBeanImpl implements ValidBean
{
//根据输入的用户名和密码判断是否有效
public boolean valid(String username,String pass)
{
//有效,返回true
if (username.equals("scott") && pass.equals("tiger"))
{
return true;
}
return false;
}
}
注意:上面的业务逻辑组件很是简单,它只是一个示意。若是是真实的应用,业务逻辑组件应该经过DAO组件来实现业务逻辑方法。
应用的业务逻辑控制器,Action则负责调用业务逻辑组件的方法,并根据业务逻辑组件方法的返回值,肯定如何响应用户请求。下面是该示例应用控制器的代码:
//业务控制器继承Action
public class LoginAction extends Action
{
//action控制器将调用的业务逻辑组件
private ValidBean vb;
//依赖注入业务逻辑组件的setter方法
public void setVb(ValidBean vb)
{
this.vb = vb;
}
//必须重写该核心方法,该方法actionForm将表单的请求参数封装成值对象
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//form由ActionServlet转发请求时建立,封装了全部的请求参数
LoginForm loginForm = (LoginForm)form;
//获取username请求参数
String username = loginForm.getUsername();
//获取pass请求参数
String pass = loginForm.getPass();
//下面为服务器端的数据校验
String errMsg = "";
//判断用户名不能为空
if (username == null || username.equals(""))
{
errMsg += "您的用户名丢失或没有输入,请从新输入";
}
//判断密码不能为空
else if(pass == null || pass.equals(""))
{
errMsg += "您的密码丢失或没有输入,请从新输入";
}
//若是用户名和密码不为空,才调用业务逻辑组件
else
{
//vb是业务逻辑组件,由容器注入
if (vb.valid(username,pass))
{
return mapping.findForward("welcome");
}
else
{
errMsg = "您的用户名和密码不匹配";
}
}
//判断是否生成了错误信息
if (errMsg != null && !errMsg.equals(""))
{
//若是有错误信息,将错误信息保存在request里,并跳转到input对应的
forward对象
request.setAttribute("err" , errMsg);
return mapping.findForward("input");
}
else
{
//若是没有错误信息,跳转到welcome对应的forward对象
return mapping.findForward("welcome");
}
}
}
在本应用中,使用了Struts的客户端数据校验,让Action继承ValidatorActionForm便可。ActionForm的代码很是简单,此处再也不赘述。
为了完成数据校验,还应该编写数据校验规则文件。在struts-config.xml文件的尾部,另有一个plug-in用来加载校验文件,其中validator-rules.xml文件位于struts压缩包的lib下,直接复制过来便可使用,而validator.xml必须本身编写,validator.xml文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 验证规则文件的文件头,包括DTD等信息 -->
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules
Configuration 1.1.3//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<!-- 验证文件的根元素 -->
<form-validation>
<!-- 全部须要验证的form都放在formset里 -->
<formset>
<!-- 须要验证的form名,该名与struts里配置的名相同 -->
<form name="loginForm">
<!-- 指定该form的username域必须知足的规则:必填、模式匹配 -->
<field property="username" depends="required,mask">
<arg key="loginForm.username" position="0"/>
<var>
<!-- 肯定匹配模式的正则表达式 -->
<var-name>mask</var-name>
<var-value>^[a-zA-Z]+$</var-value>
</var>
</field>
<!-- 指定该form的pass域必须知足的规则:必填 -->
<field property="pass" depends="required">
<msg name="required" key="pass.required"/>
<arg key="loginForm.pass" position="0"/>
</field>
</form>
</formset>
</form-validation>
上面示例程序的结构很是清晰:表现层组件(Action)配置在action-servlet.xml文件中,而业务逻辑层组件(vb)配置在applicationContext.xml文件中,若是应用中有DAO组件,将DAO组件配置在dao-context.xml文件中。将3个文件放在plug-in元素里一块儿加载。
DelegatingRequestProcessor会将请求转发到Action,该Action已经处于IoC容器管理之下,所以,能够方便地访问容器中的其余Bean。
经过配置文件能够看出,Action根本无须type属性,即struts-config.xml中Action根本没有实例化过,DelegatingRequestProcessor将请求转发给Spring容器中的同名Bean。这种转发的时机很是早,避免了建立struts-config.xml配置文件中的Action,于是性能很是好。
图6.3是采用这种整合策略的执行效果。
使用DelegatingRequestProcessor简单方便,但有一个缺点,RequestProcessor是Struts的一个扩展点,也许应用程序自己就须要扩展RequestProcessor,而DelegatingRequest- Processor已经使用了这个扩展点。
为了从新利用Struts的RequestProcessor这个扩展点,有两个作法:
● 应用程序的RequestProcessor再也不继承Struts的RequestProcessor,改成继承DelegatingRequestProcessor。
● 使用DelegatingActionProxy。
前者经常有一些未知的风险,然后者是Spring推荐的整合策略。使用Delegating- ActionProxy与DelegatingRequestProcessor的目的都只有一个,将请求转发给Spring管理的Bean。
DelegatingRequestProcessor直接替换了原有的RequestProcessor,在请求转发给action以前,转发给Spring管理的Bean;而DelegatingActionProxy则被配置成Struts的Action,即全部的请求先被ActionServlet截获,请求被转发到对应的Action,而action的实现类全都是DelegatingActionProxy,DelegatingActionProxy再将请求转发给Spring容器的Action。
能够看出,使用DelegatingActionProxy比使用DelegatingRequestProcessor要晚一步转发到Spring的context。但经过这种方式能够避免占用扩展点。
与使用DelegatingRequestProcessor相比,使用DelegatingActionProxy仅须要去掉controller配置元素,并将全部的action实现类改成DelegatingActionProxy便可。详细的配置文件以下:
<!-- XML文件的版本和编码集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- struts配置文件的文件头,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,全部的formbean都放在form-beans元素里定义 -->
<form-beans>
<!-- 定义了一个formbean,肯定formbean名和实现类 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定义action部分,全部的action都放在action-mapping元素里定义 -->
<action-mappings>
<!-- 这里只定义了一个action。必须配置action的type元素为
DelegatingActionProxy -->
<action path="/login" type="org.springframework.web.struts.
DelegatingActionProxy"
name="loginForm" scope="request" validate="true" input=
"/login.jsp" >
<!-- 定义action内的两个局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 加载国际化的资源包 -->
<message-resources parameter="mess"/>
<!-- 装载验证的资源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
<!-- 装载Spring配置文件,随应用启动建立ApplicationContext实例 -->
<plug-in className="org.springframework.web.struts. ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>
DelegatingActionProxy接收ActionServlet转发过来的请求,而后转发给Application- Context管理的Bean,这是典型的链式处理。
经过配置文件能够看出,struts-config.xml文件中配置了大量DelegatingActionProxy实例,Spring容器中也配置了同名的Action。即Struts的业务控制器分红了两个部分:第一个部分是Spring的DelegatingActionProxy,这个部分没有实际意义,仅仅完成转发;第二个部分是用户的Action实现类,该实现类负责真实的处理。
这种策略的性能比前一种策略的效果要差一些,由于须要多建立一个Delegating- ActionProxy实例。并且,J2EE应用中Action很是多,这将致使大量建立DelegatingActionProxy实例,使用一次以后,等待垃圾回收机制回收——这对性能的影响不可避免。
图6.4是DelegatingActionProxy的执行效果。
注意:使用DelegatingActionProxy的整合策略,可避免占用Struts的RequestProcessor扩展点,但下降了整合性能。
前面已经介绍了,Spring与Struts的整合还有一种策略,让Struts的Action显式获取Spring容器中的Bean。在这种策略下,Struts的Action不接受IoC容器管理,Action的代码与Spring API部分耦合,形成代码污染。这种策略也有其好处:代码的可读性很是强,Action的代码中显式调用业务逻辑组件,而无须等待容器注入。
Action中访问ApplicationContext有两种方法:
● 利用WebApplicationContextUtils工具类。
● 利用ActionSupport支持类。
经过WebApplicationContextUtils,能够显式得到Spring容器的引用(请参阅6.4.1节的内容),而ActionSupport类则提供了一个更简单的方法getWebApplicationContext(),该方法可直接获取Spring容器的引用。
所谓ActionSupport类,是指Spring提供了系列扩展。Spring扩展了Struts的Action,在Struts的Action后加上Support后缀,Spring扩展的Action有以下几个:
● ActionSupport。
● DispatchActionSupport。
● LookupDispatchActionSupport。
● MappingDispatchActionSupport。
下面的示例将展现这种整合策略,在这种整合策略下,Struts的Action改成继承Spring扩展后的Action,下面是应用的Action代码:
//新的业务控制器,继承Spring的ActionSupport类
public class LoginAction extends ActionSupport
{
//依然将ValidBean做为成员变量
private ValidBean vb;
//构造器,注意:不可在构造器中调用getWebApplicationContext()方法
public LoginAction()
{
}
//完成ValidBean的初始化
public ValidBean getVb()
{
return(ValidBean)getWebApplicationContext().getBean("vb");
}
//必须重写该核心方法,该方法actionForm将表单的请求参数封装成值对象
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//form由ActionServlet转发请求时建立,封装了全部的请求参数
LoginForm loginForm = (LoginForm)form;
//获取username请求参数
String username = loginForm.getUsername();
//获取pass请求参数
String pass = loginForm.getPass();
//下面为服务器端的数据校验
String errMsg = "";
//判断用户名不能为空
if (username == null || username.equals(""))
{
errMsg += "您的用户名丢失或没有输入,请从新输入";
}
//判断密码不能为空
else if(pass == null || pass.equals(""))
{
errMsg += "您的密码丢失或没有输入,请从新输入";
}
//若是用户名和密码不为空,才调用业务逻辑组件
else
{
//vb是业务逻辑组件,经过上面的初始化方法得到
if (getVb().valid(username,pass))
{
return mapping.findForward("welcome");
}
else
{
errMsg = "您的用户名和密码不匹配";
}
}
//判断是否生成了错误信息
if (errMsg != null && !errMsg.equals(""))
{
//若是有错误信息,将错误信息保存在request里,并跳转到input对应的
//forward对象
request.setAttribute("err" , errMsg);
return mapping.findForward("input");
}
else
{
//若是没有错误信息,跳转到welcome对应的forward对象
return mapping.findForward("welcome");
}
}
}
在上面的Action代码中,Action显式获取容器中的业务逻辑组件,而不是依靠Spring容器的依赖注入。在这种整合策略下,表现层的控制器组件再也不接受IoC容器管理。所以,没有控制器上下文,应将原有的action-servlet.xml文件删除,并修改plug-in元素,不要加载该文件。还要修改Action配置,将Action配置的type元素修改为实际的处理类。这 种整合策略也有一个好处:代码的可读性更强,对传统Struts应用开发的改变很小,容易使用。
将该Action部署在struts-config.xml中,Struts将负责建立该Action。struts-config.xml文件的源代码以下:
<!-- XML文件的版本和编码集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- Struts配置文件的文件头,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,全部的formbean都放在form-beans元素里定义 -->
<form-beans>
<!-- 定义了一个formbean,肯定formbean名和实现类 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定义action部分,全部的action都放在action-mapping元素里定义 -->
<action-mappings>
<!-- 这里只定义了一个action。action的类型为ActionSupport的子类 -->
<action path="/login" type="type="lee.LoginAction"
name="loginForm" scope="request" validate="true" input=
"/login.jsp" >
<!-- 定义action内的两个局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 加载国际化的资源包 -->
<message-resources parameter="mess"/>
<!-- 装载验证的资源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
</struts-config>
此时,Spring无须使用配置Action的配置文件,这种配置方式很是简单。只须要业务逻辑组件的配置文件,业务逻辑组件的配置文件与前面的示例没有任何改变。
该配置文件中的业务逻辑组件由Spring容器负责实现,而ActionSupport可以先定位Spring容器,而后得到容器的业务逻辑组件。
这种整合策略的执行效果与前面两种整合策略的执行效果彻底相同。从代码中分析可见,在这种整合策略下,业务控制器再次退回到Struts起初的设计。仅由strutsconfig.xml中Action充当,从而避免了像DelegatingActionProxy整合策略的性能低下,由于能够只须要建立实际的Action实例。
注意:在这种整合策略下,Struts开发者的改变最小,最接近传统Struts应用开发者的习惯。但这种整合策略会形成代码污染,由于Action类必须继承Spring的ActionSupport类。
posted @ 2009-07-19 10:23 jadmin 阅读(1) 评论(0) 编辑
虽然Spring也提供了本身的MVC组件,但一来Spring的MVC组件过于繁琐,二 来Struts的拥护者实在太多。所以,不少项目都会选择使用Spring整合Struts框架。并且Spring确实能够无缝整合Struts框架,两者结合成一个更实际的J2EE开发平台。
使用Spring的Web应用时,不用手动建立Spring容器,而是经过配置文件声明式地建立Spring容器。所以,在Web应用中建立Spring容器有以下两个方式:
● 直接在web.xml文件中配置建立Spring容器。
● 利用第三方MVC框架的扩展点,建立Spring容器。
其实第一种建立Spring容器的方式更加常见。为了让Spring容器随Web应用的启动而自动启动,有以下两个方法:
● 利用ServletContextListener实现。
● 采用load-on-startup Servlet实现。
Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类能够做为Listener使用,会在建立时自动查找WEB-INF/下的applicationContext.xml文件,所以,若是只有一个配置文件,而且文件名为applicationContext.xml,只需在web.xml文件中增长以下配置片断便可:
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
若是有多个配置文件须要载入,则考虑使用<context-param>元素来肯定配置文件的文件名。ContextLoaderListener加载时,会查找名为contextConfigLocation的参数。所以,配置context-param时,参数名字应该是contextConfigLocation。
带多个配置文件的web.xml文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及相应的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<!-- 肯定多个配置文件 -->
<context-param>
<!-- 参数名为contextConfigLocation -->
<param-name>contextConfigLocation</param-name>
<!-- 多个配置文件之间以“,”隔开 -->
<param-value>/WEB-INF/daoContext.xml,/WEB-INF/
applicationContext.xml</param-value>
</context-param>
<!-- 采用listener建立ApplicationContext实例 -->
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
</web-app>
若是没有经过contextConfigLocation指定配置文件,Spring会自动查找application- Context.xml配置文件;若是有contextConfigLocation,则利用该参数肯定的配置文件。若是没法找到合适的配置文件,Spring将没法正常初始化。
Spring根据bean定义建立WebApplicationContext对象,并将其保存在web应用的ServletContext中。大部分状况下,应用中的Bean无须感觉到ApplicationContext的存在,只要利用ApplicationContext的IoC便可。
若是须要在应用中获取ApplicationContext实例,能够经过以下代码获取:
//获取当前Web应用的Spring容器
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
除此以外,Spring提供了一个特殊的Servlet类ContextLoaderServlet。该Servlet在启动时,会自动查找WEB-INF/下的applicationContext.xml文件。
固然,为了让ContextLoaderServlet随应用的启动而启动,应将此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一点比较合适,这样能够保证Application- Context更快的初始化。
若是只有一个配置文件,而且文件名为applicationContext.xml,在web.xml文件中增长以下一段便可:
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
该Servlet用于提供“后台”服务,主要用于建立Spring容器,无须响应客户请求,所以无须配置servlet-mapping。
若是有多个配置文件,同样使用<context-param>元素来肯定多个配置文件。
事实上,无论是ContextLoaderServlet,仍是ContextLoaderListener,都依赖于ContextLoader建立ApplicationContext实例。
在ContextLoader代码的第240行,有以下代码:
String configLocation = servletContext.getInitParameter
(CONFIG_LOCATION_PARAM);
if (configLocation != null) {
wac.setConfigLocations(StringUtils.tokenizeToStringArray
(configLocation,
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
其中,CONFIG_LOCATION_PARAM是该类的常量,其值为contextConfigLocation。能够看出,ContextLoader首先检查servletContext中是否有contextConfigLocation的参数,若是有该参数,则加载该参数指定的配置文件。
ContextLoaderServlet与ContextLoaderListener底层都依赖于ContextLoader。所以,两者的效果几乎没有区别。之间的区别不是它们自己引发的,而是因为Servlet规范,Listener比Servlet优先加载。所以,采用ContextLoaderListener建立ApplicationContext的时机更早。
固然,也能够经过ServletContext的getAttribute方法获取ApplicationContext。但使用WebApplicationContextUtils类更便捷,由于无须记住ApplicationContext的属性名。即便ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE属性没有对应对象,WebApplicationContextUtils的getWebApplicationContext()方法将会返回空,而不会引发异常。
到底须要使用Listener,仍是使用load-on-startup Servlet来建立Spring容器呢?一般推荐使用Listener来建立Spring容器。但Listerner是Servlet 2.3以上才支持的标准,所以,必须Web容器支持Listener才可以使用Listerner。
注意:使用Listener建立Spring容器以前,应先评估Web容器是否支持Listener标准。
还有一种状况,利用第三方MVC框架的扩展点来建立Spring容器,好比Struts。在第2章介绍Strust框架时,知道Struts有一个扩展点PlugIn。
实际上,Spring正是利用了PlugIn这个扩展点,从而提供与Struts的整合。Spring提供了PlugIn接口的实现类org.springframework.web.struts.ContextLoaderPlugIn。这个实现类可做为Struts的PlugIn配置,Struts框架启动时,将自动建立Spring容器。
为了利用Struts的PlugIn建立Spring容器,只需在Struts配置文件中增长以下片断 便可:
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.
xml"/>
</plug-in>
其中,指定contextConfigLocation属性值时,便可以指定一个Spring配置文件的位置,能够指定多个Spring配置文件的位置。
对于一个基于B/S架构的J2EE应用而言,用户请求老是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。此时有一个问题,控制器应该如何得到业务逻辑组件?
最容易想到的策略是,直接经过new关键字建立业务逻辑组件,而后调用业务逻辑组件的方法,根据业务逻辑方法的返回值肯定结果。
实际的应用中,不多见到采用上面的访问策略,由于这是一种很是差的策略。不这样作至少有以下3个缘由:
● 控制器直接建立业务逻辑组件,致使控制器和业务逻辑组件的耦合下降到代码层次,不利于高层次解耦。
● 控制器不该该负责业务逻辑组件的建立,控制器只是业务逻辑组件的使用者。无须关心业务逻辑组件的实现。
● 每次建立新的业务逻辑组件将致使性能降低。
答案是采用工厂模式或服务定位器。采用服务定位器的模式,是远程访问的场景。在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无须理会该业务逻辑组件的建立,直接调用便可,但在调用以前,必须先找到该服务——这就是服务定位器的概念。经典J2EE应用就是这种结构的应用。
对于轻量级的J2EE应用,工厂模式则是更实际的策略。由于轻量级的J2EE应用里,业务逻辑组件不是EJB,一般就是一个POJO,业务逻辑组件的生成一般由工厂负责,并且工厂能够保证该组件的实例只需一个就够了,能够避免重复实例化形成的系统开销。
如图6.2就是采用工厂模式的顺序图。
图6.2 工厂模式顺序图
采用工厂模式,将控制器与业务逻辑组件的实现分离,从而提供更好的解耦。
在采用工厂模式的访问策略中,全部的业务逻辑组件的建立由工厂负责,业务逻辑组件的运行也由工厂负责。而控制器只需定位工厂实例便可。
若是系统采用Spring框架,则Spring成为最大的工厂。Spring负责业务逻辑组件的建立和生成,并可管理业务逻辑组件的生命周期。能够如此理解,Spring是一个性能很是优秀的工厂,能够生产出全部的实例,从业务逻辑组件,到持久层组件,甚至控制器。
如今的问题是,控制器如何访问到Spring容器中的业务逻辑组件?为了让Action访 问Spring的业务逻辑组件,有两种策略:
● Spring管理控制器,并利用依赖注入为控制器注入业务逻辑组件。
● 控制器显式定位Spring工厂,也就是Spring的容器ApplicationContext实例,并从工厂中获取业务逻辑组件实例的引用。
第一种策略,充分利用Spring的IoC特性,是最优秀的解耦策略。但不可避免带来一些不足之处,概括起来主要有以下不足之处:
● Spring管理Action,必须将全部的Action配置在Spring容器中,而struts-config.xml文件中的配置也不会减小,致使配置文件大量增长。
● Action的业务逻辑组件接收容器注入,将致使代码的可读性下降。
整体而言,这种整合策略是利大于弊。
第二种策略,与前面介绍的工厂模式并无太大的不一样。区别是Spring容器充当了业务逻辑组件的工厂。控制器负责定位Spring容器,一般Spring容器访问容器中的业务逻辑组件。这种策略是一种折衷,下降了解耦,但提升了程序的可读性。
Spring彻底支持这两种策略,既可让Spring容器管理控制器,也可让控制器显式定位Spring容器中的业务逻辑组件。
这里介绍的是第一种整合策略:让Spring管理Struts的Action。那么一样有一个问题,让Spring管理Struts的Action时,客户端的HTTP 请求如何转向Spring容器中的Action?
当使用Struts做为MVC框架时,客户端的HTTP请求都是直接向ActionServlet请求的,所以关键就是让ActionServlet将请求转发给Spring容器中的Action。这很明显能够利用Spring的另外一个扩展点:经过扩展RequestProcessor完成,使用扩展的RequestProcessor替换Struts的RequestProcessor。
Spring完成了这种扩展,Spring提供的DelegatingRequestProcessor继承Request- Processor。为了让Struts使用DelegatingRequestProcessor,还须要在struts-config.xml文件中增长以下一行:
//使用spring的RequestProcessor替换struts原有的RequestProcessor
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
完成这个设置后,Struts会将截获到的用户请求转发到Spring context下的bean,根据bean的name属性来匹配。而Struts中的action配置则无须配置class属性,即便配置了class属性也没有任何用处,即下面两行配置是彻底同样的:
//配置struts action时,指定了实现类
<action path="/user" type="lee.UserAction"/>
//配置struts action时,没有指定实现类
<action path="/user"/>
下面的示例程序在上一个示例程序的基础上稍做修改,增长了客户端验证和程序国际化部分。也调用了Spring的业务bean来验证登陆。先看修改后的struts-config.xml文件:
<!-- XML文件版本,编码集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- Struts配置文件的文件头,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,全部的formbean都放在form-beans元素里定义 -->
<form-beans>
<!-- 定义了一个formbean,肯定formbean名和实现类 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定义action部分,全部的action都放在action-mapping元素里定义 -->
<action-mappings>
<!-- 这里只定义了一个action。并且没有指定该action的type元素 -->
<action path="/login" name="loginForm"
scope="request" validate="true" input="/login.jsp" >
<!-- 定义action内的两个局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 使用DelegatingRequestProcessor替换RequestProcessor -->
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
<!-- 加载国际化的资源包 -->
<message-resources parameter="mess"/>
<!-- 装载验证的资源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
<!-- 装载Spring配置文件,随应用的启动建立ApplicationContext实例 -->
<plug-in className="org.springframework.web.struts.
ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>
修改后的struts-config.xml文件,增长加载国际化资源文件。配置Struts的action不须要class属性,完成了ApplicationContext的建立。
而后考虑web.xml文件的配置,在web.xml文件中必须配置Struts框架的加载。除此以外,由于使用了Spring管理Struts的Action,而Action是随HTTP请求启动的,所以,应将Action的做用域配置成Request,为了使用Request做用域,必须在web.xml文件中增长适当的配置。
下面是web.xml文件的代码:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及对应的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<!-- 定义一个Filter,该Filter是使用Request做用域的基础 -->
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.
RequestContextFilter </filter-class>
</filter>
<!-- 定义filter-mapping,让上面的Filter过滤全部的用户请求 -->
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 定义Struts的核心Servlet -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- 定义Struts的核心Servlet拦截全部*.do请求 -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 关于Struts标签库的配置 -->
<jsp-config>
<!-- 配置bean标签 -->
<taglib>
<taglib-uri>/tags/struts-bean</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<!-- 配置html标签 -->
<taglib>
<taglib-uri>/tags/struts-html</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<!-- 配置logic标签 -->
<taglib>
<taglib-uri>/tags/struts-logic</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
posted @ 2009-07-19 10:22 jadmin 阅读(2) 评论(0) 编辑
虽然在上面的配置片断中,仅仅配置了JDBC局部事务管理器、Hibernate局部事务管理器、JDBC全局事务管理器等。但Spring支持大部分持久化策略的事务管理器。
不论采用何种持久化策略,Spring都提供了一致的事务抽象,所以,应用开发者能在任何环境下,使用一致的编程模型。无须更改代码,应用就可在不一样的事务管理策略中切换。Spring同时支持声明式事务管理和编程式事务管理。
使用编程式事务管理,开发者使用的是Spring事务抽象,而无须使用任何具体的底层事务API。Spring的事务管理将代码从底层具体的事务API中抽象出来,该抽象能够使用在任何底层事务基础之上。
使用声明式策略,开发者一般书写不多的事务管理代码,所以,不依赖Spring或任何其余事务API。Spring的声明式事务无须任何额外的容器支持,Spring容器自己管理声明式事务。使用声明事务策略,无须在业务代码中书写任何事务代码,可让开发者更好地专一于业务逻辑的实现。Spring管理的事务支持多个事务资源的跨越,但没法支持跨越远程调用的事务上下文传播。
Spring同时支持编程式事务策略和声明式事务策略,大部分时候,都推荐采用声明式事务策略,使用声明事务策略的优点十分明显:
● 声明式事务能大大下降开发者的代码书写量。并且声明式事务几乎不须要影响应用的代码。所以,不管底层事务策略如何变化,应用程序无须任何改变。
● 应用程序代码无须任何事务处理代码,能够更专一于业务逻辑的实现。
● Spring则可对任何POJO的方法提供事务管理,并且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。
● EJB的CMT没法提供声明式回滚规则。而经过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不只可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。
● 因为Spring采用AOP的方式管理事务,所以,能够在事务回滚动做中插入用户本身的动做,而不只仅是执行系统默认的回滚。
提示:本节不打算全面介绍Spring的各类事务策略,所以本节不会介绍编程式事务。若是读者须要更全面了解Spring事务的相关方面,请参阅笔者所著的《Spring2.0宝典》 一书。
对于采用声明式事务策略,能够使用TransactionProxyFactoryBean来配置事务代理Bean。正如它的类名所暗示的,它是一个工厂Bean,工厂Bean用于生成一系列的Bean实例,这一系列的Bean实例都是Proxy。
可能读者已经想到了,既然TransactionProxyFactoryBean产生的是代理Bean,可见这种事务代理正是基于Spring AOP组件的。配置TransactionProxyFactoryBean时,同样须要指定目标Bean。
每一个TransactionProxyFactoryBean为一个目标Bean生成事务代理,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行以前加入开始事务,在目标Bean的方法正常结束以前提交事务,若是遇到特定异常则回滚事务。
TransactionProxyFactoryBean建立事务代理时,须要了解当前事务所处的环境,该环境属性经过PlatformTransactionManager实例传入,而相关事务传入规则在TransactionProxy- FactoryBean的定义中给出。
下面给出声明式事务配置文件的完整代码:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->
<property name="dataSource" <ref="dataSource"/>
<!-- mappingResources属性用来列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用来列出全部的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的链接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根据Hiberante映射建立数据表时,选择create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置DAO Bean,该Bean将做为目标Bean使用 -->
<bean id="personDAOTarget" class="lee.PersonDaoImpl">
<!-- 采用依赖注入来传入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置Hibernate的事务管理器 -->
<!-- 使用HibernateTransactionManager类,该类实现PlatformTransactionManager
接口,针对采用Hibernate持久化链接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean,它须要依赖注入一个SessionFactory
Bean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置personDAOTarget Bean的事务代理 -->
<bean id="personDAO"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<!-- 依赖注入PlatformTransactionManager的bean引用,此处使用
Hibernate的bean -->
<!-- 局部事务器,所以transactionManager 传入Hibernate事务管理器的
引用 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 须要生成代理的目标bean -->
<property name="target" ref="personDAOTarget"/>
<!-- 指定事务属性 -->
<property name="transactionAttributes">
<props>
<!-- 如下部分为定义事务回滚规则 -->
<prop key="insert*">PROPAGATION_REQUIRED,
-MyCheckedException</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
</beans>
在上面的定义文件中,没有对DAO对象采用Service层包装。一般状况下,DAO层上应有一层Service层。事务代理则以Service层Bean为目标Bean。此处为了简化配置,TransactionProxyFactoryBean直接以DAO bean做为目标bean,这一点不会影响事务代理的生成。
事务回滚规则部分定义了三个回滚规则:
第一个回滚规则表示全部以insert开始的方法,都应该知足该事务规则。PROPAGATION_REQUIRED事务传播规则指明,该方法必须处于事务环境中,若是当前执行线程已处于事务环境下,则直接执行;不然,启动新的事务而后执行该方法。该规则还指定,若是方法抛出MyCheckedException的实例及其子类的实例,则强制回滚。MyCheckedException前的“-”表示强制回滚;“+”则表示强制提交,即某些状况下,即便抛出异常也强制提交;
第二个回滚规则表示全部以update开头的方法,都遵照PROPAGATION_REQUIRED的事务传播规则;
第三个回滚规则表示除前面规定的方法外,其余全部方法都采用PROPAGATION_ REQUIRED事务传播规则,并且只读。
常见的事务传播规则有以下几个:
● PROPAGATION_MANDATORY,要求调用该方法的线程必须处于事务环境中,不然抛出异常。
● PROPAGATION_NESTED,若是执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。若是执行该方法的线程序并未处于事务中,也启动新的事务,而后执行该方法,此时与PROPAGATION_REQUIRED相同。
● PROPAGATION_NEVER,不容许调用该方法的线程处于事务环境下,若是调用该方法的线程处于事务环境下,则抛出异常。
● PROPAGATION_NOT_SUPPORTED,若是调用该方法的线程处在事务中,则先暂停当前事务,而后执行该方法。
● PROPAGATION_REQUIRED,要求在事务环境中执行该方法,若是当前执行线程已处于事务中,则直接调用;若是当前执行线程不处于事务中,则启动新的事务后执行该方法。
● PROPAGATION_REQUIRES_NEW,该方法要求有一个线程在新的事务环境中执行,若是当前执行线程已处于事务中,先暂停当前事务,启动新的事务后执行该方法;若是当前调用线程不处于事务中,则启动新的事务后执行该方法。
● PROPAGATION_SUPPORTS,若是当前执行线程处于事务中,则使用当前事务,不然不使用事务。
程序里原来使用personDAO的地方,无须变化。由于,配置文件里将personDAO目标Bean的id改为personDAOTarget,为TransactionProxyFactoryBean工厂Bean所产生的代理Bean命名为personDAO。该代理Bean会包含原有personDAO的全部方法,并且为这些方法增长了不一样的事务处理规则。
程序面向PersonDaoImpl类所实现的接口编程,TransactionProxyFactoryBean生成的代理Bean也会实现TransactionProxyFactoryBean接口。所以,原有的程序中调用DAO组件的代码无须任何改变。程序运行时,由事务代理完成原来目标Bean完成的工做。
事实上,Spring不只支持对接口的代理,整合CGLIB后,Spring甚至可对具体类生成代理。只要设置proxyTargetClass属性为true就能够。若是目标Bean没有实现任何接口,proxyTargetClass属性默认被设为true,此时Spring会对具体类生成代理。固然,一般建议面向接口编程,而不要面向具体的实现类编程。
仔细观察配置文件中两个事务代理Bean的配置时,发现两个事务代理Bean的大部分配置彻底相同,若是配置文件中包含大量这样的事务代理Bean配置,配置文件将很是臃肿。考虑到大部分事务代理Bean的配置都大同小异,能够使用Bean继承来简化事务代理的配置。
正如前面部分介绍的,Bean继承是将全部子Bean中相同的配置定义成一个模板,并将此模板Bean定义成一个抽象Bean。考虑全部事务代理Bean中,有以下部分是大体相 同的:
● 事务代理Bean所使用的事务管理器。
● 事务传播规则。
所以,如今配置文件中定义以下的事务代理模板Bean,其配置代码以下:
<!-- 定义全部事务代理Bean的模板 -->
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<!-- 为事务代理Bean注入生成代理所需的PlatformTransactionManager实例 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 定义生成事务代理通用的事务属性 -->
<property name="transactionAttributes">
<props>
<!-- 全部的方法都应用PROPAGATION_REQUIRED的事务传播规则 -->
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
而真正的事务代理Bean,则改成继承上面的事务模板Bean。考虑到将目标Bean定义在Spring容器中可能增长未知的风险,所以将目标Bean定义成嵌套Bean。
<!-- 让事务代理Bean继承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
<!-- 这里采用嵌套Bean的方式来定义目标Bean,固然也能够引用已存在的Bean -->
<property name="target">
<bean class="lee.personDAO"/>
</property>
</bean>
此时的personDAO Bean无须具体地定义事务属性,它将在其父Bean txProxyTemplate中获取事务定义属性。此处采用嵌套Bean来定义目标Bean,所以,并未将目标Bean直接暴露在Spring的上下文中让其余模块调用。固然,也可采用一个已经存在的Bean做为目标Bean;子Bean的事务属性定义,彻底可覆盖事务代理模板里的事务属性定义。以下例所示:
<!-- 让事务代理bean继承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
<!-- 这里,采用引用已存在的bean的方式来定义目标Bean -->
<property name="target" ref ="personDAOTarget"/>
<!-- 覆盖事务代理模板bean中的事务属性定义 -->
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
可见,采用Bean继承方式定义事务代理的方式,能够很好地简化事务代理的配置,能够避免配置事务代理Bean时的冗余配置。
提示:使用Bean继承能够很好地简化事务代理Bean的配置,经过将各事务代理Bean共同的配置信息提取成事务模板Bean,可让实际的事务代理Bean的配置更加简洁;并且,配置方式至关直观。尽可能将目标Bean配置成嵌套Bean,这样的方式能够保证更好的内聚性。
若是读者还记得前面介绍的AOP知识,应该知道还有一种更加简洁的配置,就是利用Bean后处理器,让Bean后处理器为容器中其余Bean自动建立事务代理。
回顾6.2.6节和6.2.7节,读者可能已经想到如何自动建立代理。是的,正是经过6.2.6节和6.2.7节所给出的两个自动代理建立类来生成事务代理。
正如前文已经提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator来建立代理时,并不必定是建立事务代理,关键在于传入的拦截器,若是传入事务拦截器,将可自动生成事务代理。
下面是使用BeanNameAutoProxyCreator自动生成事务代理的配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及相应的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 使用JDBC的局部事务策略 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSource-
TransactionManager">
<!-- 为事务管理器注入所需的数据源Bean -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->
<bean id="test1" class="lee.TransactionTestImpl">
<!-- 依赖注入目标Bean所必需的数据源Bean -->
<property name="ds" ref="dataSource"/>
</bean>
<!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->
<bean id="test2" class="lee.TestImpl">
<!-- 依赖注入目标Bean所必需的数据源Bean -->
<property name="ds" ref="dataSource"/>
</bean>
<!-- 配置事务拦截器Bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事务拦截器bean须要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对知足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是全部须要自动建立事务代理的Bean -->
<list>
<value>test1</value>
<value>test2</value>
</list>
<!-- 此处可增长其余须要自动建立事务代理的Bean -->
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增长其余新的Interceptor -->
</list>
</property>
</bean>
</beans>
若是配置文件中仅有两个目标Bean,可能不能很清楚地看出这种自动建立代理配置方式的优点,但若是有更多目标Bean须要自动建立事务代理,则能够很好地体会到这种配置方式的优点:配置文件只须要简单地配置目标Bean,而后在BeanNameAutoProxyCreator配置中增长一行便可。
提示:使用BeanNameAutoProxyCreator能够自动建立事务代理,使用DefaultAdvisor- AutoProxyCreator也可自动建立事务代理。关于后一个Bean后处理器的配置方式,请参看前面6.2.7节的内容。
posted @ 2009-07-19 10:18 jadmin 阅读(5) 评论(0) 编辑
Spring的事务管理不需与任何特定的事务API耦合。对不一样的持久层访问技术,编程式事务提供一致的事务编程风格,经过模板化的操做一致性地管理事务。声明式事务基于Spring AOP实现,却并不须要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。
Spring事务策略是经过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。该接口的源代码以下:
public interface PlatformTransactionManager
{
//平台无关的得到事务的方法
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
//平台无关的事务提交方法
void commit(TransactionStatus status) throws TransactionException;
//平台无关的事务回滚方法
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager是一个与任何事务策略分离的接口,随着底层不一样事务策略切换,应用必须采用不一样的实现类。PlatformTransactionManager接口没有与任何事务资源捆绑在一块儿,它能够适应于任何的事务策略,结合Spring的IoC容器,能够向PlatformTransactionManager注入相关的平台特性。
PlatformTransactionManager接口有许多不一样的实现类,应用程序面向与平台无关的接口编程,对不一样平台的底层支持,由PlatformTransactionManager接口的实现类完成。从而,应用程序无须与具体的事务API耦合。所以,使用PlatformTransactionManager接口,可将代码从具体的事务API中解耦出来。
即便使用特定容器管理的JTA,代码依然无须执行JNDI查找,无须与特定的JTA资源耦合在一块儿。经过配置文件,JTA资源传给PlatformTransactionManager的实现类。所以,程序的代码可在JTA事务管理和非JTA事务管理之间轻松切换。
在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition definition)方法,该方法根据一个TransactionDefinition参数,返回一个TransactionStatus对象。TransactionStatus对象表示一个事务。TransactionStatus被关联在当前执行的线程。
getTransaction(TransactionDefinition definition)返回的TransactionStatus对象,多是一个新的事务,也多是一个已经存在的事务对象。若是当前执行的线程已经处于事务管理下,返回当前线程的事务对象,不然,返回当前线程的调用堆栈已有的事务对象。
TransactionDefinition接口定义了一个事务规则,该接口必须指定以下几个属性值:
● 事务隔离,当前事务和其余事务的隔离程度。例如,这个事务可否看到其余事务未提交的数据等。
● 事务传播,一般,在事务中执行的代码都会在当前事务中运行。可是,若是一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数状况下,简单地在现有的事务上下文中运行;或者挂起现有事务,建立一个新的事务。Spring提供EJB CMT(Contain Manager Transaction,容器管理事务)中全部的事务传播选项。
● 事务超时,事务在超时前能运行多久。事务的最长持续时间。若是事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。
● 只读状态,只读事务不修改任何数据。在某些状况下(例如使用Hibernate时),只读事务是很是有用的优化。
TransactionStatus表明事务自己,它提供了简单的控制事务执行和查询事务状态的方法。这些方法在全部的事务API中都是相同的。TransactionStatus接口的源代码以下:
public interface TransactionStatus
{
//判断事务是不是新建的事务
boolean isNewTransaction();
//设置事务回滚
void setRollbackOnly();
//查询事务是否已有回滚标志
boolean isRollbackOnly();
}
Spring的事务管理由PlatformTransactionManager的不一样实现类完成。在Spring上下文中配置PlatformTransactionManager Bean时,必须针对不一样环境提供不一样的实现类。
下面提供不一样的持久层访问环境,及其对应的PlatformTransactionManager实现类的 配置。
JDBC数据源的局部事务策略:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 配置JDBC数据源的局部事务管理器 -->
<!-- 使用DataSourceTransactionManager 类,该类实现PlatformTransactionManager接口 -->
<!-- 针对采用数据源链接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.
DataSourceTransactionManager">
<!-- DataSourceTransactionManager bean须要依赖注入一个DataSource
bean的引用 -->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
对于容器管理JTA数据源,全局事务策略的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置JNDI数据源Bean -->
<bean id="dataSource" class="org.springframework.jndi.
JndiObjectFactoryBean">
<!-- 容器管理数据源的JNDI -->
<property name="jndiName" value="jdbc/jpetstore"/>
</bean>
<!-- 使用JtaTransactionManager类,该类实现PlatformTransactionManager接
口 -->
<!-- 针对采用全局事务管理的特定实现 -->
<!-- JtaTransactionManager不须要知道数据源,或任何其余特定资源 -->
<!-- 由于它使用容器的全局事务管理 -->
<bean id="transactionManager"
class="org.springframework.transaction.jta.
JtaTransactionManager" />
</beans>
对于采用Hibernate持久层访问策略时,局部事务策略的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定链接数据库链接池的最大链接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定链接数据库链接池的最小链接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定链接数据库链接池的初始化链接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定链接数据库链接池的链接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用来列出全部的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory的属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的链接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根据Hibernate映射建立数据表时,选择create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Hibernate的局部事务管理器 -->
<!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager
接口,针对采用Hibernate持久化链接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean须要依赖注入一个
SessionFactorybean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
对于采用Hibernate持久层访问策略时,全局事务策略的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置JNDI数据源Bean -->
<bean id="dataSource" class="org.springframework.jndi.
JndiObjectFactoryBean">
<!-- 容器管理数据源的JNDI -->
<property name="jndiName" value="jdbc/jpetstore"/>
</bean>
<!--定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource Bean -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出所有映射文件 -->
<property name="mappingResources">
<list>
<!-- 如下用来列出全部的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory的属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的链接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根据Hiberante映射建立数据表时,选择create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 使用JtaTransactionManager类,该类是PlatformTransactionManager接口,
针对采用数据源链接的特定实现 -->
<!-- JtaTransactionManager不须要知道数据源,或任何其余特定资源,
由于使用容器的全局事务管理 -->
<bean id="transactionManager"
class="org.springframework.transaction.jta.
JtaTransactionManager" />
</beans>
不论采用哪一种持久层访问技术,只要使用JTA数据源,Spring事务管理器的配置都是同样的,由于它们都采用的是全局事务管理。
能够看到,仅仅经过配置文件的修改,就能够在不一样的事务管理策略间切换,即便从局部事务到全局事务的切换。
提示:Spring所支持的事务策略很是灵活,Spring的事务策略容许应用程序在不一样事务策略之间自由切换,即便须要在局部事务策略和全局事务策略之间切换,只须要修改配置文件,而应用程序的代码无须任何改变。这种灵活的设计,又未尝不是由于面向接口编程带来的优点,可见面向接口编程给应用程序更好的适应性。
posted @ 2009-07-19 10:18 jadmin 阅读(2) 评论(0) 编辑
当目标Bean的实现类实现了接口后,Spring AOP能够为其建立JDK动态代理,而无须使用CGLIB建立的代理,这种代理称为代理接口。
建立AOP代理必须指定两个属性:目标Bean和处理。实际上,不少AOP框架都以拦截器做为处理。由于Spring AOP与IoC容器的良好整合,所以配置代理Bean时,彻底能够利用依赖注入来管理目标Bean和拦截器Bean。
下面的示例演示了基于AOP的权限认证,它是简单的TestService接口,该接口模拟Service组件,该组件内包含两个方法:
● 查看数据。
● 修改数据。
接口的源代码以下:
//Service组件接口
public interface TestService
{
//查看数据
void view();
//修改数据
void modify();
}
该接口的实现类实现两个方法。由于篇幅限制,本示例并未显示出完整的查看数据和修改数据的持久层操做,仅仅在控制台打印两行信息。实际的项目实现中,两个方法的实现则改为对持久层组件的调用,这不会影响示例程序的效果。实现类的源代码以下:
TestService接口的实现类
public class TestServiceImpl implements TestService
{
//实现接口必须实现的方法
public void view()
{
System.out.println("用户查看数据");
}
//实现接口必须实现的方法
public void modify()
{
System.out.println("用户修改数据");
}
}
示例程序采用Around 处理做为拦截器,拦截器中使用依赖注入得到当前用户名。实际Web应用中,用户应该从session中读取。这不会影响示例代码的效果。拦截器源代码以下:
public class AuthorityInterceptor implements MethodInterceptor
{
//当前用户名
private String user;
//依赖注入所必需的setter方法
public void setUser(String user)
{
this.user = user;
}
public Object invoke(MethodInvocation invocation) throws Throwable
{
//获取当前拦截的方法名
String methodName = invocation.getMethod().getName();
//下面执行权限检查
//对既不是管理员,也不是注册用户的状况
if (!user.equals("admin") && !user.equals("registedUser"))
{
System.out.println("您无权执行该方法");
return null;
}
//对仅仅是注册用户,调用修改数据的状况
else if (user.equals("registedUser") && methodName.equals
("modify"))
{
System.out.println("您不是管理员,没法修改数据");
return null;
}
//对管理员或注册用户,查看数据的状况
else
{
return invocation.proceed();
}
}
}
TestAction类依赖TestService。因篇幅关系,此处不给出TestAction的接口的源代码,TestActionImpl的源代码以下:
public class TestActionImpl
{
//将TestService做为成员变量,面向接口编程
private TestService ts;
//依赖注入的setter方法
public void setTs(TestService ts)
{
this.ts = ts;
}
//修改数据
public void modify()
{
ts.modify();
}
//查看数据
public void view()
{
ts.view();
}
}
配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置目标Bean -->
<bean id="serviceTarget" class="lee.TestServiceImpl"/>
<!-- 配置拦截器,拦截器做为处理使用 -->
<bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
<property name="user" value="admin"/>
</bean>
<!-- 配置代理工厂Bean,负责生成AOP代理 -->
<bean id="service" class="org.springframework.aop.framework.
ProxyFactoryBean">
<!-- 指定AOP代理所实现的接口 -->
<property name="proxyInterfaces" value="lee.TestService"/>
<!-- 指定AOP代理所代理的目标Bean -->
<property name="target" ref="serviceTarget"/>
<!-- AOP代理所须要的拦截器列表 -->
<property name="interceptorNames">
<list>
<value>authorityInterceptor</value>
</list>
</property>
</bean>
<!-- 配置Action Bean,该Action依赖TestService Bean -->
<bean id="testAction" class="lee.TestActionImpl">
<!-- 此处注入的是依赖代理Bean -->
<property name="ts" ref="service"/>
</bean>
</beans>
主程序请求testAction Bean,而后调用该Bean的两个方法,主程序以下:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
//建立Spring容器实例
ApplicationContext ctx = new FileSystemXmlApplicationContext
("bean.xml");
//得到TestAction bean
TestAction ta = (TestAction)ctx.getBean("testAction");
//调用bean的两个测试方法
ta.view();
ta.modify();
}
}
程序执行结果以下:
[java] 用户查看数据
[java] 用户修改数据
代理彷佛没有发挥任何做用。由于配置文件中的当前用户是admin,admin用户具有访问和修改数据的权限,所以代理并未阻止访问。将配置文件中的admin修改为registed- User,再次执行程序,获得以下结果:
[java] 用户查看数据
[java] 您不是管理员,没法修改数据
代理阻止了registedUser修改数据,查看数据能够执行。将registedUser修改为其余用户,执行程序,看到以下结果:
[java] 您无权执行该方法
[java] 您无权执行该方法
代理阻止用户对两个方法的执行。基于AOP的权限检查,能够下降程序的代码量,由于无须每次调用方法以前,手动编写权限检查代码;同时,权限检查与业务逻辑分离,提升了程序的解耦。
示例中的目标Bean被暴露在容器中,能够被客户端代码直接访问。为了不客户端代码直接访问目标Bean,能够将目标Bean定义成代理工厂的嵌套Bean,修改后的配置文件以下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置拦截器,拦截器做为处理使用 -->
<bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
<property name="user" value="admin"/>
</bean>
<!-- 配置代理工厂Bean,该工厂Bean将负责建立目标Bean的代理 -->
<bean id="service" class="org.springframework.aop.framework.
ProxyFactoryBean">
<!-- 指定AOP代理所实现的接口 -->
<property name="proxyInterfaces" value="lee.TestService"/>
<property name="target">
<!-- 以嵌套Bean的形式定义目标Bean,避免客户端直接访问目标Bean -->
<bean class="lee.TestServiceImpl"/>
</property>
<!-- AOP代理所须要的拦截器列表 -->
<property name="interceptorNames">
<list>
<value>authorityInterceptor</value>
</list>
</property>
</bean>
<!-- 配置Action Bean,该Action依赖TestService Bean -->
<bean id="testAction" class="lee.TestActionImpl">
<!-- 此处注入的是依赖代理Bean -->
<property name="ts" ref="service"/>
</bean>
</beans>
由上面介绍的内容可见,Spring的AOP是对JDK动态代理模式的深化。经过Spring AOP组件,容许经过配置文件管理目标Bean和AOP所需的处理。
下面将继续介绍如何为没有实现接口的目标Bean建立CGLIB代理。
若是目标类没有实现接口,则没法建立JDK动态代理,只能建立CGLIB代理。若是须要没有实现接口的Bean实例生成代理,配置文件中应该修改以下两项:
● 去掉<property name="proxyInterfaces"/>声明。由于再也不代理接口,所以,此处的配置没有意义。
● 增长<property name="proxyTargetClass">子元素,并设其值为true,经过该元素强制使用CGLIB代理,而不是JDK动态代理。
注意:最好面向接口编程,不要面向类编程。同时,即便在实现接口的状况下,也可强制使用CGLIB代理。
CGLIB代理在运行期间产生目标对象的子类,该子类经过装饰器设计模式加入到Advice中。由于CGLIB代理是目标对象的子类,则必须考虑保证以下两点:
● 目标类不能声明成final,由于final类不能被继承,没法生成代理。
● 目标方法也不能声明成final,final方法不能被重写,没法获得处理。
固然,为了须要使用CGLIB代理,应用中应添加CGLIB二进制Jar文件。
这是一种自动建立事务代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator实例,该实例将会对指定名字的Bean实例自动建立代理。实际上,BeanNameAutoProxyCreator是一个Bean后处理器,理论上它会对容器中全部的Bean进行处理,实际上它只对指定名字的Bean实例建立代理。
BeanNameAutoProxyCreator根据名字自动生成事务代理,名字匹配支持通配符。
与ProxyFactoryBean同样,BeanNameAutoProxyCreator须要一个interceptorNames属性,该属性名虽然是“拦截器”,但并不须要指定拦截器列表,它能够是Advisor或任何处理类型。
下面是使用BeanNameAutoProxyCreator的配置片断:
<!-- 定义事务拦截器bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事务拦截器bean须要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED </prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,
负责为容器中特定的Bean建立AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对知足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<list>
<!-- 下面是全部须要自动建立事务代理的Bean -->
<value>core-services-applicationControllerSevice</value>
<value>core-services-deviceService</value>
<value>core-services-authenticationService</value>
<value>core-services-packagingMessageHandler</value>
<value>core-services-sendEmail</value>
<value>core-services-userService</value>
<!-- 此处可增长其余须要自动建立事务代理的Bean -->
</list>
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增长其余新的Interceptor -->
</list>
</property>
</bean>
上面的片断是使用BeanNameAutoProxyCreator自动建立事务代理的片断。Transaction- Interceptor用来定义事务拦截器,定义事务拦截器时传入事务管理器Bean,也指定事务传播属性。
经过BeanNameAutoProxyCreator定义Bean后处理器,定义该Bean后处理器时,经过beanNames属性指定有哪些目标Bean生成事务代理;还须要指定“拦截器链”,该拦截器链能够由任何处理组成。
定义目标Bean还可以使用通配符,使用通配符的配置片断以下所示:
<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,
负责为容器中特定的Bean建立AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对知足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<!-- 此处使用通配符肯定目标bean -->
<value>*DAO,*Service,*Manager</value>
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增长其余新的Interceptor -->
</list>
</property>
</bean>
上面的配置片断中,全部名字以DAO、Service、Manager结尾的bean,将由该“bean后处理器”为其建立事务代理。目标bean再也不存在,取而代之的是目标bean的事务代理。经过这种方式,不只能够极大地下降配置文件的繁琐,并且能够避免客户端代码直接调用目标bean。
注意:虽然上面的配置片断是为目标对象自动生成事务代理。但这不是惟一的,若是有须要,能够为目标对象生成任何的代理。BeanNameAutoProxyCreator为目标对象生成怎样的代理,取决于传入怎样的处理Bean,若是传入事务拦截器,则生成事务代理Bean;不然将生成其余代理,在后面的实例部分,读者将能够看到大量使用BeanNameAutoProxy- Creator建立的权限检查代理。
Spring还提供了另外一个Bean后处理器,它也可为容器中的Bean自动建立代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更强大的自动代理生成器。它将自动应用于当前容器中的advisor,不须要在DefaultAdvisorAutoProxyCreator定义中指定目标Bean的名字字符串。
这种定义方式有助于配置的一致性,避免在自动代理建立器中重复配置目标Bean 名。
使用该机制包括:
● 配置DefaultAdvisorAutoProxyCreator bean定义。
● 配置任何数目的Advisor,必须是Advisor,不只仅是拦截器或其余处理。由于,必须使用切入点检查处理是否符合候选Bean定义。
DefaultAdvisorAutoProxyCreator计算Advisor包含的切入点,检查处理是否应该被应用到业务对象,这意味着任何数目的Advisor均可自动应用到业务对象。若是Advisor中没有切入点符合业务对象的方法,这个对象就不会被代理。若是增长了新的业务对象,只要它们符合切入点定义,DefaultAdvisorAutoProxyCreator将自动为其生成代理,无须额外 配置。
当有大量的业务对象须要采用相同处理,DefaultAdvisorAutoProxyCreator是很是有用的。一旦定义恰当,直接增长业务对象,而不须要额外的代理配置,系统自动为其增长 代理。
<beans>
<!-- 定义Hibernate局部事务管理器,可切换到JTA全局事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- 定义事务管理器时,依赖注入SessionFactory -->
<property name="sessionFactory" ref bean="sessionFactory"/>
</bean>
<!-- 定义事务拦截器 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<!-- 定义DefaultAdvisorAutoProxyCreator Bean,这是一个Bean后处理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
DefaultAdvisorAutoProxyCreator"/>
<!-- 定义事务Advisor -->
<bean class="org.springframework.transaction.interceptor.
TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref=
"transactionInterceptor"/>
</bean>
<!-- 定义额外的Advisor>
<bean id="customAdvisor" class="lee.MyAdvisor"/>
</beans>
DefaultAdvisorAutoProxyCreator支持过滤和排序。若是须要排序,可以让Advisor实现org.springframework.core.Ordered接口来肯定顺序。TransactionAttributeSourceAdvisor已经实现Ordered接口,所以可配置其顺序,默认是不排序。
采用这样的方式,同样能够避免客户端代码直接访问目标Bean,并且配置更加简洁。只要目标Bean符合切入点检查,Bean后处理器自动为目标Bean建立代理,无须依次指定目标Bean名。
注意:Spring也支持以编程方式建立AOP代理,但这种方式将AOP代理所须要的目标对象和处理Bean等对象的耦合下降到代码层次,所以不推荐使用。若是读者须要深刻了解如何经过编程方式建立AOP代理,请参阅笔者所著的《Spring2.0宝典》。
posted @ 2009-07-19 10:16 jadmin 阅读(1) 评论(0) 编辑
AOP(Aspect Orient Programming),也就是面向切面编程,做为面向对象编程的一种补充。问世的时间并不太长,甚至在国内的翻译还不太统一(有些书翻译成面向方面编程),但它确实极好地补充了面向对象编程的方式。面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。
能够这样理解,面向对象编程是从静态角度考虑程序结构,面向切面编程是从动态角度考虑程序运行过程。
Spring AOP是Spring框架的一个重要组件,极好地补充了Spring IoC容器的功能。Spring AOP将Spring IoC容器与AOP组件紧密结合,丰富了IoC容器的功能。固然,即便不使用AOP组件,依然能够使用Spring的IoC容器。
AOP从程序运行角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中各个步骤,但愿以更好的方式来组合业务处理的各个步骤。
AOP框架并不与特定的代码耦合,AOP框架能处理程序执行中的特定点,而不是某个具体的程序。AOP框架具备以下两个特征:
● 各步骤之间的良好隔离性。
● 源代码无关性。
下面是关于面向切面编程的一些术语:
● 切面,业务流程运行的某个特定步骤,就是运行过程的关注点,关注点可能横切多个对象。
● 链接点,程序执行过程当中明确的点,如方法的调用或异常的抛出。Spring AOP中,链接点老是方法的调用,Spring并无显式地使用链接点。
● 处理(Advice),AOP框架在特定的链接点执行的动做。处理有around、before和throws等类型。大部分框架都以拦截器做为处理模型。
● 切入点,系列链接点的集合,它肯定处理触发的时机。AOP框架容许开发者本身定义切入点,如使用正则表达式。
● 引入,添加方法或字段到被处理的类。Spring容许引入新的接口到任何被处理的对象。例如,能够使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。
● 目标对象,包含链接点的对象。也称为被处理对象或被代理对象。
● AOP代理,AOP框架建立的对象,包含处理。简单地说,代理就是对目标对象的增强。Spring中的AOP代理能够是JDK动态代理,也能够是CGLIB代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。
注意:面向切面编程是比较前沿的知识,而国内大部分翻译人士翻译计算机文献时,老是一边开着各类词典和翻译软件,一边逐词去看文献,不是先从整体上把握知识的架构。所以,不免致使一些术语的翻译词不达意,例如,Socket被翻译成“套接字”等。在面向切面编程的各术语翻译上,也存在较大的差别。对于Advice一词,有翻译为“通知”的,有翻译为“建议”的,如此种种,不一而足。实际上,Advice指AOP框架在特定切面所作的事情,故而笔者翻译为“处理”,但愿能够表达Advice的真正含义。
所谓AOP代理,就是AOP框架动态建立的对象,这个对象一般能够做为目标对象的替代品,而AOP代理提供比目标对象更增强大的功能。真实的情形是,当应用调用AOP代理的方法时,AOP代理会在本身的方法中回调目标对象的方法,从而完成应用的调用。
关于AOP代理的典型例子就是Spring中的事务代理Bean。一般,目标Bean的方法不是事务性的,而AOP代理包含目标Bean的所有方法,并且这些方法通过增强变成了事务性方法。简单地说,目标对象是蓝本,AOP代理是目标对象的增强,在目标对象的基础上,增长属性和方法,提供更强大的功能。
目标对象包含一系列切入点。切入点能够触发处理链接点集合。用户能够本身定义切入点,如使用正则表达式。AOP代理包装目标对象,在切入点处加入处理。在切入点加入的处理,使得目标对象的方法功能更强。
Spring默认使用JDK动态代理实现AOP代理,主要用于代理接口。也能够使用CGLIB代理。实现类的代理,而不是接口。若是业务对象没有实现接口,默认使用CGLIB代理。但面向接口编程是良好的习惯,尽可能不要面向具体类编程。所以,业务对象一般应实现一个或多个接口。
下面是一个简单动态代理模式的示例,首先有一个Dog的接口,接口以下:
public interface Dog
{
//info方法声明
public void info();
//run方法声明
public void run();
}
而后,给出该接口的实现类,实现类必须实现两个方法,源代码以下:
public class DogImpl implements Dog
{
//info方法实现,仅仅打印一个字符串
public void info()
{
System.out.println("我是一只猎狗");
}
//run方法实现,仅仅打印一个字符串
public void run()
{
System.out.println("我奔跑迅速");
}
}
上面的代码没有丝毫独特之处,是典型的面向接口编程的模型,为了有更好的解耦,采用工厂来建立Dog实例。工厂源代码以下:
public class DogFactory
{
//工厂自己是单态模式,所以,将DogFactory做为静态成员变量保存
private static DogFactory df;
//将Dog实例缓存
private Dog gundog;
//默认的构造器,单态模式须要的构造器是private
private DogFactory()
{
}
//单态模式所需的静态方法,该方法是建立本类实例的惟一方法点
public static DogFactory instance()
{
if (df == null)
{
df = new DogFactory();
}
return df;
}
//得到Dog实例
public Dog getDog(String dogName)
{
//根据字符串参数决定返回的实例
if (dogName.equals("gundog"))
{
//返回Dog实例以前,先判断缓存的Dog是否存在,若是不存在才建立,
不然直接返回缓存的Dog实例
if (gundog == null )
{
gundog = new DogImpl();
}
return gundog;
}
return null;
}
}
下面是一个通用的处理类,该处理类没有与任何特定的类耦合,它能够处理全部的目标对象。从JDK 1.3起,Java的import java.lang.reflect下增长InvocationHandler接口,该接口是全部处理类的根接口。
该类处理类的源代码以下:
public class ProxyHandler implements InvocationHandler
{
//需被代理的目标对象
private Object target;
//执行代理的目标方法时,该invoke方法会被自动调用
public Object invoke(Object proxy, Method method, Object[] args)throws
Exception
{
Object result = null;
if (method.getName().equals("info"))
{
System.out.println("======开始事务...");
result =method.invoke(target, args);
System.out.println("======提交事务...");
}
else
{
result =method.invoke(target, args);
}
return result;
}
//经过该方法,设置目标对象
public void setTarget(Object o)
{
this.target = o;
}
}
该处理类实现InvocationHandler接口,实现该接口必须实现invoke(Object proxy, Method method, Object[] args)方法,程序调用代理的目标方法时,自动变成调用invoke方法。
该处理类并未与任何接口或类耦合,它彻底是通用的,它的目标实例是Object类型,能够是任何的类型。
在invoke方法内,对目标对象的info方法进行简单增强,在开始执行目标对象的方法以前,先打印开始事务,执行目标对象的方法以后,打印提交事务。
经过method对象的invoke方法,能够完成目标对象的方法调用,执行代码以下:
result =method.invoke(target, args);
下面是代理工厂:
public class MyProxyFactory
{
/**
* 实例Service对象
* @param serviceName String
* @return Object
*/
public static Object getProxy(Object object)
{
//代理的处理类
ProxyHandler handler = new ProxyHandler();
//把该dog实例托付给代理操做
handler.setTarget(object);
//第一个参数是用来建立动态代理的ClassLoader对象,只要该对象能访问Dog接口
便可
//第二个参数是接口数组,正是代理该接口数组
//第三个参数是代理包含的处理实例
return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),
object.getClass().getInterfaces(),handler);
}
}
代理工厂里有一行代码:
Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);
Proxy.newProxyInstance()方法根据接口数组动态建立代理类实例,接口数组经过object.getClass().getInterfaces()方法得到,建立的代理类是JVM在内存中动态建立的,该类实现传入接口数组的所有接口。
所以,Dynamic Proxy要求被代理的必须是接口的实现类,不然没法为其构造相应的动态类。所以,Spring对接口实现类采用Dynamic Proxy实现AOP,而对没有实现任何接口的类,则经过CGLIB实现AOP代理。
下面是主程序:
public class TestDog
{
public static void main(String[] args)
{
Dog dog = null;
//建立Dog实例,该实例将做为被代理对象
Dog targetObject = DogFactory.instance().getDog("gundog");
//以目标对象建立代理
Object proxy = MyProxyFactory.getProxy(targetObject);
if (proxy instanceof Dog)
{
dog = (Dog)proxy;
}
//测试代理的方法
dog.info();
dog.run();
}
}
代理实例会实现目标对象实现的所有接口。所以,代理实例也实现了Dog接口,程序运行结果以下:
[java] ======开始事务...
[java] 我是一只猎狗
[java] ======提交事务...
[java] 我奔跑迅速
代理实例增强了目标对象的方法——仅仅打印了两行字符串。固然,此种增强没有实际意义。试想一下,若程序中打印字符串的地方,换成真实的事务开始和事务提交,则代理实例的方法为目标对象的方法增长了事务性。
经过前面的介绍,AOP代理就是由AOP框架动态生成的一个对象,该对象可做为目标对象使用,AOP代理包含了目标对象的所有方法。但AOP代理中的方法与目标对象的方法存在差别:AOP方法在特定切面插入处理,在处理之间回调目标对象的方法。
AOP代理所包含的方法与目标对象所包含的方法的示意图,如图6.1所示。
Spring中AOP代理由Spring的IoC容器负责生成和管理,其依赖关系也由IoC容器负责管理。所以,AOP代理可以引用容器中的其余Bean实例,这种引用由IoC容器的依赖注入提供。
Spring的AOP代理大都由ProxyFactoryBean工厂类产生,如图6.1所示,产生一个AOP代理至少有两个部分:目标对象和AOP框架所加入的处理。所以,配置ProxyFactoryBean时须要肯定以下两个属性:
● 代理的目标对象。
● 处理(Advice)。
注意:关于Advice的更多知识,此处因为篇幅缘由,没法深刻讨论。实际上,Spring也提供了不少种Advice的实现,如须要更深刻了解Spring AOP,请读者参考笔者所著的《Spring2.0宝典》。
全部代理工厂类的父类是org.springframework.aop.framework.ProxyConfig。所以,该类的属性是全部代理工厂的共同属性,这些属性也是很是关键的,包括:
● proxyTargetClass,肯定是否代理目标类,若是须要代理目标是类,该属性设为true,此时须要使用CGLIB生成代理;若是代理目标是接口,该属性设为false,默认是false。
● optimize,肯定是否使用强优化来建立代理。该属性仅对CGLIB代理有效;对JDK 动态代理无效。
● frozen,肯定是否禁止改变处理,默认是false。
● exposeProxy,代理是否能够经过ThreadLocal访问,若是exposeProxy属性为true,则可经过AopContext.currentProxy()方法得到代理。
● aopProxyFactory,所使用的AopProxyFactory具体实现。该参数用来指定使用动态代理、CGLIB或其余代理策略。默认选择动态代理或CGLIB。通常不须要指定该属性,除非须要使用新的代理类型,才指定该属性。
配置ProxyFactoryBean工厂bean时,还须要指定它的特定属性,ProxyFactoryBean的特定属性以下所示:
● proxyInterfaces,接口名的字符串数组。若是没有肯定该参数,默认使用CGLIB代理。
● interceptorNames,处理名的字符串数组。此处的次序很重要,排在前面的处理,优先被调用。此处的处理名,只能是当前工厂中处理的名称,而不能使用bean引用。处理名字支持使用通配符(*)。
● singleton,工厂是否返回单态代理。默认是true,不管 getObject()被调用多少次,将返回相同的代理实例。若是须要使用有状态的处理——例如,有状态的mixin,可改变默认设置,prototype处理。
posted @ 2009-07-19 10:15 jadmin 阅读(1) 评论(0) 编辑
Spring 框架提供了很好的扩展性,除了能够与各类第三方框架良好整合外,其IoC容器也容许开发者进行扩展。这种扩展并非经过实现BeanFactory或ApplicationContext的子类,而是经过两个后处理器对IoC容器进行扩展。Spring提供了两种经常使用的后处理器:
● Bean后处理器,这种后处理器会对容器中特定的Bean进行定制,例如功能的 增强。
● 容器后处理器,这种后处理器对IoC容器进行特定的后处理。
下面将介绍这两种经常使用的后处理器以及两种后处理器相关知识。
Bean后处理器是一种特殊的Bean,这种特殊的Bean并不对外提供服务,它无须id属性,但它负责对容器中的其余Bean执行后处理,例如为容器中的目标Bean生成代理。这种Bean可称为Bean后处理器,它在Bean实例建立成功后,对其进行进一步的增强 处理。
Bean后处理器必须实现BeanPostProcessor接口。
BeanPostProcessor接口包含两个方法:
● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,该方法的第一个参数是系统即将初始化的Bean实例,第二个参数是Bean实例的名字。
● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,该方法的第一个参数是系统刚完成初始化的Bean实例,第二个参数是Bean实例的名字。
实现该接口的Bean必须实现这两个方法,这两个方法会对容器的Bean进行后处理。两个方法会在目标Bean初始化以前和初始化以后分别调用。这两个方法用于对系统完成的默认初始化进行增强。
注意:Bean后处理器是对IoC容器一种极好的扩展,Bean后处理器能够对容器中的Bean进行后处理,这种后处理彻底由开发者决定。
下面将定义一个简单的Bean后处理器,该Bean后处理器将对容器中其余Bean进行后处理。Bean后处理器的代码以下:
//自定义Bean后处理器,负责后处理容器中全部的Bean
public class MyBeanPostProcessor implements BeanPostProcessor
{
//在初始化bean以前,调用该方法
public Object postProcessBeforeInitialization(Object bean , String
beanName)throws BeansException
{
//仅仅打印一行字符串
System.out.println("系统正在准备对" + beanName + "进行初始化...");
return bean;
}
//在初始化bean以后,调用该方法
public Object postProcessAfterInitialization(Object bean , String
beanName)throws BeansException
{
System.out.println("系统已经完成对" + beanName + "的初始化");
//若是系统刚完成初始化的bean是Chinese
if (bean instanceof Chinese)
{
//为Chinese实例设置name属性
Chinese c = (Chinese)bean;
c.setName("wawa");
}
return bean;
}
}
下面是Chinese的源代码,该类实现了InitializingBean接口,还额外提供了一个初始化方法,这两个方法都由Spring容器控制回调。
public class Chinese implements Person,InitializingBean
{
private Axe axe;
private String name;
public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
public void setAxe(Axe axe)
{
System.out.println("Spring执行依赖关系注入...");
this.axe = axe;
}
public void setName(String name)
{
this.name = name;
}
public void useAxe()
{
System.out.println(name + axe.chop());
}
public void init()
{
System.out.println("正在执行初始化方法 init...");
}
public void afterPropertiesSet() throws Exception
{
System.out.println("正在执行初始化方法 afterPropertiesSet...");
}
}
配置文件以下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring 配置文件的dtd>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- Spring配置文件的根元素 -->
<beans>
<!-- 配置bean后处理器,能够没有id属性,此处id属性为了后面引用 -->
<bean id="beanPostProcessor" class="lee.MyBeanPostProcessor"/>
<bean id="steelAxe" class="lee.SteelAxe"/>
<bean id="chinese" class="lee.Chinese" init-method="init">
<property name="axe" ref="steelAxe"/>
</bean>
</beans>
本应用的chinese具备两个初始化方法:
● init-method指定初始化方法。
● 实现InitializingBean接口,提供了afterPropertiesSet初始化方法。
MyBeanPostProcessor类实现了BeanPostProcessor接口,并实现了该接口的两个方法,这两个方法分别在初始化方法调用以前和以后获得回调。
注意:上面的配置文件配置Bean后处理器时,依然为Bean处理器指定了id属性,指定id属性是为了方便程序经过该id属性访问Bean后处理器。大部分时候,程序无须手动访问该Bean后处理器,所以无须为其指定id属性。
主程序以下:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
//CLASSPATH路径下的bean.xml文件建立Resource对象
ClassPathResource isr = new ClassPathResource("bean.xml");
//以Resource对象做为参数,建立BeanFactory的实例
XmlBeanFactory factory = new XmlBeanFactory(isr);
//获取Bean后处理器实例
MyBeanPostProcessor beanProcessor =
(MyBeanPostProcessor)factory.getBean("beanPostProcessor");
//注册BeanPostProcessor实例
factory.addBeanPostProcessor(beanProcessor);
System.out.println("程序已经实例化BeanFactory...");
Person p = (Person)factory.getBean("chinese");
System.out.println("程序中已经完成了chinese bean的实例化...");
p.useAxe();
}
}
若是使用BeanFactory做为Spring容器,必须手动注册Bean后处理器,所以在程序中先获取Bean后处理器实例,而后手动注册——这就是在配置文件中指定Bean后处理器id属性的缘由。经过BeanFactory的addBeanPostProcessor能够注册BeanPostProcessor实例。程序执行结果以下:
[java] 程序已经实例化BeanFactory...
[java] Spring实例化主调bean:Chinese实例...
[java] Spring实例化依赖bean:SteelAxe实例...
[java] 系统正在准备对steelAxe进行初始化...
[java] 系统已经完成对steelAxe的初始化
[java] Spring执行依赖关系注入...
[java] 系统正在准备对chinese进行初始化...
[java] 正在执行初始化方法 afterPropertiesSet...
[java] 正在执行初始化方法 init...
[java] 系统已经完成对chinese的初始化
[java] 程序中已经完成了chinese bean的实例化...
[java] wawa钢斧砍柴真快
在配置文件中配置chinese实例时,并未指定name属性值。但程序执行时,name属性有了值,这就是Bean后处理器完成的,在Bean后处理器中判断Bean是不是Chinese实例,而后设置它的name属性。
容器中一旦注册了Bean后处理器,Bean后处理器会自动启动,在容器中每一个Bean建立时自动工做,完成加入Bean后处理器须要完成的工做。
实现BeanPostProcessor接口的Bean后处理器可对Bean进行任何操做,包括彻底忽略这个回调。BeanPostProcessor一般用来检查标记接口或将Bean包装成一个Proxy的事情。Spring的不少工具类,就是经过Bean后处理器完成的。
从主程序中看到,采用BeanFactory做为Spring容器时,必须手动注册BeanPost- Processor。而对于ApplicationContext,则无须手动注册。ApplicationContext可自动检测到容器中的Bean后处理器,自动注册。Bean后处理器会在Bean实例建立时,自动启动。即主程序采用以下代码,效果彻底同样:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
ApplicationContext ctx = new ClassPathXmlApplicationContext
("bean.xml");
Person p = (Person)factory.getBean("chinese");
System.out.println("程序中已经完成了chinese bean的实例化...");
p.useAxe();
}
}
使用ApplicationContext做为容器,无须手动注册BeanPostProcessor。所以,若是须要使用Bean后处理器,Spring容器建议使用ApplicationContext,而不是BeanFactory。
上一节介绍了一个简单的Bean后处理器,上面的Bean后处理器负责对容器中的Chinese Bean进行后处理,无论Chinese Bean如何初始化,老是将Chinese Bean的name属性设置为wawa。这种后处理看起来做用并非特别大。
实际上,Bean后处理器完成的工做更加实际,例如生成Proxy。Spring框架自己提供了大量的Bean后处理器,这些后处理器负责对容器中的Bean进行后处理。
下面是Spring提供的两个经常使用的后处理器:
● BeanNameAutoProxyCreator,根据Bean实例的name属性,建立Bean实例的代理。
● DefaultAdvisorAutoProxyCreator,根据提供的Advisor,对容器中全部的Bean实例建立代理。
上面提供的两个Bean后处理器,都用于根据容器中配置的拦截器建立目标Bean代理,目标代理就在目标Bean的基础上修改获得。
注意:若是须要对容器中某一批Bean进行特定的处理,能够考虑使用Bean后处理器。
除了上面提供的Bean后处理器外,Spring还提供了一种容器后处理器。Bean后处理器负责后处理容器生成的全部Bean,而容器后处理器则负责后处理容器自己。
容器后处理器必须实现BeanFactoryPostProcessor接口。实现该接口必须实现以下一个方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
实现该方法的方法体就是对Spring容器进行的处理,这种处理能够对Spring容器进行任意的扩展,固然也能够对Spring容器不进行任何处理。
相似于BeanPostProcessor,ApplicationContext可自动检测到容器中的容器后处理器,而且自动注册容器后处理器。但若使用BeanFactory做为Spring容器,则必须手动注册后处理器。
下面定义了一个容器后处理器,这个容器后处理器实现BeanFactoryPostProcessor接口,但并未对Spring容器进行任何处理,只是打印出一行简单的信息。该容器后处理器的代码以下:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
//容器后处理器对容器进行的处理在该方法中实现
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory)
throws BeansException
{
System.out.println("程序对Spring所作的BeanFactory的初始化没有意
见...");
}
}
将该Bean做为普通Bean部署在容器中,而后使用ApplicationContext做为容器,容器会自动调用BeanFactoryPostProcessor处理Spring容器。程序执行效果以下:
[java] 程序对Spring所作的BeanFactory的初始化没有意见...
实现BeanFactoryPostProcessor接口的Bean后处理器不只可对BeanFactory执行后处理,也能够对ApplicationContext容器执行后处理。容器后处理器还可用来注册额外的属性编辑器。
注意:Spring没有提供ApplicationContextPostProcessor。也就是说,对于Application- Context容器,同样使用BeanFactoryPostProcessor做为容器后处理器。
Spring已提供以下两个经常使用的容器后处理器,包括:
● PropertyResourceConfigurer,属性占位符配置器。
● PropertyPlaceHolderConfigurer,另外一种属性占位符配置器。
下面将详细介绍这两种经常使用的容器后处理器。
Spring提供了PropertyPlaceholderConfigurer,它是一个容器后处理器,负责读取Java属性文件里的属性值,并将这些属性值设置到Spring容器定义中。
经过使用PropertyPlaceholderConfigurer后处理器,能够将Spring配置文件中的部分设置放在属性文件中设置。这种配置方式固然有其优点:能够将部分类似的配置(如数据库的urls、用户名和密码)放在特定的属性文件中,若是只须要修改这部分配置,则无须修改Spring配置文件,修改属性文件便可。
下面的配置文件配置了PropertyPlaceholderConfigurer后处理器,在配置数据源Bean时,使用了属性文件中的属性值。配置文件的代码以下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置一个容器后处理器Bean -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.
PropertyPlaceholderConfigurer">
<!-- locations属性指定属性文件的位置 -->
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 若是有多个属性文件,依次在下面列出来 -->
</list>
</property>
</bean>
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="${jdbc.driverClassName}"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="${jdbc.url}"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="${jdbc.username}"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
在上面的配置文件中,配置driverClass和jdbcUrl等信息时,并未直接设置这些属性的属性值,而是设置了${jdbc.driverClassName}和${jdbc.url}属性值。这代表Spring容器将从propertyConfigurer指定属性文件中搜索这些key对应的value,并为该Bean的属性值设置这些value值。
如前所述,ApplicationContext会自动检测部署在容器的容器后处理器,无须额外的注册,容器自动注册。所以,只需提供以下Java Properties文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/j2ee
jdbc.username=root
jdbc.password=32147
经过这种方法,可从主XML配置文件中分离出部分配置信息。若是仅须要修改数据库链接属性,则无须修改主XML配置文件,只须要修改属性文件便可。采用属性占位符的配置方式,能够支持使用多个属性文件。经过这种方式,可将配置文件分割成多个属性文件,从而下降修改配置的风险。
注意:对于数据库链接等信息集中的配置,能够将其配置在Java属性文件中,但不要过多地将Spring配置信息抽离到Java属性文件中,不然可能会下降Spring配置文件的可读性。
PropertyOverrideConfigurer是Spring提供的另外一个容器后处理器,这个后处理器的额做用与上面介绍的容器后处理器做用大体相同。但也存在些许差异:PropertyOverride- Configurer使用的属性文件用于覆盖XML配置文件中的定义。即PropertyOverride- Configurer容许XML配置文件中有默认的配置信息。
若是PropertyOverrideConfigurer的属性文件有对应配置信息,XML文件中的配置信息被覆盖;不然,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的属性文件,应是以下的格式:
beanName.property=value
beanName是属性占位符试图覆盖的Bean名,property是试图覆盖的属性名。看以下配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,而且指定了Schema信息 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置一个属性占位符Bean。ApplictionContext能自动识别
PropertyPlaceholderConfigurer Bean -->
<bean id="propertyOverrider"
class="org.springframework.beans.factory.config.
PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 若是有多个属性文件,依次在下面列出来 -->
</list>
</property>
</bean>
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定链接数据库的驱动 -->
<property name="driverClass" value="dd"/>
<!-- 指定链接数据库的URL -->
<property name="jdbcUrl" value="xx"/>
<!-- 指定链接数据库的用户名 -->
<property name="user" value="dd"/>
<!-- 指定链接数据库的密码 -->
<property name="password" value="xx"/>
</bean>
</beans>
上面的配置文件中,指定数据源Bean的各类属性值时,只是随意指定了几个属性值,很明显经过这几个属性值没法链接到数据库服务。
但由于Spring容器中部署了一个PropertyOverrideConfigurer的容器后处理器,并且Spring容器使用ApplicationContext做为容器,它会自动检测容器中的容器后处理器,无须额外的注册,容器自动注册该后处理器。
PropertyOverrideConfigurer后处理器读取dbconn.properties文件中的属性,用于覆盖目标Bean的属性。所以,若是属性文件中有dataSource Bean属性的设置,则配置文件中指定的属性值将没有任何做用。
dbconn.properties属性文件以下:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://wonder:3306/j2ee
dataSource.username=root
dataSource.password=32147
注意属性文件的格式必须是:
beanName.property=value
也就是说,dataSource必须是容器中真实存在的bean名,不然程序将出错。
注意:程序没法知道BeanFactory定义是否被覆盖。仅仅经过察看XML配置文件,没法知道配置文件的配置信息是否被覆盖。若有多个PorpertyOverrideConfigurer对同一Bean属性定义了覆盖,最后一个覆盖获胜。
posted @ 2009-07-19 10:12 jadmin 阅读(2) 评论(0) 编辑
前面介绍了Hibernate的一些相关知识点,距离Hibernate进入实际开发还有一段路要走。Hibernate做为持久层解决方案,必须与其余表现层技术组合在一块儿才可造成一个J2EE开发框架。常常看到网上一些朋友给出的Hibernate入门示例,竟然在JSP页面中访问Hibernate Configuratioin对象。甚至看到某些所谓的精通J2EE书籍,也竟然在JSP页面中访问Hibernate的Configuration对象——这种现状很是让人担心,Hibernate并非万金油,并非说项目中使用Hibernate就怎么了不得了,而是经过使用Hibernate,可让J2EE应用架构更科学,可让开发者以更好的面向对象的方式进行项目开发。
反过来讲,即便不使用Hibernate,而使用普通的JDBC持久化解决方案,也不该该在JSP(表现层)访问到JDBC API(持久层API)。下面介绍如何让Hibernate和Struts进行整合,整合Spring部分将在后面章节介绍。
工厂模式是指当应用程序中A组件须要B组件协助时,并非直接建立B组件的实例,而是经过B组件的工厂——该工厂能够生成某一个类型组件的实例。在这种模式下,A组件无须与B组件以硬编码方式耦合在一块儿,而只须要与B组件的工厂耦合。
对于A组件而言,它只关心工厂生产的实例是否知足某种规范,即实现了某个接口(知足接口规范,便可供本身正常调用)。这种模式提供了对象之间清晰的角色划分,下降了程序的耦合。
接口产生的所有实例一般实现相同接口,接口里定义所有实例共同拥有的方法,这些方法在不一样的实现类中实现方式不一样。程序调用者无须关心方法的具体实现,从而下降了系统异构的代价。
下面是工厂模式的示例代码:
//Person接口定义
public interface Person
{
/**
* @param name 对name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name);
/**
* @param name 对name告别
* @return 告别的字符串
*/
public String sayGoodBye(String name);
}
该接口定义Person的规范,该接口必须拥有两个方法:能打招呼、能告别。规范要求实现该接口的类必须具备这两个方法:
//American类实现Person接口
public class American implements Person
{
/**
* @param name 对name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name)
{
return name + ",Hello";
}
/**
* @param name 对name告别
* @return 告别的字符串
*/
public String sayGoodBye(String name)
{
return name + ",Good Bye";
}
}
下面是实现Person接口的另外一个实现类Chinese
public class Chinese implements Person
{
/**
* @param name 对name打招呼
* @return 打招呼的字符串
*/
public String sayHello(String name)
{
return name + ",您好";
}
/**
* @param name 对name告别
* @return 告别的字符串
*/
public String sayGoodBye(String name)
{
return name + ",下次再见";
}
}
而后看Person工厂的代码:
public class PersonFactory
{
/**
* 得到Person实例的工厂方法
* @ param ethnic 调用该实例工厂方法传入的参数
* @ return返回Person实例
*/
public Person getPerson(String ethnic)
{
//根据参数返回Person接口的实例
if (ethnic.equalsIgnoreCase("chin"))
{
return new Chinese();
}
else
{
return new American();
}
}
}
最简单的工厂模式的框架基本如上所示。
主程序部分仅仅须要与工厂耦合,而无须与具体的实现类耦合在一块儿。下面是主程序部分:
public class FactroyTest
{
public static void main(String[] args)
{
//建立PersonFactory的实例,得到工厂实例
PersonFactory pf = new PersonFactory();
//定义接口Person的实例,面向接口编程
Person p = null;
//使用工厂得到Person的实例
p = pf.getPerson("chin");
//下面调用Person接口的方法
System.out.println(p.sayHello("wawa"));
System.out.println(p.sayGoodBye("wawa"));
//使用工厂得到Person的另外一个实例
p = pf.getPerson("ame");
//再次调用Person接口的方法
System.out.println(p.sayHello("wawa"));
System.out.println(p.sayGoodBye("wawa"));
}
}
主程序从Person接口的具体类中解耦出来,并且程序调用者无须关心Person的实例化过程,角色划分清晰。主程序仅仅与工厂服务定位结合在一块儿,得到工厂的引用,程序将可得到全部工厂产生的实例。具体类的变化,重要接口不发生任何改变,调用者程序代码部分几乎无须发生任何改动。
第1章介绍了J2EE应用的架构,最上面的表现层,表现层与MVC框架的控制器交互,控制器负责调用业务逻辑组件的业务逻辑方法来处理用户请求,而业务逻辑组件则依赖于DAO组件提供的数据库原子操做,这种模式也被称为DAO模式。
由上面关于J2EE应用架构的介绍可见,控制器老是依赖于业务逻辑组件,而业务逻辑组件老是依赖于DAO组件。也就是说,控制器须要调用业务逻辑组件的方法,而业务逻辑组件须要调用DAO组件的方法。
DAO模式的分层很是清晰,持久层访问被封装在DAO层下,而决不会扩散到业务逻辑层,更不会在JSP页面(表现层)中进行持久层访问。
注意:即便在早期的Model 1(使用JSP + JavaBean建立应用的模式,没有使用MVC设计模式)模式下,持久层访问也被封装在JavaBean中完成,而不是直接在JSP页面中进行数据库访问。对于直接在JSP中访问持久层API的作法,能够说根本不了解J2EE开发。
那么控制器采用怎样的方式访问业务逻辑组件呢?应该采用工厂模式,让控制器与业务逻辑组件的实现类分离,仅与业务逻辑工厂耦合;一样,业务逻辑组件也应该采用工厂模式访问DAO模式,而不是直接与DAO实现类耦合。
后面的案例部分会介绍更实际的整合策略,此处仅仅介绍DAO模式下两个工厂模式策略。
在J2EE应用开发中,可扩展性是一个随时须要关注的问题。而DAO组件是常常须要增长的项目组件,若是每次须要增长一个DAO组件都须要修改代码是至关让人沮丧的事情。为了不这种状况,采用XML配置文件来管理全部的DAO组件,这种DAO组件配置文件的代码以下:
<?xml version="1.0" encoding="GBK"?>
<daoContext>
<!-- 配置应用须要的sonDao组件 -->
<dao id="sonDao" class="org.yeeku.dao.impl.SonDaoImpl"/>
<!-- 配置应用须要的personDao组件 -->
<dao id="personDao" class="org.yeeku.dao.impl.PersonDaoImpl"/>
</daoContext>
查看上面的配置文件能够看出,应用中有配置了两个DAO组件,由于每一个DAO组件在J2EE应用中仅须要一个实例就足够了,所以DAO工厂类提供了一个缓存池来缓存每一个DAO实例,并负责在应用启动时建立全部的DAO。
下面是DAO工厂类的代码:
public class DaoFactory
{
//用于缓存DAO实例的Map对象
private Map<String, Dao> daoMap = new HashMap<String , Dao>();
//将DAO工厂写成单态模式
private static DaoFactory df;
//DAO工厂的构造器
private DaoFactory()throws Exception
{
//使用SAXReader来负责解析daoContext.xml配置文档
Document doc = new SAXReader().read(new File(ConstantsUtil.realPath
+ "\\daoContext.xml"));
//获取文档的根文档
Element root = doc.getRootElement();
//获取daoContext根元素的全部子元素
List el = root.elements();
for (Iterator it = el.iterator();it.hasNext() ; )
{
//每一个子元素对应一个DAO组件
Element em = (Element)it.next();
String id = em.attributeValue("id");
//获取实现类
String impl = em.attributeValue("class");
//经过反射,根据类名建立DAO组件的实例
Class implClazz = Class.forName(impl);
Dao d = (Dao)implClazz.newInstance();
//将建立的DAO组件放入缓存池中
daoMap.put(id, d);
}
}
//单态模式必须提供一个入口方法来建立DAO工厂的方法
public static DaoFactory instance()throws Exception
{
//若是DAO工厂还未建立
if (df == null)
{
df = new DaoFactory();
}
return df;
}
//下面的方法用于根据DAO组件ID获取DAO组件
public Dao getDao(String id)
{
return daoMap.get(id);
}
}
经过上面的工厂类代码能够看出,DAO工厂负责初始化全部的DAO组件。系统每增长一个DAO组件,无须再修改任何代码,仅仅须要在daoContext.xml配置文件中增长配置便可。
注意:这种整合策略很是优秀。可扩展性很好,若是应用须要增长一个DAO组件,只须要修改配置文件,并提供相应的DAO组件实现便可。并且,若是有一天须要重构DAO组件,只须提供修改过的DAO组件实现类,而业务逻辑组件无须任何改变。
业务逻辑组件代码无须与DAO实现类耦合,业务逻辑组件的代码面向DAO组件的接口编程,将业务逻辑组件和DAO组件的耦合下降到接口层次。
与此相似的是,业务逻辑组件彻底能够采用这种编程模式,业务逻辑组件的配置文件代码以下:
<?xml version="1.0" encoding="GBK"?>
<appContext>
<!-- 配置应用须要的业务逻辑组件,每一个业务逻辑组件对应一个app元素 -->
<app id="wawa" class="org.yeeku.service.impl.WawaServiceImpl"/>
</appContext>
业务逻辑组件工厂一样可根据该配置文件来初始化全部业务逻辑组件,并将业务逻辑组件放入缓存池中,让控制器与业务逻辑组件的耦合下降到接口层次。业务逻辑组件的工厂类代码以下:
public class AppFactory
{
private Map<String , Object> appMap = new HashMap<String , Object>();
//业务逻辑组件工厂采用单态模式
private static AppFactory df;
//业务逻辑组件工厂的私有构造器
private AppFactory()throws Exception
{
//使用SAXReader来负责解析appContext.xml配置文档
Document doc = new SAXReader().read(new File(ConstantsUtil.realPath
+ "\\appContext.xml"));
//获取文档的根文档
Element root = doc.getRootElement();
//获取appContext根元素的全部子元素
List el = root.elements();
for (Iterator it = el.iterator();it.hasNext() ; )
{
//每一个app元素对应一个业务逻辑组件
Element em = (Element)it.next();
String id = em.attributeValue("id");
//根据配置文件指定的业务逻辑组件实现类来建立业务逻辑组件实例
String impl = em.attributeValue("class");
Class implClazz = Class.forName(impl);
Object d = implClazz.newInstance();
//将业务逻辑组件放入缓存池中
appMap.put(id , d);
}
}
//单态模式必须提供入口方法,用于建立业务逻辑组件工厂
public static AppFactory instance()throws Exception
{
//若是业务逻辑组件工厂为空
if (df == null)
{
df = new AppFactory();
}
return df;
}
//根据业务逻辑组件的id属性获取业务逻辑组件
public Object getApp(String id)
{
//直接从缓存池中取出业务逻辑组件,并返回
return appMap.get(id);
}
}
从某种程度上来说,这种方式与后来Spring的控制反转(Inversion of Control,IoC)容器有殊途同归之妙,但Spring的IoC容器则提供了更多的功能。
上面的两个类中都用到了一个ConstantsUtil,它仅用于保存一个全局变量,有一个public static的realPath属性,该属性用于保存应用在服务器中的路径。
posted @ 2009-07-19 10:08 jadmin 阅读(1) 评论(0) 编辑
一般,Hibernate执行持久化过程当中,应用程序没法参与其中。全部的数据持久化操做,对用户都是透明的,用户没法插入本身的动做。
经过事件框架,Hibernate容许应用程序能响应特定的内部事件,从而容许实现某些通用的功能,或对Hibernate功能进行扩展。
Hibernate的事件框架由两个部分组成:
● 拦截器机制,对于特定动做拦截,回调应用中的特定动做。
● 事件系统,重写Hibernate的事件监听器。
经过Interceptor接口,能够从Session中回调应用程序的特定方法,这种回调机制可以让应用程序在持久化对象被保存、更新、删除或加载以前,检查并修改其属性。
经过Interceptor接口,能够在数据进入数据库之间,对数据进行最后的检查,若是数据不符合要求,能够修改数据,从而避免非法数据进入数据库。固然,一般无须这样作,只是在某些特殊的场合下,才考虑使用拦截器完成检查功能。
使用拦截器可按以下步骤进行:
(1)定义实现Interceptor接口的拦截器类;
(2)经过Session启用拦截器,或者经过Configuration启用全局拦截器。
下面是一个拦截器的示例代码,该拦截器没有进行任何实际的操做,仅仅打印出标志代码:
public class MyInterceptor extends EmptyInterceptor
{
//更新的次数
private int updates;
//插入的次数
private int creates;
//删除数据时,将调用onDelete方法
public void onDelete(Object entity,Serializable id,Object[]
state,String[] propertyNames, Type[] types)
{
//do nothing
}
//同步Session和数据库中的数据
public boolean onFlushDirty(Object entity, Serializable id, Object[]
currentState, Object[] previousState, String[] propertyNames, Type[]
types)
{
//每同步一次,修改的累加器加1
updates++;
for ( int i=0; i < propertyNames.length; i++ )
{
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) )
{
currentState[i] = new Date();
return true;
}
}
return false;
}
//加载持久化实例时,调用该方法
public boolean onLoad(Object entity,Serializable id,Object[]
state,String[] propertyNames,Type[] types)
{
System.out.println("========================");
for ( int i=0; i < propertyNames.length; i++ )
{
if ( "name".equals( propertyNames[i] ) )
{
System.out.println(state[i]);
state[i] = "aaa";
return true;
}
}
return false;
}
//保存持久化实例时,调用该方法
public boolean onSave(Object entity,Serializable id,Object[]
state,String[] propertyNames,Type[] types)
{
creates++;
for ( int i=0; i<propertyNames.length; i++ )
{
if ( "createTimestamp".equals( propertyNames[i] ) )
{
state[i] = new Date();
return true;
}
}
return false;
}
//提交刷新
public void postFlush(Iterator entities)
{
System.out.println("建立的次数: " + creates + ", 更新的次数: " +
updates);
}
public void preFlush(Iterator entities)
{
updates=0;
creates=0;
}
//事务提交前,触发该方法
public void beforeTransactionCompletion(Transaction tx)
{
System.out.println("事务即将结束");
}
//事务提交后,触发该方法
public void afterTransactionCompletion(Transaction tx)
{
System.out.println("事务已经结束");
}
}
在上面的拦截器实现类中,实现了不少方法,这些方法都是在Hibernate执行特定动做时自动调用。
完成了拦截器的定义,下面是关于拦截器的使用。拦截器的使用有两种方法:
● 经过SessionFactory的openSession(Interceptor in)方法打开一个带局部拦截器的Session。
● 经过Configuration的setInterceptor(Interceptor in)方法设置全局拦截器。
下面是使用局部拦截器的示例代码:
public class HibernateUtil
{
//静态类属性 SessionFactory
public static final SessionFactory sessionFactory;
//静态初始化块,完成静态属性的初始化
static
{
try
{
//采用默认的hibernate.cfg.xml来启动一个Configuration的实例
Configuration configuration=new Configuration().configure();
//由Configuration的实例来建立一个SessionFactory实例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("初始化sessionFactory失败." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,所以再也不须要
对线程同步
public static final ThreadLocal session = new ThreadLocal();
//不加拦截器的打开Session方法
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//若是该线程尚未Session,则建立一个新的Session
if (s == null)
{
s = sessionFactory.openSession();
//将得到的Session变量存储在ThreadLocal变量的Session里
session.set(s);
}
return s;
}
//加拦截器的打开Session方法
public static Session currentSession(Interceptor it) throws
HibernateException
{
Session s = (Session) session.get();
//若是该线程尚未Session,则建立一个新的Session
if (s == null)
{
//以拦截器建立Session对象
s = sessionFactory.openSession(it);
//将得到的Session变量存储在ThreadLocal变量的Session里
session.set(s);
}
return s;
}
//关闭Session对象
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
上面的Hibernate工具类提供了两个currentSession方法,分别用于不使用拦截器获取Session对象和使用拦截器获取Session对象。
下面是主程序使用拦截器的代码片断:
private void testUser()
{
//以拦截器开始Session
Session session = HibernateUtil.currentSession(new MyInterceptor());
//开始事务
Transaction tx = session.beginTransaction();
//执行下面的代码时,能够看到系统回调onSave等方法
/*
User u = new User();
u.setName("Yeeku Lee");
u.setAge(28);
u.setNationality("中国");
session.persist(u);
u.setAge(29);
u.setAge(30);
session.persist(u);
*/
//执行下面的代码时,能够看到系统回调onLoad等方法
Object o = session.load(User.class , new Integer(1));
System.out.println(o);
User u = (User)o;
System.out.println(u.getName());
//提交事务时,能够看到系统回调事务相关方法
tx.commit();
HibernateUtil.closeSession();
}
Hibernate 3的事件系统是功能更强大的事件框架,事件系统能够替代拦截器,也能够做为拦截器的补充来使用。
基本上,Session接口的每一个方法都有对应的事件。如LoadEvent和FlushEvent等。当Session调用某个方法时,Hibernate Session会生成对应的事件,并激活对应的事件监听器。
系统默认监听器实现的处理过程,完成了全部的数据持久化操做,包括插入和修改等操做。若是用户定义了本身的监听器,则意味着用户必须完成对象的持久化操做。
例如,能够在系统中实现并注册LoadEventListener监听器,该监听器负责处理全部调用Session的load()方法的请求。
监听器是单态模式对象,即全部同类型的事件处理共享同一个监听器实例,所以监听器不该该保存任何状态,即不该该使用成员变量。
使用事件系统可按以下步骤进行:
(1)实现本身的事件监听器类;
(2)注册自定义事件监听器,代替系统默认的事件监听器。
实现用户的自定义监听器有以下3个方法:
● 实现对应的监听器接口,这是难以想象的,实现接口必须实现接口内的全部方法,关键是必须实现Hibernate对应的持久化操做,即数据库访问,这意味着程序员彻底取代了Hibernate的底层操做。
● 继承事件适配器,能够选择性地实现须要关注的方法,但依然试图取代Hibernate完成数据库的访问,这也不太现实。
● 继承系统默认的事件监听器,扩展特定方法。
实际上,前两种方法不多使用。由于Hibernate的持久化操做也是经过这些监听器实现的,若是用户取代了这些监听器,则应该本身实现全部的持久化操做,这意味着用户放弃了Hibernate的持久化操做,而改成本身完成Hibernate的核心操做。
一般推荐采用第三种方法实现本身的事件监听器。Hibernate默认的事件监听器都被声明成non-final,从而方便用户继承。
下面是用户自定义监听器的示例:
//自定义LoadListener,继承默认的DefaultLoadEventListener实现类
public class MyLoadListener extends DefaultLoadEventListener
{
//在LoadEventListener接口仅仅定义了这个方法
public Object onLoad(LoadEvent event, LoadEventListener.LoadType
loadType)throws HibernateException
{
//先调用父类的onLoad方法,从而完成默认的持久化操做
Object o = super.onLoad(event, loadType);
//加入用户的自定义处理
System.out.println("自定义的load事件");
System.out.println(event.getEntityClassName() + "==========" +
event.getEntityId());
return o;
}
}
下面还有一个MySaveListener,用于监听SaveEvent事件:
//自定义SavaListener,继承默认的DefaultSaveEventListener实现类
public class MySaveListener extends DefaultSaveEventListener
{
//该方法完成实际的数据插入动做
protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event)
{
//先执行用户自定义的操做
System.out.println(event.getObject());
//调用父类的默认持久化操做
return super.performSaveOrUpdate(event);
}
}
注意:扩展用户自定义监听器时,别忘了在方法中调用父类的对应方法。
注册用户自定义监听器也有两种方法:
● 编程式,经过使用Configuration对象编程注册。
● 声明式,在Hibernate的XML格式配置文件中进行声明,使用Properties格式的配置文件将没法配置自定义监听器。
下面的示例代码,经过编程方式使用自定义监听器:
public class HibernateUtil2
{
//静态类属性 SessionFactory
public static final SessionFactory sessionFactory;
//静态初始化块,完成静态属性的初始化
static
{
try
{
Configuration cfg = new Configuration();
//注册loadEventListener监听器
cfg.getSessionEventListenerConfig().setLoadEventListener
( new MyLoadListener() );
//注册saveListener监听器
cfg.getSessionEventListenerConfig().setSaveEventListener
(new MySaveListener() );
//由Configuration实例来建立一个SessionFactory实例
sessionFactory = cfg.configure().buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("初始化sessionFactory失败." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,所以再也不须要
对线程同步
public static final ThreadLocal session = new ThreadLocal();
//不加拦截器的打开Session方法
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//若是该线程尚未Session,则建立一个新的Session
if (s == null)
{
s = sessionFactory.openSession();
//将得到的Session变量存储在ThreadLocal变量的Session里
session.set(s);
}
return s;
}
//关闭Session对象
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
若是不想修改代码,也能够在配置文件中使用事件监听器,注册事件监听器的Hibernate配置文件代码以下:
<?xml version='1.0' encoding='GBK'?>
<!-- Hibernate配置文件的文件头,包含DTD等信息 -->
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.
dtd">
<!-- Hibernate配置文件的根元素 -->
<hibernate-configuration>
<session-factory>
<!—设置数据库驱动 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver
</property>
<!-- 数据库服务的url -->
<property name="connection.url">jdbc:mysql://localhost/hibernate
</property>
<!-- 数据库服务的用户名 -->
<property name="connection.username">root</property>
<!-- 数据库服务的密码 -->
<property name="connection.password">32147</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">5</property>
<!-- 设置数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect
</property>
<!-- 显示Hibernate生成的SQL语句 -->
<property name="show_sql">true</property>
<!-- 配置应用启动时,是否启动自动建表 -->
<property name="hbm2ddl.auto">update</property>
<!-- 列出全部的持久化映射文件 -->
<mapping resource="User.hbm.xml"/>
<!-- 注册事件监听器 -->
<listener type="load" class="lee.MyLoadListener"/>
<listener type="save" class="lee.MySaveListener"/>
</session-factory>
</hibernate-configuration>
使用配置文件注册事件监听器虽然方便,但也有不利之处,经过配置文件注册的监听器不能共享实例。若是多个<listener/>元素中使用了相同的类,则每个引用都将产生一个新的拦截器实例。若是须要在多个事件之间共享监听器的实例,则必须使用编程方式注册事件监听器。
注意:虽然监听器类实现了特定监听器的接口,在注册的时候还要明确指出注册的事件。这是由于一个类可能实现多个监听器的接口,注册时明确指定要监听的事件,能够使得启用或者禁用某个事件监听的配置工做更简单。
posted @ 2009-07-19 09:42 jadmin 阅读(2) 评论(0) 编辑
每一个业务逻辑方法都是由一系列的数据库访问完成,这一系列的数据访问可能会修改多条数据记录,这系列的修改应该是一个总体,毫不能仅修改其中的几条。也就是说,多个数据库原子访问应该绑定成一个总体——这就是事务。事务是一个最小的逻辑执行单元,整个事务不能分开执行,要么同时执行,要么同时放弃执行。
事务是一步或几步基本操做组成的逻辑执行单元,这些基本操做做为一个总体执行单元,它们要么所有执行,要么所有取消,毫不能仅仅执行部分。通常而言,每次用户请求,对应一个业务逻辑方法,一个业务逻辑方法每每具备逻辑上的原子性,应该使用事务。例如,一个转帐操做,对应修改两个帐户的余额,这两个帐户的修改要么同时生效,要么同时取消——同时生效是转帐成功,同时取消是转帐失败;但不可只修改其中一个帐户,那将破坏数据库的完整性。
一般来说,事务具有以下4个特性:原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持续性(durability)。这4个特性也简称为ACID性。
● 原子性:事务是应用中最小执行单位,就如原子是天然界最小颗粒,具备不可再分的特征同样。事务是应用中不可再分的最小逻辑执行体。
● 一致性:事务执行的结果,必须使数据库从一个一致性状态,变到另外一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。若是系统运行发生中断,某个事务还没有完成而被迫中断,而该未完成的事务对数据库所作的修改已被写入数据库,此时,数据库就处于一种不正确的状态。好比银行在两个帐户之间转帐,从A帐户向B帐户转入1000元。系统先减小A帐户的1000元,而后再为B帐户增长1000元。若是所有执行成功,数据库处于一致性状态。若是仅执行完A帐户金额的修改,而没有增长B帐户的金额,则数据库就处于不一致性状态。所以,一致性是经过原子性来保证的。
● 隔离性:各个事务的执行互不干扰,任意一个事务的内部操做对其余并发的事务,都具备隔离性。也即并发执行的事务之间不能互相影响。
● 持续性:持续性也称为持久性(persistence),指事务一旦提交,对数据所作的任何改变,都要记录到永久存储器中,一般保存进物理数据库。
Hibernate直接使用JDBC链接和JTA资源,不添加任何附加锁定行为。Hibernate只添加自动版本管理,而不会锁定内存中的对象,也不会改变数据库事务的隔离级别。基本上,使用 Hibernate就好像直接使用JDBC(或者JTA/CMT)进行数据库访问。
Hibernate中SessionFactory对象的建立代价很高,它是线程安全的对象,被设计成能够为全部的应用程序线程所共享。一般,SessionFactory会在应用程序启动时建立,一旦建立了SessionFactory将不会轻易关闭,只有当应用关闭时,SessionFactory才会关闭。
而Session的对象是轻量级的,它也是线程不安全的。对于单个业务进程单个工做单元而言,Session只被使用一次。建立Session时,并不会当即打开与数据库之间的链接,Session只在须要进行数据库操做时,才会获取JDBC链接。所以,打开和关闭Session,并不会对性能形成很大的影响。甚至即便没法肯定一个请求是否须要数据访问,也能够打开Session对象,由于若是不进行数据库访问,Session不会获取JDBC链接。
相反,数据库事务应该尽量的短。从而,下降数据库锁定形成的资源争用。数据库长事务会致使应用程序没法承载高并发的负荷。
由上面的介绍可知,Hiberante的Session和事务是紧密相关的,由于事务是经过Session来打开的。那么事务的范围是多大?单个Session能够跨越多个数据库事务吗?事务和Session的对应关系又如何呢?下面将介绍Hibernate Session和事务的关系。
数据库操做必须在Hibernate的Session管理下进行,但不推荐由于一次简单的数据库原子调用,就打开和关闭一次Session,数据库事务也是如此。由于,对于一次原子操做打开的事务没有任何意义——事务应该是将多个操做步骤组合成一个逻辑总体。
事务是按顺序发送并组成一个逻辑总体的原子操做单元。
注意:也就是说单个的SQL语句发送以后,自动事务提交模式失效了。这种自动提交模式仅为SQL控制台设计,在实际项目没有太大的实用价值。Hibernate禁止事务当即自动提交模式,或者让应用服务器禁止事务自动提交。
一般,建议每一个请求对应一个Session。在这种模式下,来自客户端的请求被发送到服务器端,此处可能对应一个业务逻辑方法。在这个业务逻辑方法内,一个新的Hibernate Session被打开,而后开始事务,在事务内执行这个操做单元中全部的数据库操做。一旦操做完成,须要发送给客户端的响应也准备就绪。此时,提交事务,而后关闭Session。在这种模式下,Session和用户请求是一对一的关系,这是一种理想的Session管理模式。
为了达到这种效果,推荐使用一个ThreadLocal变量,把Session绑定处处理客户端请求的线程上去。这种方式可让运行在该线程上的全部程序代码轻松地访问Session。也能够在一个ThreadLocal变量中保持事务上下文环境,不过这依赖于所选择的数据库事务划分机制。这种实现模式被称之为ThreadLocal Session和Open Session in View。
下面是一个HibernateUtil类,该类将Hibernate Session存放在一个ThreadLocal变量中,对于同一个线程的请求,将能够轻松访问该Session。
public class HibernateUtil
{
public static final SessionFactory sessionFactory;
//静态初始化块,使用该类时使用该代码块
static
{
try
{
//采用默认的hibernate.cfg.xml来启动一个Configuration的实例
Configuration configuration=new Configuration().configure();
//由Configuration的实例来建立一个SessionFactory实例
sessionFactory = configuration.buildSessionFactory();
}
catch (Throwable ex)
{
System.err.println("初始化sessionFactory失败." + ex);
throw new ExceptionInInitializerError(ex);
}
}
//ThreadLocal是隔离多个线程的数据共享,不存在多个线程之间共享资源,所以再也不须要
对线程同步
public static final ThreadLocal session = new ThreadLocal();
//该方法用于获取当前线程的Session对象
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
//若是该线程尚未Session,则建立一个新的Session
if (s == null)
{
s = sessionFactory.openSession();
//将得到的Session变量存储在ThreadLocal变量的Session里
session.set(s);
}
return s;
}
//该方法用于关闭当前线程里的Session
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
if (s != null)
s.close();
session.set(null);
}
}
在上面的代码中,Hibernate Session被绑定到当前线程。当调用currentSession方法时,若是当前线程中的Session已经建立出来,那么将返回这个已经存在的Session实例。
每次请求对应一个Session的模式不只能够用于设计操做单元,甚至不少业务处理流程都须要组合一系列的用户操做,即用户对数据库的交叉访问。
可是,对于企业应用,跨用户交互的数据库事务是没法接受的。例如,在第一个页面,用户打开对话框,打开一个特定Session装入的数据,能够随意修改对话框中的数据,修改完成后,将修改结果存入数据库。
从用户的角度来看,这个操做单元被称为应用程序长事务。在一个J2EE应用实现中,能够有不少方法来实现这种应用程序长事务。
一个比较差的作法是,当用户思考时,应用程序保持Session和数据库事务是打开的,并保持数据库锁定,以阻止并发修改,从而保证数据库事务隔离级别和原子操做。这种数据库锁定会致使应用程序没法扩展并发用户的数目。
所以,不要使用每一个应用对应一次Hibernate Session的模式,也不要使用每次Http Session对应一次Hibernate Session的模式。
注意:几乎全部状况下,都不要使用每一个应用对应一次Hibernate Session的模式,也不要使用每次Http Session对应一次Hibernate Session的模式。
对于这种状况,Hibernate主要有以下两种模式来解决这个问题:
● 脱管对象,若是采用每次用户请求对应一次Session的模式。那么,前面载入的实例在用户思考的过程当中,始终与Session脱离,处于脱管状态。都处于与Session脱离的状态。Hibernate容许把脱管对象从新关联到Session上,而且对修改进行持久化。在这种模式下,自动版本化被用来隔离并发修改。这种模式也被称为使用脱管对象的每一个请求对应一个Hibernate Session。
● 长生命周期Session,Session能够在数据库事务提交以后,断开和底层的JDBC链接。当新的客户端请求到来时,它又从新链接上底层的JDBC链接。这种模式被称为每一个应用程序事务对应一个Session,由于应用程序事务至关长(跨越多个用户请求),因此也被称为每次应用事务对应一个Hibernate Session。
posted @ 2009-07-19 09:11 jadmin 阅读(5) 评论(0) 编辑
数据过滤并非一种常规的数据查询方法,而是一种总体的筛选方法。数据过滤也可对数据进行筛选,所以,将其放在Hibernate的数据查询框架中介绍。
若是一旦启用了数据过滤器,则无论数据查询,仍是数据加载,该过滤器将自动做用于全部数据,只有知足过滤条件的记录才会被选出来。
过滤器与定义在类和集合映射文件上的“where”属性很是类似。它们的区别是过滤器能够带参数,应用程序能够在运行时决定是否启用指定的过滤器,以及使用什么样的参数值。而映射文件上的“where”属性将一直生效,且没法动态传入参数。
过滤器的用法很像数据库视图,区别是视图在数据库中已经定义完成,而过滤器则还需在应用程序中肯定参数值。
过滤器的使用分红三步:
(1)定义过滤器。使用filter-def元素定义过滤器;
(2)使用过滤器。使用filter元素使用过滤器;
(3)在代码中启用过滤器。
前两个步骤都是在Hibernate的映射文件中完成的,其中filter-def是hibernate-mapping元素的子元素,而filter元素是class集合元素的子元素。
filter-def元素用于定义一个过滤器,filter则将指定的过滤器应用到指定的持久化类。
一个持久化类或集合能够使用多个过滤器,而一个过滤器也能够做用于多个持久化类或集合。
看下面的映射文件示例:
<?xml version="1.0"?>
<!-- Hibernate配置文件的文件头,包含DTD等信息 -->
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Hibernate 配置文件的根元素 -->
<hibernate-mapping >
<!-- 每一个class元素定义一个持久化类 -->
<class name="Category" table="category">
<!-- 定义标识属性 -->
<id name="id" column="category_id" >
<!-- 指定主键生成器策略 -->
<generator class="native"/>
</id>
<!-- 映射name属性 -->
<property name="name" type="string"/>
<!-- 映射effectiveStartDate属性 -->
<property name="effectiveStartDate" column="eff_start_date"
type="java.util.Date"/>
<!-- 映射effectiveEndDate属性 -->
<property name="effectiveEndDate" column="eff_end_date"
type="java.util.Date"/>
<!-- 映射N-N关联属性 -->
<set cascade="none" inverse="true" name="products"
table="product_category">
<!-- 定义关联属性的key,对应链接表中的外键列 -->
<key column="category_id"/>
<!-- 定义关联属性 -->
<many-to-many column="product_id" class="Product"/>
</set>
<!-- 使用过滤器,并设置过滤器条件 -->
<filter name="effectiveDate" condition=":asOfDate BETWEEN
eff_start_date and eff_end_date"/>
</class>
<!-- 定义第二个持久化类 -->
<class name="Product" table="product">
<!-- 定义标识属性 -->
<id name="id" column="product_id" >
<!-- 指定主键生成器策略 -->
<generator class="native"/>
</id>
<!-- 映射name属性 -->
<property name="name" type="string"/>
<!-- 映射stockNumber属性 -->
<property name="stockNumber" column="stock_number" type="int"/>
<!-- 映射effectiveStartDate属性 -->
<property name="effectiveStartDate" column="eff_start_date"
type="java.util.Date"/>
<!-- 映射effectiveEndDate属性 -->
<property name="effectiveEndDate" column="eff_end_date"
type="java.util.Date"/>
<!-- 映射N-N关联属性 -->
<set cascade="all" name="categories" fetch="join"
table="product_category" >
<!-- 定义关联属性的key,对应链接表中的外键列 -->
<key column="product_id"/>
<!-- 定义关联属性 -->
<many-to-many column="category_id"
class="Category" fetch="join">
<!-- 对关联属性使用第一个过滤器 -->
<filter name="effectiveDate"
condition=":asOfDate BETWEEN eff_start_date and
eff_end_date"/>
<!-- 对关联属性使用第二个过滤器 -->
<filter name="category" condition="category_id = :catId"/>
</many-to-many>
</set>
<filter name="effectiveDate" condition=":asOfDate BETWEEN
eff_start_date AND eff_end_date"/>
</class>
<!-- 定义第一个过滤器,该过滤器包含一个date类型的参数 -->
<filter-def name="effectiveDate">
<filter-param name="asOfDate" type="date"/>
</filter-def>
<!-- 定义第二个过滤器,该过滤器包含一个long类型的参数 -->
<filter-def name="category">
<filter-param name="catId" type="long"/>
</filter-def>
</hibernate-mapping>
在上面的配置文件中,定义了两个过滤器,过滤器的定义经过filter-def元素完成。定义过滤器时,只须要指定过滤器的名字,以及过滤器的参数便可。如Java里的一个方法声明,只有方法名和参数列表,具体的方法实现是没有的。
过滤器的过滤条件是使用过滤器时才肯定的,使用过滤器经过filter元素肯定,filter的condition属性用于肯定过滤条件,知足该条件的记录才会被抓取到。
系统默认不启用过滤器,必须显式经过enableFilter(String filterName)才能够启用过滤器,该方法返回一个Filter实例,Filter包含setParameter方法用于为过滤器参数赋值。
一旦启用了过滤器,过滤器在整个Session内有效,全部的数据加载将自动应用该过滤条件,直到调用disableFilter方法。
看下面的使用过滤器的示例代码:
private void test() throws Exception
{
//获取Hibernate Session对象
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//启用第一个过滤器
session.enableFilter("effectiveDate")
//为过滤器设置参数
.setParameter("asOfDate", new Date());
//启动第二个过滤器
session.enableFilter("category")
//为过滤器设置参数
.setParameter("catId", new Long(2));
//执行查询,该查询没有任何的查询条件
Iterator results = session.createQuery("from Product as p")
.iterate();
//遍历结果集
while (results.hasNext())
{
Product p = (Product)results.next();
System.out.println(p.getName());
//此处获取Product关联的种类,过滤器也将自动应用过滤
Iterator it = p.getCategories().iterator();
System.out.println(p.getCategories().size());
while (it.hasNext())
{
Category c = (Category)it.next();
System.out.println(c.getName());
}
}
tx.commit();
HibernateUtil.closeSession();
}
经过使用过滤器定义经常使用的数据筛选规则,若是是临时的数据筛选,仍是使用常规查询比较好。对于从前使用行列表达式视图的地方,此处能够考虑使用过滤器。
posted @ 2009-07-19 09:08 jadmin 阅读(0) 评论(0) 编辑
Hibernate还支持使用SQL查询,使用SQL查询能够利用某些数据库的特性,或者用于将原有的JDBC应用迁移到Hibernate应用上。使用命名的SQL查询还能够将SQL语句放在配置文件中配置,从而提升程序的解耦,命名SQL查询还能够用于调用存储过程。
若是是一个新的应用,一般不要使用SQL查询。
SQL查询是经过SQLQuery接口来表示的,SQLQuery接口是Query接口的子接口,所以彻底能够调用Query接口的方法:
● setFirstResult(),设置返回结果集的起始点。
● setMaxResults(),设置查询获取的最大记录数。
● list(),返回查询到的结果集。
但SQLQuery比Query多了两个重载的方法:
● addEntity,将查询到的记录与特定的实体关联。
● addScalar,将查询的记录关联成标量值。
执行SQL查询的步骤以下:
(1)获取Hibernate Session对象;
(2)编写SQL语句;
(3)以SQL语句做为参数,调用Session的createSQLQuery方法建立查询对象;
(4)若是SQL语句包含参数,调用Query的setXxx方法为参数赋值;
(5)调用SQLQuery对象的addEntity或addScalar方法将选出的结果与实体或标量值关联;
(6)调用Query的list方法返回查询的结果集。
看下面的SQL查询示例:
private void test()
{
//获取Hibernate Session对象
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//编写SQL语句
String sqlString = "select {s.*} from student s where s.name like '马军'";
//以SQL语句建立SQLQuery对象
List l = session.createSQLQuery(sqlString)
//将查询到的记录与特定实体关联起来
.addEntity("s",Student.class)
//返回所有的记录集
.list();
//遍历结果集
Iterator it = l.iterator();
while (it.hasNext())
{
//由于将查询结果与Student类关联,所以返回的是Student集合
Student s = (Student)it.next();
Set enrolments = s.getEnrolments();
Iterator iter = enrolments.iterator();
while(iter.hasNext())
{
Enrolment e = (Enrolment)iter.next();
System.out.println(e.getCourse().getName());
}
}
//提交事务
tx.commit();
//关闭Session
HibernateUtil.closeSession();
}
上面的示例显示了将查询记录关联成一个实体的示例。事实上,SQL查询也支持将查询结果转换成标量值,转换成标量值能够使用addScalar方法,如:
Double max = (Double) session.createSQLQuery("select max(cat.weight) as maxWeight from cats cat")
.addScalar("maxWeight", Hibernate.DOUBLE);
.uniqueResult();
使用SQL查询,若是须要将查询到的结果转换成特定实体,就要求为选出的字段命名别名。这别名不是随意命名的,而是以“/”实例名.属性名“/”的格式命名,例如:
//依次将多个选出的字段命名别名,命名别名时都以ss做为前缀,ss是关联实体的别名
String sqlStr = "select stu.studentId as {ss.studentNumber},"
+ "stu.name as {ss.name} from "
+ "student as stu where stu.name like '杨海华'";
List l = session.createSQLQuery(sqlStr)
//将查询出的ss实例,关联到Student类
.addEntity("ss",Student.class)
.list();
在第一个示例中,以{s.*}表明该表的所有字段,且关联实例的别名也被指定为s。
注意:若是不使用{s.*}的形式,就可以让实体别名和表别名互不相同。关联实体的类型时,被关联的类必须有对应的setter方法。
能够将SQL语句不放在程序中,而放在配置文件中,这种方式以松耦合的方式配置SQL语句,能够提升程序解耦。
在Hibernate的映射文件中定义查询名,而后肯定查询所用的SQL语句,而后就能够直接调用该命名SQL查询。在这种状况下,不须要调用addEntity()方法,由于在配置命名SQL查询时,已经完成了查询结果与实体的关联。
下面是命名SQL查询的配置片断:
<!-- 每一个sql-query元素定义一个命名SQL查询 -->
<sql-query name="mySqlQuery">
<!-- 关联返回的结果与实体类 -->
<return alias="s" class="Student"/>
<!-- 定义命名SQL查询的SQL语句 -->
SELECT {s.*}
from student s WHERE s.name like'杨海华'
</sql-query>
sql-query元素是hibernate-mapping元素的子元素。所以,sql-query定义的名能够直接经过Session访问,上面定义的mySqlQuery查询能够直接访问,下面是使用该命名SQL查询的示例代码:
private void testNamedSQl()
{
//获取Hibernate Session对象
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//调用命名查询,直接返回结果
List l = session.getNamedQuery("mySqlQuery")
.list();
//遍历结果集
Iterator it = l.iterator();
while (it.hasNext())
{
//在定义SQL查询时,已经将结果集与Student类关联起来
//所以,集合里的每一个元素都是Student实例
Student s = (Student)it.next();
Set enrolments = s.getEnrolments();
Iterator iter = enrolments.iterator();
while(iter.hasNext())
{
Enrolment e = (Enrolment)iter.next();
System.out.println("=====================================");
System.out.println(e.getCourse().getName());
System.out.println("=====================================");
}
}
tx.commit();
HibernateUtil.closeSession();
}
Hibernate 3增长了存储过程的支持,该存储过程只能返回一个结果集。
下面是Oracle 9i的存储过程示例:
CREATE OR REPLACE FUNCTION selectAllEmployments
RETURN SYS_REFCURSOR
AS
st_cursor SYS_REFCURSOR;
BEGIN
OPEN st_cursor FOR
SELECT EMPLOYEE, EMPLOYER,
STARTDATE, ENDDATE,
REGIONCODE, EID, VALUE, CURRENCY
FROM EMPLOYMENT;
RETURN st_cursor;
END;
若是须要使用该存储过程,能够先将其定义成命名SQL查询,例如:
<!-- 定义命名SQL查询,name属性指定命名SQL查询名 -->
<sql-query name="selectAllEmployees_SP" callable="true">
<!-- 定义返回列与关联实体类属性之间的映射 -->
<return alias="emp" class="Employment">
<!-- 依次定义每列与实体类属性的对应 -->
<return-property name="employee" column="EMPLOYEE"/>
<return-property name="employer" column="EMPLOYER"/>
<return-property name="startDate" column="STARTDATE"/>
<return-property name="endDate" column="ENDDATE"/>
<return-property name="regionCode" column="REGIONCODE"/>
<return-property name="id" column="EID"/>
<!-- 将两列值映射到一个关联类的组件属性 -->
<return-property name="salary">
<!-- 映射列与组件属性之间的关联 -->
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query>
调用存储过程还有以下须要注意的地方:
● 由于存储过程自己完成了查询的所有操做,因此调用存储过程进行的查询没法使用setFirstResult()/setMaxResults()进行分页。
● 存储过程只能返回一个结果集,若是存储过程返回多个结果集,Hibernate将仅处理第一个结果集,其余将被丢弃。
● 若是在存储过程里设定SET NOCOUNT ON,将有更好的性能表现。固然也能够没有该设定。
posted @ 2009-07-19 09:02 jadmin 阅读(1) 评论(0) 编辑
条件查询是更具面向对象特点的数据查询方式。条件查询可经过以下3个类完成:
● Criteria,表明一次查询。
● Criterion,表明一个查询条件。
● Restrictions,产生查询条件的工具类。
执行条件查询的步骤以下:
(1)得到Hibernate的Session对象。
(2)以Session对象建立Criteria对象。
(3)增长Criterion查询条件。
(4)执行Criteria的list等方法返回结果集。
看下面的条件查询示例:
private void test()
{
//获取Hibernate Session对象
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//建立Criteria和添加查询条件同步完成
//最后调用list方法,返回查询到的结果集
List l = session.createCriteria(Student.class)
//此处增长限制条件必须是Student已经存在的属性
.add( Restrictions.gt("studentNumber" , new Long(20050231) ) )
//若是要增长对Student的关联类的属性的限制则必须从新createCriteria()
/若是此关联属性是集合,则只要集合里任意一个对象的属性知足下面条件
.createCriteria("enrolments")便可
.add( Restrictions.gt("semester" , new Short("2") ) )
.list();
Iterator it = l.iterator();
//遍历查询到的记录
while (it.hasNext())
{
Student s = (Student)it.next();
System.out.println(s.getName());
Set enrolments = s.getEnrolments();
Iterator iter = enrolments.iterator();
while(iter.hasNext())
{
Enrolment e = (Enrolment)iter.next();
System.out.println(e.getCourse().getName());
}
}
tx.commit();
ibernateUtil.closeSession();
}
在条件查询中,Criteria接口表明一次查询,该查询自己不具有任何的数据筛选功能,Session调用createCriteria(Class clazz)方法对某个持久化类建立条件查询实例。
Criteria包含以下两个方法:
● Criteria setFirstResult(int firstResult),设置查询返回的第一行记录。
● Criteria setMaxResults(int maxResults),设置查询返回的记录数。
这两个方法与Query的这两个方法用法类似,都用于完成查询分页。
而Criteria还包含以下经常使用方法:
● Criteria add(Criterion criterion),增长查询条件。
● Criteria addOrder(Order order),增长排序规则。
● List list(),返回结果集。
Criterion接口表明一个查询条件,该查询条件由Restrictions负责产生,Restrictions是专门用于产生查询条件的工具类,它的方法大部分都是静态方法,经常使用的方法以下:
● static Criterion allEq(Map propertyNameValues),判断指定属性(由Map参数的key指定)和指定值(由Map参数的value指定)是否彻底相等。
● static Criterion between(String propertyName,Object lo, Object hi),判断属性值在某个值范围以内。
● static Criterion ilike(String propertyName, Object value),判断属性值匹配某个字符串。
● static Criterion ilike(String propertyName, String value,MatchMode matchMode),判断属性值匹配某个字符串,并肯定匹配模式。
● static Criterion in(String propertyName,Collection values),判断属性值在某个集合内。
● static Criterion in(String propertyName,Object[] values),判断属性值是数组元素的其中之一。
● static Criterion isEmpty(String propertyName),判断属性值是否为空。
● static Criterion isNotEmpty(String propertyName),判断属性值是否不为空。
● static Criterion isNotNull(String propertyName),判断属性值是否为空。
● static Criterion isNull(String propertyName),判断属性值是否不为空。
● static Criterion not(Criterion expression),对Criterion求否。
● static Criterion sizeEq(String propertyName, int size),判断某个属性的元素个数是否与size相等。
● static Criterion sqlRestriction(String sql),直接使用SQL语句做为筛选条件。
● static Criterion sqlRestriction(String sql, Object[] values, Type[] types),直接使用带参数占位符的SQL语句做为条件,并指定多个参数值。
● static Criterion sqlRestriction(String sql, Object value, Type type),直接使用带参数占位符的SQL语句做为条件,并指定参数值。
Order实例表明一个排序标准,Order有以下构造器:
Order(String propertyName, boolean ascending),根据propertyName排序,是否采用升序,若是后一个参数为true,采用升序排序,不然采用降序排序。
若是须要使用关联类的属性来增长查询条件,则应该对属性再次使用createCriteria方法。看以下示例:
session.createCriteria(Person.class)
.add(Restrictions.like("name" , "dd%"))
.createCriteria("addresses")
.add(Restrictions.like("addressdetail" , "上海%"))
.list();
上面的代码表示创建Person类的条件查询,第一个查询条件是直接过滤Person的属性,即选出name属性以dd开始的Person实例,第二个查询条件则过滤Person关联实例的属性,其中addresses是Person类的关联持久化类Address,而addressdetail则是Address类的属性。值得注意的是,查询并非查询Address持久化类,而是查询Person持久化类。
注意:使用关联类的条件查询,依然是查询原有持久化类的实例,而不是查询被关联类的实例。
posted @ 2009-07-19 08:59 jadmin 阅读(5) 评论(0) 编辑
Hibernate提供了异常强大的查询体系,使用Hibernate有多种查询方式。能够选择使用Hibernate的HQL查询,或者使用条件查询,甚至能够使用原生的SQL查询语句,此外还提供了一种数据过滤功能,这些均可用于筛选目标数据。
下面分别介绍Hibernate的4种数据筛选方法:
HQL是Hibernate Query Language的缩写,HQL的语法很像SQL的语法,但HQL是一种面向对象的查询语言。所以,SQL的操做对象是数据表和列等数据对象,而HQL的操做对象是类、实例、属性等。
HQL是彻底面向对象的查询语言,所以能够支持继承和多态等特征。
HQL查询依赖于Query类,每一个Query实例对应一个查询对象。使用HQL查询可按以下步骤进行:
(1)获取Hibernate Session对象;
(2)编写HQL语句;
(3)以HQL语句做为参数,调用Session的createQuery方法建立查询对象;
(4)若是HQL语句包含参数,调用Query的setXxx方法为参数赋值;
(5)调用Query对象的list等方法遍历查询结果。
看下面的查询示例:
public class HqlQuery
{
public static void main(String[] args)throws Exception
{
HqlQuery mgr = new HqlQuery();
//调用查询方法
mgr.findPersons();
//调用第二个查询方法
mgr.findPersonsByHappenDate();
HibernateUtil.sessionFactory.close();
}
//第一个查询方法
private void findPersons()
{
//得到Hibernate Session
Session sess = HibernateUtil.currentSession();
//开始事务
Transaction tx = sess.beginTransaction();
//以HQL语句建立Query对象.
//执行setString方法为HQL语句的参数赋值
//Query调用list方法访问查询的所有实例
List pl = sess.createQuery("from Person p where p.myEvents.title
= :eventTitle")
.setString("eventTitle","很普通事情")
.list();
//遍历查询的所有结果
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
//提交事务
tx.commit();
HibernateUtil.closeSession();
}
//第二个查询方法
private void findPersonsByHappenDate()throws Exception
{
//得到Hibernate Session对象
Session sess = HibernateUtil.currentSession();
Transaction tx = sess.beginTransaction();
//解析出Date对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date start = sdf.parse("2005-01-01");
System.out.println("系统开始经过日期查找人" + start);
//经过Session的createQuery方法建立Query对象
//设置参数
//返回结果集
List pl = sess.createQuery(
"from Person p where p.myEvents.happenDate between :firstDate
and :endDate")
.setDate("firstDate",start)
.setDate("endDate",new Date())
.list();
//遍历结果集
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
tx.commit();
HibernateUtil.closeSession();
}
}
经过上面的示例程序,可看出查询步骤基本类似。Query对象能够连续屡次设置参数,这得益于Hibernate Query的设计。
一般,setXxx方法的返回值都是void,但Hibernate Query的setXxx方法返回值是Query自己。所以,程序经过Session建立Query后,直接屡次调用setXxx方法为HQL语句的参数赋值,再直接调用list方法返回查询到的所有结果便可。
Query还包含两个方法:
● setFirstResult(int firstResult),设置返回的结果集从第几条记录开始。
● setMaxResults(int maxResults),设置本次查询返回的结果数。
这两个方法用于实现Hibernate分页。
下面简单介绍HQL语句的语法。
HQL语句自己是不区分大小写的。也就是说,HQL语句的关键字和函数都是不区分大小写的。但HQL语句中所使用的包名、类名、实例名和属性名都区分大小写。
from子句是最简单的HQL语句,也是最基本的HQL语句。from关键字后紧跟持久化类的类名。例如:
from Person
代表从Person持久化类中选出所有的实例。
大部分时候,推荐为该Person的每一个实例起别名。例如:
from Person as p
在上面的HQL语句中,Person持久化类中的实例的别名为p,既然 p是实例名,所以也应该遵照Java的命名规则:第一个单词的首字母小写,后面每一个单词的首字母大写。
命名别名时,as关键字是可选的,但为了增长可读性,建议保留。
from后还可同时出现多个持久化类,此时将产生一个笛卡儿积或跨表的链接。
select子句用于肯定选择出的属性,固然select选择的属性必须是from后持久化类包含的属性。例如:
select p.name from Person as p
select能够选择任意属性,不只能够选择持久化类的直接属性,还能够选择组件属性包含的属性,例如:
select p.name.firstName from Person as p
select也支持将选择出的属性存入一个List对象中,例如:
select new list(p.name , p.address) from Person as p
甚至能够将选择出的属性直接封装成对象,例如:
select new ClassTest(p.name , p.address) from Person as p
前提是ClassTest支持p.name和p.address的构造器,假如p.name的数据类型是 String,p.address的数据类型是String,则ClassTest必须有以下的构造器:
ClassTest(String s1, String s2)
select还支持给选中的表达式命名别名,例如:
select p.name as personName from Person as p
这种用法与new map结合使用更广泛。如:
select new map(p.name as personName) from Person as p
在这种情形下,选择出的是Map结构,以personName为key,实际选出的值做为value。
HQL也支持在选出的属性上,使用汇集函数。HQL支持的汇集函数与SQL彻底相同,有以下5个:
● avg,计算属性平均值。
● count,统计选择对象的数量。
● max,统计属性值的最大值
● min,统计属性值的最小值。
● sum,计算属性值的总和。
例如:
select count(*) from Person
select max(p.age) from Person as p
select子句还支持字符串链接符、算术运算符以及SQL函数。如:
select p.name || "" || p.address from Person as p
select子句也支持使用distinct和all关键字,此时的效果与SQL中的效果彻底相同。
HQL语句被设计成能理解多态查询,from后跟的持久化类名,不只会查询出该持久化类的所有实例,还会查询出该类的子类的所有实例。
以下面的查询语句:
from Person as p
该查询语句不只会查询出Person的所有实例,还会查询出Person的子类,如Teacher的所有实例,前提是Person和Teacher完成了正确的继承映射。
HQL支持在from子句中指定任何Java类或接口,查询会返回继承了该类的持久化子类的实例或返回实现该接口的持久化类的实例。下面的查询语句返回全部被持久化的对象:
from java.lang.Object o
若是Named接口有多个持久化类,下面的语句将返回这些持久化类的所有实例:
from Named as n
注意:后面的两个查询将须要多个SQL SELECT语句,所以没法使用order by子句对结果集进行排序,从而,不容许对这些查询结果使用Query.scroll()方法。
where子句用于筛选选中的结果,缩小选择的范围。若是没有为持久化实例命名别名,能够直接使用属性名引用属性。
以下面的HQL语句:
from Person where name like 'tom%'
上面HQL语句与下面的语句效果相同:
from Person as p where p.name like "tom%"
在后面的HQL语句中,若是为持久化实例命名了别名,则应该使用完整的属性名。两个HQL语句均可返回name属性以tom开头的实例。
复合属性表达式增强了where子句的功能,例如以下HQL语句:
from Cat cat where cat.mate.name like "kit%"
该查询将被翻译成为一个含有内链接的SQL查询,翻译后的SQL语句以下:
select * from cat_table as table1 cat_table as table2 where table1.mate =
table2.id and table1.name like "kit%"
再看下面的HQL查询语句:
from Foo foo where foo.bar.baz.customer.address.city like"guangzhou%"
翻译成SQL查询语句,将变成一个四表链接的查询。
=运算符不只能够被用来比较属性的值,也能够用来比较实例:
from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate
from Cat cat, Cat mate
where cat.mate = mate
特殊属性(小写)id能够用来表示一个对象的标识符。(也能够使用该对象的属性名。)
from Cat as cat where cat.id = 123
from Cat as cat where cat.mate.id = 69
第二个查询是一个内链接查询,但在HQL查询语句下,无须体会多表链接,而彻底使用面向对象方式的查询。
id也可表明引用标识符。例如,Person类有一个引用标识符,它由country属性 与medicareNumber两个属性组成。
下面的HQL语句有效:
from Person as person
where person.id.country = 'AU'
and person.id.medicareNumber = 123456
from Account as account
where account.owner.id.country = 'AU'
and account.owner.id.medicareNumber = 123456
第二个查询跨越两个表Person和Account。是一个多表链接查询,但此处感觉不到多表链接查询的效果。
在进行多态持久化的状况下,class关键字用来存取一个实例的鉴别值(discriminator value)。嵌入where子句中的Java类名,将被做为该类的鉴别值。例如:
from Cat cat where cat.class = DomesticCat
where子句中的属性表达式必须以基本类型或java.lang.String结尾,不要使用组件类型属性结尾,例如Account有Person属性,而Person有Name属性,Name有firstName属性。
看下面的情形:
from Account as a where a.person.name.firstName like "dd%" //正确
from Account as a where a.person.name like "dd%" //错误
HQL的功能很是丰富,where子句后支持的运算符异常丰富,不只包括SQL的运算符,还包括EJB-QL的运算符等。
where子句中容许使用大部分SQL支持的表达式:
● 数学运算符+、–、*、/ 等。
● 二进制比较运算符=、>=、<=、<>、!=、like等。
● 逻辑运算符and、or、not等。
● in、not in、between、is null、is not null、is empty、is not empty、member of和not member of等。
● 简单的case、case ... when ... then ... else ... end和case、case when ... then ... else ... end等。
● 字符串链接符value1 || value2或使用字符串链接函数concat(value1 , value2)。
● 时间操做函数current_date()、current_time()、current_timestamp()、second()、minute()、hour()、day()、month()、year()等。
● HQL还支持EJB-QL 3.0所支持的函数或操做substring()、trim()、lower()、upper()、length()、locate()、abs()、sqrt()、bit_length()、coalesce()和nullif()等。
● 还支持数据库的类型转换函数,如cast(... as ...),第二个参数是Hibernate的类型名,或者extract(... from ...),前提是底层数据库支持ANSI cast() 和extract()。
● 若是底层数据库支持以下单行函数sign()、trunc()、rtrim()、sin()。则HQL语句也彻底能够支持。
● HQL语句支持使用?做为参数占位符,这与JDBC的参数占位符一致,也可以使用命名参数占位符号,方法是在参数名前加冒号 :,例如 :start_date和:x1等。
● 固然,也可在where子句中使用SQL常量,例如'foo'、6九、'1970-01-01 10:00: 01.0'等。
● 还能够在HQL语句中使用Java public static final 类型的常量,例如eg.Color.TABBY。
除此以外,where子句还支持以下的特殊关键字用法。
● in与between...and可按以下方法使用:
from DomesticCat cat where cat.name between 'A' and 'B'
from DomesticCat cat where cat.name in ( 'Foo','Bar','Baz')
● 固然,也支持not in和not between...and的使用,例如:
from DomesticCat cat where cat.name not between 'A' and 'B'
from DomesticCat cat where cat.name not in ( 'Foo','Bar','Baz' )
● 子句is null与is not null能够被用来测试空值,例如:
from DomesticCat cat where cat.name is null;
from Person as p where p.address is not null;
若是在Hibernate配置文件中进行以下声明:
<property name="hibernate.query.substitutions">true 1, false 0</property>
上面的声明代表,HQL转换SQL语句时,将使用字符1和0来取代关键字true和false。而后将能够在表达式中使用布尔表达式,例如:
from Cat cat where cat.alive = true
● size关键字用于返回一个集合的大小,例如:
from Cat cat where cat.kittens.size > 0
from Cat cat where size(cat.kittens) > 0
● 对于有序集合,还可以使用minindex与maxindex函数表明最小与最大的索引序数。同理,能够使用minelement与maxelement函数表明集合中最小与最大的元素。 例如:
from Calendar cal where maxelement(cal.holidays) > current date
from Order order where maxindex(order.items) > 100
from Order order where minelement(order.items) > 10000
● 能够使用SQL函数any、some、all、exists、in操做集合里的元素,例如:
//操做集合元素
select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
//p的name属性等于集合中某个元素的name属性
select p from NameList list, Person p
where p.name = some elements(list.names)
//操做集合元素
from Cat cat where exists elements(cat.kittens)
from Player p where 3 > all elements(p.scores)
from Show show where 'fizard' in indices(show.acts)
注意这些结构变量size、elements、indices、minindex、maxindex、minelement、maxelement 等,只能在where子句中使用。
● where子句中,有序集合的元素(arrays, lists, maps)能够经过[ ]运算符访问。例如:
//items是有序集合属性,items[0]表明第一个元素
from Order order where order.items[0].id = 1234
//holidays是map集合属性,holidays[national day]表明其中一个元素
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
and person.nationality.calendar = calendar
//下面同时使用list 集合和map集合属性
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11
在[]中的表达式甚至能够是一个算术表达式,例如:
select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item
借助于HQL,能够大大简化选择语句的书写,提升查询语句的可读性,看下面的HQL语句:
select cust
from Product prod,
Store store
inner join store.customers cust
where prod.name = 'widget'
and store.location.name in ( 'Melbourne', 'Sydney' )
and prod = all elements(cust.currentOrder.lineItems)
若是翻译成SQL语句,将变成以下形式:
SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
stores store,
locations loc,
store_customers sc,
product prod
WHERE prod.name = 'widget'
AND store.loc_id = loc.id
AND loc.name IN ( 'Melbourne', 'Sydney' )
AND sc.store_id = store.id
AND sc.cust_id = cust.id
AND prod.id = ALL(
SELECT item.prod_id
FROM line_items item, orders o
WHERE item.order_id = o.id
AND cust.current_order = o.id
)
查询返回的列表(list)能够根据类或组件属性的任何属性进行排序,例如:
from Person as p
order by p.name, p.age
还可以使用asc或desc关键字指定升序或降序的排序规则,例如:
from Person as p
order by p.name asc , p.age desc
若是没有指定排序规则,默认采用升序规则。便是否使用asc关键字是没有区别的,加asc是升序排序,不加asc也是升序排序。
返回汇集值的查询能够对持久化类或组件属性的属性进行分组,分组所使用的group by子句。看下面的HQL查询语句:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
相似于SQL的规则,出如今select后的属性,要么出如今汇集函数中,要么出如今group by的属性列表中。看下面示例:
//select后出现的id出如今group by以后,而name属性则出如今汇集函数中
select foo.id, avg(name), max(name)
from Foo foo join foo.names name
group by foo.id
having子句用于对分组进行过滤,以下:
select cat.color, sum(cat.weight), count(cat)
from Cat cat
group by cat.color
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
注意:having子句用于对分组进行过滤,所以having子句只能在有group by子句时才能够使用,没有group by子句,不能使用having子句。
Hibernate的HQL语句会直接翻译成数据库SQL语句。所以,若是底层数据库支持的having子句和group by子句中出现通常函数或汇集函数,HQL语句的having子句和order by 子句中也能够出现通常函数和汇集函数。
例如:
select cat
from Cat cat
join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc
注意:group by子句与 order by子句中都不能包含算术表达式。
若是底层数据库支持子查询,则能够在HQL语句中使用子查询。与SQL中子查询类似的是,HQL中的子查询也须要使用()括起来。如:
from Cat as fatcat
where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )
若是select中包含多个属性,则应该使用元组构造符:
from Cat as cat
where not ( cat.name, cat.color ) in (
select cat.name, cat.color from DomesticCat cat
)
对于集合属性,Hibernate默认采用延迟加载策略。例如,对于持久化类Person,有集合属性scores。加载Person实例时,默认不加载scores属性。若是Session被关闭,Person实例将没法访问关联的scores属性。
为了解决该问题,能够在Hibernate映射文件中取消延迟加载或使用fetch join,例如:
from Person as p join p.scores
上面的fetch语句将会初始化person的scores集合属性。
若是使用了属性级别的延迟获取,能够使用fetch all properties来强制Hibernate当即抓取那些本来须要延迟加载的属性,例如:
from Document fetch all properties order by name
from Document doc fetch all properties where lower(doc.name) like '%cats%'
HQL查询还支持将查询所用的HQL语句放入配置文件中,而不是代码中。经过这种方式,能够大大提供程序的解耦。
使用query元素定义命名查询,下面是定义命名查询的配置文件片断:
<!-- 定义命名查询 -->
<query name="myNamedQuery">
<!-- 此处肯定命名查询的HQL语句 -->
from Person as p where p.age > ?
</query>
该命名的HQL查询能够直接经过Session访问,调用命名查询的示例代码以下:
private void findByNamedQuery()throws Exception
{
//得到Hibernate Session对象
Session sess = HibernateUtil.currentSession();
//开始事务
Transaction tx = sess.beginTransaction();
System.out.println("执行命名查询");
//调用命名查询
List pl = sess.getNamedQuery("myNamedQuery")
//为参数赋值
.setInteger(0 , 20)
//返回所有结果
.list();
//遍历结果集
for (Iterator pit = pl.iterator() ; pit.hasNext(); )
{
Person p = ( Person )pit.next();
System.out.println(p.getName());
}
//提交事务
tx.commit();
HibernateUtil.closeSession();
}
posted @ 2009-07-19 08:48 jadmin 阅读(4) 评论(0) 编辑
Hibernate彻底以面向对象的方式来操做数据库,当程序里以面向对象的方式操做持久化对象时,将被自动转换为对数据库的操做。例如调用Session的delete()方法来删除持久化对象,Hibernate将负责删除对应的数据记录;当执行持久化对象的set方法时,Hibernate将自动转换为对应的update方法,修改数据库的对应记录。
问题是若是须要同时更新100 000条记录,是否是要逐一加载100 000条记录,而后依次调用set方法——这样不只繁琐,数据访问的性能也十分糟糕。对这种批量处理的场景,Hibernate提供了批量处理的解决方案,下面分别从批量插入、批量更新和批量删除3个方面介绍如何面对这种批量处理的情形。
若是须要将100 000条记录插入数据库,一般Hibernate可能会采用以下作法:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
User u = new User (.....);
session.save(customer);
}
tx.commit();
session.close();
但随着这个程序的运行,总会在某个时候运行失败,而且抛出OutOfMemoryException(内存溢出异常)。这是由于Hibernate的Session持有一个必选的一级缓存,全部的User实例都将在Session级别的缓存区进行了缓存的缘故。
为了解决这个问题,有个很是简单的思路:定时将Session缓存的数据刷新入数据库,而不是一直在Session级别缓存。能够考虑设计一个累加器,每保存一个User实例,累加器增长1。根据累加器的值决定是否须要将Session缓存中的数据刷入数据库。
下面是增长100 000个User实例的代码片断:
private void testUser()throws Exception
{
//打开Session
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//循环100 000次,插入100 000条记录
for (int i = 0 ; i < 1000000 ; i++ )
{
//建立User实例
User u1 = new User();
u1.setName("xxxxx" + i);
u1.setAge(i);
u1.setNationality("china");
//在Session级别缓存User实例
session.save(u1);
//每当累加器是20的倍数时,将Session中的数据刷入数据库,并清空Session缓存
if (i % 20 == 0)
{
session.flush();
session.clear();
tx.commit();
tx = session.beginTransaction();
}
}
//提交事务
tx.commit();
//关闭事务
HibernateUtil.closeSession();
}
上面代码中,当i%20 == 0时,手动将Session处的缓存数据写入数据库,并手动提交事务。若是不提交事务,数据将依然缓存在事务处——未进入数据库,也将引发内存溢出的异常。
这是对Session级别缓存的处理,还应该经过以下配置来关闭SessionFactory的二级 缓存。
hibernate.cache.use_second_level_cache false
注意:除了要手动清空Session级别的缓存外,最好关闭SessionFactory级别的二级缓存。不然,即便手动清空Session级别的缓存,但由于在SessionFactory级别还有缓存,也可能引起异常。
上面介绍的方法一样适用于批量更新数据,若是须要返回多行数据,能够使用scroll()方法,从而可充分利用服务器端游标所带来的性能优点。下面是进行批量更新的代码片断:
private void testUser()throws Exception
{
//打开Session
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//查询出User表中的全部记录
ScrollableResults users = session.createQuery("from User")
.setCacheMode(CacheMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
int count=0;
//遍历User表中的所有记录
while ( users.next() )
{
User u = (User) users.get(0);
u.setName("新用户名" + count);
//当count为20的倍数时,将更新的结果从Session中flush到数据库
if ( ++count % 20 == 0 )
{
session.flush();
session.clear();
}
}
tx.commit();
HibernateUtil.closeSession();
}
经过这种方式,虽然能够执行批量更新,但效果很是很差。执行效率不高,并且须要先执行数据查询,而后再执行数据更新,而且这种更新将是逐行更新,即每更新一行记录,都须要执行一条update语句,性能很是低下。
为了不这种状况,Hibernate提供了一种相似于SQL的批量更新和批量删除的HQL语法。
Hibernate提供的HQL语句也支持批量的UPDATE和DELETE语法。
批量UPDATE和DELETE语句的语法格式以下:
UPDATE | DELETE FROM? ClassName [WHERE WHERE_CONDITIONS]
关于上面的语法格式有如下四点值得注意:
● 在FROM子句中,FROM关键字是可选的。即彻底能够不写FROM关键字。
● 在FROM子句中只能有一个类名,该类名不能有别名。
● 不能在批量HQL语句中使用链接,显式的或隐式的都不行。但能够在WHERE子句中使用子查询。
● 整个WHERE子句是可选的。
假设,须要批量更改User类实例的name属性,能够采用以下代码片断完成:
private void testUser()throws Exception
{
//打开Session
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//定义批量更新的HQL语句
String hqlUpdate = "update User set name = :newName";
//执行更新
int updatedEntities = session.createQuery( hqlUpdate )
.setString( "newName", "新名字" )
.executeUpdate();
//提交事务
tx.commit();
HibernateUtil.closeSession();
}
从上面代码中能够看出,这种语法很是相似于PreparedStatement的executeUpdate语法。实际上,HQL的这种批量更新就是直接借鉴了SQL语法的UPDATE语句。
注意:使用这种批量更新语法时,一般只须要执行一次SQL的UPDATE语句,就能够完成全部知足条件记录的更新。但也可能须要执行多条UPDATE语句,这是由于有继承映射等特殊状况,例若有一个Person实例,它有Customer的子类实例。当批量更新Person实例时,也须要更新Customer实例。若是采用joined-subclass或union-subclass映射策略,Person和Customer实例保存在不一样的表中,所以可能须要多条UPDATE语句。
执行一个HQL DELETE,一样使用 Query.executeUpdate() 方法,下面是一次删除上面所有记录的代码片断:
private void testUser()throws Exception
{
//打开Session实例
Session session = HibernateUtil.currentSession();
//开始事务
Transaction tx = session.beginTransaction();
//定义批量删除的HQL语句
String hqlUpdate = "delete User";
//执行批量删除
int updatedEntities = session.createQuery( hqlUpdate )
.executeUpdate();
//提交事务
tx.commit();
//关闭Session
HibernateUtil.closeSession();
}
由Query.executeUpdate()方法返回一个整型值,该值是受此操做影响的记录数量。实际上,Hibernate的底层操做是经过JDBC完成的。所以,若是有批量的UPDATE或DELETE操做被转换成多条UPDATE或DELETE语句,该方法返回的是最后一条SQL语句影响的记录行数。