【问题标题】:How to handle the transaction in J2EE 1.4 with plain JDBC如何使用纯 JDBC 在 J2EE 1.4 中处理事务
【发布时间】:2015-08-28 22:28:15
【问题描述】:

我正在开发一个酒店预订网站。它是一个 J2EE 1.4 Web 应用程序,使用 JSP 和 JDBC。

我有一种方法负责预订酒店房间。

booking()

从这个方法我调用其他四个方法

bookRooms()
makePayment()
confirmUserByMail()
confirmUserBySMS()

我知道两个用户可以尝试同时预订同一个房间,而在我当前的系统中,两个用户最终可能会预订同一个房间。

我应该如何处理事务以避免这个并发问题?

这可能是很常见的情况,但我以前从未处理过这种情况,所以请指导我。

【问题讨论】:

  • 在 bookRooms 方法中,您可以保留房间。在 makePayment 方法中,您可以预订房间或释放房间。这就像一个数据库事务,所有更改都已完成,或者您回滚更改。
  • @Gilbert Le Blanc 你能否详细说明一下。
  • 如果您可以切换到 spring - 它具有基于 txns 的注释、更好的安全性、bean 支持...请参阅github.com/tgkprog/sel2inSmsSend 的示例@阅读我的说明
  • “J2EE 使用 JSP 和 JDBC”,换句话说,你手头没有 EJB?您真的是指十多年前的 J2EE 1.2/1.3/1.4 中的“J2EE”吗?您实际上不使用 Java EE 5 或 6 或 7 吗?还是您认为 Tomcat 是“J2EE”服务器?您的目标/部署到哪台服务器?标准 Java EE 仅提供用于完全透明的自动事务管理的 EJB。另见 a.o. stackoverflow.com/questions/18369356/… 了解一些相关背景信息。
  • @BalusC 基本上我们有一个在 J2EE 1.4 中开发的旧应用程序,它运行在 Tomcat Web 服务器中,这个新部分是对该主应用程序的增强。并且应用程序所有者对升级任何东西都不感兴趣。所以我不能使用 EjB /spring 等,尽管它们提供完全透明的自动事务管理。我理解你的观点,但我的双手被束缚了。我必须对 Jsp 和 JDBC 严格。

标签: java jsp jakarta-ee jdbc transactions


【解决方案1】:

最简单的方法是在你的代码中加锁或者使用数据库提供的锁。你可以在java中使用volatile或者其他并发工具。

如果用户量不太大,可以使用消息队列。用户的访问被封装成对象,将对象放入队列等待线程处理。如果用户太多,会占用大量内存空间。

我听说过另一种方式,叫乐观锁,但我没有在实践中使用过,你可以试试。

ps如果用户量很大,或许你应该使用像Memcached这样的缓存。操作内存中的数据,稍后将这些更改映射到数据库

【讨论】:

    【解决方案2】:

    您的伪代码表明,在调用此方法时,用户已经获取了酒店详细信息/付款详细信息。 通常有两种方法可以设计这样的系统。 这将需要决定是否在用户选择了一个房间并且在填写其他详细信息(如 付款等。这类似于编程语言/数据库中的悲观锁。您需要在 UI 上向用户显示他必须完成交易 否则,他将不得不重新开始。在应用程序/数据库中,您必须确保选定的房间被锁定并且不能被任何人选择 否则对于那个特定的时间间隔。在您的数据模型(模式)中,您可以添加一个标志来指示是否选择了房间,以便当其他用户搜索房间时 此特定房间不显示且不可选择。 另一种方法是在最终逻辑事务提交时(调用 booking() 方法时)使用乐观锁定 - 检查房间的可用性以及是否已经 由另一个逻辑事务预订 - 将用户带到预订过程的开始。这通常不像网站的客户喜欢 酒店预订和第一种方法通常是良好的用户体验。

    【讨论】:

      【解决方案3】:

      在您的服务中使用@TransactionAttribute 进行声明式事务管理。

      请记住,通过电子邮件和 SMS 进行的用户确认本质上不是交易性的,只能在交易成功完成后执行。

      因此,您可以创建一个控制器来调用事务性 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】:

        假设底层数据库是Oracle 11g

        booking(){
        start Trasaction;
        ...
        
        bookRooms()
        {
        
        1. 维护一张Room_Bookings 表,房间号和时隙为unique key
        2. 当用户选择房间并继续进行时,在Room_Bookings 表中插入 oom 详细信息。
        3. 如果多个用户同时选择同一个房间; Oracle 将抛出
        4. 的异常

        唯一的约束违反异常。

        4.当你在Java代码中收到异常时,throw RuntimeException

        1. 对于成功的用户,调用其他方法(即makePayment()..

          (根据以下讨论编辑第 6 点)

        2. 当这个成功的用户签出时间段结束时——即预订房间的时间段;从“Room_Bookings”表中删除房间详细信息条目,以便进一步预订。

          }
          如果收到带有自定义消息的 RuntimeException 回滚事务; 否则提交事务; 结束交易。 }

        另外,从技术角度来看,您可以使用 EJB 容器管理事务和两阶段提交协议; 1 用于数据库资源,1 用于 EJB。 您的方法可能如下所示--

        @TransactionAttribute(TransactionAttributeType.REQUIRED)

        预订(){

        .. ..

        }

        【讨论】:

        • 如果我维护一个 Room_Bookings 表,房间号为唯一键,它会工作吗?同一个房间可以预订不同的时间段,所以我不厚,只有“房间号作为唯一键”才有效。
        • 在这种情况下,我的第 6 点将不起作用。所以它也应该有 Booking slot / Timing 作为唯一成员。所以总房间号+时间槽将作为复合唯一键。计时字段可能具有高达毫秒的精度,以保持事务的自动性。
        【解决方案5】:

        我假设您使用的是纯 JDBC 代码,并且这里不涉及 ORM 工具。您可以放

        conn.setAutoCommit(false);

        所以在提交事务之前,您可以使用上面提到的锁定机制,不确定 J2EE 1.4 是否可以使用锁定,如果没有,请为每个酒店房间或酒店房间组创建队列。一旦提交,将所有等待请求重定向到其他队列。

        【讨论】:

          【解决方案6】:

          我不会在数据库上使用锁,而是在可能的情况下在 (ROOM, DAY) 的数据库保留表上插入一个 UNIQUE 约束。

          然后我会像这样在我的代码中使用事务

          Connection con = somehowGetYourConnection();
          
          try {
              con.setAutoCommit(false);
          
              //query1
              //...
          
              //some more stuff
              //...
          
              //query 2, inserting into the table of the reservation
              //...
          
              con.commit();
          
          }
          catch(SQLException e)
          {
              //log somehow
              //...
          
              con.rollback();
          
              //you will need somehow to tell your user that the reservation failed
              //...
          
          }
          

          现在,当您要插入预订表并提交时,如果同一天和同一个房间已经有另一个预订,将启动 SQLException 并且整个预订事务将被回滚。

          通过这种方式,您可以确定在一天结束时您不会对同一个房间进行双重预订。

          【讨论】:

            【解决方案7】:

            我确信有一些高级方法可以做到这一点,但我只会使用一个简单的布尔值 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

            【讨论】:

            • 我不认为这样可以防止并发问题。
            • 要解决这个问题,我认为您应该在 Java 中使用 synchronized 或 volatile 关键字。好文章在这里:vogella.com/tutorials/JavaConcurrency/…
            • 为什么这被否决了?它具有 4 个答案的大部分元素。事实上,所有 4 个答案都尽力解决问题。要么全部 4 个都应该被否决,要么根本没有。
            • Java 级别的并发控制在这种情况下是不合适的,因为企业应用程序通常分布在两个或多个服务器上。
            【解决方案8】:

            在这种情况下,您可以像这样使用同步方法

            public synchronized void  booking()
            {
                Start Transaction;
            }
            

            在这里我们实现了一个解决方案,因为 Synchronized 有它自己的性质,它一次只允许一个用户,在该用户完成工作后,它将允许下一个执行

            【讨论】:

              【解决方案9】:

              要解决您的并发用例,您需要使用用户的会话来存储或获取来自 JDBC 引擎的连接。

              • EJB 会话容器解决方案可以是一个更复杂的选项(请参阅上面的解决方案),因为您使用服务器的 EJB 层(如果存在)...

              • 如果您想要一个更简单的解决方案,您可以使用 HTTPSession 对象来存储您的连接。这里的挑战是根据您的部署场景/用例设置/限制您的 JDBC 连接池并对其进行测试。这可以通过 JDBC 引擎的一组配置参数或在初始化期间实现。

              web.xml 配置中有一个相关部分用于将 JDBC 连接称为应用程序资源。我会使用 Servlet Context Listener 对象来初始化与相关配置的连接,并使用 HTTP Session Listener 对象来处理与会话相关的连接管理和日志记录。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-06-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-01-13
                相关资源
                最近更新 更多