Transaction Annotation
在EJB 3.0 的应用中,transaction properties最常声明在session bean定义的方法中。如果一个方法requires transaction, 所有这个方法中的操作(包括database updates)仅在方法正常退出时才被提交。如果在这个方法或调用这个方法的方法中抛出了application exception,transaction manager会回滚所有的变化(i.e., database updates). 声明transaction所用的标注是@TransactionAttribute,可以带如下参数:
REQUIRED: 有则用之,没有就创建一个新的用。
MANDATORY: the caller method必须自带事务. 否则,将抛出一个错误。
REQUIRESNEW: 被标注的方法必须在新的事务中执行. 如果the caller method自带事务, 则之前的那个事务将被挂起.
SUPPORTS: 有则用之,没有就算了。
NOT_SUPPORTED: 有则出错。
如果一个方法没有事务标注, 则它使用EJB 3.0 container指定的默认值:REQUIRED
在EJB 3.0中,EntityManager必须运行在一个事务性的context中以保证数据库完整性(database integrity). transaction manager总是在事务被提交的时候同步数据库(在current thread之后或在next database query之前)。你也可以在一个事务中调用EntityManager.flush()随时同步数据库。
Transaction Rollback
当应用抛出一个RuntimeException的时候transation将失败,这通常与数据库有关或者也有可能是一个ApplicationException.这里是一个自定义ApplicationException的例子。你可以在任何地方抛出它以引发事务失败---即使并不是数据库错误。
@ApplicationException(rollback
=
true
)
public
class
TransException
extends
Exception {
public
TransException () { }
}
当一个事务失败的时候,database会回滚到事务开始前的状态,即使你在事务中调用了flush()。所有被管理的entity beans均处在detached的状态。如果你想在事务失败后继续使用这些entity beans, 你不得不手工设置它们的ID为零。
A Transactional Method Example
在下面的例子中,我们在updateExchangeRate()的for循环中产生随机的application exceptions,因为方法被声明为事务性的,这些exceptions将导致所有的更新失败。
@Stateless
public
class
TransCalculator
implements
Calculator {
@PersistenceContext
protected
EntityManager em;
//
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public
void
updateExchangeRate (
double
newrate)
throws
Exception {
Collection
<
TimedRecord
>
rc
=
em.createQuery(
"
from TimedRecord r
"
).getResultList();
int
size
=
rc.size ();
for
(Iterator iter
=
rc.iterator(); iter.hasNext();) {
TimedRecord r
=
(TimedRecord) iter.next();
r.setSaving(r.getSaving()
*
newrate);
r.setResult(r.getResult()
*
newrate);
//
Emulate a failure
//
Calculate failure probability for each loop
//
in order for the overall failure probability
//
to be 50%
double
prob
=
Math.pow (
0.5
,
1
.
/
size);
if
(Math.random()
>
prob) {
//
Emulated failure causes rollback
throw
new
TransException ();
//
Or throw a RuntimeException to trigger rollback
}
}
}
点击下面的按钮以载入新的汇率更新程序。试着多次更新汇率的值你将看到要么所有的记录被更新,要么所有的记录都没有被更新。永远不会出现只有部分记录被更新的情况,即使异常是在循环的中期进行的。出现异常的概率为50%
**************************************************************************************
第二小节:Application Transactions
介绍
标准的JTA事务是基于线程的。事务在线程结束时要么commit要么rollback.在线程结束时,事务被提交;数据的变更将flush到database;并且persistence context被销毁。对于有些web应用来说,这种"one thread"的限制令人感到不快。比如,我们考虑一个购物车程序。用户需要浏览一系列的页面:选择商品,输入信用卡的资料,输入货运地址,预览最终的结算,最后结束整个事务。每一次页面提交在服务器端都会被一个独立的线程处理。但是事务必须在最后一个页面提交时才能被commit.
在 EJB 3.0中, 你可以把多线程的database updates缓存到EntityManager中,只在应用结束一次逻辑会话时commit the changes in a batch(比如在购物车checkout的时候)。在这个例子中,我们将模拟一次多页面aplication transaction.
The sample application
这次的示例应用程序是对前面“currency exchange rate update”例子一次小小的改动,当你提交更改一次新的currency exchange rate后,应用更新所有entity beans对象中的计算记录但是不会把变化flush到database,然后应用转到另外一个页面询问是否更新这些记录的timestamp,如果你选择“yes”,这些bean对象的timestamp属性将被更新同时在a single batch中flush到database.
The extended EntityManager
为了使上面的事务工作,你首先要指定 @PersistenceContext的type属性为PersistenceContextType.EXTENDED.这告诉container EntityManager在跨线程区域内维护它的persistence context (也就是它所管理的entity beans对象)
@Stateful
public
class
ApptransCalculator
implements
Calculator, Serializable {
@PersistenceContext(
type
=
PersistenceContextType.EXTENDED
)
protected
EntityManager em;
//
}
Commit the transaction
然后,我们通过使用参数NOT_SUPPORTED告诉JTA事务处理器不要在线程结束时提交更新.EntityManager应该仅在stateful session bean销毁时更新数据库. 你应该还记得我们可以通过调用用@Remove标记的方法来销毁一个statuful session bean.我们在用@Remove标记的方法中显示的调用EntityManager.flush()来send in all updates to the database。
@Stateful
public
class
ApptransCalculator
implements
Calculator, Serializable {
//
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public
void
updateExchangeRate (
double
newrate) {
//
}
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public
void
updateTimestamp () {
//
}
@Remove
public
void
checkout()
throws
Exception {
em.flush ()
}
}