参与到事务中的实体管理就像其它资源一样.我们在这本书中可以看到例子.扩展的持久化上下文有很多有趣的事务行为你可以使用.允许你调用EntityManager的操作,像persist( ), merge( ), 和remove( )在一个事务外当你与一个扩展的持久化上下文交互时.那些插入,更新,和删除排队直到扩展持久化上下文支持一个活动的事务和提交.换句话来讲,数据库不被涉及直到持久化上下文变成关联一个事务时.同时,任何被运行的查询不被保存到数据库中连接在他们完成之后.让我们看这样的一个例子.
1 EntityManager manager = entityManagerFactory.createEntityManager(EXTENDED);
2 manager.persist(newCabin);
3 manager.merge(someCustomer);
4 manager.remove(someReservation);
5
6 userTransaction.begin( );
7 manager.flush( );
8 userTransaction.commit( );
第一行创建一个扩展的持久化上下文.第2-4行创建,更新,和删除一些实体.这些动作排队直到持久化上下文变成支持一个事务在第6行.调用一个EntityManager方法的行为在一个事务的持久化上下文内.批量动作的提交在第7行.
你可以真正的开发这种行为通过使用有状态会话Bean.前面,我们显示的创建两个reservations 通过TRavelAgentBean的例子:
TravelAgent tr1 = (TravelAgent)getInitialContext( ).lookup("TravelAgentRemote");
tr1.setCruiseID(cruiseID);
tr1.setCabinID(cabin_1);
tr1.setCustomer(customer0);
TravelAgent tr2 = (TravelAgent)getInitialContext( ).lookup("TravelAgentRemote");;
tr2.setCruiseID(cruiseID);
tr2.setCabinID(cabin_2);
tr2.setCustomer(customer);
javax.transaction.UserTransaction tran = ...; // Get the UserTransaction.
tran.begin( );
tr1.bookPassage(visaCard,price);
tr2.bookPassage(visaCard,price);
tran.commit( );
这个想法是我们有一个客户想要创建多个预定为他的家人.这些多个预定将会参加到一个事务中,由客户端初始化.这实际上是一个非常差的系统设计,因为你有一个事务跨路多个远程调用.远程客户,通常,不可靠的实体,尤其是被人操作的客户端.数据库更新锁保持远程 bookPassage( ) 调用和数据库的连接是打开的.如果是人工的旅行代理决定去吃午餐在事务执行期间,这些资源和连接将会被保持直到事务超时.为了解决这个问题,我们可以使用查询非事务行为的实体管理操作.让我们编写TravelAgentBean 的例子:
import javax.ejb.*;
import javax.persistence.*;
import static javax.persistence.PersistenceContextType.*;
import static javax.ejb.TransactionAttributeType.*;
@Stateful
@TransactionAttribute(NOT_SUPPORTED)
public class TravelAgentBean implements TravelAgentRemote {
@PersistenceContext(unitName="titan", EXTENDED)
private EntityManager entityManager;
@EJB ProcessPaymentLocal processPayment;
private Customer customer;
private Cruise cruise;
private Cabin cabin;
public Customer findOrCreateCustomer(String first, String last) {
...
}
public void setCabinID(int id) {
...
}
public void setCruiseID(int id) {
...
}
public TicketDO bookPassage(CreditCardDO card, double price)
throws IncompleteConversationalState {
if (customer == null || cruise == null || cabin == null) {
throw new IncompleteConversationalState( );
}
try {
Query getCruiseCabin = entityManager.createQuery(
"SELECT cc FROM CruiseCabin cc WHERE" +
"cc.cabin = :cabin AND cc.cruise = :cruise");
getCruiseCabin.setParameter("cabin", cabin);
getCruiseCabin.setParameter("cruise", cruise);
CruiseCabin cc = (CruiseCabin)getCruiseCabin.getSingleResult( );
if (cc.getIsReserved( ))
throw new EJBException ("Cabin is already reserved");
cc.setIsReserved(true);
Reservation reservation = new Reservation(
customer, cruise, cabin, price);
entityManager.persist(reservation);
this.process.byCredit(customer, card, price);
TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
return ticket;
} catch(Exception e) {
throw new EJBException(e);
}
}
> @TransactionAttribute
(REQUIRED)
> @Remove
public void checkout( ) {
entityManager.flush( ); // really not necessary
}
}
我们首先要做的是使每个非事务的业务方法通过 @TRansactionAttribute(NOT_SUPPORTED)注释在组件类上.下一步是,一个扩展的持久化上下文被注入到entityManager字段,所以我们可以同一个外在的事务交互.@Remove注释被移动从bookPassage()到一个新的checkout()方法,所以多个bookPassage()方法调用可以在相同的TravelAgent会话.因为bookPassage( )方法现在是非事务的,任何预定可以被这个方法创建,现在查询和无数据库资源被保存在它们的调用之间.预定被提交到数据库,当扩展的持久化上下文支持一个事务在checkout()方法内.
checkout( ) 方法的EntityManager.flush( )操作不是必需的;扩展的持久化上下文自动被支持当方法开始时.它是一个好的习惯,无论如何都要调用flush()方法,因为,它提醒开发者读取实际发生的代码.
最后的事情是修改ProcessPayment EJB.因为,ProcessPaymentBean使用原一的JDBC记录付款,这些付款没有向实体管理操作排队.为了促进这个,我们需要写另外一个实体组件代表一个付款和改变ProcessPayment EJB 使用一个实体管理,并非JDBC:
@Entity
public class Payment implements java.io.Serializable
{
private int id;
private Customer customer;
private double amount;
private String type;
private String checkBarCode;
private int checkNumber;
private String creditCard;
private Date creditCardExpiration;
@Id @GeneratedValue
public int getId( ) { return id; }
public void setId(int id) { this.id = id; }
@ManyToOne
public Customer getCustomer( ) { return customer; }
public void setCustomer(Customer cust) { this.customer = cust; }
public double getAmount( ) { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public String getType( ) { return type; }
public void setType(String type) { this.type = type; }
public String getCheckBarCode( ) { return checkBarCode; }
public void setCheckBarCode(String checkBarCode)
{ this.checkBarCode = checkBarCode; }
public int getCheckNumber( ) { return checkNumber; }
public void setCheckNumber(int checkNumber) { this.checkNumber = checkNumber; }
public String getCreditCard( ) { return creditCard; }
public void setCreditCard(String creditCard) { this.creditCard = creditCard; }
public Date getCreditCardExpiration( ) { return creditCardExpiration; }
public void setCreditCardExpiration(Date creditCardExpiration) {
this.creditCardExpiration = creditCardExpiration; }
}
这个实体代表在前面ProcessPayment EJB例子中使用的PAYMENT表.下一步,让我们编写ProcessPaymentBean 使用这个新实体:
package com.titan.processpayment;
import com.titan.domain.*;
import javax.ejb.*;
import javax.annotation.Resource;
import javax.persistence.*;
import static javax.ejb.TransactionAttributeType.*;
import static javax.persistence.PersistenceContextType.*;
@Stateful
@TransactionAttribute(SUPPORTS)
public class ProcessPaymentBean implements ProcessPaymentLocal
{
final public static String CASH = "CASH";
final public static String CREDIT = "CREDIT";
final public static String CHECK = "CHECK";
@PersistenceContext(unitName="titan", type=EXTENDED)
private EntityManager entityManager;
@Resource(name="min") int minCheckNumber = 100;
public boolean byCash(Customer customer, double amount)
throws PaymentException
{
return process(customer, amount, CASH, null, -1, null, null);
}
public boolean byCheck(Customer customer, CheckDO check, double amount)
throws PaymentException
{
if (check.checkNumber > minCheckNumber)
{
return process(customer, amount, CHECK,
check.checkBarCode, check.checkNumber, null, null);
}
else
{
throw new PaymentException("Check number is too low. Must be at
least "+minCheckNumber);
}
}
public boolean byCredit(Customer customer, CreditCardDO card,
double amount) throws PaymentException
{
if (card.expiration.before(new java.util.Date( )))
{
throw new PaymentException("Expiration date has passed");
}
else
{
return process(customer, amount, CREDIT, null,
-1, card.number,
new java.sql.Date(card.expiration.getTime( )));
}
}
private boolean process(Customer cust, double amount, String type,
String checkBarCode, int checkNumber, String creditNumber,
java.sql.Date creditExpDate) throws PaymentException
{
Payment payment = new Payment( );
payment.setCustomer(cust);
payment.setAmount(amount);
payment.setType(type);
payment.setCheckBarCode(checkBarCode);
payment.setCheckNumber(checkNumber);
payment.setCreditCard(creditNumber);
payment.setCreditCardExpiration(creditExpDate);
entityManager.persist(payment);
return true;
}
}
ProcessPaymentBean 类使用@transactionAttribute (SUPPORTS)注释,因为它可能或不执行在一个事务中.很容易改变process( ) 方法并且移除很多冗余的JDBC语法从原始的版本.我们必需有一些作做的事情,不合实际的事情在ProcessPayment EJB中强制这个例子工作.JAVA持久化规范不允许你传递一个扩展的持久化上下文到一个无状态会话Bean,当没有事务时.因此,ProcessPayment变成一个有状态会话Bean,有一个扩展的持久化上下文注入到其中.因为ProcessPayment是一个EJB被嵌入到travelAgentBean,他们共享相同的扩展持久化上下文.我们可以使用这一点绕开传递问题.
提供不理想的变化来使ProcessPayment 例子工作,我不建议使用扩展持久化上下文的查询特性如果你需要交互同其它的EJB.它是不幸的 EJB 3.0 专家的组无法修复明显的可用性问题,暴露在这一段中.因为专家组的一两个顽固成员无法决定解决这一问题,两个厂商决定修复这些问题在它们的实现中.
同travelAgentBean 和 ProcessPaymentBean 改变的完成,我们现在焦点放在重新实现的客户端:
TravelAgent tr1 = (TravelAgent)getInitialContext( ).lookup("TravelAgentRemote");
tr1.setCruiseID(cruiseID);
tr1.setCabinID(cabin_1);
tr1.setCustomer(customer0);
tr1.bookPassage(visaCard,price);
tr1.setCruiseID(cruiseID);
tr1.setCabinID(cabin_2);
tr1.setCustomer(customer);
tr1.bookPassage(visaCard,price);
tr1.checkout( );
比较客户端的代码从原始的例子中,这个代码已经变得非常单一化了.如你所看到的,我们管理很少的travelAgentBean中的因数和简单的客户端逻辑.我们解决了我们原始的使用多个bookPassage( )调用的情况.同时,我们管理使用数据库资源更有效并且仍代表一个工作单元.