我们要怎样才能解决并发问题呢?一种方式是使用乐观锁设计模式.乐观锁并不是把所有的传统的感觉锁在里面.它在bookPassage( ) 方法下工作,我们假设没有其它的用户在相同时间里预定相同的Cabin.那么,在时务提交时,我们让数据库决定是否Cabin被预定.如果被预定,我们抛出一个异常并回滚事务.换句话来说,我们是乐观的,那么没有其它的用户来预定相同的Cabin.这如何运作? 这如何避免表级锁? 好吧,为了使用乐观锁,我们必需把泰坦预定系统稍做修改和使用Java持久化的特殊功能.
我们需要做的第一件事是创建一个新的实体类保存关于一个指定巡航上指定小屋的信息.让我们调用这个新的实体类CruiseCabin。一个CruiseCabin实体类将会被创建为每次巡航的每个小屋:
package com.titan.domain;
import javax.persistence.*;
@Entity
public class CruiseCabin {
private int id;
private Cabin cabin;
private Cruise cruise;
private boolean isReserved;
private long version;
@Id @GeneratedValue
public int getId( ) {
return id;
}
public void setId(int id) {
this.id = id;
}
@OneToOne
public Cabin getCabin( ) {
return cabin;
}
public void setCabin(Cabin cabin) {
this.cabin = cabin;
}
@OneToOne
public Cruise getCruise( ) {
return cruise;
}
public void setCruise(Cruise cruise) {
this.cruise = cruise;
}
public boolean getIsReserved( ) {
return isReserved;
}
public void setIsReserved(boolean is) {
isReserved = is;
}
@Version
protected long getVersion( ) {
return version;
}
protected void setVersion(long version) {
this.version = version;
}
}
CruiseCabin实体类参考小屋和它属于的巡航.isReserved 属性让我们知道是否有人预定小屋为巡航.一个新的有趣的属性是version,它使用注释@javax.persistence.Version.一个@Version属性是一个栏位在CruiseCabin表,它将保存一个指定的CruiseCabin 记录的版本ID.只要CruiseCabin 实体被更新,版本栏会增长.当事务开始提交处理和业务逻辑更新CruiseCabin,实体管理器首先检查是否内存在CruiseCabin实例匹配的version属性,数据库中现在存储的版本栏.如果版本匹配,那么version属性增长.如果他们不匹配,那么实体管理抛出一个异常,并且整个事务回滚.让我们改变bookPassage()使用新功能:
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 for cruise");
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);
}
}
bookPassage( )方法为相关的CruiseCabin实体类操纵查询.如果它被预定,它终止事务。如果不是,那么设置isReserved属性和继续同其它的方法.在事务提交时,实体管理调用一个SQL查询验让和增长实体的version栏.我们假定查询CruiseCabin有一个ID为1并且当前version是101:
update CRUISE_CABIN set isReserved=true, version=version + 1
where id = 1 AND version = 101;
如果这个更新返回零个修改记录,那么实体管理知道CruiseCabin被更新通过其它的事务,并且一个并发的事务会发生.在这种错误情形下,它抛出javax.persistence.OptimisticLock-Exception 异常和回滚事务.另外,事务成功完成,并且查询CruiseCabin被更新为预定和它的version属性增长.这种乐观锁解创建一个快速的写锁在一我们的数据库中的一行代替串行级别的大范围的表锁.
应该注意的是,乐观锁的设计模式并不是一直工作的.如果你数据库中有一行有更高的数据写争夺,那么使用乐观锁模式会降低效率,因为,它将会创建很多回滚,会增加更多的系统开支.在那种情况下,串行化解决可能会更好.重新设计你的数据模式可能会更适合在这种情形,然而,如果在你的数据库中有更高的并发访问同一个指定行,那么,你的系统可能不依比例决定.
我们需要做的第一件事是创建一个新的实体类保存关于一个指定巡航上指定小屋的信息.让我们调用这个新的实体类CruiseCabin。一个CruiseCabin实体类将会被创建为每次巡航的每个小屋:
package com.titan.domain;
import javax.persistence.*;
@Entity
public class CruiseCabin {
private int id;
private Cabin cabin;
private Cruise cruise;
private boolean isReserved;
private long version;
@Id @GeneratedValue
public int getId( ) {
return id;
}
public void setId(int id) {
this.id = id;
}
@OneToOne
public Cabin getCabin( ) {
return cabin;
}
public void setCabin(Cabin cabin) {
this.cabin = cabin;
}
@OneToOne
public Cruise getCruise( ) {
return cruise;
}
public void setCruise(Cruise cruise) {
this.cruise = cruise;
}
public boolean getIsReserved( ) {
return isReserved;
}
public void setIsReserved(boolean is) {
isReserved = is;
}
@Version
protected long getVersion( ) {
return version;
}
protected void setVersion(long version) {
this.version = version;
}
}
CruiseCabin实体类参考小屋和它属于的巡航.isReserved 属性让我们知道是否有人预定小屋为巡航.一个新的有趣的属性是version,它使用注释@javax.persistence.Version.一个@Version属性是一个栏位在CruiseCabin表,它将保存一个指定的CruiseCabin 记录的版本ID.只要CruiseCabin 实体被更新,版本栏会增长.当事务开始提交处理和业务逻辑更新CruiseCabin,实体管理器首先检查是否内存在CruiseCabin实例匹配的version属性,数据库中现在存储的版本栏.如果版本匹配,那么version属性增长.如果他们不匹配,那么实体管理抛出一个异常,并且整个事务回滚.让我们改变bookPassage()使用新功能:
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 for cruise");
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);
}
}
bookPassage( )方法为相关的CruiseCabin实体类操纵查询.如果它被预定,它终止事务。如果不是,那么设置isReserved属性和继续同其它的方法.在事务提交时,实体管理调用一个SQL查询验让和增长实体的version栏.我们假定查询CruiseCabin有一个ID为1并且当前version是101:
update CRUISE_CABIN set isReserved=true, version=version + 1
where id = 1 AND version = 101;
如果这个更新返回零个修改记录,那么实体管理知道CruiseCabin被更新通过其它的事务,并且一个并发的事务会发生.在这种错误情形下,它抛出javax.persistence.OptimisticLock-Exception 异常和回滚事务.另外,事务成功完成,并且查询CruiseCabin被更新为预定和它的version属性增长.这种乐观锁解创建一个快速的写锁在一我们的数据库中的一行代替串行级别的大范围的表锁.
应该注意的是,乐观锁的设计模式并不是一直工作的.如果你数据库中有一行有更高的数据写争夺,那么使用乐观锁模式会降低效率,因为,它将会创建很多回滚,会增加更多的系统开支.在那种情况下,串行化解决可能会更好.重新设计你的数据模式可能会更适合在这种情形,然而,如果在你的数据库中有更高的并发访问同一个指定行,那么,你的系统可能不依比例决定.