我正在开发一个酒店预订网站。它是一个J2EE 1.4 Web应用程序,使用JSP和JDBC。
我有一种方法负责预订酒店房间。
booking()
从这种方法我称之为其他四种方法
bookRooms()
makePayment()
confirmUserByMail()
confirmUserBySMS()
我知道有两个用户可以尝试同时预订同一个房间,而在我目前的系统中,两个用户可能最终会使用同一个房间。
我应该如何处理事务以避免这种并发问题?
这可能是非常常见的情况,但我以前从未处理过这种情况,所以请指导我。
答案 0 :(得分:1)
最简单的方法是在代码中添加锁或使用数据库提供的锁。您可以在java中使用volatile或其他并发工具。
如果用户数量不是太大,可以使用消息队列。来自用户的访问被封装到对象中,将对象放入队列并等待线程处理。如果用户太多,它将耗费大量内存空间。
我听说过另一种称为乐观锁的方法,但我从未在实践中使用它,你可以试试。
ps如果用户数量非常大,也许您应该使用内存中的Memcached.Manipulate数据等缓存,并将这些更改映射到数据库
答案 1 :(得分:1)
您的伪代码意味着在调用此方法时,用户已捕获酒店详细信息/付款详细信息。 通常有两种方法来设计这样的系统。 这将需要决定当用户选择房间时是否实际锁定房间并且在填写其他细节时花费时间 这类似于程序语言/数据库中的悲观锁定。您需要在UI上向用户显示他必须完成交易 x时间,否则他将不得不重新开始。在应用程序/数据库中,您必须确保所选房间已锁定且任何人都无法选择 否则为该特定时间间隔。在您的数据模型(架构)中,您可以添加一个标志以指示是否选择了房间,以便在其他用户搜索房间时 这个特定的房间没有出现,也无法选择。 另一种方法是使用乐观锁定,在最终逻辑事务提交时(当你调用booking()方法时) - 你检查房间的可用性,如果已经 由另一个逻辑交易预订 - 将用户带到预订流程的开始。这通常不像网站的客户那样 酒店预订和第一种方法通常是良好的用户体验。
答案 2 :(得分:1)
假设底层数据库是Oracle 11g
booking(){
start Trasaction;
...
bookRooms()
{
Room_Bookings
表,其中房间号和时间位为unique key
。Room_Bookings
表中插入oom详细信息。 唯一约束违规例外。
4.当您在Java代码中收到Exception时,throw RuntimeException
对于成功的用户,请致电其他方法(即makePayment()..
)
(根据以下讨论编辑第6点)
当此成功用户签出或时间段结束时 - 即预订房间的时间段;从'Room_Bookings'表中删除房间详细信息条目,以便进一步预订。
}
如果收到带有自定义消息的RuntimeException回滚事务;
否则提交交易;
结束交易。
}
另外,从技术角度来看,您可以使用带有两阶段提交协议的EJB容器管理事务; 1表示数据库资源,1表示EJB。 您的方法可能如下所示 -
@TransactionAttribute(TransactionAttributeType.REQUIRED)
预订(){
.. ..
}
答案 3 :(得分:1)
在您的服务中使用@TransactionAttribute
进行声明式事务管理。
请注意,通过电子邮件和短信进行的用户确认不属于交易性质,只应在成功完成交易后执行。
因此,您可以创建一个控制器来调用事务性BookingService和非事务性NotificationService:
@WebServlet("/book")
public class BookingServlet extends HttpServlet {
@Resource
NotificationService notificationService;
@Resource
BookingService bookingService;
@Override
public void doPost(...) {
bookingService.booking(...);
notificationService.confirmUserByMail(...);
notificationService.confirmUserBySMS(...);
}
}
@Stateless
public class BookingServiceImpl implements BookingService {
@Resource
private DataSource dataSource;
@TransactionAttribute(REQUIRED)
@Override
public void booking(...) {
bookRooms(...);
makePayment(...);
}
private void bookRooms(...) {
//use dataSource here
}
private void makePayment(...) {
//use dataSource here
}
}
@Stateless
public class NotificationServiceImpl implements NotificationService {
@Override
public void notifyUserByMail(...) {
...
}
@Override
public void notifyUserBySMS(...) {
...
}
}
答案 4 :(得分:1)
我假设你使用普通的JDBC代码,这里没有涉及ORM工具。你可以把
conn.setAutoCommit(假);
所以在提交事务之前你可以使用上面提到的锁定机制,不确定是否可以使用J2EE 1.4进行锁定,如果没有,请为每个酒店房间或酒店房间组创建队列。一旦提交,将所有等待请求重定向到其他队列。
答案 5 :(得分:1)
我不会在数据库上使用锁,而是在可能的情况下在(ROOM,DAY)的预订数据库表上插入UNIQUE约束。
然后我会在我的代码中使用事务,就像这样
{{1}}
现在,当您要将插入插入预订表并提交时,如果同一天已经有另一个预订且同一个房间将启动SQLException并且整个预订交易将进入回滚。
通过这种方式,您可以确保在一天结束时不会对同一个房间进行双重预订。
答案 6 :(得分:0)
我确信有很多高级方法可以做到这一点,但我只想使用一个简单的布尔值isFull,并在预订时使其成为现实。
boolean roomIsFull = false;
if (roomIsFull)
{
//Sir this room is already booked. Please Pick Another.
}
else
{
//Do your stuff.
roomIsFull = true;
}
然后您仍然可以使用当前的代码。
为了确保只有一个房间可以使用synchronized关键字来确保该方法可以使用一次(不是最好的,因为两个不同的房间不能一次预订)
其他选项是使用volatile关键字,因此如果正在访问房间对象,其他方法可以看到。
来源:http://www.vogella.com/tutorials/JavaConcurrency/article.html#concurrency_overview
答案 7 :(得分:0)
你可以制作一个arraylist
ArrayList<String> roomFilled = new ArrayList<String>();
然后,当人们注册时,通过
检查房间是否被填满if(roomFilled.contains("roomNameHere"){
//Say that the room already has owner AND don't allow the user to book the room!
}else{
//Allow the user to get the room
}
每当有人购买房间时,你就会“标记”房间,这样就可以了解
roomFilled.add("roomNameHere");
另外不要忘记添加这个
roomFilled.remove("roomNameHere");
订阅日期结束时。
答案 8 :(得分:0)
在这种情况下,你可以像这样使用同步方法
public synchronized void booking()
{
Start Transaction;
}
在这里,我们实现了一个解决方案,因为Synchronized具有自己的特性,一次只允许一个用户,在用户完成作业之后,它将允许接下来执行
答案 9 :(得分:0)
要解决并发用例,您需要使用用户的会话来存储或获取JDBC引擎的连接。
EJB会话容器解决方案可以是一个选项(参见上面的解决方案),当您使用服务器的EJB层(如果存在)时,它会更复杂......
如果您需要更简单的解决方案,可以使用HTTPSession对象来存储连接。这里的挑战是根据您的部署方案/用例设置/限制JDBC连接池并对其进行测试。这可以通过JDBC引擎的一组配置参数或在初始化期间实现。
web.xml配置中有一个相关部分,用于将JDBC Connection作为应用程序资源引用。我将使用Servlet Context Listener对象来初始化与相关配置和HTTP Session Listener对象的连接,以处理与会话相关的连接管理和日志记录。