hibernate自身没有事务管理器,更不支持分步式事务,但是它可以和一些应用服务器(weblogic, orion, websphere..),参与全局的事务管理。

至于查询,只要sql能够实现的,hql都可以实现。

guty,你好!我也在研究Hibernate,并且对Hibernate非常感兴趣。最近深入的研究了Hibernate的事务机制,感觉存在很大的缺陷。我不同意你前面对Hibernate事务机制的肯定,对EJB事务的否定。

下面详细阐述一下我对Java中的事务处理以及Hibernate事务的观点。

Hibernate的Transaction可以是基于数据库驱动提供的JDBC Transaction,也可以是基于App Server提供的JTA Transaction的,根据配置文件来配置,默认使用JDBC Transaction。但实际上只能使用JDBC Transaction。为什么呢?

因为Hibernate的Transaction是Session创建的,Transaction生命周期比Session短,更精确的来说,Transaction要比Session下面的Connection短,也就是说Transaction必须在一个Connection连接会话中启动和结束,如下:

Session s = sf.openSession();
Transaction tx = s.beginTransaction();
...
s.flush();
tx.commit();
s.close();

因此tx.commit()实质上就是Connection.commit()操作,而数据库实际执行如下JDBC操作:

Connection conn = ds.getConnection();
conn.setAutoCommit(false);
...
conn.commit();
conn.close();

所以Hibernate的Transaction根本就是换汤不换药的JDBC事务处理。虽然在配置文件中,tx可以配置为使用App Server JTA,但是JTA的事务范围是跨连接的,跨方法调用的。也就是说JTA的事务必须要先于Session启动,晚于Session关闭后提交,如下:

tx.begin(); // JTA Transaction
s = sf.openSession;
...
s.close();
tx.close();

而Hibernate中,tx必须晚与Session创建,先于Session关闭之前提交。因此当Hibernate的tx是配置使用App Server的JTA的Transaction,就出现冲突了。

此时,再按照上面的代码执行,App Server就会报错。必须先s.close(),然后再tx.commit()。当然这样也有问题,就是session没有完整的垃圾回收,会造成内存泄漏问题。

另外即便上面的操作能够成功,但是由于Hibernate中tx是从session获得,因此,JTA的事务范围也会被限制在Connection生命周期内,那么和Connection事务范围就完全一样了,丧失了JTA的跨方法调用,跨连接,甚至跨数据库的事务安全能力。

想像一个简单的情况,银行转账100元: bank.transfer(account1, account2, 100)
{
account1.reduce(100);
account2.add(100);
}
该方法分别调用account1的reduce方法和account2的add方法。

首先,在account类的reduce和add方法中必须使用tx(等于是conn.commit()),来保证数据库修改成功,如果你不调用tx.commit()的话,其实就是不向数据库发送commit的SQL语句,数据库根本就不进行真正的写操作。

同时bank.transfer方法也必须保证操作的原子性,来保证转账的安全。所以也必须使用tx,就是这样:
{
s = sf.openSession();
tx = s.beginTransaction();
account1.reduce(100);
account2.add(100);
tx.commit();
s.close();
}
但是实际上在该方法中tx根本不起作用。因为它们根本都不在一个Session中进行操作,而是分别启动了3个Session,在各自的Session中commit自己单独的数据库操作,因而根本无法保障转账的安全。(除非分别把s和tx保持在ThreadLocal中,不过这样会造成事务范围过大,代码难于跟踪,容易存在潜在的内存问题)

为了保持转账的安全,你只能直接使用App Server的JTA(此时必须配置hibernate.properties使用App Server的DataSource,不能使用Hibernate自带的那些连接池,否则JTA不起作用),就是这样:
{
tx = (UserTransaction) ctx.lookup("javax.transaction.UserTransaction");
tx.begin();
account1.reduce(100);
account2.add(100);
tx.commit();
}

但是此时App Server又会报错,因为在JTA中,不能使用JDBC Transaction,也就是必须把acount中的tx的操作都去掉。但是一旦去掉,account的reduce和add方法在单独调用的时候,根本就不会去写数据库,因为根本就没有向数据库commit,两难的处境!

总结如下:

Hibernate的Transaction就是JDBC的conn.commit,而Session总是默认把Connection.setAutoCommit(false),你要不用Hibernate的tx的话,单独的方法调用根本就不写数据库,你必须tx.commit()来保证数据库提交。但是你要用Hibernate的tx的话,你就必须放弃使用JTA或者EJB的事务功能,这等于是放弃了跨方法调用,跨数据库连接的安全保障,就像上面的银行转账的例子,你没办法保证转账的成功。

这样的问题,只有使用EJB的事务处理功能才能很好的解决:首先,在DAO代码中放弃所有的Hibernate的tx,不要写任何tx代码;然后在Session Bean中调用DAO到的方法声明为Required,那么一切问题都解决了。当然必须要由Session Bean来调用DAO才行。

我不明白你怎么会觉得EJB的事务是失败的?EJB的事务当然不能滥用,所有的EJB方法都声明为Required只有不懂的人才会这样做。EJB的事务是智能化和动态的,解决了很多事务控制的难题。而Hibernate的事务控制简直就是一个败笔。

robin,谢谢你如此详细的回复。我赞同你绝大多数的观点,我们的分歧可能只是一些理解上的小误差。

1,关于方法的事务性和复用性的问题,在你的例子中,你显然希望account.add()和account.reduce()都有事务处理,而且能够被单独调用,也能够参与bank.transfer()这样外部事务。
的确,如果每个方法内部都创建各自session和transaction,问题很难得到解决,就如你所说的。这其实也不能算是hibernate的问题,应该归咎到jdbc上吧:)
但事实上,在用hibernate或jdbc编城时,大家可以采用多种手段来解决这个问题:
a, 借助J2EE Server的CMT(你的方法)
b, 自己实现CMT(我用AOP和ThreadLocal实现过)
c, 如果程序有统一入口,干脆在入口处创建、提交session和事务。例如Web程序中,可以用filter和ThreadLocal实现。
a,b两种方法都是基于J2EE的CMT模型,也就是方法描述事务,method declartive transaction。事务可以声明成required,supports....,好像确实很强大。
但在实际项目中,我后来发现对于hibernate来说,CMT意义不大。反而是c更为简单,而且免去了方法调用时的事务检查。我的程序中,90%的事务都是required和supports,对于hibernate来说,都是commit,它不会像ejb那样提交未修改的数据。5%的事务是requiresnew,我在方法中单独开一个新session,方法结束前提交就行了。5%的事务的isolation level是serializable,可以直接使用lock方法。

2, CMT的缺点,我还是坚持认为CMT有很多缺点
a, 如果和entity bean结合,CMT的能否正确设置,会较严重地影响系统性能
b, 各种app server对CMT的实现程度有所不同,同样的代码在不同的server有时甚至会有不同的行为
c, 有的方法的事务性依赖于外部调用环境,想不起具体的例子了,我们去年一个银行项目中,就有好几处这样的问题,后来只好定义不同的方法。
d, CMT本身是否是个完善的计算机事务模型,好像还没有得到业界的确认。

guty,你的回复详细的解释了你前面对于Hibernate事务的观点,我基本上同意你的看法。另外我也想继续讨论该话题:)

1、CMT我只用过WebLogic,在其他App Server上是否会带来兼容性问题,没有经验。但是我想,很可能会出现这样的问题。每个App Server厂商对J2EE规范的实现都有扩展,这也是无可避免的事情。

2、CMT是通过XML配置文件来调节,我认为比硬编码实现灵活的多。

3、我基本上也不赞成使用CMP/BMP。

4、Hibernate的事务可以配置为使用JTA,但是使用JTA的时候和App Server不兼容,这基本上是一个误导。我建议Hibernate作者去掉JTA支持。

5、如果在线程全局使用ThreadLocal保存Session,这是一个不错的办法,我也用该方法来实现Lazy initialization。但是我不是很赞成对于数据库有修改的操作也找方抓药。

对于数据库修改操作应该尽早提交事务,否则当一个线程执行时间比较长的时候,早该提交的事务,会拖延到最后才提交,另外Hiberbate总是尽量延迟对数据库的操作,不到flush,Hibernate不会向数据库发送SQL,所以
在这个过程中,其他线程从数据库中读到的数据还是旧的。

另外,如果已经flush了,还没有提交,要到filter才提交的话,这段时间中,数据库的相关资源会锁定住,等待提交。

还有,在该方法下,每个和数据库操作相关的线程最初都会启动Hibernate事务,最后都会提交Hibernate事务,对于大多数情况下都是readonly的查询数据操作,其实本来不需要事务控制,这样是否会因此严重影响查询数据操作的性能?

to guty:
非常感谢guty给我们提供了hibernate非常好的实践实例,我本人也非常喜欢O/R maping,他用起来真是很爽。

但是有些疑惑让我没有立即选择hibernate,当然是其稳定性和性能,当然不是否定hibernate,而这是我对待新技术的一贯方法。

在机会成熟情况下,我会实战一次hibernate,因为我一年前用过Castor,非常喜欢,现在Castor处理XML已经成为我的工具箱中对付object -->XML转换的利器,前段时间用它实现JMS Mail,将Mail转为XML message发送,真是流利。

我现在常用的数据库持久化工具是EJB CMP,为什么这样选择,正好可以回答guty的几个问题:
>1. 对象重构,如果要改动属性、方法、对象关系,也许界面操作就不那么轻松了;

因为在EJB中,首先是建立Domain Model,实体bean或数据模型或数据表结构只是Domain 模型的实现。如果改动一个基本对象,如Customer,要两部分工作:接口设计改变;剩余的是实现改变,实现有两种:数据model要改变;数据库结构改变。
以上改变无论采取什么技术如O/R mapping是都要做的。

在EJB中,数据库结构改变后,通过Jbuilder的数据库导入技术,就可以立即将改变后的数据表映射成实体bean,这个工作是容器完成的。也就是说数据库结构改变后,实际实体bean同步改变,因此EJB的实体bean并没有给我们带来附加的工作。

2.版本控制问题 因为Jbuilder整合了cvs等主流工具,也很方便,通过wincvs也可以.

3.本地调试,通过Jbuilder整合的Junit或cactus的导航设置,非常方便。

4.对象上百个后,需要有一个清晰的web和EJB控制框架,将这几百个对象以组件形式封装,比如JMSEmail.jar是专门负责异步发送Email的EJB组件,而Sequence.jar是产生序列号的EJB组件,这点可以参考petstore,Petstore实际使用了很多EJB组件,真正自己的应用实现代码很少。

这里我想多说一句,EJB分布式组件是其强大的优点,程序员编码时可以划分成组件调试编码,很方便,组合在一起发布时,由于容器的自动集群功能,发布到一台服务器后,将自动在几百台集群服务器上运行,由服务器自动实现动态负载平衡,以及单点错误恢复,这些Jboss就已经做到。

这些成熟的框架使得我一直在使用EJB。


>1、CMT是通过XML配置文件来调节,我认为比硬编码实现灵活的多

各有各的好处吧,硬编码起码在程序的可读性上要方便。适当而简洁的配置文件是有益的,但J2EE的配置文件过度而且复杂,只能通过工具自动生成,反而成了开发的累赘。

>Hibernate的事务可以配置为使用JTA,但是使用JTA的时候和App Server不兼容,这基本上是一个误导。我建议Hibernate作者去掉JTA支持

我说了,这是APPServer和JDBC的问题,而且可以通过一定的编程模式去避免。支持JTA至少可以支持BMP编程,而且让非EJB程序参与全局事务。

>如果在线程全局使用ThreadLocal保存Session,这是一个不错的办法,我也用该方法来实现Lazy initialization。但是我不是很赞成对于数据库有修改的操作也找方抓药。

我使用ThreadLocal变量来保存当前的Session Stack和Transaction Stack,每个事务做完还是会立刻提交的。例如在一个Web调用中:
request -> filter() -> bank.tranfer() -> account.increase()
-> account.decrease()
所有需要访问数据库的方法中,通过HibernateSession.currentSession()方法来获取Session,currentSession()的逻辑是如果取当前ThreadLocal的Stack中的顶层对象,如果stack为空,则创建一个,并压入stack.
因此,在此次调用中,实际上是bank.transfer()中打开一个Session和Transaction,在account.increase()和account.decrease()中都取当前的Session。
filter()方法中,会调用HibernateSession.closeSession()方法,方法中如果发现stack中有Session,会提交Transaction和关闭Session。
另外,HibernateSession还提供了newSession方法,供那些"requiresnew"的方法调用,在这些方法中除了打开新的Session,压入stack,还要负责关闭并取出Session。

>其他线程从数据库中读到的数据还是旧的
>另外,如果已经flush了,还没有提交,要到filter才提交的话,这段时间中,数据库的相关资源会锁定住,等待提交

的确是这样,而且任何事务机制都是这种情况,所以都要避免长事务,Hibernate可以支持Optimistic Lock。

>还有,在该方法下,每个和数据库操作相关的线程最初都会启动Hibernate事务,最后都会提交Hibernate事务,对于大多数情况下都是readonly的查询数据操作,其实本来不需要事务控制,这样是否会因此严重影响查询数据操作的性能

还是那句话,hibernate就是JDBC Transaction机制。多数数据库缺省的isolation level都是read-commited,并发读不会有问题,不会锁资源的,所以不应该影响性能吧。


CMP在JBuilder里面开发确实确实很方便了,不过在JBuilder里面也只是1个表映射1个CMP,如果表非常多,并且很多表之间存在关联呢?

我遇到过数据库里面有80多张表的情况,其中绝大部分的表都会用到连接查询,如果是用CMP映射的话,必须把这些所有的关联关系都设计成CMR,那就意味着,这80多个CMP必须在一个EJB Deginer里面设计,并且只能打成一个jar包。这是非常恐怖的事情,稍微错一点点,全都完蛋了。

如果是BMP,可以在SQL里面写连接查询,如果是Hibernate,也一样。但是CMP做不到,只能用CMR,不在一个jar包里面的CMP没有办法CMR,所以80多个CMP打在一个包里面多么恐怖,光是那3个配置文件就几乎都几千行,可维护性太脆弱了。即使在JBuilder里面也是动不动报错。

后来那个项目果然失败了!

现在CMP都不会直接暴露给客户端,用Session Bean来封装Entity Bean,那么CMP的事务功能,安全性检查都没有必要,光是一个O/R Mapping的功能,就不一定非用CMP不可。用Session Bean+JDO(O/R Mapping)一样可以实现分布式。更准确的来说,甚至业务对象都可以不用Session Bean,前面加上一层Session Facade就可以分布式了。

我也想谢谢banq为大家提供了这么好的论坛,也拜读了你的很多大作,这里是我见过最专业的java中文论坛。

但不同意见还是要说的:)

>因为在EJB中,首先是建立Domain Model,实体bean或数据模型或数据表结构只是Domain 模型的实现。如果改动一个基本对象,如Customer,要两部分工作:接口设计改变;剩余的是实现改变,实现有两种:数据model要改变;数据库结构改变。

我的refactor要求自动化程度比较高,希望对象代码改变后,所有引用该对象的其它代码都自动更新,UML图自动更新,数据库表自动更新。Hibernate的persistent对象和jdo一样,都是POJO (pure old java object),因此利用支持refactor的ide,例如idea和eclipse,很容易实现引用代码自动更新。UML图就不必说了,Hibernate自带了SchemaUpdate工具,能够自动更新数据库表结构。

而客户代码是通过接口来访问EJB,这些接口是工具自动产生的,所以refactor时很难做到客户代码自动更新。

>版本控制问题 因为Jbuilder整合了cvs等主流工具,也很方便,通过wincvs也可以

我的意思是,cvs上保存什么文件,保存接口和配置文件吗?对版本控制来说,只保存源文件是比较好的,其它在下载后自动产生,但这需要较长的时间。而pojo的问题就少得多,只需要自动产生mapping文件就行乐。

> 本地调试,通过Jbuilder整合的Junit或cactus的导航设置,非常方便

我的本地调试是指在客户端的vm中调试,就目前来说,再好的服务器端调试环境都很难让人满意。

> 对象上百个后,需要有一个清晰的web和EJB控制框架

对于复杂的应用来说,一个理想的Domain Model是必须的,往往需要复杂的对象结构和关系。而EJB不支持继承关系,对集合的支持也是敷衍了事,因此它虽然是规范,但是只能算个O-R的半成品,大多数传统的O-R产品都比它的功能要强大,比它更符合实际应用。

另外,顺便提一下对J2EE规范的看法。J2EE规范大多是大公司之间在某些中间件领域妥协的结果,能够写到规范上的内容是比较容易取同的部分,而较为复杂、高级的需求往往被延迟或搁置。因此即便是实现了J2EE规范,在很多应用中只是实际需求的一个子集。

> 前面加上一层Session Facade就可以分布式了。

非常同意。

再往下做,你会发现EJB越来越成为累赘。RMI很难实现吗?JNDI真的有必要吗?Cluster有必要做得那么复杂吗?

然后你也许会自己做一个“服务器”。


>再往下做,你会发现EJB越来越成为累赘。RMI很难实现吗?JNDI真的有必要吗?Cluster有必要做得那么复杂吗?

>然后你也许会自己做一个“服务器”。

这还是使用Java应用程序客户端的情况。

如果是Web程序,或者客户端是其它语言,连RMI也没有必要,Cluster和会话机制都可以使用Web Server的,这是更为稳定和成熟的方案。

guty,你好,继续讨论Hibernat的事务


一、使用ThreadLocal来保存Session和Transaction:

关于在ThreadLocal中保存transaction,你没有仔细说明Transaction何时创建,何时提交,我理解为是创建Session后,立刻创建Transaction,web层调用都结束后,提交Transaction,关闭Session。这样,可以写一个Filter来专门处理提交Transaction和关闭Session。

通过TreadLocal和Filter统一来管理Session和Thread,的确省心省力。但是这样做有一个疑问:如果业务方法只是查询数据库,没有修改操作,那么有必要启动Transaction吗?

在上面这样的管理方法中,实际上web层调用到DAO的时候,都会启动Session和Transaction,最后都会提交Transaction和关闭Session。如果只是查询数据库,没有修改的话,就没有必要启动Transaction了。

而HibernateSession.currentSession()方法不可能分辨出来后面的调用是只读,还是有修改,总是要启动一个Transaction的,而在Filter里面也总是提交Transaction的。

那么我关心的问题就是,虽然Hibernate的Transaction是JDBC Transaction,不会造成很大的系统开销,但是对数据库只读查询做commit,这种开销究竟有多大呢?是否会严重影响性能呢?

我做了一个简单的试验,用Hibernate查询MySQL数据库,反复查询500次数据库,做统计。由于只是查询数据,因此是没有必要使用Transaction的,经过比较,使用了Transaction之后,平均查询时间多了600ms,也就是说平均每次查询要多1ms的时间开销。而在我的AMD Duron700的机器上,MySQL数据库的CPU占用率从19%上升到24%。

在真实的环境中,不会出现一个线程反复多次查询数据库的情况,因此多出来的1ms的时间开销完全可以忽略不计。但是数据库的开销还是非常可观的,如果是并发线程访问,我相信数据库的开销还要增加。

所以我觉得可以把你的方案再改良一下,Filter就不用改了,检测一下tx不为空才提交。而HibernateSession.currentSession()可以给它传一个参数,比如说只查询数据,不修改数据库的时候,给它传0,光创建Session或者取Session,而不启动Transaction,不为0的时候才启动Transaction。

总体来说,使用这个办法还是相当简单易行的。


>如果是Web程序,或者客户端是其它语言,连RMI也没有必要,Cluster和会话机制都可以使用Web Server的,这是更为稳定和成熟的方案

如果Web Server和App Server不在一台物理机器上呢?你还是必须要用Session Bean。

我觉得你好像概念稍有混淆,Web Server的Cluster是Web Server的Cluster,而App Server的Cluster是App Server的Cluster,在一个大型Web应用中,完全有可能Web层进行Cluster,来分担繁重的HTTP请求,而App Server也进行Cluster,来分担繁重的业务运算,这是不搭界的两回事。

如果是单纯的Web应用,会话是在Web层实现的,使用HttpSession,而不需要使用Stateful Session Bean,但是单纯的Web Server本身不能进行HttpSession的Cluster吧?只有ServletContainer才提供的吧,比如Weblogic的HttpSession的内存复制技术。

Hi robbin, guty,
Both of you are much more knowledgeable on Hibernate than me, but I have doubt about its restriction on JTA support.
I have implemented my own PM, similar to JDO or hibenate, did run into trouble with JTA due to incompatibility on different Appserver( WAS4, WAS5, WL6.1, WL7), but I managed to work them out. to support CMT, I register a callback object to receive the "before_completion" event from container Transaction coordinator, and do commit there. cleanup can be done in it as well or in "after_completion"( I don't like the inconsistency but seems have to work differently with different server). within a CMT transaction, I may have to create/close multiple user sessions/local transactions, I just need to make my transaction be aware of whether it's under CMT or local control. That's it.
If I can do it, why those much smarter Hibernate people can't do it?

With EJB and CMP, it has improved a lot but still not suitable for large projects, I have worked with several clients, they have hundreds of tables to model, can't imagine they could get the project done would they just use CMP.

When working with large repositories( let's say with objects over 100 ), tooling support is very important, WSAD, jbuilder support on EJB is fine, but due to the nature of EJB, those IDE sucks in big time when handling EJB gen/deployment. I am not favor of JDO, it's stanadard at API level but much less weighted, I hope there will be a good independent tool that can set on top different JDO implementations.

Hibernate is a clear winner in Opensource PM, but it need a nice GUI tool for productivity.

Toplink has a good tooling and nice conceptual model, but impelenation sucks, now they were in oracle's hand, so it seems can go no where.

Castor, I think an pioneer on XML binding and database mapping framework, I really got excited about it when I first study it few months ago, but after more I study in this category, I am backing off it, it's jack of trade, master of none.

Cheers
-Jevang

不是到hibernate和apache项目组的ojb相比如何

to robbin
如果谁把10个以上的CMP放在一个EJB Module里面设计,那么他就没有充分理解EJB组件的概念。

好的J2EE带给我们的是:到处是有规则的分类,直至类的方法。
大到框架,一个框架下可以有很多组件,每个组件里有相关的CMP实体bean和实现逻辑运算的session bean。

多完美的一个系统,那些把所有东西放在一起的思维习惯和设计思路是陈旧的,我们要相信,任何事情都可以细化,细化 再细化、然后封装、分派,不断往下分,直到不能分为止,一个J2EE的大系统就这样被你分解成很多积木。这样的J2EE系统才是真正的J2EE系统。

从软件设计的角度,当然不应该把很多CMP都放在一起。可是确实很复杂的情况下会出现这样的问题的。

CMP可以使用CMR来表示多表之间通过外键关联的关系。但是你仍然会遇到即使没有键关联的表仍然需要连接查询的情况,这是一个非常普遍的现象。

如果是Hibernate,可以在HSQL里面定义outer join,BMP也可以写JDBC,而CMP没有任何办法来解决该问题,除非你把需要的连接查询都定义为CMR,但那样的话,凡是有需要连接查询,或者有键关联的表都必须打在一个包里面。你如果不打在一个jar包里面,如果能够建立CMR?不是我想放在一个jar里面,而是不得不放在一个jar里面。基本上CMP还是非常笨拙的。

CMP的另一大缺点是不能动态SQL,guty已经提到了,一个SQL就要定义一个EJBFinder方法,在编译的时候就确定死了。在实际应用中,经常会遇到不确定查询条件的查询,比如说用户在页面上用下拉列表来选择查询的条件,用户既有可能什么限制条件都不选,也有可能选择某几个条件。这时候你怎么办?假设有n个查询条件,你要写 C1n + C2n + C3n +...+ Cnn(C是组合公式的符合,n是下标,1...n是上标)个EJBFinder方法才行,很恐怖吧。

其实JDBC的PrepareStatement也不能很好的解决这个问题,因为它是按照1,2这样的次序来set参数的。用Statement是肯定不行的,会严重影响数据库,甚至会导致数据库down掉(我的实际经验)。但是Hibernate就解决的不错,因为它可以按照 :name 这样的形式来设定SQL中的Placeholder,这样set参数就可以按照参数名称传递,因为次序不是死的,在程序里面就很容易根据用户选择的查询条件,动态的产生SQL,动态的set参数了。

CMP2.0还有一个大问题是不支持order by,当然你可以在Java里面对取出来的集合排序,但是速度和数据库里面就排好序速度不在一个数量级了。Hibernate不但可以order by,还可以group by,having,子查询,真是没有办法比下去了。

其实对于动态SQL和排序问题,特定的App Server也可以做,但那不是CMP2.0的规范罢了,所以为了可移植性,也不敢随便去用。

其实和BEA和IBM的工程师接触过,他们会建议你不要轻易使用Entity Bean的。Stateful Session Bean也是一个很慢的东东,考虑到大多数情况下是纯Web应用,不需要用Stateful Session Bean,我个人倾向于这样的业务层架构:

交易安全性优先:
Session Facade -> Stateless Session Bean -> DAO -> O/R Mapping

性能优先:
Session Facade -> Java class -> DAO -> O/R Mapping